These are custom notes that extend my notes from an Udemy course "Kubernetes for the Absolute Beginners - Hands-on". All course content rights belong to course creators.
This is the fourth article in the series, the previous one being Kubernetes Controllers | My Public Notepad
Let's look at the process of deploying our application in a production environment.
Let's assume we have a web server that needs to be deployed in a production environment. For high availability reasons, we need multiple instances of the web server running.
Also, whenever newer versions of application builds become available on the Docker registry, we would like to upgrade our Docker instances seamlessly. When upgrading the instances we do not want to upgrade all of them at once. This may impact users accessing our applications so we might want to upgrade them one after the other. That kind of upgrade is known as rolling updates.
If one of the upgrades we performed resulted in an unexpected error, we'd need to undo the
recent change. We should be able to roll back the changes that were recently carried out.
We might want to make multiple changes to our environment such as upgrading the underlying Web Server versions as well as scaling our environment and also modifying the resource allocations etc. We don't want to apply each change immediately after the command is run, instead we'd like to apply a pause to our environment, make the changes and then resume so that all the changes are rolled out together.
All of these capabilities are available with the Kubernetes Deployments.
Pods deploy single instances of our application such as the web application. Each container is encapsulated in pods. Multiple pods are deployed using Replication Controllers or Replica Sets and then comes Deployment which is a Kubernetes object that comes higher in the hierarchy.
Deployment provides us with the capability to:
- upgrade the underlying instances seamlessly using rolling updates
- undo changes
- pause and resume changes as required
How to create a deployment?
As with pods and replica sets, for this new Kubernetes object type, we first create the deployment definition file. Its contents is the same to the replica set definition file, except for the kind which is now going to be Deployment.
deployment_definition.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
type: front-end
spec:
template:
metadata:
name: myapp-pod
labels:
app: my-app
type: front-end
spec:
containers:
- name: nginx-container
image: nginx:1.7.0
replicas: 3
selector:
matchLabels:
type: front-end
template has a pod definition inside it.
To create deployment:
$ kubectl create -f deployment_definition.yaml
deployment.apps/myapp-deployment created
If number of deployment definition attributes is small, we can create a deployment without the full-blown definition file:
$ kubectl create deployment <deployment_name> --image=<image_name> --replicas=<replicas_count>
If we try to create a deployment that has the name same as some already created deployment, we'll get the error:
$ kubectl create -f ./minikube/deployment/deployment.yaml
Error from server (AlreadyExists): error when creating "./minikube/deployment/deployment.yaml": deployments.apps "myapp-deployment" already exists
This applies to any other Kubernetes object type.
Kubernetes object type that we put as the kind value is case-sensitive. If we use the name which does not start with capital letter, we'll get an error like this:
$ kubectl create -f /root/deployment-definition-1.yaml
Error from server (BadRequest): error when creating "/root/deployment-definition-1.yaml": deployment in version "v1" cannot be handled as a Deployment: no kind "deployment" is registered for version "apps/v1" in scheme "k8s.io/apimachinery@v1.29.0-k3s1/pkg/runtime/scheme.go:100"
To see created deployments:
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-deployment 3/3 3 3 105s
or, version with the plural:
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-deployment 3/3 3 3 107s
Deployment automatically creates a replica set:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp-deployment-6bddbfd569 3 3 3 3m35s
Deployment contains a single pod template, and generates one replicaset per revision.
To get more details about ReplicaSet:
$ kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp-deployment-6bddbfd569 3 3 3 3m58s nginx-container nginx pod-template-hash=6bddbfd569,type=front-end
The replica sets ultimately create pods:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
myapp-deployment-6bddbfd569-n7j4z 1/1 Running 0 6m6s
myapp-deployment-6bddbfd569-qzpgl 1/1 Running 0 6m6s
myapp-deployment-6bddbfd569-xlnz8 1/1 Running 0 6m6s
or
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-deployment-6bddbfd569-n7j4z 1/1 Running 0 6m8s
myapp-deployment-6bddbfd569-qzpgl 1/1 Running 0 6m8s
myapp-deployment-6bddbfd569-xlnz8 1/1 Running 0 6m8s
Note that:
replica_set_name = <deployment_name>-<pod_template_hash>
pod_name = <replica_set_name>-<arbitrary_id>=<deployment_name>-<pod_template_hash>-<arbitrary_id>
To see all the created Kubernetes objects at once run:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myapp-deployment-6bddbfd569-n7j4z 1/1 Running 0 14m
pod/myapp-deployment-6bddbfd569-qzpgl 1/1 Running 0 14m
pod/myapp-deployment-6bddbfd569-xlnz8 1/1 Running 0 14m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/myapp-deployment 3/3 3 3 14m
NAME DESIRED CURRENT READY AGE
replicaset.apps/myapp-deployment-6bddbfd569 3 3 3 14m
In this output above, observe how naming of Kubernetes objects unveils their hierarchy:
deployment >>
replicaset >>
pods
To get extensive information about the deployment:
$ kubectl describe deployment myapp-deployment
Name: myapp-deployment
Namespace: default
CreationTimestamp: Tue, 07 May 2024 23:44:32 +0100
Labels: app=myapp
type=front-end
Annotations: deployment.kubernetes.io/revision: 1
Selector: type=front-end
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=my-app
type=front-end
Containers:
nginx-container:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Node-Selectors: <none>
Tolerations: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: myapp-deployment-6bddbfd569 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 6s deployment-controller Scaled up replica set myapp-deployment-6bddbfd569 to 3
Updates and rollbacks in a deployment
Before we look at how we upgrade our application, let's try to understand rollouts and versioning in a deployment.
Rollouts and Versioning
After deployment is created, it triggers a rollout. A new rollout creates a new deployment revision. Let's call it Revision 1.
In the future when the application is upgraded, meaning when the container version is updated to a new one, a new rollout is triggered and a new deployment revision is created named Revision 2.
This helps us keep track of the changes made to our deployment and enables us to roll back to a previous version of deployment if necessary.
You can see the status of your rollout by running:
$ kubectl rollout status deployment/deployment_name
For example:
$ kubectl rollout status deployment/myapp-deployment
deployment "myapp-deployment" successfully rolled out
Note that it's mandatory to include the name of the Kubernetes object type - deployment in this case. If we don't include it, we'll get an error:
$ kubectl rollout status myapp-deployment
error: the server doesn't have a resource type "myapp-deployment"
To see the revisions and history of rollouts run:
$ kubectl rollout history deployment/myapp-deployment
deployment.apps/myapp-deployment
REVISION CHANGE-CAUSE
1 <none>
Deployment Strategies
There are two types of deployment strategies:
- recreate
- rolling update
Let's assume that we have five replicas of our web application instance deployed.
One way to upgrade these to a newer version is to destroy all of these and then create newer versions of application instances, meaning first destroy the five running instances, and then deploy five new instances of the new application version. The problem with this, as you can imagine, is that during the period after the older versions are down and before any newer version is up, the application is down and inaccessible to users. This strategy is known as the recreate strategy and thankfully this is not the default deployment strategy.
The second strategy is where we do not destroy all of them at once. Instead, we take down the older version and bring up a newer version one by one. This way the application never goes down and the upgrade is seamless. This strategy is called rolling update.
If we do not specify a strategy while creating the deployment, it will assume it to be rolling update (rolling update is the default deployment strategy).
How to update deployment?
Update can mean updating our application version, updating the version of Docker containers used, updating their labels or updating the number of replicas, etc.
We modify a deployment definition file (e.g. we change the version/tag of the container image) and then run:
$ kubectl apply -f deployment_definition.yaml
Running this command applies the changes, a new rollout is triggered and a new revision of the deployment is created.
But there is another way to do the same thing:
$ kubectl set image deployment/myapp-deployment nginx-container=nginx:1.7.1
You can use this command to update the image of our application but doing it this way will result in the deployment definition file having a different configuration.
How a deployment performs an upgrade (update)?
The difference between the recreate and rolling update strategies can be seen when we look at the deployments in detail:
$ kubectl describe deployment myapp-deployment
When the recreate strategy was used, the events indicate that the old replica set was scaled down to zero first and then the new replica set scaled up to five.
However, when the rolling update strategy was used, the old replica set was scaled down one at a time, simultaneously scaling up the new replica set one at a time.
We can see that during the update we have two replica sets, the old and a new one.
When a brand new deployment is created, say, to deploy five replicas, it first creates a replica set automatically, which in turn creates the number of pods required to meet the number of replicas.
When we upgrade our application, the Kubernetes deployment object creates a new replica set under the hood and starts deploying the containers there at the same time taking down the pods in the old replica set following a rolling update strategy.
This can be seen when we list the replica sets:
$ kubectl get replicasets
Here we'd see the old replica set with zero pods and the new replica set with five pods.
How a deployment performs a roll back?
Let's assume that once we upgrade our application, we realize something isn't right, something's wrong with the new version of build we used to upgrade. So we would like to roll back our update.
Kubernetes deployments allow us to roll back to a previous revision.
To undo a change run:
$ kubectl rollout undo deployment/myapp-deployment
The deployment will then destroy the pods in the new replica set and bring the older ones up in the
old replica set.
When we compare the output of the kubectl get replica sets command before and after the rollback, we will be able to notice this difference before the rollback.
The first replica set had zero pods and new replica set had five pods and this is reversed after the rollback is finished.
To summarize the commands:
kubectl create - to create the deployment
kubectl get deployments - to list the deployments
kubectl apply - to update the deployments
kubectl set image - to update the deployments
kubectl rollout status - to see the status of rollouts
kubectl rollout undo - to roll back a deployment operation
---