Mirantis acquires amazee.io, the only ZeroOps Application Delivery Hub.   Read Blog Post  |  View Press Release  |  Visit amazee.io

What are Kubernetes Deployments?

Eric Gregory - July 12, 2022
image

One of the biggest challenges for implementing cloud native technologies is learning the fundamentals—especially when you need to fit your learning into a busy schedule. In this series, we’ll break down core cloud native concepts, challenges, and best practices into short, manageable exercises and explainers, so you can learn five minutes at a time. 

In the last lesson, we learned about Kubernetes Objects and discussed one of the most important abstractions in Kubernetes: the Pod. This time, we’ll investigate another Object that can help us to manage Pods at scale: the Deployment.

Table of Contents

  1. What is Kubernetes?

  2. Setting Up a Kubernetes Learning Environment

  3. The Architecture of a Kubernetes Cluster

  4. Introducing Pods (and other Kubernetes objects)

  5. What are Kubernetes Deployments? ← You are here

 

These lessons assume a basic understanding of containers. If you need to get up to speed on Docker and containerization, download our free ebook, Learn Containers 5 Minutes at a Time. This concise, hands-on primer explains:

  • The key concepts underlying containers—and how to use core tools like image registries
  • Fundamentals of container networking that are essential for understanding container orchestrators like Kubernetes
  • How to deploy containerized apps in single-container and multi-container configurations

 

What is a Deployment?

In practice, it is rarely ideal to deploy or directly manage a single pod. Containers (and the pods that contain them) are meant to be ephemeral, and if you’re working on Kubernetes, you’re working at scale. It is much more common to interact directly with a Deployment: a grouping of functionally identical pods that can be launched and managed together. 

These duplicate pods allow you to achieve the resiliency and scalability that Kubernetes is designed to foster: traffic may be balanced between them as needed, and if a pod goes down, it can be immediately replaced.

The Deployment Object draws upon the Pod as well as another important abstraction: the ReplicaSet. This defines the number of duplicate Pods the system should maintain–so if you specify that you intend for three replicas of an NGINX Pod to be running, Kubernetes will work to ensure that this is the case. If a Pod goes down, the system will start a new Pod to meet the expectation of three replicas. A ReplicaSet is also used as a reference point by the Horizontal Pod Autoscaler (HPA) when defining minimum and maximum numbers of replicas to run. (We’ll talk more about the HPA in a future lesson.)

It is perfectly possible to write a manifest for a ReplicaSet, just as it is possible to write a manifest for a Pod—but the beauty of the Deployment is that it joins those two objects so you can create, delete, update, replicate, and otherwise manage groupings of Pods through a single interface. 

You might recall that we created a deployment with a line in kubectl in our lesson on setting up a Kubernetes environment:

% kubectl create deployment nginx --image=nginx --port=80

There, we issued our spec directly through kubectl. That works for a simple deployment, but for more complicated configurations, we may want to use a manifest. Let’s have a look at a Deployment manifest. We can ask kubectl to give us a template: 

% kubectl create deployment nginx --image=nginx --port=80 -o yaml --dry-run=client

How is this different from the previous command? The dry-run argument prepares a request for the API Server without actually submitting it. (You can replace the “client” option with “server” to submit it to the API Server without persisting the request to the cluster, which allows you to validate the request as a test.) We’ve also used the -o argument to output this practice request in the same YAML format we would like to use for our manifest. The result:

apiVersion: apps/v1
kind: Deployment
metadata:
 creationTimestamp: null
 labels:
   app: nginx
 name: nginx
spec:
 replicas: 1
 selector:
   matchLabels:
     app: nginx
 strategy: {}
 template:
   metadata:
     creationTimestamp: null
     labels:
       app: nginx
   spec:
     containers:
     - image: nginx
       name: nginx
       ports:
       - containerPort: 80
       resources: {}
status: {}

Create a blank file called deployment.yml. It can be located anywhere on your filesystem, but if you want to keep organized, you can place it in a directory called 5mins. Open the file with your favorite code editor and copy over the kubectl output.

