From Learn Kubernetes in a Month of Lunches by Elton Stoneman

This article delves into getting started running pods with controllers in Kubernetes.

Take 40% off Learn Kubernetes in a Month of Lunches by entering fccstoneman2 into the discount code box at checkout at

Running pods with controllers

Pods are too simple to be useful on their own—they’re isolated instances of an application, and each pod is allocated to one node. If that node goes offline then the pod is lost and Kubernetes doesn’t replace it. You could try to get high availability by running several pods, but there’s no guarantee Kubernetes won’t run them all on the same node. Even if you get pods spread across several nodes, you need to manage them yourself – and why do that when you’ve an orchestrator which can manage them for you?

This is where controllers come in. A controller is a Kubernetes resource which manages other resources—it works with the Kubernetes API to watch the current state of the system, compares it to the desired state of its resources, and makes any changes it needs. Many controllers exist, but the main one for managing pods is the deployment, which solves the problems I’ve described. If a node goes offline and you lose the pod, the deployment creates a replacement pod on another node; if you want to scale your deployment you can specify how many pods you want and the deployment controller runs them across many nodes. Figure 1 shows the relationship between deployments, pods and containers.

Figure 1. Deployment controllers manage pods and pods manage containers

You can create deployment resources with Kubectl, specifying the container image you want to run and any other configuration for the pod. Kubernetes creates the deployment, and the deployment creates the pod.

TRY IT NOW  Create another instance of the web application, this time using a deployment. The only required parameters are the name for the deployment and the image to run.

 # create a deployment called "hello-kiamol-2", running the same web app:
 kubectl create deployment hello-kiamol-2 --image=kiamol/ch02-hello-kiamol
 # list all the pods:
 kubectl get pods

You can see my output in figure 2. Now you have two pods in your cluster – the original one you created with the Kubectl run command, and the new one created by the deployment. The deployment-managed pod has a name generated by Kubernetes, which is the name of the deployment followed by a random suffix.

Figure 2. Create a controller resource and it creates its own resources – deployments create pods

One important thing to realize from this exercise is that you created the deployment but you didn’t directly create the pod. The deployment specification described the pod you wanted, and the deployment created that pod. The deployment is a controller which checks with the Kubernetes API to see which resources are running, realizes the pod it should be managing doesn’t exist and uses the Kubernetes API to create it. The exact mechanism doesn’t matter; you can work with the deployment and rely on it to create your pod.

How the deployment keeps track of its resources matters because it’s a pattern which Kubernetes uses a lot. Any Kubernetes resource can have labels applied which are simple key-value pairs. You can add labels to record your own data—you might add a label to a deployment with the name release and the value 20.04 to indicate this deployment is from the 20.04 release cycle. Kubernetes also uses labels to loosely couple resources, mapping the relationship between objects like a deployment and its pods.

TRY IT NOW  The deployment adds labels to pods it manages. Use Kubectl to print out the labels the deployment adds, and then list the pods which match that label:

 # print the labels which the deployment adds to the pod:
 kubectl get deploy hello-kiamol-2 -o jsonpath='{.spec.template.metadata.labels}'
 # list pods which have that matching label:
 kubectl get pods -l app=hello-kiamol-2

My output is in figure 3, where you can see some internals of how the resources are configured. Deployments use a template to create pods, and part of that template is a metadata field which includes the labels for the pod(s). In this case the deployment adds a label called app with the value hello-kiamol-2 to the pod. Querying pods which have a matching label return the single pod managed by the deployment.

Figure 3. Deployments add labels when they create pods, and you can use those labels as filters

Using labels to identify the relationship between resources is such a core pattern in Kubernetes that it’s worth a diagram to make sure it’s clear. Resources can have labels applied at creation, and then added, removed or edited during their lifetime. Controllers use a label selector to identify the resources they manage—and that can be a simple query matching resources with a particular label, as you see in figure 4.

Figure 4. Controllers identify the resources they manage using labels and selectors

This is flexible because it means controllers don’t need to maintain a list of all the resources they manage, the label selector is part of the controller specification and controllers can find matching resources at any time by querying the Kubernetes API. It’s also something you need to be careful with, because you can edit the labels for a resource and end up breaking the relationship between it and its controller.

