Kubernetes Pod, ReplicaSet and Deployment

Kubernetes Pod, ReplicaSet and Deployment
Photo by Rinson Chory / Unsplash

What is Kubernetes?

Kubernetes is an open source container orchestration engine for automating deployment, scaling, and management of containerized applications. It’s supported by all hyperscaller cloud providers and widely used by different companies. Amazon, Google, IBM, Microsoft, Oracle, Red Hat, SUSE, Platform9, IONOS and VMware offer Kubernetes-based platforms or infrastructure as a service (IaaS) that deploy Kubernetes.

Pod

A Pod is the smallest Kubernetes deployable computing unit. It contains one or more containers with shared storage and network. Usually, Pods have a one-to-one relationship with containers. To scale up, we add more Pods and to scale down, we delete pods. We don't add more containers to a pod for scaling purposes.

A Pod can have multiple containers with different types. We use a multi-container Pod when the application needs a helper container to run side by side with it. This helper container will be created when the application container is created and it will be deleted if the application container is deleted. They also share the same network (which means that they can communicate with each other using localhost) and same storage (using volumes).

We can create a pod using the following command,

$ kubectl run nginx --image nginx
create a Pod named nginx using the nginx docker image

This command will create a Pod named nginx using the nginx docker image available in docker-hub. To confirm the pod is created successfully, We can run the following command that will list pods in the default namespace,

$ kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          15s
list pods in default namespace.

We also can create Pods using yaml configuration file,

apiVersion: v1
kind: Pod
metadata:
  name: redis-pod
spec:
  containers:
  - name: redis-container
    image: redis
    ports:
    - containerPort: 6379
redis-pod.yaml

To create this redis Pod, run the following command,

$ kubectl create -f redis-pod.yaml
create redis pod from yaml file

We also can create a multi-container Pod using yaml file,

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: web-app
  name: web-app
  namespace: default
spec:
  containers:
  - image: web-app
    imagePullPolicy: Always
    name: web-app
  - image: log-agent
    imagePullPolicy: IfNotPresent
    name: log-agent
  priority: 0
  restartPolicy: Always
multi-container-pod.yaml

To create this Pod, run the following command,

$ kubectl create -f multi-container-pod.yaml
create multi-container pod from yaml file

This will deploy a logging agent alongside with our web app container to process the logs and send it to a central logging service for example. This pattern is called sidecar.

Replicaset

A replicaset ensures that the specified number of Pods (replicas) are running at all times. If a Pod goes down for any reason, It automatically creates a new one using the template specified in the yaml file. Here is an example replicaset definition file,

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  labels:
    app: nginx
  name: nginx-replicaset
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
      dnsPolicy: ClusterFirst
      restartPolicy: Always
nginx-replicaset.yaml

To create this repicaset, run the following command,

$ kubectl create -f nginx-replicaset.yaml
create nginx replicaset from yaml file

Note: In order for the replicaset to work, the spec.selector.matchLabels must match the spec.template.labels because the replicaset uses this template to create pods when needed.

Deployment

A Deployment is the recommended choice when it comes to deploy stateless applications in Kubernetes. It automatically creates a replicaset under the hood to ensure the specified number of Pods are running at all times. It also describe how to deploy a new version using deployment strategy, here is an example yaml definition file for a deployment,

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-nginx-deploy
  labels:
    app: nginx-deploy
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.14.2
        ports:
        - containerPort: 8080
nginx-deploy.yaml

To create this nginx Deployment, run the following command,

nginx-deploy.yaml redis-pod.yaml
create redis pod from yaml file

This will create a deployment named example-nginx-deploy using the nginx docker image available in docker-hub. To confirm the deployment is created successfully, We can run the following command that will list deployments in the default namespace,

$ kubectl get deploy
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
example-nginx-deploy   5/5     5            5           63s
list deployments in default namespace.

This will create a ReplicaSet under the hood to make sure all replicas are up all time,

$ kubectl get rs
NAME                             DESIRED   CURRENT   READY   AGE
example-nginx-deploy-bbc95f979   5         5         5       2m19s
list replicasets in default namespace.

It will also create 5 replicas using the same docker image specified in the yaml definition file, It uses the name of the deployment as a prefix for the name of the pod as shown below,

