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 ... 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!
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:
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.