Rolling Updates and Rollbacks using Kubernetes Deployments

2874

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

...

Deploy Commands:

 rollout        Manage a deployment rollout

 rolling-update Perform a rolling update of the given ReplicationController

 scale          Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job

```

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!

Read the next articles in the series: 

Helm: The Kubernetes Package Manager

Federating Your Kubernetes Clusters — The New Road to Hybrid Clouds

Enjoy Kubernetes with Python

Want to learn more about Kubernetes? Check out the new, online, self-paced Kubernetes Fundamentals course from The Linux Foundation. Sign Up Now!

Sebastien Goasguen (@sebgoa) is a long time open source contributor. Member of the Apache Software Foundation, member of the Kubernetes organization, he is also the author of the O’Reilly Docker cookbook. He recently founded skippbox, which offers solutions, services and training for Kubernetes.