$ kubectl get pods
NAME                                   READY   STATUS    RESTARTS   AGE
example-nginx-deploy-bbc95f979-2g7kh   1/1     Running   0          97s
example-nginx-deploy-bbc95f979-dlq8l   1/1     Running   0          2m6s
example-nginx-deploy-bbc95f979-gb97h   1/1     Running   0          97s
example-nginx-deploy-bbc95f979-n6xdj   1/1     Running   0          97s
example-nginx-deploy-bbc95f979-pwphh   1/1     Running   0          97s
list pods in default namespace.

Note: In order for the deployment to work, the spec.selector.matchLabels must match the spec.template.labels because the deployment uses this template to create pods when the current number of pods doesn't match the desired number of pods.

Scaling a Deployment

When creating a Deployment, We need to specify the number or replicas (default is one). Sometimes we need to scale up (increase the number of replicas) or scale down (decrease the number of replicas) manually. We can do this by running the following commands:

Scaling up example-nginx-deply from 5 replicas to 10 replicas:

$ kubectl scale deployment example-nginx-deploy --replicas 10
deployment.apps/example-nginx-deploy scaled
Scaling up example-nginx-deply from 5 replicas to 10 replicas

To confirm, We can list all pods in the default namespace. As you can see below, There are five new replicas created.

$ kubectl get pods
NAME                                    READY   STATUS              RESTARTS   AGE
example-nginx-deploy-5674dfc4b7-7zzpr   0/1     ContainerCreating   0          3s
example-nginx-deploy-5674dfc4b7-8dhhx   0/1     ContainerCreating   0          3s
example-nginx-deploy-5674dfc4b7-8v8mw   1/1     Running             0          32m
example-nginx-deploy-5674dfc4b7-95j9p   1/1     Running             0          32m
example-nginx-deploy-5674dfc4b7-gl97j   0/1     ContainerCreating   0          3s
example-nginx-deploy-5674dfc4b7-gtq4w   1/1     Running             0          32m
example-nginx-deploy-5674dfc4b7-kcj2f   0/1     ContainerCreating   0          3s
example-nginx-deploy-5674dfc4b7-m4wkq   0/1     ContainerCreating   0          3s
example-nginx-deploy-5674dfc4b7-tsw6s   1/1     Running             0          32m
example-nginx-deploy-5674dfc4b7-xfg7q   1/1     Running             0          32m
list pods in default namespace.

Scaling down example-nginx-deply from 10 replicas to 3 replicas:

$ kubectl scale deployment example-nginx-deploy --replicas 3
deployment.apps/example-nginx-deploy scaled
Scaling down example-nginx-deply from 10 replicas to 3 replicas

To confirm, We can list all pods in the default namespace. As you can see below, There are only three replicas running and the rest are terminating.

$ kubectl get pods
NAME                                    READY   STATUS              RESTARTS   AGE
example-nginx-deploy-5674dfc4b7-6pncj   1/1     Terminating   0          33s
example-nginx-deploy-5674dfc4b7-8v8mw   1/1     Running       0          39m
example-nginx-deploy-5674dfc4b7-95j9p   1/1     Running       0          39m
example-nginx-deploy-5674dfc4b7-9r7kv   1/1     Terminating   0          33s
example-nginx-deploy-5674dfc4b7-gtq4w   1/1     Running       0          39m
example-nginx-deploy-5674dfc4b7-lgwnj   1/1     Terminating   0          33s
example-nginx-deploy-5674dfc4b7-m4hc8   1/1     Terminating   0          33s
example-nginx-deploy-5674dfc4b7-zr485   1/1     Terminating   0          33s
list pods in default namespace.

Zero downtime Deployment

The main point of using a deployment in Kubernetes is that we can easily deploy a new version of our application using the deployment strategy defined in the deployment and we can also roll it back easily if this new version didn't work as expected with zero downtime.

We use .spec.strategy to specify how we want to deploy this new version. We have two types of deployment strategies, Recreate and RollingUpdate. RollingUpdate is the default value.

Recreate Deployment Strategy means that all current pods with the old version will be killed and new pods will be created using the new version. This will cause the application to be down for sometime (depends on how long it takes to start the app). This is not the preferred option because of the downtime.

RollingUpdate Deployment Strategy means that it will run both versions (the old and new one) for sometime until the deployment is completed. based on the .spec.strategy.rollingUpdate.maxUnavailable and .spec.strategy.rollingUpdate.maxSurge values, it will create new pods with the new version and delete old pods with the old version. if we configured this correctly, we can assure that the deployment is zero downtime deployment.

Here is an example to make it more clear,

spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 30%
      maxUnavailable: 20%