TRY IT NOW  The deployment doesn’t have a direct relationship with the pod it created, it only knows there needs to be one pod with labels which match its label selector. Edit the labels on the pod and the deployment no longer recognizes it.

 # list all pods, showing the pod name and labels:
 kubectl get pods -o,LABELS:metadata.labels
 # update the "app" label for the deployment's pod:
 kubectl label pods -l app=hello-kiamol-2 --overwrite app=hello-kiamol-x
 # fetch pods again:
 kubectl get pods -o,LABELS:metadata.labels

What did you expect to happen? You can see from my output in figure 5 that changing the pod label effectively removes the pod from the deployment. At that point the deployment sees that no pods exist which match its label selector and it creates a new one. The deployment has done its job, but by editing the pod directly you now have an unmanaged pod.

Figure 5. If you meddle with the labels on a pod you can remove it from the control of the deployment

This can be a useful technique in debugging—removing a pod from a controller to connect to it and investigate a problem, while the controller starts a replacement pod which keeps your app running at the desired scale. You can also do the opposite, editing the labels on a pod to fool a controller into acquiring that pod as part of the set it manages.

TRY IT NOW  Return the original pod to the control of the deployment by setting its app label back to match the label selector.

 # list all pods with a label called "app", showing the pod name and labels:
 kubectl get pods -l app -o,LABELS:metadata.labels
 # update the "app" label for the the unmanaged pod:
 kubectl label pods -l app=hello-kiamol-x --overwrite app=hello-kiamol-2
 # fetch pods again:
 kubectl get pods -l app -o,LABELS:metadata.labels

This exercise effectively reverses the previous exercise, setting the app label back to hello-kiamol-2 for the original pod in the deployment. Now when the deployment controller checks with the API it sees two pods which match its label selector—but it’s only supposed to manage a single pod, and it deletes one (using a set of deletion rules to decide which one). You can see in figure 6 that the deployment removed the second pod and retained the original:

Figure 6. More label meddling – you can force a deployment to adopt a pod if the labels match

Pods run your application containers, but like containers pods they’re meant to be short-lived. You’ll almost always use a higher-level resource like a deployment to manage pods for you. That gives Kubernetes a better chance of keeping your app running if there are issues with containers or nodes, but ultimately the pods are running the same containers you run yourself, and the end-user experience for your apps is the same.

TRY IT NOW  Kubectl’s port forward command sends traffic into a pod, but you don’t have to find the random pod name for a deployment – you can configure the port forward on the deployment resource, and the deployment selects one of its pods as the target.

 # run a port forward from your local machine to the deployment:
 kubectl port-forward deploy/hello-kiamol-2 8080:80
 # browse to http://localhost:8080
 # when you're done, exit with ctrl-c

You can see my output in figure 7—the same app running in a container from the same Docker image, but this time in a pod managed by a deployment.

Figure 7. Pods and deployments are layers on top of containers, but the app still runs in a container

Pods and deployments are the only resources we’ll cover in this article. You can deploy simple apps by using the Kubectl run and create commands, but more complex apps need lots more configuration, and those commands won’t do. It’s time to enter the world of Kubernetes YAML.

Defining deployments in application manifests

Application manifests are one of the most attractive aspects of Kubernetes, but also one of the most frustrating. When you’re wading through hundreds of lines of YAML trying to find the small misconfiguration which has broken your app, it can seem like the API was deliberately written to confuse and irritate you. At those times you need to remember that Kubernetes manifests are a complete description of your app, which can be versioned and tracked in source control, and it results in the same deployment on any Kubernetes cluster.

Manifests can be written in JSON or YAML; JSON is the native language of the Kubernetes API, but YAML is preferred for manifests because it’s easier to read, let’s you define multiple resources in a single file, and—most importantly—can record comments in the specification. Listing 1 is the simplest app manifest you can write; it defines a single pod using the same container image we’ve already used:

Listing 1 – pod.yaml: a single pod to run a single container

 # manifests always specify the version of the Kubernetes API
 # and the type of resource
 apiVersion: v1
 kind: Pod
 # metadata for the resource includes the name (mandatory)
 # and labels (optional)
   name: hello-kiamol-3
 # the spec is the actual specification for the resource
 # for a pod the minimum is the container(s) to run,
 # with container name and image
     - name: web
       image: kiamol/ch02-hello-kiamol

