DEV Community

loading...
Cover image for Learning Kubernetes - Part II: Pods, Labels, and Services

Learning Kubernetes - Part II: Pods, Labels, and Services

Vasco Ramos
・7 min read

Welcome to the second part of my Learning Kubernetes series. This second post builds on the concepts introduced in the previous one and explores a new (better) way of defining objects. We will also explore what Services are and how they can help you better expose your applications.

But first... a quick recap!

Kubernetes is an orchestration tool that allows us to manage containerized applications across a group of nodes.

In the previous post, we talked about:

  • What is Kubernetes
  • What it's used for
  • Some simple concepts and tools around it: Pods and kubectl.

To review what a Pod is, this is what we saw in the previous post:

A pod is the smallest unit inside the Kubernetes cluster, and it represents a collection of application containers and volumes running in the same isolated execution environment.

Also, not to forget that all containers inside the same Pod share:

  • IP address
  • Namespace
  • Storage

One last thing, we saw how to create objects (more precisely, Pods) using kubectl. In this post, I want to introduce you to the de facto way of creating and managing objects in Kubernetes and some new concepts.

So, let's jump right in! 😄

The brave "new" declarative world!

Brave new world banner
The declarative approach to Infrastructure is natural to DevOps and has gained even more relevance with the surge of GitOps. Immutability is a core concept in the declarative approach. In the case of Kubernetes, as it's said in Kubernetes: Up and Running:

Immutable container images are at the core of everything that you will build in Kubernetes. It is possible to imperatively change running containers, but this is an anti-pattern [...] And even then, the changes must also be recorded through a declarative configuration update later, after the fire is out.

So... how do we do this? Let's go back to the example in Part I. When we created the Pod, we did so by running the following (imperative) command:

kubectl run kubernetes-hello-world --image=paulbouwer/hello-kubernetes:1.9 --port=8080
Enter fullscreen mode Exit fullscreen mode

Now, we will do the same thing but with a declarative configuration.

apiVersion: v1
kind: Pod                                            # 1
metadata:
  name: kubernetes-hello-world                       # 2
spec:                                                # 3
  containers:
    - image: paulbouwer/hello-kubernetes:1.9         # 4
      name: hello-kubernetes                         # 5
      ports:
        - containerPort: 8080                        # 6
Enter fullscreen mode Exit fullscreen mode

As you can see, this YAML manifest is equivalent to the previous command. Now let's see the most important concepts in this definition:

  1. Kind: specifies the kind of Kubernetes object to be created.
  2. Name: defines the name for the object.
  3. Spec is the specification of the object's desired state. The main specification here, at least in the case of Pods, is the array of containers (in our case, there's only 1 container in that array).
  4. Image is the container's image to be executed.
  5. (Containers) Name is the name for the container in the pod (it must be unique).
  6. Container Port is the port on which the container is listening.

Now, to create the Pod we need to "send" the manifest to the Kubernetes API. We do this by running the following command:

# assume the manifest is stored in a file named 'pod.yml'
kubectl apply -f pod.yml
Enter fullscreen mode Exit fullscreen mode

You should see an output stating the object was created. If you describe the object with

kubectl describe pod/kubernetes-hello-world
Enter fullscreen mode Exit fullscreen mode

and compare it to the description of the object created with the previous method, you will see they are similar in every way.

Health Checks

When you run your app as a container, Kubernetes automatically keeps it alive through a health check. This process guarantees that your app is always running, and if the app fails, Kubernetes restarts it immediately.

This default behavior is helpful in simple scenarios. However, in most cases, it's not enough.

That's the utility of health checks for application liveness. It allows you to run customized health checks to verify that your app is running and working.

The following manifest builds on the previous one by adding a custom health check capability, using what is called a Liveness Probe.

apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-hello-world-health
spec:                                       
  containers:
    - image: paulbouwer/hello-kubernetes:1.9
      name: hello-kubernetes                         
      livenessProbe:
        httpGet:
          path: /
          port: 8080
        initialDelaySeconds: 5                       # 1
        timeoutSeconds: 1                            # 2
        periodSeconds: 10                            # 3
        failureThreshold: 3                          # 4
      ports:
        - containerPort: 8080 
Enter fullscreen mode Exit fullscreen mode
  1. This first field specifies the number of seconds to delay the probe's first execution.
  2. Specifies that the probe must respond within the X-second timeout (in our case: 1-second timeout).
  3. Specifies that the probe will be called every X seconds (in our case: 10 seconds).
  4. Specifies that the container will fail and restart if the probe fails more than X times in a row (in our case: 3 times).

