There are parts of your application that need updates from time to time. You are using docker and you want to have no downtime during this updates? Let’s talk about it!
What we need to update?
There are three parts of application, which we will be updating:
- Backend code
- Frontend code
- DB structure
We will try to update them separately and then we will talk about how we can update them together.
This is how our architecture might look like:
Assume, we have a server to serve static files, e.g. NGINX, and we want to update some html files. So we need another container to build(concatenate, minify, uglify, etc) assets and place them to a volume with static files.
The problem is that browsers can cache old assets and user will have a mix of old and new assets. Also, if you dynamically load some files, users can have old index.html and try to load new versions of other files.
The solution is versioning. Each new build must have a unique id, and you need to keep old versions.
For backend updates we will use dockercloud/haproxy image which is just brilliant. Why brilliant? When you run docker-compose scale, it automatically connects new containers to HAProxy and removes old ones. It can work with Docker Swarm and doesn’t need Consul or other tools.
So we have architecture like this:
docker-compose build api
This will build a new API.
docker-compose scale api=4
HAProxy will add them to the configuration and will send requests to them.
By default, HAProxy adds new containers immediately, but we need to give them time to prepare. Therefore I added some delay before linking new containers to the script that works with docker events. You can find it here
Now we can remove old containers. But if we run docker-compose scale api=2, docker will remove new ones and keep old. So we need to remove them with docker stop and docker rm.
I wrote a bash script that runs these steps, and now you can update API with one command.
It is the easiest and the hardest part at the same time. If migrations will not break your current backend, you just need to add another container, which will connect to the database and run migrations.
Updating database and code
For example, you want to change column type. You also want to be able to rollback, of course.
To achieve it, you need to keep your API and DB compatible with each other after every migration. It might be hard to do.
You can find some other examples here
As you can see, zero downtime with docker is possible and seems rather easy. But you should be careful when you are updating different parts of your application at once.
Check the example here.
Also, you can check Refactoring controller actions in Ruby on Rails.
And read about Domain-driven design for Ruby on Rails.
Originally published at korolvs.com on August 11, 2016.