rolling update configuration

In this example deployment, we have ten replicas and RollingUpdate strategy with maxSurge is 30% (it can also be an absolute number) which means that this deployment can run 30% more replicas if needed (instead of creating 10 replicas, it can create 13 replicas temporary for both old and new versions) and maxUnavailable is 20% (it can also be an absolute number) which is the maximum number of unavailable pods during the update process (20% of ten replicas is two pods)

The default value for maxSurge and maxUnavailable is 25%.

Rollout and Rollback a Deployment

To deploy a new version of a deployment we use the following command,

$ kubectl set image deployment/example-nginx-deploy nginx-container=nginx:1.16.1 --record
deployment.apps/example-nginx-deploy image updated
deploy nginx 1.16.1

Note: The record flag is deprecated but there is no alternative to it yet. for more info check this.

To check the status of the deployment,

$ kubectl rollout status deployment/example-nginx-deploy
Waiting for deployment "example-nginx-deploy" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "example-nginx-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-nginx-deploy" rollout to finish: 4 of 5 updated replicas are available...
deployment "example-nginx-deploy" successfully rolled out
check the status of the rollout

As you can see from the logs, it first created two pods with the new version. Once they are up and running it starts to terminate old pods (using old version) and create new pods (using new version) one by one to make sure the application is up and running at all times.

we can see this more clearly from the deployment events,

$ kubectl describe deployments


Name:                   example-nginx-deploy
Namespace:              default
CreationTimestamp:      Sun, 18 Sep 2022 11:42:41 +0200
Labels:                 app=example-nginx-deploy
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=example-nginx-deploy
Replicas:               5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  20% max unavailable, 30% max surge
Pod Template:
  Labels:  app=example-nginx-deploy
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   example-nginx-deploy-78788d9bbd (5/5 replicas created)
Events:
  Type    Reason             Age    From                   Message
  ----    ------             ----   ----                   -------
  Normal  ScalingReplicaSet  8m13s  deployment-controller  Scaled up replica set example-nginx-deploy-78788d9bbd to 2
  Normal  ScalingReplicaSet  8m13s  deployment-controller  Scaled down replica set example-nginx-deploy-bbc95f979 to 4
  Normal  ScalingReplicaSet  8m13s  deployment-controller  Scaled up replica set example-nginx-deploy-78788d9bbd to 3
  Normal  ScalingReplicaSet  4m57s  deployment-controller  Scaled down replica set example-nginx-deploy-bbc95f979 to 3
  Normal  ScalingReplicaSet  4m57s  deployment-controller  Scaled up replica set example-nginx-deploy-78788d9bbd to 4
  Normal  ScalingReplicaSet  4m53s  deployment-controller  Scaled down replica set example-nginx-deploy-bbc95f979 to 2
  Normal  ScalingReplicaSet  4m53s  deployment-controller  Scaled up replica set example-nginx-deploy-78788d9bbd to 5
  Normal  ScalingReplicaSet  4m49s  deployment-controller  Scaled down replica set example-nginx-deploy-bbc95f979 to 1
  Normal  ScalingReplicaSet  4m46s  deployment-controller  Scaled down replica set example-nginx-deploy-bbc95f979 to 0

Let's say nginx 1.16.1 is a buggy version and we want to rollback to the previous version. First we need to check the rollout history,

kubectl rollout history deployment/example-nginx-deploy
deployment.apps/example-nginx-deploy
REVISION  CHANGE-CAUSE
1         <none>
3         kubectl set image deployment/example-nginx-deploy nginx=nginx:1.14.2 --record=true
4         kubectl set image deployment/example-nginx-deploy nginx=nginx:1.16.1 --record=true

To rollback to previous version, Run the following command,

kubectl rollout undo deployment/example-nginx-deploy
deployment.apps/example-nginx-deploy rolled back
rollback to nginx 1.14.2

Conclusion

A Pod is the smallest Kubernetes deployable computing unit. It contains one or more containers with shared storage and network. A replicaset ensures that the specified number of Pods (replicas) are running at all times. If a Pod goes down for any reason, It automatically creates a new one using the template specified in the yaml file. A Deployment is the recommended choice when it comes to deploy stateless applications in Kubernetes. It automatically creates a replicaset under the hood to ensure the specified number of Pods are running at all times. It can be easily scaled up or down using kubectl scale command. It also enables us to easily rollout a new version of our application using the specified deployment strategy and rollback to previous version if needed.