18 Deployment Make Your Applications Never Go Down

18 Deployment Make Your Applications Never Go Down #

Hello, I’m Chrono.

In the previous lesson, we used kubeadm to set up a small Kubernetes cluster consisting of two nodes. Compared to minikube running on a single machine, this cluster is closer to a real environment. Experimenting here will make it easier for us to transition to a production system in the future.

With this Kubernetes environment in place, we will now delve into other API objects derived from the knowledge of Pods that we learned in the “Beginner’s Guide” section.

The API object we will be looking at today is called “Deployment”. As the name suggests, it is specifically designed for deploying applications and ensuring their availability. It is commonly used for deploying stateless applications and is the most commonly used and useful object in Kubernetes.

Why do we need Deployment #

In [Lesson 13], we learned about the API objects Job and CronJob, which represent offline tasks and scheduled tasks in the production environment. By wrapping Pods and adding control fields to them, we can run temporary and scheduled tasks based on Pods.

Now, besides “offline tasks,” there is another major type of task - that is, “online tasks.” How should we handle them in Kubernetes?

Let’s first see if Pods are sufficient. Because they can be arbitrarily orchestrated using “containers” in YAML, and there is also a “restartPolicy” field, whose default value is Always. It can monitor the status of containers in Pods and automatically restart the containers if any exceptions occur.

However, the “restartPolicy” can only ensure the normal operation of the containers. Have you ever thought about what to do if something goes wrong with the Pods outside the containers? For example, if someone accidentally deletes a Pod using kubectl delete, or if the node on which the Pod is running experiences a power failure, the Pod will disappear completely from the cluster, and there will be no way to control the containers.

We also know that online tasks are not as simple as just starting a Pod. There are many complex operations such as multiple instances, high availability, and version updates. For example, for the simplest requirement of multiple instances, to improve the service capacity of the system and cope with sudden traffic and pressure, we need to create replicas of the application and monitor their status in real-time. If we still only use Pods, we will go back to the manual management approach, without leveraging the advantages of Kubernetes’ automated operations and maintenance.

In fact, the solution is simple because Kubernetes has provided us with a way to handle this problem, which is “single responsibility” and “object composition.” Since Pods cannot manage themselves, we can create a new object to manage them, using the same form as Job/CronJob - “object nesting.”

This new API object used to manage Pods and implement online business applications is called Deployment.

How to Describe a Deployment Using YAML #

Let’s start by using the kubectl api-resources command to see the basic information about Deployment:

kubectl api-resources

NAME         SHORTNAMES   APIVERSION   NAMESPACED   KIND
deployments  deploy       apps/v1      true        Deployment

From the output, we can see that the abbreviation for Deployment is “deploy”, its apiVersion is “apps/v1”, and the kind is “Deployment”.

Therefore, based on our previous understanding of Pod and Job, you should know how to write the YAML header for a Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: xxx-dep

Of course, we can still use the kubectl create command to create a template for the Deployment, saving us from the hassle of repetitive manual input.

The way to create a template for Deployment is similar to that for Job. First, specify the type as Deployment (abbreviated as “deploy”), then specify its name, and finally use the --image parameter to specify the image name.

For example, with the following command, I created an object named ngx-dep that uses the nginx:alpine image:

export out="--dry-run=client -o yaml"
kubectl create deploy ngx-dep --image=nginx:alpine $out

The resulting template for Deployment would look something like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      containers:
      - image: nginx:alpine
        name: nginx

Comparing it with Job/CronJob, you will find similarities and differences. The similar aspect is that both have the spec and template fields, and the template field contains a Pod. The difference lies in the additional replicas and selector fields in the “spec” section. You should be smart enough to guess that these two new fields might be the key to the special capabilities of Deployment, such as achieving multiple instances and high availability.

Key Fields of Deployment #

Let’s start with the replicas field. Its meaning is relatively simple and clear, which is the “number of replicas”, that is, specifying how many Pod instances should be running in the Kubernetes cluster.