A few of these details—the creationTimestamps and status—aren’t super-relevant to us right now; we can leave those to the API Server. But let’s make some changes and additions to the fields that do interest us: 

  • On line 9, change the replicas spec to 5

  • On line 21, change the container image to nginx:1.7.9

  • Add two new lines between lines 6 and 7. On the first of these new lines, at the same hierarchical level as line 6, add a new label: tier: backend. On the second new line, add a new label: env: dev

When you’re finished, the manifest should look like this:

apiVersion: apps/v1
kind: Deployment
metadata:
 creationTimestamp: null
 labels:
   app: nginx
   tier: backend
   env: dev
 name: nginx
spec:
 replicas: 5
 selector:
   matchLabels:
     app: nginx
 strategy: {}
 template:
   metadata:
     creationTimestamp: null
     labels:
       app: nginx
   spec:
     containers:
     - image: nginx:1.7.9
       name: nginx
       ports:
       - containerPort: 80
       resources: {}
status: {}

So what have we done here? We specified that we want: 

  • 5 replicas in this deployment 

  • a specific version number for our container image, rather than the latest version that is used by default

  • Two new labels added to the deployment object’s metadata.  

Labels are string key-value pairs that help us organize and keep track of our objects. We can define both the key and the value; for example, we added a key “tier” and the value “backend.” Other deployments might use the same key and a different value like “frontend.” 

Ultimately, every field in the manifest is a key-value pair. Remember how etcd is a key-value data store? These are the keys and values that get stored. All the configuration and status data that defines the workings of the cluster is ultimately broken down into pairs of keys and values, which the API Server transmits either by the default JavaScript Object Notation (JSON) format or as Protocol Buffers (aka Protobufs).

If we want to learn about the possible fields for a given object, we can use the explain functionality of kubectl again. For example:

% kubectl explain deployment  
KIND:     Deployment
VERSION:  apps/v1

DESCRIPTION:
Deployment enables declarative updates for Pods and ReplicaSets.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

metadata  <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

spec <Object>
Specification of the desired behavior of the Deployment.

status <Object>
Most recently observed status of the Deployment.

Note that the FIELDS section tells us what type of value each key takes—kind takes a string, spec takes an object, and so on. We can drill down further with the explain command:

% kubectl explain deployment.spec
KIND:     Deployment
VERSION:  apps/v1

RESOURCE: spec <Object>

DESCRIPTION:
Specification of the desired behavior of the Deployment.

DeploymentSpec is the specification of the desired behavior of the Deployment.

FIELDS:
minReadySeconds <integer>
Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)

paused <boolean>
Indicates that the deployment is paused.

progressDeadlineSeconds <integer>
The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.

replicas <integer>
Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.

revisionHistoryLimit <integer>
The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.

selector <Object> -required-
Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels.

strategy <Object>
The deployment strategy to use to replace existing pods with new ones.

template <Object> -required-
Template describes the pods that will be created.

You can use dot notation to drill down further into each field. But for now, let’s go ahead and send our deployment manifest to the API Server. From the directory where you’ve stored the manifest:

% kubectl apply -f deployment.yml

Check that your deployment is running—or if you prefer, you can check the status of the individual Pods.

% kubectl get deployments        
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   5/5     5            5           15s
% kubectl get pods               
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f7ccf9478-9f7nm   1/1     Running   0          15s
nginx-f7ccf9478-c6p8v   1/1     Running   0          15s
nginx-f7ccf9478-gtdmv   1/1     Running   0          15s
nginx-f7ccf9478-vskgd   1/1     Running   0          15s
nginx-f7ccf9478-wrhl9   1/1     Running   0          15s

Perfect. Now we can delete our Deployment and stop Minikube.

% kubectl delete deployment nginx
% minikube stop

In this lesson, we got multiple replica Pods up and running within the cluster using a Deployment. But these are ephemeral Pods—we expect them to be destroyed and replaced. In the meantime, other Pods would need to be able to connect to the NGINX backend we’ve started to define. So how can we ensure that churning instances of a hypothetical frontend can find churning instances of the backend? 

That’s where the Service object comes in. In the next lesson, we’ll explain the Service abstraction, start to connect two simple services across separate Pods, and learn a little more about Kubernetes networking along the way.