Implementing graceful shutdown for docker containers in go (part 2)

In part 1 I demonstrated how to implement graceful shutdown of http containers in kubernetes, however there were some issues that arose where requests could cause errors. After a bit of discussion on stack overflow I have updated my code to include an extra http listener to service pre-stop and readiness checks from kubernetes.

The way this works is as such:

  • A container needs to be shutdown
  • A pre-stop call is made
    • We take this opportunity to update our readiness to false, so that we are removed from the proxy
    • As this operation is synchronous you can optionally sleep here for a few seconds, to give kubernetes time to check and update based on your readiness (useful if scaling dramatically)
  • A SIGTERM is received
    • As before we wrap up all current requests and shut down
    • We have 10 seconds to do this before we get a SIGKILL
  • Our container is shut down

Testing

If you would like to test this on your cluster I have a sample project uploaded onto github which follows my model for running minimal go apps in a kubernetes cluster.

You can follow the setup on that page, or tweak the files to point at a different docker registry, then just run make to get a docker image.

Following on from that you need to test in kubernetes, for which you have 2 options:

  • Scaling
    • This is useful to simulate growing and shrinking your service, but can occasionally have issues when doing large scaling operations in a single command (i.e. from 20 containers to 1)
    • Scaling up and down incrementally would be advised for any services (i.e. from 20 to 19, then 19 to 18, etc)
  • Rolling Updates
    • This is the setup I have opted for, swapping out one container for a new one over a period of time
    • The files included always use the latest docker image so actually update from the old latest, to the new latest
      • This isn’t meant to be how production deploys work, you should use versions, but its good for development

Given you have a running kubernetes cluster you can bring up the service as such:

./cluster/kubectl.sh create -f ~/Go/src/github.com/chriswhitcombe/httpgraceful/01-httpgraceful-controller.json
./cluster/kubectl.sh create -f ~/Go/src/github.com/chriswhitcombe/httpgraceful/01-httpgraceful-service.json

Obviously substitute paths accordingly for yourself.

This will give you a hello world style output on port 31000 of any of your node ip’s. You can then modify the backend cluster either via scaling:

./cluster/kubectl.sh scale --replicas=10 rc httpgraceful-controller-1

Or via a rolling update

./cluster/kubectl.sh rollingupdate httpgraceful-controller-1 -f ~/Go/src/github.com/chriswhitcombe/httpgraceful/02-httpgraceful-controller.json

Throughout this you should always get a request routed to an active cluster and see no errors on the client side. I have noticed occasionally I do see a spike in latency which needs a little investigation however we are talking of a handful of requests in 100k only during a rollout of upgrades, which I plan to retest when not running in vagrant but on more production like hardware.

My stats during a rolling update showed:

  • Replicas: 10
  • Requests: 197,357
  • Average Request: 1039 ms (I have a 1000 ms sleep in the code)
  • Min: 1000 ms
  • Max 57,477 ms (this is to be investigated)
  • Standard Deviation: 1364 ms
  • Errors: 0%

I plan to retest this on a more production like system with far more replicas (nearer to 1000), where I expect to see less jitter in timings. After the rollout was complete the timings settled down to a standard deviation around 1040 ms.

Read More

Implementing graceful shutdown for docker containers in go (part 1)

As part of ensuring the servers I write are compatible with docker and kubernetes I wanted to ensure all my http servers shutdown gracefully so as not to drop any transactions. This is especially useful when scaling down in kubernetes or as part of rolling deploys of new pods, ensuring your containers have the best chance to not drop any transactions.

The standard shutdown procedure for a docker container is to send the container a SIGTERM to indicate a desire to shutdown, if the container doesn’t shutdown it is issued with a SIGKILL after a grace period. As such we need to hook the SIGTERM and ensure we finish all our communications.

My solution for this is as per the following gist:

There are a couple of important things to point out here. Firstly you will notice the use of manners, which is a wrapper for the standard http server to allow for graceful shutdown.

You will also notice the all important hooks for both SIGTERM and also Ctrl+C, which makes things a lot easier for quick tests and also operation outside of docker.