With this field, Kubernetes has a clear “desired state” for application deployment. The Deployment object can act as an operations and monitoring personnel, automatically adjusting the number of Pods in the cluster.

For example, when the Deployment object is initially created, the number of Pods is definitely 0. Then, based on the Pod template in the YAML file, it will create the required number of Pods one by one.

Next, Kubernetes will continue to monitor the running status of the Pods. If a Pod accidentally disappears and the number does not meet the “desired state”, it will use core components such as apiserver and scheduler to select new nodes and create new Pods until the number matches the “desired state”.

The workflow inside is very complex, but for us external users, it is very simple to set up. It only requires a replicas field, and there is no need for manual monitoring and management. The whole process is fully automated.

Next, let’s look at another key field, selector. Its role is to “select” the Pods to be managed by the Deployment. The sub-field, matchLabels, defines the labels that the Pod objects should carry. It must be exactly the same as the “labels” defined in the Pod template under “template”. Otherwise, the Deployment will not be able to find the Pods to control, and the apiserver will tell you that there is a YAML format validation error and it cannot be created.

The usage of this selector field may seem a bit redundant at first glance. In order to ensure successful creation of the Deployment, we must repeat the label twice in the YAML: once in selector.matchLabels, and another in template.metadata. Like here, you have to write app: ngx-dep in these two places consecutively:

...
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    ...

You may wonder: Why is it so complicated? Why can’t we just use the Pod defined in the “template” directly, just like the Job object?

This is because the application scenarios for online and offline businesses are very different. Pods in offline businesses are basically disposable and only related to a specific business. They are tightly bound in the Job object and are generally not used by other objects.

On the other hand, online businesses are much more complicated because Pods are always running. In addition to being deployed and run in a Deployment, they may also be referred to by other API objects for management, such as the Service object responsible for load balancing.

So, in fact, Deployment and Pods have a loose combination relationship. Deployment does not actually “own” the Pod objects. It only helps to ensure that there are enough replicas of the Pods running. That’s it. If Pods are “hard-coded” in the template like in Jobs, other objects will not be able to manage these Pods.

Now that we understand this, how should we describe the combination relationship between Deployment and Pods?

Kubernetes uses a “labeling” approach, adding various labels in the “metadata” of API objects. With this, we can select objects with specific identifiers using query-like statements similar to those in relational databases. Through this label-based design, Kubernetes removes the strong coupling between Deployment and Pods defined in the template, turning the combination relationship into a “weak reference”.

Although it is said like this, understanding the spec definition in the Deployment YAML is still a difficulty for many Kubernetes beginners.

So, I created a diagram that uses different colors to distinguish the fields in the Deployment YAML and highlights the connection between matchLabels and labels with dashed lines. I hope this can help you understand the combination relationship between Deployment and the Pods it manages.

Image

How to Use kubectl to Operate Deployments #

After writing the YAML for the Deployment, we can use kubectl apply to create the object:

kubectl apply -f deploy.yml

To check the status of the Deployment, we still use the kubectl get command:

kubectl get deploy

Image

The information displayed here is important:

  • READY indicates the number of running Pods. The number before the forward slash represents the current count, and the number after the slash is the desired count. Therefore, “2/2” means that there should be two Pods running, and two Pods are currently running.
  • UP-TO-DATE refers to the number of Pods that are already updated to the latest state. When deploying a large number of Pods or when Pods take some time to start, it takes time for the Deployment to be fully effective. UP-TO-DATE shows how many Pods have finished deploying and reached the “desired state” defined in the template.
  • AVAILABLE goes a step further than READY and UP-TO-DATE. It not only requires the Pods to be running but also in a healthy state and able to provide services externally. AVAILABLE is the most important Deployment metric.
  • The last item, AGE, represents the time from the creation of the Deployment until now; in other words, the running time.

Since the Deployment manages Pods, we also need to use the kubectl get pod command to check the status of the Pods:

kubectl get pod

Image

From the screenshot, you can see that the Pods managed by the Deployment automatically have names. The naming rule is the combination of the Deployment name and two random strings (actually, the Hash value of the Pod template).

