1. Overview

Kubernetes is a container orchestration platform that makes it easy to deploy applications. One mechanism Kubernetes offers that simplifies application deployment is the ability to record deployment and rollback as necessary.

In this tutorial, we’ll take a look at the Deployment resource of Kubernetes and dive deep into its update rollout mechanism.

2. Rollout of Deployment in Kubernetes

In Kubernetes, the Deployment resource is a declarative approach for managing the Pod and ReplicaSet resources. Specifically, we define the desired state of the application using DeploymentSpec. Then, the Deployment controller constantly works and monitors to ensure the actual state is as expected.

At a minimum, Deployment for an application would define the number of replicas as well as the pod template:

$ cat myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: alpine:latest
        command: ["sleep", "infinity"]

The manifest file above defines a custom application that uses the alpine image. By setting the replica count to three, we can be sure that there’ll always be three pods running at any given time.

Besides defining its steady state specification, Deployment also allows us to define the way we want the strategy to release updates, known as rollout. For example, changing the version of the underlying image for Deployment would constitute an update to the resource. The update will then require a rollout method to propagate the change to all the pods managed by the Deployment resources.

3. Rollout Strategies

We can control the rollout mechanism of the Deployment resource using the strategy field of DeploymentSpec. Specifically, we can change the rollout strategy using the type field under the strategy field.

There are mainly two different ways for rolling out an update for Deployment resources, namely Recreate and RollingUpdate.

The Recreate rollout method recreates all the pod instances at the same time. This can be problematic for running services that are serving live traffic as the recreation of all the instances at the same time causes downtime. For example, we can change the rollout method to the myapp-deployment resource to Recreate:

$ cat myapp-deployment-recreate.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    ...

The RollingUpdate strategy, which is the default rollout method for Deployment resources takes a more gradual approach. Instead of recreating all the pods at the same time, we can perform the update to a subset of the pods, leaving the rest of the pods to continue to serve the traffic.

Furthermore, by setting the maxSurge and maxUnavailable parameters, we can further refine the strategy. For example, we can update our myapp-deployment resource to perform rolling updates with maxUnavailable set to 0 and maxSurge to 2:

$ cat myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    ...

For the resource above, during a rollout, the controller spawns two new pods under Deployment that are running the latest definition due to our maxSurge of 2. Additionally, by setting maxUnavailable to 0, the controller won’t terminate any running instances until either one of the new pods is ready to accept traffic.

If we’re constrained by the compute resources available, we can instead set maxSurge to 0 and maxUnavailable to a non-zero value. This configuration makes the controller terminate a running pod before starting an updated pod.

4. Managing the Rollout Using the kubectl Command

The kubectl command-line tool offers the rollout command for managing the rollout. Let’s look at the different subcommands we can use for checking and modifying the rollout of Deployment.

Before we begin, let’s create myapp-deployment in our cluster:

$ cat myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: alpine:3.16
        command: ["sleep", "infinity"]
$ kubectl apply -f myapp-deployment.yaml
deployment.apps/myapp-deployment created

Our subsequent demonstrations use this particular Deployment in the cluster as the working example.

4.1. Checking the History of the Deployment Rollout

To check the rollout history of a Deployment resource, we can use the history subcommand of the kubectl rollout command. Specifically, we can run kubectl rollout history deployment/myapp-deployment:

$ kubectl rollout history deployment/myapp-deployment
deployment.apps/myapp-deployment
REVISION  CHANGE-CAUSE
1         <none>

The output gives us a Revision column and the Change-Cause column. Whenever there’s a new change to the same Deployment resource, the controller increments the Revision value by one.

Besides that, the Change-Cause column is a column that displays the kubernetes.io/change-cause annotation on our application. The kubernetes.io/change-cause annotation is a way for us to document the changes we apply to the Deployment resource.

To simulate a version bump upgrade for our application, let’s change the image version from 3.16 to 3.17:

