Rolling Updates and Rollbacks using Kubernetes Deployments

As I described in the previous article, with minikube running or with access to a remote Kubernetes cluster, you can start exploring more advanced deployment scenarios than running a single Pod. One of the strengths of Kubernetes is the ability to define a container-based unit (i.e., Pod) in a declarative resource called a Deployment. This Deployment can be scaled up and down and can also be used to keep track of all the versions being deployed, opening the door for simple rollbacks.

Deployments are a higher abstraction, which create ReplicaSets resources. ReplicaSets watch over the Pods and make sure the correct number of replicas are always running. When you want to update a Pod, you can modify the Deployment manifest. This modification will create a new ReplicaSet, which will be scaled up while the previous ReplicaSet will be scaled down, providing no down-time deployment of your application.

The command line kubectl gives you some hints about what is possible. In particular, in version v1.4.0, you have some clearly labeled Deploy Commands returned by the kubectl help. Note that the rolling-update command only applies to ReplicationControllers; hence, we will not use it. This type of update was driven client side, whereas with Deployments resources rolling updates are now all done server side.

$ kubectl --help ... 

Of interest for this blog is the rollout command, which allows you to manage various versions of a deployment.

$ kubectl rollout --help
...
Available Commands: history View rollout history pause Mark the provided resource as paused resume Resume a paused resource status Watch rollout status until it's done undo Undo a previous rollout

Quick and Dirty Deployment

If you cannot wait to get started, make use of the kubectl run command to generate a Deployment. It is a very handy wrapper. Executing it will generate a Deployment, which will create a ReplicaSet, which will create your Pod. For example to run Ghost:

$ kubectl run ghost --image=ghost 
$ kubectl get pods,rs,deployments
NAME READY STATUS RESTARTS AGE
po/ghost-943298627-ev1zb 1/1 Running 0 33s
NAME DESIRED CURRENT READY AGE
rs/ghost-943298627 1 1 0 33s
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/ghost 1 1 1 0 33s ```

To quickly expose this deployment, we need to create a service. This can be done with the kubectl expose command.

$ kubectl expose deployment ghost --port=2368 --type=NodePort

With the service in place you can open the Ghost application with your browser. Of course, you can choose another type of service.

To scale the deployment and have some quick fun, simply use the kubectl scale command and watch new Pods being created.

$ kubectl scale deployments ghost --replicas=4 deployment "ghost" scaled 
$ kubectl get pods --watch

To create a Deployment from a manifest, head over to the Deployment documentation, check the specification, and write your own manifest in yaml or json, then use the kubectl create -f command. Note that Deployments are extensions API, they should become first class citizens in an upcoming release. However, kubectl run is a handy wrapper that gets your started in a flash.

Modifying a Deployment

Before modifying a Deployment, let’s have a look at the rollout command and see what the history returns:

$ kubectl rollout history deployments ghost deployments "ghost" REVISION CHANGE-CAUSE 1 <none>

We have one revision, representing the creation step. And the cause is empty <none>. To get the change cause to be recorded, we need to start a Deployment with a — record option. Each subsequent action will be recorded in an annotation stored in the Deployment resource. Let’s do it, because it is quite fun.

$ kubectl run ghost-recorded --image=ghost:0.9 --record deployment "ghost-recorded" created 
$ kubectl rollout history deployments ghost-recorded deployments "ghost-recorded" 
REVISION CHANGE-CAUSE
1 kubectl run ghost-recorded --image=ghost:0.9 --record

There are now many ways to perform an update. One that I like a lot is just to edit the Deployment with my editor using kubectl edit like so:

$ kubectl edit deployments ghost-recorded
deployment "ghost-recorded" edited
$ kubectl rollout history deployments ghost-recorded deployments "ghost-recorded" 
REVISION CHANGE-CAUSE
1 kubectl run ghost-recorded --image=ghost:0.9 --record
2 kubectl edit deployments ghost-recorded

It is handy, but the annotation is not very helpful, we don’t know what we changed. So, instead let’s use the set command. Currently, this lets you change only the image of a deployment.

$ kubectl set image deployment/ghost-recorded ghost-recorded=ghost:0.11 
deployment "ghost-recorded" image updated
$ kubectl rollout history deployments ghost-recorded deployments "ghost-recorded" 
REVISION CHANGE-CAUSE
1 kubectl run ghost-recorded --image=ghost:0.9 --record
2 kubectl edit deployments ghost-recorded
3 kubectl set image deployment/ghost-recorded ghost-recorded=ghost:0.11

Every time we make a change, a new ReplicaSet is created. This new ReplicaSet is scaled up and the previous one scaled down, but not deleted… Let’s check it out:

$ kubectl get rs 
NAME DESIRED CURRENT READY AGE
ghost-recorded-214016590 0 0 0 5m
ghost-recorded-3297419894 0 0 0 3m
ghost-recorded-3372327543 1 1 1 1m

If you check the Pods, you will see that your running Pod has the hash of the latest ReplicaSet:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
ghost-recorded-3372327543-d0xbk 1/1 Running 0 2m

Here you have it, rolling update with Kubernetes Deployments, giving you zero down-time!

Rollbacks

How do you roll back ? Simply set the revision that you want. Kubernetes will scale up the corresponding ReplicaSet, and scaled down the current one, and you will have rolled back. Let’s do it, so you believe me:

$ kubectl rollout undo deployments ghost-recorded --to-revision=1 deployment "ghost-recorded" rolled back 
$ kubectl rollout history deployments ghost-recorded deployments "ghost-recorded" 
REVISION CHANGE-CAUSE
2 kubectl edit deployments ghost-recorded
3 kubectl set image deployment/ghost-recorded ghost-recorded=ghost:0.11
4 kubectl run ghost-recorded --image=ghost:0.9 --record

Here we rolled back to the first revision. The revision index was incremented, but the ReplicaSets stayed the same. We just scale up the original one.

While doing a rollout or rollback, you can define a specific strategy. This strategy is specified in the Deployment manifest. Currently, it only lets you specify the maximum number of Pods that can be unavailable during an update, as well as the maximum number of Pods that can be created above the declared number of replicas.

You can also pause and resume rollout and rollbacks with kubectl rollout pause/resume

There you have it, I think this has never been that easy to perform rolling updates and the crucial rollbacks. The ReplicaSets are brought to bear to keep a history of deployments and provide a zero downtime update. Also critical in all of this, is that the service exposing the application never gets changed. Indeed the service selects the Pods based on labels, whatever happens in the deployment the service is the same. This Deployment resource can also be used to do more involved update patterns, like canary deployments.

Have fun rolling!

Originally published at www.linux.com on November 15, 2016.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.