Okay, now that the objects have been created successfully and both the Deployment and Pods are in good condition and operating normally, it’s time to test the effect of the Deployment. Let’s verify if the application deployed by the Deployment can indeed achieve “never offline” as mentioned earlier.

Let’s try deleting a Pod using kubectl delete to simulate a scenario where a Pod fails:

kubectl delete pod ngx-dep-6796688696-jm6tt

Then, check the status of the Pods again:

kubectl get pod

Image

You will be “pleasantly surprised” to find that the deleted Pod did disappear, but Kubernetes, under the management of the Deployment, quickly created a new Pod, ensuring that the number of application instances always meets the quantity defined in the YAML.

This proves that the Deployment indeed achieves its intended goal of keeping the application “always online” and “never offline.”

After the Deployment is successfully deployed, you can adjust the number of Pods at any time to achieve the so-called “application scaling.” This was a difficult task for operations teams before Kubernetes appeared, but it has now become easy and straightforward due to Deployments.

kubectl scale is a command specifically used for “scaling up” or “scaling down.” You simply specify the desired number of replicas using the --replicas parameter, and Kubernetes will automatically add or remove Pods to achieve the “desired state” for the final number of Pods.

For example, the following command scales the Nginx application to five replicas:

kubectl scale --replicas=5 deploy ngx-dep

Image

However, please note that kubectl scale is an imperative operation, and scaling up or down is a temporary measure. If the application needs to maintain a specific number of Pods for a long time, it is recommended to edit the Deployment’s YAML file, change the “replicas” field, and then modify the object’s status using the declarative kubectl apply.

Since the Deployment uses the selector field, I will take this opportunity to briefly mention how to use the labels field in Kubernetes.

Previously, we “attached” various “labels” to objects. When using the kubectl get command, you can add the -l parameter and use the ==, !=, in, notin expressions to easily filter out the desired objects based on their “labels” (similar to the #tag feature in social media). The effect is the same as the selector field in the Deployment.

Here are two examples. The first command finds all Pods with the “app” label equal to nginx, and the second command finds all Pods with the “app” label equal to ngx, nginx, or ngx-dep:

kubectl get pod -l app=nginx
kubectl get pod -l 'app in (ngx, nginx, ngx-dep)'

Image

Summary #

Alright, today we learned about an important object in Kubernetes: Deployment. It represents an online business and has a structure similar to Job/CronJob. It also encapsulates Pod objects and adds additional control functionality to achieve application high availability. You can also refer back to Lecture 13 for a deeper understanding.

Let me summarize today’s content:

  1. Pods can only manage containers and not themselves, so Deployments were introduced to manage Pods.
  2. Deployments have three key fields. The template is similar to Job and defines the Pod template to be run.
  3. The replicas field defines the “desired quantity” of Pods, and Kubernetes will automatically maintain the number of Pods at a normal level.
  4. The selector field defines the rule for filtering Pods based on labels, and it must match the labels of Pods in the template.
  5. The kubectl apply command is used to create a Deployment, and the kubectl scale command is used for scaling up or down the application.

After learning about the Deployment API object, we should no longer use “naked Pods” in the future. Even if we run only one Pod, it should be created as a Deployment. Although the replicas field value is 1, the Deployment will ensure the application is always online.

In addition, as the most commonly used object in Kubernetes, Deployments have more capabilities. They support rolling updates, version rollbacks, automatic scaling, and other advanced features, which we will learn in detail in the “Advanced Topics” section.

Homework #

Finally, it’s time for homework. I have two questions for you to consider:

  1. What happens if the replicas field in a Deployment is set to 0? What is the significance of this?
  2. In which scenarios do you think Deployments can be applied? Are there any drawbacks or limitations?

Feel free to share your thoughts in the comments section.

In this chapter, we learned about advanced Kubernetes objects, which are very important for cloud computing and cluster management. Think deeply and build a solid foundation as we continue with more in-depth lessons. See you in the next class.