This is a lot more information than you need for a Kubectl run command, but the big advantage of the application manifest is that it’s declarative. Kubectl run and Kubectl create are imperative operations, it’s you telling Kubernetes to do something. Manifests are declarative—you tell Kubernetes what you want the end result to be, and it goes off and decides what it needs to do to make that happen.

TRY IT NOW  You still use Kubectl to deploy apps from manifest files, but you use the apply command which tells Kubernetes to apply the configuration in the file to the cluster. Run another pod for this article’s sample app using a YAML file with the same contents as listing 1.

 # switch from the root of the kiamol repository to the chapter 2 folder:
 cd ch02
 # deploy the application from the manifest file:
 kubectl apply -f pod.yaml
 # list running pods:
 kubectl get pods

The new pod works in the same way as a pod created with the Kubectl run command—it’s allocated to a node and it runs a container. My output in figure 8 shows that when I applied the manifest, Kubernetes decided it needed to create a pod to get the current state of the cluster up to my desired state. This is because the manifest specifies a pod named hello-kiamol-3, and no such pod existed.

Figure 8. Applying a manifest sends the YAML file to the Kubernetes API which applies changes

Now that the pod is running, you can manage it in the same way with Kubectl—listing the details of the pod and running a port-forward to send traffic into the pod. The big difference is that the manifest is easy to share, and manifest-based deployments are repeatable. I can run the same Kubectl apply command with the same manifest any number of times and the result is always be the same – a pod named hello-kiamol-3 running my web container.

TRY IT NOW  Kubectl doesn’t even need a local copy of a manifest file—it can read the contents from any public URL. Deploy the same pod definition direct from the file on GitHub.

 # deploy the application from the manifest file:
 kubectl apply -f

You’ll see the simple output in figure 9—the resource definition matches the pod running in the cluster, and Kubernetes doesn’t need to do anything and Kubectl shows that the matching resource is unchanged.

Figure 9. Kubectl can download manifest files from a web server and send them to the Kubernetes API

Application manifests start to get more interesting when you work with higher-level resources. You can define a deployment in a YAML file, and one of the required fields is the specification of the pod which the deployment should run. That pod specification is the same API for defining a pod on its own, and the deployment definition is a composite which includes the pod spec. Listing 2 shows the minimal definition for a deployment resource, running yet another version of the same web app.

Listing 2 – deployment.yaml: a deployment and pod specification

 # deployments are part of the "apps" version 1 API spec
 apiVersion: apps/v1
 kind: Deployment
 # the deployment needs a name
   name: hello-kiamol-4
 # the spec includes the label selector the deployment uses
 # to find its own managed resources - I'm using the "app" label,
 # but this could be any combination of key-value pairs
       app: hello-kiamol-4
   # the template is used when the deployment creates pods
     # pods in a deployment don’t  have a name,
     # but they need to specify labels which match the selector
         app: hello-kiamol-4
     # the pod spec lists the container name and image
         - name: web
           image: kiamol/ch02-hello-kiamol

This manifest is for a completely different resource (which runs the same application), but all Kubernetes manifests are deployed in the same way using Kubectl apply. This gives you a nice layer of consistency across all your apps – no matter how complex they are, you’ll define them in one or more YAML files, and deploy them using the same Kubectl command.

TRY IT NOW  Apply the deployment manifest which creates a new deployment, which in turn creates a new pod.

 # run the app using the deployment manifest:
 kubectl apply -f deployment.yaml
 # find pods managed by the new deployment:
 kubectl get pods -l app=hello-kiamol-4

You can see my output in figure 10, it’s the same end result as creating a deployment with Kubectl create, but my whole app specification is clearly defined in a single YAML file.

Figure 10. Applying a manifest creates the deployment because no matching resource existed

As the app grows in complexity and I need to specify how many replicas I want, what CPU and memory limits should apply, how Kubernetes can check whether the app is healthy, where the application configuration settings come from and where it writes data—I do all this by adding to the YAML.

That’s all for this article. You can learn more about the book on our browser-based liveBook platform here.