This approach ends up working nicely, you can boot a number of instances of this server and for the most part scale up and down without seeing any dropped requests. However I have noticed that when I scale to a single instance I do see some errors for a very brief period. I’m not sure if this is due to it being a single instance, or just less than the number of test nodes I was running but I’m wondering if there is some issue around etcd timing its changes across the cluster as requests come in.

Overall the approach works for the use case in mind, as a cluster system I don’t expect to run single instances of services that get scaled up and down rapidly, but I’m hoping I can investigate more to find out where the issue is coming from.

Read More

Running minimal golang apps in kubernetes cluster

I’ve recently been working at migrating some of my projects to docker, and ultimately kubernetes to run inside a cluster. It’s an interesting workflow that results in very rapid development cycles and the ability to constantly verify an application via a rolling deploy and testing with GoConvey.

The process looks a bit like this:

  • Write code
  • Build & unit test code
  • Build minimal docker container with statically linked go app
  • Push docker container to local registry
  • Deploy into kubernetes cluster as a rolling deploy

This process ensures that its very easy for developers to constantly be testing in a local environment, which for me is the perfect development setup.

A quick caveat, as this is quite complex to setup I have chosen to skip a few things like the unit tests and rolling deployments. These are well documented elsewhere, but if you get stuck just comment and I’ll do a follow up post.

The setup

So here is the overall setup:

Kubernetes

  • Everything runs on a single computer, in this case my mac
  • The docker registry runs inside a boot2docker VM
  • Kubernetes runs locally via vagrant
  • Kubernetes runs a master and a pair of nodes (minions in the old kubernetes speak)
  • The nodes run 3 copies of our container
    • We don’t care which nodes they run on
  • A service exposes our application to the host machine via a fixed port on both nodes

Note: Blue = The computer, Red = VM, Green = Docker Container, Purple = Relevant parts of kubernetes (there’s more, but its easier to think this way)

First Steps

For doing this work, you need to have:

  • Go 1.4 installed (and setup for cross compilation if you’re not on linux)
  • Git
  • Boot2docker running (for mac users)
  • Make
  • Vagrant & VirtualBox (VirtualBox was tested but should work with any provider)

To get up and running I have created a sample project that contains all the files needed for this work. As you are working in go the best way to get hold of this will be

go get github.com/chriswhitcombe/minimal-docker-go

This will clone the project under your $GOPATH/src/ structure for you ready to be used.

Run your registry

Before we build our code you need to have a working local docker registry. You could skip this stage and push to the public docker repository, but that slows the cycle time for feedback and ideally you should only push releases to that, not just ad-hoc development builds.

Luckily the docker team have made it very easy to get your own local registry up and running. Make sure you have boot2docker ready to go:

boot2docker init
boot2docker up

Then you can setup your registry with one line:

docker run -p 5000:5000 -d --name=basic_registry registry

Finally you need to check the IP this is running on from boot2docker

boot2docker ip

You can then load <ipaddress>:5000 into your browser and confirm you get a message stating you have hit the registry server. Take a note of the IP address and port, you will need this later!

Build the app and docker image

Open a terminal prompt in the minimal-docker-go project and run the make file, this does a few things:

  • This builds your code
    • It cross compiles for linux as the target system
    • It statically links the c libraries go needs
    • It forces all packages we depend on to recompile under these rules also
    • In the end you get a binary for linux that is totally self contained
  • This builds and pushes your docker image to your local docker registry
    • Thanks to our self contained binary we build off the scratch image, resulting in a tiny file
    • Note you may need to change localhost here if you have issues
  • Finally it cleans up the binary to keep things tidy

Once you are done here you should have a local docker registry with a copy of your container, in which it has your code.

Note: The use of the scratch container here is really pretty amazing, you are essentially sitting on top of a kernel with hardly anything else in your way. The final image size ends up being around 6mb for a container, your app, the garbage collector, everything!

Boot kubernetes

Your first job is to download and extract kubernetes from github to somewhere useful on your machine. As noted above we will be using vagrant to simulate our cluster. However before we get started there is one important change to make to the configuration. Normally docker pulls images from a valid https endpoint, however in our example we’re using a simple local registry that doesn’t have a valid https endpoint. We need to ensure that we can pull our docker images in kubernetes.

Open a terminal and cd into the kubernetes folder.

