Managing State with Deployments
Course Reading
Learning objectives
- Discuss Deployment configuration details
- Scale Deployments up and down
- Implement rolling updates and rollback
- Use Labels to select various objects
Overview
When you run kubectl run
, the default controller is a Deployment. This chapter focuses more in detail in the configuration of them. Deployments, like other objects, can be defined in YAML or JSON, and when added to the cluster, a ReplicaSet and Pod will be created automatically.
Deployment settings can be changed via an update which will create a new ReplicaSet which will then create new Pods. Updates can be applied as a block change or via a rolling update. Most updates can be configured by editing the YAML file defining the object and then running a kubectl apply
. A kubectl edit
can also be used to modify the in-use config. Previous ReplicaSet versions are kept in case a rollback is needed.
This section also focuses on labels. These are essential for Kubernetes administration, but they are not an API resource. Labels are user defined key-value pairs attached to resources stored in the object metadata. Tey can be used to query or select resources in the cluster, which allows for flexible and complex management. Since labels are arbitrary, you can select all resources that share a label value without having to figure out what kind or how many of these resources exist.
Deployments
Deployments came about as enhanced versions of ReplicaControllers (RC). ReplicaControllers ensure a specified number of Pod replicas are running at any one time. They also give the ability to perform rolling updates, however all updates are managed on the client side, which is problematic if the client loses connection which can leave the cluster in an unplanned state.
To avoid these issues Deployments were introduced in the apps/v1
API group. They allow for server-side updates to Pods at specified rates. Deployments can be used for canary and other deployment patterns. A Deployment generates a ReplicaSet, which offer more selection features than ReplicaControllers. One example is matchExpressions
.
You can create a Deployment from an existing image with kubectl
like so
kubectl create deployment dev-web --image=nginx
Object relationship
The diagram shows relationships between Kubernetes object. Each of the boxes represent the controllers running as a thread of the kube-controller-manager, where each controller queries the kube-apiserver for the current state of the tracked object.
Walking through the diagram, we first see a Nginx container running version 1.11. Kubernetes does not directly manage the container. Instead, the kubelet daemon checks the Pod spec by checking the runtime engine for the current status. Next to the container we see a Pod, which is a representation of a watch loop checking the container status. The kubelet continues to compare the container spec to the response from the runtime engine, and will terminate the Pod if needed. Next to the Pod is an example of a multi-container Pod. This works on the same principle as a single container Pod, except the watch loop needs to monitor both containers in this case.
Next is the ReplicaSet, which is a controller that ensured a certain number of replicas of a Pod are running. Each Pod is deployed with the same specification. If a Pod is terminated or a new one is found, the ReplicaSet will create or terminate Pods until the number of running Pods matching the specified replica count. Since all the replicas are the same, any could be terminated should the spec demand a change in the number of running Pods.
Finally we have the deployment, which is a controller that manages the ReplicaSets. They allow us to manage the version of images running in the Pods they oversee. If a Deployment is changes, a new ReplicaSet is created, which in turn deploys new Pods using the updated podSpec. Once new Pods are running the Deployment instructs the old ReplicaSet to start terminating its Pods and eventually itself once the Pods are all shut down, and only the new ReplicaSet, with its Pods, is running.
Deployment details
If you ran the command to create the nginx
deployment in the Deployments section above you could then list the YAML or JSON output for the resources it just created like so:
kubectl get deployments,rs,pods -o yaml
kubectl get deployments,rs,pods -o json
The first few lines of the YAML response should look like this.
apiVersion: v1
items:
- apiVersion: apps/v1
kind: Deployment
Let's discuss each of these lines.
First is the apiVersion
. This denotes the stability of the object we ar looking at. v1
denotes the object as stable, and it referring to the List
type that the items
parameter defines.
Within the items
we see the first object has an apiVersion
of apps/v1
, which means it is also stable. We also see the kind
field, showing us we are looking at the specification for a Deployment
.
Deployment configuration metadata
Continuing from the YAML output we got from the previous section, we next see the metadata
field for the deployment which looks something like this.
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-05-10T18:21:45Z"
generation: 1
labels:
app: dev-web
name: dev-web
namespace: default
resourceVersion: "278969"
uid: 519529f2-6e77-4413-8df5-c498bd127e71
This is the section that would contain any labels, annotations, or other non-configuration information. Here's what each of the sub-parameters in the metadata
is used for:
annotations
These values do not configure the object, but are helpful to provide further information for tracking or for third-party applications. Unlike labels, annotations cannot be used for selection with kubectl
.
creationTimestamp
This is the original creation time of the object. It does not update when the object is updated.
generation
Is a count of how many times the object has been edited. Changing things such as the number of replicas would modify this field.
labels
Labels are similar to annotations except they can be used for selection with kubectl
or other API calls. This is helpful for cluster administration to select groups of different objects that may be related or for excluding other objects from queries.
name
This metadata field is a required string value. When we created the object we passed it, dev-web
. The name must be unique to the namespace the object is a part of.
resourceVersion
This is a value tied to the etcd database to help with object concurrency. Changes to the database cause this number to change.
uid
A unique ID for the life of the object.
Also, it should be noted that the output will not include every configuration setting, as many are set to false by default and therefore are not shown, such as podAffinity
and nodeAffinity
.
Deployment configuration spec
Continuing on down the output, we next see the spec
section. There a two spec
declarations for a deployment, the first is for ReplicaSet, and the second for the Pod. The first one looks like this in the output from previously.
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: dev-web
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
progressDeadlineSeconds
This is the time until a progress error is reported when a change occurs. These errors could happen for a variety of reasons, such as quotas, image issues, or limit ranges.
replicas
Since this spec
defines a ReplicaSet, this parameter is needed to determine how many replicas of a Pod the ReplicaSet needs to ensure. If you changes the replicas to 2 with a kubectl edit
, a second Pod would be spun up.
revisionHistoryLimit
This is the number of old ReplicaSet specifications that are retained in case a rollback is needed.
selector
The selector
field is a collection of values ANDed together. All must be satisfied for the replica to match. Do not create a Pod that matches these selectors as the Deployment controller could potentially try to control these resource, leading to issues.
The matchLabels
sub-parameter define set-based requirements for the Pod selector. This is often paired with the matchExpressions
statement as well to further define where resources should be scheduled.
strategy
The strategy section defines values regarding updates for Pods. It works with the later defined type
field which specifies the strategy for updating Pods. In the case of the example, it is a RollingUpdate
, which has other parameters that can be defined to control the number of Pods deleted at a time. Even through it is defined last here, it is what is read to determine the object being configured.
For a RollingRestart
we can define maxSurge
and maxUnavailable
fields. The maxSurge
parameter defines the number of Pods that can be created above the desired limit. It can be a percentage, like the default 25% here, or an absolute number. This creates a certain number of new Pods before deleting any old ones for continued access to the running application. The maxUnavailable
field is the configured number or percentage of Pods that can be in a state other than Ready
during the update process.
The strategy
could also be set to Recreate
which would delete all existing Pods before creating any new ones.
Deployment configuration Pod template
Next is the template
section, which is actually contained within the spec
section we discussed about above. it looks something like this:
template:
metadata:
creationTimestamp: null
labels:
app: dev-web
spec:
containers:
- image: nginx:1.13.7-alpine
imagePullPolicy: IfNotPresent
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
This is the template used for each of the Pods to be deployed. Again, let's break down each section.
template
The template header defines the configuration that is passed to the ReplicaSet for how to deploy an object (in this case Pods/containers).
We again see a metadata
section which serves the same function as discussed here.
containers
This keyword indicates the following items of the array are specification for the containers that will be deployed in the Pod. Each container has some more keywords to configure.
The first is image
, which is the name of the image passed to the container runtime engine. The engine pulls the image and creates the Pod.
We also define an imagePullPolicy
to define when and if an image should be downloaded or if the locally cached image should be used. This can take three values. IfNotPresent
pulls the image only if it is not present locally. Always
will make the kubelet alway query the container registry to pull the image before launching a new one. Never
will make the kubelet never pull the image, which causes startup to fail if the image is not present locally already.
The name
field denoted the stub of the Pod name, with a unique string being appended to the end.
resources
by default are empty. This is where any resource restrictions or limits, like for CPU or memory, would be configured for the containers.
The terminationMessagePath
defines the location where to output success or failure information from a container. The terminationMessagePolicy
holds the termination method. The default is File
, but it can also be set to FallbackToLogsOnError
which uses the last chunk of container logs if the message file is empty and the container is showing an error.
dnsPolicy
This determines is the DNS queries should go through coredns. If set to Default
, the node's DNS resolution configuration is used.
restartPolicy
This defines is containers should be restarted if killed. Automatic restarts are part of the usual strength of Kubernetes.
schedulerName
This allows for the use of a custom scheduler if you don't want to use the Kubernetes default.
securityContext
A flexible setting to pass one or many security settings. These could include SELinux context, AppArmor values, users, and UIDs for the containers to use.
terminationGracePeriodSeconds
Sets the amount of time to wait for a SIGTERM
to run before a SIGKILL
is used to terminate the container.
Deployment configuration status
Now, let's take a look at the status
section of the output for our Deployment object.
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2022-05-10T18:21:47Z"
lastUpdateTime: "2022-05-10T18:21:47Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2022-05-10T18:21:45Z"
lastUpdateTime: "2022-05-10T18:21:47Z"
message: ReplicaSet "dev-web-565cb67bd6" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 1
replicas: 1
updatedReplicas: 1
Some sections to note in the status
are the availableReplicas
and observedGeneration
fields. The availableReplicas
indicates how many replicas were configured by the ReplicaSet. This is then later compared to readyReplicas
to determine if all have been fully created without error.
The observedGeneration
parameter shows how often the deployment has been updated, which can be used to understand the rollout and rollback situation of the deployment.
Scaling and rolling updates
The kube-apiserver allows most configuration settings to be updated, however there are some that are immutable which can differ depending on the version of Kubernetes deployed.
A more common update is scaling the number of replicas for a Deployment. You could in theory also scale a Deployment to 0 replicas, leaving just a ReplicaSet and a Deployment, which behind the scenes is what happens when a Deployment is deleted. As an example, let's scale the Nginx deployment we created earlier.
kubectl scale deploy/dev-web --replicas=4
For immutable values, we can edit the deployment and trigger an update. Again, using the example of our Nginx deployment we could do
kubectl edit deployment dev-web
This would open up a text editor and you could make a change, like for example the image version.
...
containers:
- image: nginx:1.8 # <-- Update this to an older version
imagePullPolicy: IfNotPresent
name: nginx
...
The edit
command then triggers the update of the Deployment. While the Deployment shows its age as the original creation time, inspecting the Pods would show that they had been recently created.
Deployment rollbacks
Some previous ReplicaSets and Deployments are kept in the case a rollback is needed. The number kept is configurable and changes version to version. Let's take a loot at how rollbacks can be made.
kubectl create deploy ghost --image=ghost
kubectl get deploy ghost -o yaml
This is what the deployment should look like:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-05-11T16:17:57Z"
generation: 1
labels:
app: ghost
name: ghost
namespace: default
resourceVersion: "296422"
uid: 985f8c5a-8040-4c00-81b9-833dd03bde0a
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: ghost
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: ghost
spec:
containers:
- image: ghost
imagePullPolicy: Always
name: ghost
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2022-05-11T16:18:15Z"
lastUpdateTime: "2022-05-11T16:18:15Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2022-05-11T16:17:57Z"
lastUpdateTime: "2022-05-11T16:18:15Z"
message: ReplicaSet "ghost-5c7f765cdc" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 1
replicas: 1
updatedReplicas: 1
If an update were to fail, you can roll back to a previous version with a kubectl rollout undo
kubectl set image deployment/ghost ghost=ghost:09 --all
kubectl rollout history deployment/ghost
kubectl get pods
When getting the Pods, you should see the new ghost
Pod is erroring, likely with a ErrImagePull
or ImagePullBackOff
status since the tag we specified on the image was bad.
To fix this we can undo the change.
kubectl rollout undo deployment/ghost
kubectl get pods
You should then see the ghost
Pod working normally again. You can also specify a specific revision to roll back to using the --to-revision=
flag and specifying the revision number from the rollout history
command.
Deployments can be paused and resumes as well with
kubectl rollout pause deployment/ghost
kubectl rollout resume deployment/ghost
ReplicationControllers can have rolling updates applied with kubectl rolling-update
command, but as mentioned before, this is done on the client side. So if a connection is lost the update will stop.
Using DaemonSets
A newer object is a DaemonSet
, which ensures a single Pod is running on each cluster node. Every Pod the DaemonSet manages uses the same image, and when new nodes are added, the controller will deploy a new, identical Pod on it. If the node is removed, the controller will also delete the Pod first.
DaemonSets are useful in that it ensured a particular container is always running on every node, which in a large dynamic environment is helpful so an administrator does not always need to remember to deploy certain things, like for example a logging or metric generation application.
There are ways to effect the kube-apiserver so that DaemonSets do not run on certain nodes.
Labels
Labels are an important tool for cluster administration. They can be used for selection using an arbitrary string regardless of the object type. Labels are immutable as of apps/v1
.
Any resource can contain labels in its metadata. By default, when you use kubectl create
to launch a Deployment, some labels are added.
...
labels:
app: ghost
pod-template-hash: 5c7f765cdc
...
Here are some examples of using and adding labels.
For example, you could get Pod with the label app
and a value of ghost
.
kubectl get pods -l app=ghost
Or you could get all the Pods with the app
label and list their values as another column like so.
kubectl get pods -L app
Labels are usually defined in Pod templates in Deployment spec but you can also add them on the fly.
kubectl label pods ghost-<uid> foo=bar
And you can show the labels on each pod with a get
kubectl get pods --show-labels
Lab Exercises
Lab 7.1 - Working with ReplicaSets
This lab explored the objects that help to manage containers, a core task of Kubernetes. As Kubernetes has matured, the objects used to do this have changed. We will look at ReplicaSets
which don't have more of the newer features Deployments have for management. We will also look at Deployments
which manage ReplicaSets for us, as well as DaemonSets
for when we want to ensure a container is run on every node of the cluster.
The ReplicaSet was an evolution on Replica Controllers, which differ only on the selectors available for use. The only reason to use a ReplicaSet instead of a Deployment is if you will not need to update software or orchestrate updates.
First, check to see that there are no resources currently on the cluster.
kubectl get rs
You should find that No resources found in default namespace.
Next, create a YAML file to define a simple ReplicaSet.
vim rs.yaml
And in the YAML enter the following specification
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-one
spec:
replicas: 2
selector:
matchLabels:
system: ReplicaOne
template:
metadata:
labels:
system: ReplicaOne
spec:
containers:
- name: nginx
image: nginx:1.15.1
ports:
- containerPort: 80
And then create the ReplicaSet.
kubectl create -f rs.yaml
Then view the newly created ReplicaSet and the Pods it created. You should see two Pods running.
kubectl describe rs rs-one
kubectl get pods
Now let's delete the ReplicaSet, but not the Pods it controls.
kubectl delete rs rs-one --cascade=orphan
Then, try to describe the ReplicaSet and view the Pods again.
kubectl describe rs rs-one
kubectl get pods
When you describe
the ReplicaSet you should see an error like Error from server (NotFound): replicasets.apps "rs-one" not found
but when you list the Pods you should still see the two running that you had before.
We can then recreate the ReplicaSet, and as long as the selector field is the same, it should take ownership of the running Pods. Pod software versions cannot be updated with this method.
kubectl create -f rs.yaml
Now view the AGE
of both the ReplicaSet and the Pods it manages.
kubectl get rs
kubectl get pods
You should see the ReplicaSet is newer than the Pods, as the Pods have been unchanged since they were created by the original ReplicaSet.
Next we will isolate one of the Pods from the ReplicaSet, which we will do by editing the label of the Pod. We will update the system
parameter to be IsolatedPod
.
kubectl edit pod rs-one-<unique ID>
Now if you check the ReplicaSet, you should see two Pods are still running.
kubectl get rs
Now if you get the Pods as well, you should see there are now three running. And if you view them again using while showing the system
label, you will see one has the IsolatedPod
value we had set earlier. The ReplicaSet has done its job to make sure that the configured number of Pods are running under it'd defined criteria (in this case with the system
label of ReplicaOne
).
kubectl get pods
kubectl get po -L system
Now if you delete the ReplicaSet and get the Pods and ReplicaSets
kubectl delete rs rs-one
kubectl get pods,rs
you should only see the orphaned Pod with the changed system
label.
Finally, delete the orphaned Pod.
kubectl delete pod -l system=IsolatedPod
Lab 7.2 - Working with DaemonSets
DaemonSets
are watch loops just like Deployments, but they serve the purpose to ensure that when nodes are added to the cluster a Pod is run on them. Deployments on the other hand only ensure that a certain number of Pods are created in general, not where they are allocated. DaemonSets are helpful since they ensure an application is on each node which can be helpful for things like logging and metrics, especially on large clusters where nodes are swapped often. DaemonSets are also very helpful because they manage the garbage collection of Pods when nodes are terminated from the cluster. Starting in v1.12
, the scheduler handles the deployment of DaemonSets which allows for exclusion of certain nodes from the DaemonSet's scope.
First, we will copy the YAML file we created for the ReplicaSet and use it as the base for the DaemonSet.
cp rs.yaml ds.yaml
vim ds.yaml
In the vim
editor window update the kind
to DaemonSet
, the name
to ds-one
, the system
label to DaemonSetOne
, and remove the replicas
definition.
Then, create and verify the new DaemonSet.
kubectl create -f ds.yaml
kubectl get ds,pods
Then verify the image running inside each Pod.
kubectl describe pod ds-one-<unique ID> | grep Image:
You should see both are running an Nginx v1.15.1 container.
Lab 7.3 - Rolling Updates and Rollbacks
One advantage of micro-services is that an update can be applied to a container without disrupting the response to client requests.
In this section we will use the OnDelete
and RollingUpdate
strategies to update our DaemonSet. The OnDelete
will upgrade a container once the one it is replacing is deleted. A RollingUpdate
will bring up a new container immediately to replace the out-of-date Pod.
First let's check the update strategy of the DaemonSet we specified in lab 7.2.
kubectl get ds ds-one -o yaml | grep -A 4 Strategy
You should see that it is currently using a RollingUpdate
. Let's edit the DaemonSet to use an OnDelete
strategy by changing the type
field in under the updateStrategy
.
kubectl edit ds ds-one
Now let's update the Nginx image the Pods are using. And then we will verify that the image has not yet changed.
kubectl set image ds ds-one nginx=nginx:1.16.1-alpine
kubectl describe po ds-one-<unique ID> | grep Image:
Now delete one of the Pods. Verify the new Pod is up an running and check the image again.
kubectl delete pod ds-one-<unique ID>
kubectl get pod
kubectl describe pod ds-one-<new unique ID> | grep Image:
Check the other Pod and you should see it is still running the 1.15.1
image.
Now let's create a new DaemonSet using the original as a base, updating the name to ds-two
and changing the strategy back to the default RollingUpdate
.
kubectl get ds ds-one -o yaml > ds2.yaml
vim ds2.yaml
kubectl create -f ds2.yaml
Verify the image. It should be using the new 1.16.1-alpine
image that we updated to earlier.
kubectl get pods
kubectl describe pod ds-two-<unique ID> | grep Image:
Now let's change the image back to 1.15.1
with the edit
command and then check the DaemonSet and Pods.
kubectl edit ds ds-two
kubectl get ds ds-two
kubectl get pods
kubectl describe pod ds-two-<unique ID> | grep Image:
Notice the Pods for ds-two
are much newer than the DaemonSet itself and that the Nginx image is 1.15.1
. This is because the edit took effect right away because we changed the strategy to RollingUpdate
.
Then view the rollout status and history and then clean up the resources.
kubectl rollout status ds ds-two
kubectl rollout history ds ds-two
kubectl rollout history ds ds-two --revision=2
kubectl delete ds ds-one ds-two
Knowledge check
- The
replicas
field determines the number of duplicate Pods deployed by a Deployment - The
strategy
field is a header that has to do with updating Pods. - Labels are metadata used to select an object with
kubectl
, based on an arbitrary string, regardless of the object type - The
history
argument is passed tokubectl rollout
to see the revision history of an object - The
undo
argument is passed tokubectl rollout
to return to a previous object version