Now we create the pod by running:

# assume the manifest is stored in a file named 'pod-health.yml'
kubectl apply -f pod-health.yml
Enter fullscreen mode Exit fullscreen mode

If you run kubectl get pods, you will have the following output (or something similar):
Pods list

This shows you the two pods we created. the first one without a custom health check, and the second one with a custom health check that restarts the Pod if the container fails more than 3x in a row.

Labels and Annotations

Labels and Annotations are cornerstone concepts in Kubernetes that let you work in sets of objects that represent how you think about your app.

Labels are key/value pairs that can be attached to Kubernetes objects such as Pods and Deployments. They can be arbitrary and are useful for attaching semantic information used to group Kubernetes objects.

Annotations are key/value pairs designed to hold non-semantic information that can be used by tools and libraries.

As we will see, labels are essential to the definition of some Kubernetes objects such as Services and Deployments.

To exemplify the usage of labels, and following the recommend usage of labels in Kubernetes, we will create different versions of our 2 Pods by adding labels. Here you have the manifests:

apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-hello-world-labeled
  labels:                                     # label section
    name: hello-world
    part-of: hello-world
    version: labeled
spec:
  containers:
    - image: paulbouwer/hello-kubernetes:1.9
      name: hello-kubernetes
      ports:
        - containerPort: 8080
Enter fullscreen mode Exit fullscreen mode
apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-hello-world-health-labeled
  labels:                                     # label section
    name: hello-world-health
    part-of: hello-world
    version: labeled
spec:
  containers:
    - image: paulbouwer/hello-kubernetes:1.9
      name: hello-kubernetes
      livenessProbe:
        httpGet:
          path: /
          port: 8080
        initialDelaySeconds: 5
        timeoutSeconds: 1
        periodSeconds: 10
        failureThreshold: 3
      ports:
        - containerPort: 8080
Enter fullscreen mode Exit fullscreen mode

By creating these two pods following a similar approach as in the others, we get this output:
Get all pods

Label Selectors

Show Labels

The first interaction with labels is to quickly see all the labels associated with the objects when we list them, just as we can see in the following image.

Get Pods (show labels)

Selector

The other, more handy interaction, is to list the objects that have the specified labels. The following image shows two different ways to do this.

Label selector

Services

As we saw, we currently have four Pods: two of them are labeled and the other two are not.

Imagine that we want to expose the two pods that are labeled. How do we do that? The answer is Services!

Services provide an abstract way to expose an app running on a logical set of Pods and a policy by which to access them. The set of Pods targeted by a Service is usually determined by a LabelSelector.

Services provide three policies to expose your app:

  • ClusterIP (default type): exposes the service on an internal IP in the cluster. This makes the Service only reachable from within the cluster.
  • NodePort: exposes the service on the same port of each node by forwarding traffic to that port to the service. It's a superset (an expansion) of ClusterIP.
  • LoadBalancer: creates an external load balancer in the current cloud (if supported) and assigns a fixed, external IP to the Service. The load balancer directs traffic to the nodes in your cluster using NodePort, so it's a superset of NodePort.

As my Kubernetes Cluster is in a cloud setup (GCP), I will expose my application with a service of type LoadBalancer by creating and applying the following YAML:

apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: LoadBalancer      # service type: ClusterIP (default), NodePort, LoadBalancer
  selector:               # label selector (of pods)
    part-of: hello-world
    version: labeled
  ports:
    - port: 80            # the port in which the service gets requests
      protocol: TCP       # the communication protocol
      targetPort: 8080    # the port at which incoming requests are forward
Enter fullscreen mode Exit fullscreen mode

And you will have the following output:
Service

As you can see, the service is exposed in IP 35.197.19.160, at port 80. So if I access http://35.197.19.160:80, I see my app up and running:
app view

Wrap Up

That's all for this post! As of a summary, we:

  • transitioned from imperative object definitions to declarative ones;
  • explored how to define custom health checks to our pods;
  • learned the basics of how labels and annotations work;
  • understood how to expose applications using services.

In the next post, I will address how to create true deployments with dynamic pod creation and destruction, and also how to use Ingress as a way to expose and load balance our applications.


If you liked my post, you can follow me, see my other posts or check my Kubernetes repo with additional resource examples.

Discussion (2)

Collapse
jmnmv12 profile image
João Miguel Nunes Medeiros Vasconcelos

I really liked the first part os the series, and this post complements very well all the information you presented last time. Good work, can't wait for the next part 💻

Collapse
vascoalramos profile image
Vasco Ramos Author

Thank you João for your words! It has been a very interesting experience!