Open kubernetes/cluster/vagrant/config-default.sh and search for the line containing “insecure-registry”, in my release it looks as such:

EXTRA_DOCKER_OPTS="-b=cbr0 --insecure-registry 10.0.0.0/8"

We need to change this so images from our insecure registry are accepted, as such take the IP from your boot2docker VM and update accordingly, mine ended up looking as such:

EXTRA_DOCKER_OPTS="-b=cbr0 --insecure-registry 192.168.59.103:5000"

With that in place, we can bring up our cluster by running:

export KUBERNETES_PROVIDER=vagrant
export NUM_MINIONS=2
./cluster/kube-up.sh

This ensures our script knows to boot via vagrant and creates a cluster with a master and 2 minions (now known as nodes). Go get a cup of tea, this takes a while!

Once its finally booted you can get the IP’s of your nodes

./cluster/kubectl.sh get nodes
NAME       LABELS                            STATUS
10.245.1.3 kubernetes.io/hostname=10.245.1.3 Ready
10.245.1.4 kubernetes.io/hostname=10.245.1.4 Ready

Deploy the pods

There are enough tutorials on kubernetes terminology around on youtube, I won’t go into it. What we will do here is bring up a controller that ensures we have 3 replicas of our container (from our local docker registry) running across our 2 nodes. We don’t care how many run and where, we just care that we have 3.

As can be seen from the gist we run a controller, which runs 3 replicas of our controller. You may need to adjust the IP of your docker registry host accordingly. It’s important to note we are demanding the latest container version, which ensures docker asks our registry for the latest image and doesn’t use its cached copy. To get our cluster into this state we submit this file to kubernetes, it then takes responsibility of booting docker containers on various nodes and keeping the correct number of replicas up. To do this run:

./cluster/kubectl.sh create -f 01-goapp-controller.json

You can check on the progress of this by running:

./cluster/kubectl.sh get pods

In the end your cluster should look like this:

NAME                   READY STATUS  RESTARTS AGE
goapp-controller-08xl7 1/1   Running 0        15s
goapp-controller-33lr2 1/1   Running 0        15s
goapp-controller-ev3bp 1/1   Running 0        15s

Create the services

Now that our pods are running we need to be able to access them. In our example we don’t need access from elsewhere in our cluster, but from outside our cluster, however the approach is the same. We need to create a service, which proxies to our pods.

Most of this is pretty standard, however its important to note that we have specified a type of Nodeport to ensure that the service is exposed on the host machines (in this case our minions/nodes), we have also specified a port to expose this service on just to make our lives easier.  In a real world deployment there would be a managed load balancer in front of the service, but for now this does the job.

As above you can run this config into your cluster as such:

./cluster/kubectl.sh create -f 01-goapp-service.json

And you can confirm its all looking good as such:

./cluster/kubectl.sh get services
NAME          LABELS              SELECTOR      IP(S)          PORT(S)
goapp-service <none>              app=goapp-app 10.247.249.142 80/TCP
kubernetes    component=apiserver <none>        10.247.0.1     443/TCP

Test

Finally we have our service deployed, so we can test. Remember this is now being exposed on our host machines, to get their IP’s just run:

./cluster/kubectl.sh get nodes
NAME       LABELS                            STATUS
10.245.1.3 kubernetes.io/hostname=10.245.1.3 Ready
10.245.1.4 kubernetes.io/hostname=10.245.1.4 Ready

In the above example you can hit either 10.245.1.3:31000 or 10.245.1.4:31000 and our service will respond. Whats really cool is even if our service isn’t running on that node/minion then kubernetes is smart enough to proxy us to another one! You can change the replication to 1 and redeploy, you will still get results from both IP’s!

You can also tear the cluster down as such:

./cluster/kubectl.sh delete -f 01-goapp-controller.json
./cluster/kubectl.sh delete -f 01-goapp-service.json

Summary

Now that you have a cluster up and running you can change code and run make to get a tiny container that is entirely self contained. Then using kubernetes you can quickly tear down and redeploy an entire, highly resilient, cluster of the containers to arbitrary machines.

In the future I will blog about how to test using goconvey and use rolling deploys to ensure zero downtime in your cluster, but hopefully this is a good starting point for those exploring kubernetes and go. Any questions just drop them in the comments.

Read More