11 minutes
Kubernetes 101: Building Scalable Applications - Deployments

Deployments
Deployments are the standard for running applications in Kubernetes, it protects Pods and will automatically restart them if anything goes wrong. Additionally, it offer features that add to the scalability and reliability of the application:
- Scalability: Scaling the number of application instances to meet the demand.
- Updates and Update Strategy: Zero-downtime application updates
We use the kubectl create deploy
command to create a Deployment:
[email protected]:~$ kubectl create deployment myweb --image=nginx --replicas=3
deployment.apps/myweb created
[email protected]:~$ kubectl describe deploy myweb
Name: myweb
Namespace: default
CreationTimestamp: Mon, 01 Nov 2021 09:08:57 +0000
Labels: app=myweb
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=myweb
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=myweb
Containers:
nginx:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: myweb-8764bf4c8 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 2m29s deployment-controller Scaled up replica set myweb-8764bf4c8 to 3
[email protected]:~$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myweb-8764bf4c8-6gxv8 1/1 Running 0 4m23s
pod/myweb-8764bf4c8-6mvn8 1/1 Running 0 4m23s
pod/myweb-8764bf4c8-q72nq 1/1 Running 0 4m23s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 36m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/myweb 3/3 3 3 4m23s
NAME DESIRED CURRENT READY AGE
replicaset.apps/myweb-8764bf4c8 3 3 3 4m23s
We created the myweb
deployment based on the nginx
image with 3 replicas or desired Pods. Notice the Labels
and Selector
fields.
The Deployment created the ReplicaSet
to ensure that a specified number of Pods are always running at any given time, and it created the Pods. Both the ReplicaSet and the Pods are managed by the Deployment.
You cannot manage Pods independently when they are part of a Deployment. When trying to delete a Pod, the Deployment kicks in and uses the ReplicaSet to make sure we have 3 running Pods:
[email protected]:~$ kubectl delete pod myweb-8764bf4c8-6gxv8
kpod "myweb-8764bf4c8-6gxv8" deleted
[email protected]:~$ kubectl get pods
NAME READY STATUS RESTARTS AGE
myweb-8764bf4c8-6mvn8 1/1 Running 0 14m
myweb-8764bf4c8-q72nq 1/1 Running 0 14m
myweb-8764bf4c8-qf2vc 0/1 ContainerCreating 0 5s
Deployment Scalability
Before Deployments
existed, ReplicaSets
were used to manage scalability. In the previous section we saw that our deployment created the necessary ReplicaSet
: Manage ReplicaSets only through Deployments. We do not care about managing ReplicaSets
individually.
We can use the kubectl scale deployment
command to manually scale an existing deployment:
kubectl scale deployment my-deployment --replicas=5
[email protected]:~$ kubectl scale deployment myweb --replicas=5
deployment.apps/myweb scaled
[email protected]:~$ kubectl describe deploy myweb | grep -i replicas
Replicas: 5 desired | 5 updated | 5 total | 5 available | 0 unavailable
[email protected]:~$ kubectl get pods
NAME READY STATUS RESTARTS AGE
myweb-8764bf4c8-44zxq 0/1 ContainerCreating 0 3s
myweb-8764bf4c8-6mvn8 1/1 Running 0 36m
myweb-8764bf4c8-7dpnx 0/1 ContainerCreating 0 3s
myweb-8764bf4c8-q72nq 1/1 Running 0 36m
myweb-8764bf4c8-qf2vc 1/1 Running 0 22m
Additionally, there’s the kubectl edit deployment
command which opens a text-editor for you, similar to systemctl edit
for editing Systemd Unit files. This command, however, does not allow you to modify every single setting of a deployment.
In the below example I changed the deployment namespace and replicas:
[email protected]:~$ kubectl edit deploy myweb
A copy of your changes has been stored to "/tmp/kubectl-edit-3283969971.yaml"
error: the namespace from the provided object "secret" does not match the namespace "default". You must pass '--namespace=secret' to perform this operation.
As you can see, Kubernetes isn’t happy about changing the namespace.
Deployment Updates
Deployments allow for zero-downtime application updates.
When an update is applied, a new ReplicaSet is created with the new properties: Pods with the new properties are started in the new ReplicaSet. After updating, the old ReplicaSet is no longer used and may be deleted. Or, you can keep it around for rolling-back. The deployment.spec.revisionHistoryLimit
is set to keep the last 10 ReplicaSets.
The deployment.spec.strategy.type
property defines how to handle updates:
RollingUpdate
: The default value. Replaces old Pods with new Pods in such a way to ensure the application remains available to users.Recreate
: Kill all existing Pods before creating new ones. The application will be down. More on the this later…
Let’s perform a rolling update of Nginx using the kubectl set
command. The command only accepts a limited amount of arguments.
[email protected]:~$ kubectl create deploy mynginx --image=nginx:1.14
deployment.apps/mynginx created
[email protected]:~$ kubectl describe deploy mynginx
Name: mynginx
Namespace: default
CreationTimestamp: Sat, 02 Apr 2022 10:29:52 +0000
Labels: app=mynginx
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=mynginx
Replicas: 1 desired | 1 updated | 1 total | 0 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=mynginx
Containers:
nginx:
Image: nginx:1.14
[email protected]:~$ kubectl get all --selector app=mynginx
NAME READY STATUS RESTARTS AGE
pod/mynginx-6b9d85f696-w4wpt 1/1 Running 0 64s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mynginx 1/1 1 1 64s
NAME DESIRED CURRENT READY AGE
replicaset.apps/mynginx-6b9d85f696 1 1 1 64s
Notice the Image
field in the output of the kubectl describe
command, the default StrategyType
, as well as how the middle part of the Pod name matches the suffix of the ReplicaSet
name: pod/mynginx-6b9d85f696-w4wpt
=> replicaset.apps/mynginx-6b9d85f6960
. We can conclude that this Pod belongs to that ReplicaSet.
Now, update the image version to 1.17
.
The kubectl set
command only accepts a limited amount of arguments.
[email protected]:~$ kubectl set
env image resources selector serviceaccount subject
[email protected]:~$ kubectl set image deploy mynginx nginx=nginx:1.17
deployment.apps/mynginx image updated
[email protected]:~$ kubectl get all --selector app=mynginx
NAME READY STATUS RESTARTS AGE
pod/mynginx-6b9d85f696-w4wpt 1/1 Running 0 7m4s
pod/mynginx-6d9cd8f877-g4dkv 0/1 ContainerCreating 0 8s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mynginx 1/1 1 1 7m4s
NAME DESIRED CURRENT READY AGE
replicaset.apps/mynginx-6b9d85f696 1 1 1 7m4s
replicaset.apps/mynginx-6d9cd8f877 1 1 0 9s
We see that our old ReplicaSet and Pod are still there, our application is still available, while a new Pod with the new Nginx image is being created. Once the new Pod is running, the old Pod will be deleted but the old (empty) ReplicaSet will still be there:
[email protected]:~$ kubectl get all --selector app=mynginx
NAME READY STATUS RESTARTS AGE
pod/mynginx-6d9cd8f877-g4dkv 1/1 Running 0 2m4s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mynginx 1/1 1 1 9m
NAME DESIRED CURRENT READY AGE
replicaset.apps/mynginx-6b9d85f696 0 0 0 9m
replicaset.apps/mynginx-6d9cd8f877 1 1 1 2m5s
The rolling update is complete and the old ReplicaSet is still available in case we need to roll back (covered later on in this article).
Labels, Selectors, and Annotations
Labels are key:value pairs that are defined in resources like Pods, Deployments and Services. They are either set automatically or can be set manually by an administrator. Each label key that is attached to a single object resource must be unique, though different objects can have the same label key:value pairs. This allows us to group objects, or map a specific structure onto objects, and query only the objects with a specific label.
If we look back at our previous deployment, we can see that each object in the deployment has the app=mynginx
label set:
[email protected]:~$ kubectl describe pod mynginx-6d9cd8f877-g4dkv | grep Labels:
Labels: app=mynginx
[email protected]:~$ kubectl describe rs mynginx | grep Labels:
Labels: app=mynginx
[email protected]:~$ kubectl describe deploy mynginx | grep Labels:
Labels: app=mynginx
So using a label selector, we can target the related objects of a specific application.
e.g.:
[email protected]:~$ kubectl get all --selector app=mynginx
NAME READY STATUS RESTARTS AGE
pod/mynginx-6d9cd8f877-g4dkv 1/1 Running 0 29m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mynginx 1/1 1 1 36m
NAME DESIRED CURRENT READY AGE
replicaset.apps/mynginx-6b9d85f696 0 0 0 36m
replicaset.apps/mynginx-6d9cd8f877 1 1 1 29m
Our kubectl create deployment
command automatically set the app=appname
label, where appname
is the name of the deployment.
Example:
[email protected]:~$ kubectl create deploy mylabel --image=nginx
deployment.apps/mylabel created
[email protected]:~$ kubectl label deploy mylabel state=demo
deployment.apps/mylabel labeled
[email protected]:~$ kubectl get deploy --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
mylabel 1/1 1 1 45s app=mylabel,state=demo
[email protected]:~$ kubectl get deploy --selector state=demo
NAME READY UP-TO-DATE AVAILABLE AGE
mylabel 1/1 1 1 70s
Notice that while we’ve given the deployment mylabel
a new label, this new label is not inherited by the resources or objects created by the deployment:
[email protected]:~$ kubectl get all --show-labels
NAME READY STATUS RESTARTS AGE LABELS
pod/mylabel-566dc5f574-ctkqg 1/1 Running 0 7m28s app=mylabel,pod-template-hash=566dc5f574
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE LABELS
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 168m component=apiserver,provider=kubernetes
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
deployment.apps/mylabel 1/1 1 1 7m28s app=mylabel,state=demo
NAME DESIRED CURRENT READY AGE LABELS
replicaset.apps/mylabel-566dc5f574 1 1 1 7m28s app=mylabel,pod-template-hash=566dc5f574
[email protected]:~$ kubectl get all --selector state=demo
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mylabel 1/1 1 1 8m58s
We can also remove a label. Let’s remove the label with the key app
from the Pod mylabel-566dc5f574-ctkqg
:
[email protected]:~$ kubectl label pod mylabel-566dc5f574-ctkqg app-
pod/mylabel-566dc5f574-ctkqg unlabeled
[email protected]:~$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mylabel-566dc5f574-ctkqg 1/1 Running 0 12m
pod/mylabel-566dc5f574-pxkdz 0/1 ContainerCreating 0 5s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 174m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mylabel 0/1 1 0 12m
NAME DESIRED CURRENT READY AGE
replicaset.apps/mylabel-566dc5f574 1 1 0 12m
[email protected]:~$ kubectl get all --selector app=mylabel
NAME READY STATUS RESTARTS AGE
pod/mylabel-566dc5f574-pxkdz 1/1 Running 0 3m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mylabel 1/1 1 1 15m
NAME DESIRED CURRENT READY AGE
replicaset.apps/mylabel-566dc5f574 1 1 1 15m
Our deployment could no longer find the Pod which is supposed to have the app=mylabel
label, so it created a new Pod: mylabel-566dc5f574-pxkdz
.
Since the Pod with the removed label is no longer managed by our deployment, we can delete it without our deployment (or rather ReplicaSet) recreating it.
Annotations can’t be used in queries, but are useful to provide detailed non-identifying metadata in an object: maintainer, author, license, …
Update Strategy
When a Deployment changes, the Pods are immediately updated according to the Update Strategy :
RollingUpdate
: Updates Pods one at a time to guarantee availability of the application.Recreate
: All Pods are killed and new Pods are created. This leads to temporary unavailability of the application which can be useful when different versions of an application cannot run simultaneously (e.g. a database).
The task of the Deployment is to ensure that enough Pods are running at all times. When a change is made, the changed version is deployed in a new ReplicaSet. The old ReplicaSet is scaled to 0 (deactivated) once the update was confirmed as successful. We can use kubectl rollout history
to get details about recent transactions, and kubectl rollout undo
to undo a previous change.
The RollingUpdate
options guarantee a certain minimal and maximum number of Pods to be always available:
maxUnavailable
: Determines the maximum number of Pods that are upgraded at the same time.maxSurge
: The number of Pods that can run beyond the desired number of Pods specified in the ReplicaSet to guarantee minimal availability.
[email protected]:~$ kubectl get deploy mylabel -o yaml
...
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: mylabel
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
...
Deployment History
At this point we know that Deployment updates create a new ReplicaSet with new properties, the old ReplicaSet is kept but is scaled down to 0 Pods. Since the old ReplicaSet is kept around, we can easily undo a change. We can use kubectl rollout history
to get details about recent roll outs, and kubectl rollout undo
to undo a previous change.
Let’s start by updating our mylabel
deployment. We’ll give all the Pods a new environment variable: foo=bar
:
kubectl set env deploy mylabel foo=bar
deployment.apps/mylabel env updated
[email protected]:~$ kubectl rollout history deploy mylabel
deployment.apps/mylabel
REVISION CHANGE-CAUSE
1 <none>
2 <none>
[email protected]:~$ kubectl rollout history deploy mylabel --revision=1
deployment.apps/mylabel with revision #1
Pod Template:
Labels: app=mylabel
pod-template-hash=566dc5f574
Containers:
nginx:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
[email protected]:~$ kubectl rollout history deploy mylabel --revision=2
deployment.apps/mylabel with revision #2
Pod Template:
Labels: app=mylabel
pod-template-hash=57f55bcb47
Containers:
nginx:
Image: nginx
Port: <none>
Host Port: <none>
Environment:
foo: bar
Mounts: <none>
Volumes: <none>
We can see that we added the environment variable in revision 2. So let’s roll back revision 1:
[email protected]:~$ kubectl rollout undo deploy mylabel --to-revision=1
deployment.apps/mylabel rolled back
Deployment Alternatives
There are two additional Deployments alternatives:
StatefulSets
: the workload API object used to manage stateful applications. We’ll cover these once we know more about Networking and Storage.DaemonSet
: ensures that all (or some) Nodes run a copy of a Pod (1 Pod, no replicas). As nodes are added to the cluster, Pods are added to them. As nodes are removed from the cluster, those Pods are garbage collected. Deleting a DaemonSet will clean up the Pods it created.
A simple use case for a DaemonSet is for example the need to run some sort of Agent on every worker-node.
The YAML code for DaemonSets needs to be created from scratch, you can’t use kubectl create
to generate the YAML :(
Example YAML code:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginxdaemon
namespace: default
labels:
k8s-app: nginxdaemon
spec:
selector:
matchLabels:
name: nginxdaemon
template:
metadata:
labels:
name: nginxdaemon
spec:
containers:
- name: nginx
image: nginx
[email protected]:~$ kubectl create -f daemon.yaml
daemonset.apps/nginxdaemon created
[email protected]:~$ kubectl get ds,pods
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/nginxdaemon 1 1 1 1 1 <none> 13s
NAME READY STATUS RESTARTS AGE
pod/nginxdaemon-5nn27 1/1 Running 0 13s
ContainersDockerKubernetesDeploymentsLabelsSelectorsUpdateStrategy
2191 Words
2021-11-03 00:00 +0000