We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.
This post assumes that you have basic understanding of Kubernetes terms like pods and deployments.
We want to deploy a Rails application on Kubernetes.
We assume that the assets:precompile
task would be
run as part of the Docker image build process.
We want to run rake tasks such as
db:migrate
and db:seed
on the initial deployment,
and just db:migrate
task on each later deployment.
We cannot run these tasks while building the Docker image as it would not be able to connect to the database at that moment.
So, how to run these tasks?
We assume that we have a Docker image
named myorg/myapp:v0.0.1
which contains the source code for our Rails application.
We also assume that we have included database.yml
manifest
in this Docker image
with the required configuration needed for connecting to the database.
We need to create a Kubernetes deployment template with the following content.
1apiVersion: extensions/v1beta1
2kind: Deployment
3metadata:
4 name: myapp
5spec:
6 template:
7 spec:
8 containers:
9 - image: myorg/myapp:v0.0.1
10 name: myapp
11 imagePullPolicy: IfNotPresent
12 env:
13 - name: DB_NAME
14 value: myapp
15 - name: DB_USERNAME
16 value: username
17 - name: DB_PASSWORD
18 value: password
19 - name: DB_HOST
20 value: 54.10.10.245
21 ports:
22 - containerPort: 80
23 imagePullSecrets:
24 - name: docker_pull_secret
Let's save this template file as myapp-deployment.yml
.
We can change the options and environment variables in above template as per our need. The environment variables specified here will be available to our Rails application.
To apply above template for the first time on Kubernetes, we will use the following command.
1$ kubectl create -f myapp-deployment.yml
Later on, to apply the same template after modifications such as change in the Docker image name or change in the environment variables, we will use the following command.
1$ kubectl apply -f myapp-deployment.yml
After applying the deployment template, it will create a pod for our application on Kuberentes.
To see the pods, we use the following command.
1$ kubectl get pods
Let's say that our app is now running
in the pod named myapp-4007005961-1st7s
.
To execute a rake task, for e.g. db:migrate
on this pod,
we can run the following command.
1$ kubectl exec myapp-4007005961-1st7s \
2 -- bash -c \
3 'cd ~/myapp && RAILS_ENV=production bin/rake db:migrate'
Similarly, we can execute db:seed
rake task as well.
If we already have an automated flow for deployments on Kubernetes, we can make use of this approach to programmatically or conditionally run any rake task as per the needs.
We faced some issues while using Kubernetes Jobs to run migration and seed rake tasks.
If the rake task returns a non-zero exit code, the Kubernetes job keeps spawning pods until the task command returns a zero exit code.
To get around the issue mentioned above we needed to unnecessarily implement additional custom logic of checking job status and the status of all the spawned pods.
Capturing the command's STDOUT or STDERR was difficult using Kubernetes job.
Some housekeeping was needed such as manually terminating the job if it wasn't successful. If not done, it will fail to create a Kubernetes job with the same name, which is bound to occur when we perform later deployments.
Because of these issues, we choose not to rely on Kubernetes jobs to solve this problem.