Kubernetes 101 - First 3 Components to Learn (part 3)

Overview

Part 1 and Part 2 covered pods and services, which are two foundational building blocks to run an app in Kubernetes. This post will describe a third piece called Deployment that helps you run the pod with more reliability.

Deployment

A Deployment is a better way to run your pod, especially in a production environment. It describes how the pod is deployed, accessed, upgraded and scaled. Under the covers it also creates a helper, called a ReplicaSet, to ensure the pod continues running in its requested state. The ReplicaSet is the critical secret sauce that provides auto-healing. If an instance of the pod terminates, the ReplicaSet starts a new instance to replace it.

Simple Example

Here’s a deployment object spec to run the hello pod covered in Part 1 that we’ll store in a file named hello-world-deployment.yaml.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
  labels:
    app: hello-website
spec:
  replicas: 3	
  selector:
    matchLabels:
      app: hello-website
  template:
    metadata:
      labels:
        app: hello-website
    spec:
      containers:
        - name: hello-container
          image: docker.io/busybox:latest
          command: ["/bin/sh"]
          args: ["-c", "while true; do echo 'Hello'; sleep 3; done"]
        - name: web-service
          image: docker.io/nginx:latest
          ports:
            - containerPort: 80

You can see that the container definition is the same as it was in the Part 1 pod example. The deployment spec adds a replicas field to specify how many instances of the pod to run. It also adds controls for how updates are applied, which will be covered later.

Demo

Apply the deployment file, as we did for the pod, and a deployment resource is created in the cluster.

~ » kubectl apply -f hello-world-deployment.yaml
deployment.apps/hello-deployment created
~ » kubectl get deployments
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
hello-deployment     3/3     3            3           32s

A ReplicaSet is also created to manage the pod instances and make sure the correct number are running.

~ » kubectl get rs
NAME                            DESIRED   CURRENT   READY   AGE
hello-deployment-987c7866b      3         3         3       48s
~ » kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
hello-deployment-987c7866b-bhxh9      2/2     Running   0          37s
hello-deployment-987c7866b-qnqdg      2/2     Running   0          37s
hello-deployment-987c7866b-kl28b      2/2     Running   0          37s

You can see the ReplicaSet in action if you delete one of the pod instances. Here we’ll delete hello-deployment-987c7866b-bhxh9.

~ » kubectl delete pod hello-deployment-987c7866b-bhxh9
pod "hello-deployment-987c7866b-bhxh9" deleted

Very briefly, the ready count will drop to 2. But very quickly it will be back up to 3 as the ReplicaSet launches a new instance to replace the one we deleted.

~ » kubectl get rs
NAME                            DESIRED   CURRENT   READY   AGE
hello-deployment-987c7866b      3         3         2       3m23s
~ » kubectl get rs
NAME                            DESIRED   CURRENT   READY   AGE
hello-deployment-987c7866b      3         3         3       116s
~ » kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
hello-deployment-987c7866b-qnqdg      2/2     Running   0          2m13s
hello-deployment-987c7866b-kl28b      2/2     Running   0          2m13s
hello-deployment-987c7866b-hqvmc      2/2     Running   0          63s
Scaling

We can change the desired instance count several ways, such as editing the resource definition file and reapplying it. It’s also simple to adjust it directly with the kubectl scale command as follows.

~ » kubectl scale deployment hello-deployment --replicas=5
deployment.apps/hello-deployment scaled
~ » kubectl get rs
NAME                            DESIRED   CURRENT   READY   AGE
hello-deployment-987c7866b      5         5         3       4m20s
~ » kubectl get rs
NAME                            DESIRED   CURRENT   READY   AGE
hello-deployment-987c7866b      5         5         5       4m32s
~ » kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
hello-deployment-987c7866b-kl28b      2/2     Running   0          4m40s
hello-deployment-987c7866b-hqvmc      2/2     Running   0          3m30s
hello-deployment-987c7866b-2hj6d      2/2     Running   0          79s
hello-deployment-987c7866b-hmbxn      2/2     Running   0          24s
hello-deployment-987c7866b-qbfcq      2/2     Running   0          24s

Along with the deployment definition and status, a kubectl describe command will show events that have happened for it. Below shows events for the initial scaling when created plus the change when we scaled to 5.

~ » kubectl describe deployment hello-deployment
Name:                   hello-deployment
Namespace:              default
CreationTimestamp:      Fri, 01 Apr 2022 14:01:37 -0700
Labels:                 app=hello-website
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=hello-website
Replicas:               5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=hello-website
  Containers:
   hello-container:
    Image:      docker.io/busybox:latest
    Port:       <none>
    Host Port:  <none>
    Command:
      /bin/sh
    Args:
      -c
      while true; do echo 'Hello'; sleep 3; done
    Environment:  <none>
    Mounts:       <none>
   web-service:
    Image:        docker.io/nginx:latest
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Progressing    True    NewReplicaSetAvailable
  Available      True    MinimumReplicasAvailable
OldReplicaSets:  <none>
NewReplicaSet:   hello-deployment-987c7866b (5/5 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  18m   deployment-controller  Scaled up replica set hello-deployment-987c7866b to 3
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled up replica set hello-deployment-987c7866b to 5
  
Update Strategy & Rollbacks

There are a number of options or strategies for how pod instances are replaced when applying an update. That might be a good topic for a future post. For now, a couple simple examples are Recreate and RollingUpdate.

Recreate strategy will immediately delete all instances of the old version and create new instances using the new version. An example resource definition is below. Obviously this strategy causes availability downtime since all instances are deleted immediately, so it’s not acceptable for all apps.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
  labels:
    app: hello-website
spec:
  replicas: 3	
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: hello-website
  template:
    metadata:
      labels:
        app: hello-website
    spec:
      containers:
        - name: hello-container
          image: docker.io/busybox:latest
          command: ["/bin/sh"]
          args: ["-c", "while true; do echo 'Hello'; sleep 3; done"]
        - name: web-service
          image: docker.io/nginx:latest
          ports:
            - containerPort: 80

RollingUpdate strategy improves on that by only deleting and creating a few instances at a time, in a rolling wave until all instances have been updated. An example resource definition is below. There are extra fields, such as MaxSurge and MaxUnavailable, that let us specify how aggressive the rolling update happens.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
  labels:
    app: hello-website
spec:
  replicas: 3	
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2        # how many pod instances to add at one time
      maxUnavailable: 0  # how many pod instances can be unavailable at one time
  selector:
    matchLabels:
      app: hello-website
  template:
    metadata:
      labels:
        app: hello-website
    spec:
      containers:
        - name: hello-container
          image: docker.io/busybox:latest
          command: ["/bin/sh"]
          args: ["-c", "while true; do echo 'Hello'; sleep 3; done"]
        - name: web-service
          image: docker.io/nginx:latest
          ports:
            - containerPort: 80

Conclusion

The Kubernetes Deployment is the most common way to run workloads. It runs your pod in a way that automatically heals and facilitates dynamic scaling.

In this 3 part series, you saw some basic building blocks for running your app in Kubernetes. After defining a few basic components, your app can be deployed and running in no time.