Digital Ocean Rolling Deploy of Docker containers

Introduction

More and more companies are moving to containerized deployments and it is very common to do rolling deploys of a new image across multiple machines piece by piece to avoid downtime. Generally, I see devs using bash scripts for their build and deploy steps but I have been increasingly moving towards replacing bash with go as much as possible. This post is to show what a go script looks like to deploy a new docker image across different servers.

Prerequisites

To complete this tutorial, you will need:

  • Two or more servers with docker installed
  • A Load balancer
  • Basic knowledge of Golang

Show me the code

The entire script is available as a snippet on Gitlab here.

The steps are as follows:

Step 1 — Create a DigitalOcean Access Token

To use the DigitalOcean SDK and container registry you will first need to create an access token. You can create one in the API section found in the left navigation bar in the Token/Keys tab - docs here

Step 2 — Create a DigitalOcean container registry

You will need a container registry to hold all your application images. DigitalOcean provides a container registry option backed by Spaces. Product page here

Step 3 — Get all droplets by tag to deploy to

The first thing we need is to get a list of droplets to deploy to. Assuming the droplets are tagged with nginx-service we can get a list of droplets using DropletService ListByTag(context.Context, string, *ListOptions) ([]Droplet, *Response, error) method. Ref: Line 143 of the script

Step 4 — Get the load balancer

Now we will fetch the load balancer that hosts the droplets from step 4. We need the loadbalancer so that we can remove droplets from it so they don’t get any traffic while the containers are being swapped - this avoids any user disruptions. Unfortunately, there is no list by tag method on the load balancer service - so we have to List all loadbalancers and use the first one that has the tag. Ref Line 174 of the script

Step 5 — Drain the server from the load balancer

To avoid deploying while client requests are in flight you will drain the droplet from the load balancer. This step is simple as we can simple untag the droplet that leads to it getting picked up by the loadbalancer by calling the UntagResources(context.Context, string, *UntagResourcesRequest) (*Response, error) method. Ref Line 61 of the script

Step 6 — Connect the docker client to the remote server

Now we need to create a docker client that connects to the docker daemon running on the droplet that was drained. Docker runs on port 2375 so make sure that the machine that this script runs on can access it on the remote droplet. Ref Line 211

Step 7 — Stop the current running container

Before you can deploy the new image - you will need to stop and remove the current version of the container on the server. First we will get the ID of the container with the service tag nginx-service Line 225 and then we can call the ContainerStop(ctx, containerID, timeout) method to kill the container Line 91

Step 8 — Pull new version of authenticated image

Now we need to pull the new version of the image from DigitalOcean’s container registry. We use the ImagePull method in the Docker SDK with DigitalOcean’s access token as both the username and password for auth. Ref Line 242. This makes sure that the new image is available on the remote server to be run.

Step 9 — Start new container

We can now start the container using the image pulled. We need to create a container config that maps the host port to the container port and tag the container. Ref Line 261. Creating the container config is separate from actually running the container. The config then gets passed to the ContainerStart method ref Line 119

Step 10 — Add droplet back to load balancer

With the image updated the droplet can be safely added back to the load balancer and it can serve client requests using the new version of the image. This is as simple as retagging the droplet so that the loadbalancer picks it up again. Ref Line 125

Conclusion

This post shows how to do a rolling deploy on droplets but is more so to show that Golang is an excellent language to replace adhoc bash scripts. It is also extremely portable because of easy cross-compilation and statically linked binaries. I encourage you - next time you are planning on using bash to script something - ask yourself can I use Go instead?