1. Overview

Labels and annotations are popular tagging mechanisms in Kubernetes. Further, labels and annotations are both key-value pairs that we can use to tag metadata for resources such as pods. However, there are a few key differences too.

In this tutorial, our main objective is to compare labels with annotations in Kubernetes.

2. Format and Structure

At first glance, labels and annotations appear to have a similar structure, as both allow us to add metadata to a pod in the form of key-value pairs. However, in this section, we’ll learn how labels and annotations differ in structure.

Let’s start by noting that both labels and annotations have the same components for a key in the form of a prefix/name. While the prefix portion is optional and should be a valid DNS subdomain, the name portion is required and must be less than or equal to 63 characters. Further, the *name must begin with an alphanumeric character, with a dash (), underscore (*_), dot(.), or alphanumeric characters in between**:

^([a-zA-Z0-9]([-a-zA-Z0-9_.]*)?[a-zA-Z0-9])$

So far, we can infer no difference between labels and annotations when considering the key part of the key-value pair. However, we must inspect the key-value pair’s value part to determine their significant differences.

Now, let’s go ahead and inspect the regular expression that Kubernetes uses to validate label values:

^(([a-zA-Z0-9]([-a-zA-Z0-9_.]*)?[a-zA-Z0-9]))?$

We can notice that label values can be empty, unlike label names. Besides that, they have the same restrictions, including a maximum length of 63 characters. On the other hand, annotation values are much more flexible and can contain multi-line strings with non-alphanumeric characters and symbols in addition to alphanumeric characters. Moreover, the annotation value can take up to 256KB, which allows us to store relatively large text.

Next, let’s define a few labels and annotations within an nginx pod’s manifest.yaml file:

$ cat manifest.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: dashboard
    environment: test
  annotations:
    build-info: |
      {
        "commit": "abcd123",
        "timestamp": "2023-05-01T10:00:00Z"
      }
spec:
  containers:
    - name: frontend-nginx
      image: nginx

Lastly, let’s create the pod using the manifest.yaml file, and inspect the labels and annotations in the pod:

$ kubectl apply -f manifest.yaml
pod/nginx-pod created
$ kubectl get pods nginx-pod -oyaml | grep -A2 'labels:'
  labels:
    app: dashboard
    environment: test
$ kubectl get pods nginx-pod -oyaml | grep -A5 'annotations:'
  annotations:
    build-info: |
      {
        "commit": "abcd123",
        "timestamp": "2023-05-01T10:00:00Z"
      }

Great! It looks like we’ve got this one right. Further, we must note that while we stored short and simple strings in labels, we used annotation to store an entire JSON object.

3. Querying and Filtering

As labels and annotations are tagging mechanisms, we can use either to filter the Kubernetes resources. However, Kubernetes maintains an internal label index, making labels more efficient for querying and filtering resources. So, Kubernetes recommends using labels for filtering and querying the resources wherever feasible.

Let’s start by using the kubectl command with the –selector flag to query for pods running the dashboard app:

$ kubectl get pods -selector='app=dashboard'
NAME        READY   STATUS    RESTARTS   AGE
nginx-pod   1/1     Running   0          5m33s

The result looks as expected.

Next, let’s extend our query to include only those pods that are running in the test environment:

$ kubectl get pods --selector='app in (dashboard), environment in (test)'
NAME        READY   STATUS    RESTARTS   AGE
nginx-pod   1/1     Running   0          7m36s

Perfect result! We must appreciate the fact that querying via labels is so convenient.

Lastly, let’s also see a scenario where we want to use build-info annotation to find out the pods running a specific code commit:

$ kubectl get pods -o json | \
jq -r '.items[] | select(.metadata.annotations."build-info" | fromjson | .commit=="abcd123") | .metadata.name'
nginx-pod

Although we could filter the pods using annotations, it required us to use the jq command as an external utility.

4. Integration With Controllers

For toy projects, running code within standalone pods might be okay. However, Kubernetes recommends running enterprise applications using controllers, such as Deployment, StatefulSet, Cronjob, and so on, that can ensure that the actual state of pods matches a desired state.

Further, the controllers manage the pod resources based on their labels using the selector and matchLabels field in the spec. However, such an integration isn’t available for annotations.

Let’s go ahead and create the deployment.yaml manifest file to run the dashboard app in the production environment while ensuring that there are three replicas available at any time:

$ cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dashboard
      environment: production
  template:
    metadata:
      labels:
        app: dashboard
        environment: production
    spec:
      containers:
        - name: frontend-nginx
          image: nginx

We must note that the pod labels under the template field match the ones specified under the matchLabels field.

Next, let’s apply this manifest to create the frontend-nginx-deployment deployment that can manage the frontend-nginx containers:

$ kubectl apply -f deployment.yaml 
deployment.apps/frontend-nginx-deployment created
$ kubectl get pods
NAME                                         READY   STATUS              RESTARTS   AGE
frontend-nginx-deployment-7f8459fff4-9qr4q   1/1     Running             0          3s
frontend-nginx-deployment-7f8459fff4-p27lb   0/1     ContainerCreating   0          3s
frontend-nginx-deployment-7f8459fff4-w8njq   0/1     ContainerCreating   0          3s

As expected, we can see that Kubernetes is starting three pods through the deployment controller. Further, it’s useful to reiterate that the frontend-nginx-deployment deployment selects the pod resources to be managed using the label values in the matchLabels field in the specs of the selector field.

Lastly, let’s see the management of pods in action by deleting one of the pods and verifying the before-and-after state of the pods:

$ kubectl get pods
NAME                                         READY   STATUS    RESTARTS   AGE
frontend-nginx-deployment-7f8459fff4-9qr4q   1/1     Running   0          6s
frontend-nginx-deployment-7f8459fff4-p27lb   1/1     Running   0          6s
frontend-nginx-deployment-7f8459fff4-w8njq   1/1     Running   0          6s

$ kubectl delete pod frontend-nginx-deployment-7f8459fff4-9qr4q
pod "frontend-nginx-deployment-7f8459fff4-9qr4q" deleted

$ kubectl get pods
NAME                                         READY   STATUS    RESTARTS   AGE
frontend-nginx-deployment-7f8459fff4-5khjh   1/1     Running   0          4s
frontend-nginx-deployment-7f8459fff4-p27lb   1/1     Running   0          18s
frontend-nginx-deployment-7f8459fff4-w8njq   1/1     Running   0          18s

Fantastic! The frontend-nginx-deployment controller quickly detected that one of the pods was down, spawning a new pod as a replacement.

5. Conclusion

In this article, we used labels and annotations as key-value pairs to attach metadata to pods. Furthermore, we focused on identifying important differences between labels and annotations in terms of structure, accessibility, and direct integration with controllers.