FROMto tell Docker to download a public image, which is then used as the base for our custom image. In particular we use an image which contains a specific version of Ruby.
RUNto execute a command inside the image that we are building. In particular we use
apt-getto install some libraries. Note that the libraries available on the default Ubuntu repositories are usually quite old: if you want to get the latest versions you need to update the repository list and tell APT to download the libraries directly from the maintainer's repository. In particular, just before
apt-get, we can add the following commands to update the Node.js and Yarn repositories:
.git, which would be a waste of resources. For this we need to create a
.dockerignorefile in the Rails root: you can usually take inspiration from your
.gitignore, since the aim and syntax are very similar.
-toption, followed by its argument, is optional: we use it to assign a name and a tag to the new image. This makes it easier to find the image later. We can also use a repository name as the image name, in order to allow push to that repository later. The image name is the part before the colon, while the tag is the part after the colon. Note that we could also omit the tag
latestsince it is the default value used in case the tag is omitted. The last dot is a required argument and indicates the path to the Dockerfile.
~/.kube/kubernetes-rails-example-kubeconfig.yaml. Then you need to pass a
kubektlwhenever you invoke a command, or you can set an environment variable:
config/kubedirectory in your Rails application.
apiVersionsets the API version for the configuration file;
kindsets the type of configuration file;
metadatais used to assign a name to the deployment;
replicastells Kubernetes to spin up a given number of pods;
selectortells Kubernetes what template to use to generate the pods;
templatedefines the template for a pod;
specsets the Docker image that we want to run inside the pods and other configurations, like the container port that must be exposed.
kubectl get podsshows the pods and their status;
ImagePullBackOffprobably you have not configured properly the secret to download the image from your private repository;
kubectl describe pod pod-name;
kubectl delete --all pods.
EXTERNAL-IPand type it in your browser address bar: our website is up and running!
kubectl get services).
imageattribute) inside config/kube/deployment.yml:
SECRET_KEY_BASE, in your Git repository! In the next section we will see how to use Rails credentials to safely store your secrets.
rails-app) and displays them. This is useful for getting started, however logs are not persistent and you need to make them searchable. For this reason you need to send them to a centralized logging service: we can use Logz.io for example, which offers a managed ELK stack. In order to send the logs from Kubernetes to ELK we use Fluentd, which is a log collector written in Ruby and a CNCF graduated project.
kubectl apply. Note that if you use services different from Logz.io the strategy is very similar and you can find many configuration examples on the Github repository fluent/fluentd-kubernetes-daemonset.
bundle installand create an example worker:
PagesController#homemethod (or anywhere else) to create a background job every time a request is made:
commandwhich overrides the default command defined in the Docker image. You can also pass some arguments to Sidekiq using an
REDIS_URLvariable, so that Sidekiq and Rails can connect to Redis to get and process the jobs. You should also add the same env variable to your web deployment, so that your Rails application can connect to Redis and schedule the jobs. For Redis itself you can use Kubernetes StatefulSets, you can install it on a custom server or use a managed solution: although it is easy to manage a single instance of Redis, scaling a Redis cluster is not straightforward and if you need scalability and reliability probably you should consider a managed solution.
kubectl apply -f config/kube.
bundle installand create an initializer:
kubectl get pods.
terminalfor maintenance tasks. Create the following file and then run
kubectl apply -f kube/config:
sleep infinity, which is basically a no-op that consumes less resources and keeps the container running.
terminal, you can run this command:
--to separate the Kubernetes arguments from the command arguments. For example:
kubectl execreturns the status code of the command executed (i.e.
0if the rake task is executed successfully).
rake db:migrate. Pros: zero downtime; deployment is very simple. Cons: it is very difficult to make code backward compatible; you probably need an additional restart after the migration.
latest), otherwise an automatic reschedule may fetch the new image before the migration is complete. If you choose this strategy, you need to use a Kubernetes Job to deploy a single pod with the new image and run the migration and then, if the migration succeeds, update the image on all pods. Pros: common and reliable strategy. Cons: if you don't write backward compatible migrations, some errors may occur while the new code is being rolled out; you need to use different Docker tags for each image version if you want to prevent accidental situations where the new code runs before the migration.
chmod +x deploy.sh):
kubectl top nodes/pods;
2.5.1, so that you don't forget to increase the patch version when there is a new patch available. However that is not enough: when a new OS patch is available, the Ruby maintainers release a new version of the image, with the same tag (for example the image with tag
2.5is not always the same). This means that you should ckeck Docker Hub frequently to see if the base image has received some updates (or subscribe to the official security mailing lists for Ruby, Ubuntu, etc.): if there are new updates, build your image again and deploy.