FROM
to 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.RUN
to execute a command inside the image that we are building. In particular we use apt-get
to 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:tmp
or .git
, which would be a waste of resources. For this we need to create a .dockerignore
file in the Rails root: you can usually take inspiration from your .gitignore
, since the aim and syntax are very similar.-t
option, 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 latest
since 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.EXPOSE
.http://localhost:3000
.~/.kube/kubernetes-rails-example-kubeconfig.yaml
. Then you need to pass a --kubeconfig
option to kubektl
whenever you invoke a command, or you can set an environment variable:Secrets
:config/kube
directory in your Rails application.apiVersion
sets the API version for the configuration file;kind
sets the type of configuration file;metadata
is used to assign a name to the deployment;replicas
tells Kubernetes to spin up a given number of pods;selector
tells Kubernetes what template to use to generate the pods;template
defines the template for a pod;spec
sets the Docker image that we want to run inside the pods and other configurations, like the container port that must be exposed.rails-app
.kubectl get pods
shows the pods and their status;Running
;ImagePullBackOff
probably you have not configured properly the secret to download the image from your private repository;kubectl describe pod pod-name
;kubectl delete --all pods
.LoadBalancer Ingress
or EXTERNAL-IP
and type it in your browser address bar: our website is up and running!kubectl get services
).image
attribute) inside config/kube/deployment.yml:image
attribute):SECRET_KEY_BASE
, in your Git repository! In the next section we will see how to use Rails credentials to safely store your secrets.env
inside config/kube/deployment.yml:RAILS_LOG_TO_STDOUT
to enabled
.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 install
and create an example worker:PagesController#home
method (or anywhere else) to create a background job every time a request is made:command
which overrides the default command defined in the Docker image. You can also pass some arguments to Sidekiq using an args
key.REDIS_URL
variable, 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 install
and create an initializer:-it
options.kubectl get pods
.terminal
for 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 exec
returns the status code of the command executed (i.e. 0
if 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
instead of 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.5
is 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.