$ cat myapp-deployment.yaml | sed 's;3.16;3.17;' | kubectl apply -f -
deployment.apps/myapp-deployment configured

The command above first reads the content of the myapp-deployment.yaml using the cat command. Then, it pipes the output to sed to substitute the string 3.16 with 3.17. Finally, it pipes the output to the kubectl apply command.

Let’s look at the rollout history list for myapp-deployment now:

$ kubectl rollout history deployment/myapp-deployment
deployment.apps/myapp-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

As we can see, there are now two records of rollout history for myapp-deployment. However, the Change-Cause column for the second revision remains empty. This is because we didn’t annotate our Deployment resource using kubernetes.io/change-cause. Let’s annotate our Deployment resource to document the change:

$ kubectl annotate deployment/myapp-deployment kubernetes.io/change-cause="update image version from 3.16 to 3.17"
deployment.apps/myapp-deployment annotate
$ kubectl rollout history deployment/myapp-deployment
deployment.apps/myapp-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         update image version from 3.16 to 3.17

It’s worth noting that annotating the Deployment resource doesn’t technically result in a rollout and therefore won’t result in a new history record.

4.2. Rolling Back a Deployment Rollout

One advantage of having historical records of all the deployment rollouts is that we can roll back our Deployment resources to any previous revisions using the kubectl rollout undo command. For example, we can rollback our myapp-deployment to the immediate previous revision using the kubectl rollout undo command:

$ kubectl rollout undo deployment/myapp-deployment
deployment.apps/myapp-deployment rolled back

To roll back to a specific revision, we can pass the –to-revision option followed by the revision number we want to roll back to. For instance, we can rollback the myapp-deployment resource to revision 2 using the –to-revision=2 option:

$ kubectl rollout undo --to-revision=2 deployment/myapp-deployment

4.3. Pausing and Resuming a Deployment Rollout

The Deployment controller always monitors and triggers a rollout whenever it detects any specification drift between the running pods and the backing definition. In other words, applying an update to the Deployment resource causes the controller to instantly perform a rolling update to the pods.

To prevent the controller from automatically rolling out the updates, we can issue the kubectl rollout pause command on our Deployment resource. By marking our Deployment resource as paused, the controller won’t automatically roll out the changes on the Deployment level to the pods.

For example, let’s pause our myapp-deployment using kubectl rollout pause:

$ kubectl rollout pause deployment/myapp-deployment
deployment.apps/myapp-deployment paused

Then, we can change the image version of the Deployment manifest to 2.15, just to observe the effect it has on the paused Deployment:

$ cat myapp-deployment.yaml | sed 's;3.16;3.15;' | kubectl apply -f -
deployment.apps/myapp-deployment configured

Although the command says that the Deployment resource has been configured, the update isn’t rolled out to all the underlying pods because Deployment has been paused. To check the status of the rollout, we use the kubectl rollout status command, followed by the resource name:

$ kubectl rollout status deployment/myapp-deployment
Waiting for deployment "myapp-deployment" rollout to finish: 0 out of 3 new replicas have been updated...

To resume the rollout, we can issue the kubectl rollout resume command to the resource:

$ kubectl rollout resume deployment/myapp-deployment
deployment.apps/myapp-deployment resumed

Checking on the rollout status of our myapp-deployment resource now shows that the rollout is successful:

$ kubectl rollout status deployment/myapp-deployment
deployment "myapp-deployment" successfully rolled out

5. Conclusion

In this article, we learned that the Deployment resource, an abstraction over ReplicaSet, allows us to easily roll out a change. Then, we discussed the difference between Recreate and RollingUpdate strategies.

Furthermore, we saw the kubectl rollout command for various operational needs. For instance, using kubectl rollout history, we can retrieve the rollout history for a particular Deployment. Then, the kubectl rollout undo command allows us to undo a rollout. Finally, the kubectl rollout pause and resume sub-commands allow us to temporarily pause an in-progress rollout and resume them.