Resources

Kubernetes Labels, Selectors, and Annotations | Getting Started

November 30, 2021

Labels, Selectors, and Annotations each have their role to play when configuring and working with Kubernetes. Let’s jump in.

Cameron Pavey
Full Stack Developer

Kubernetes has many moving parts, and it is essential to wrap your head around quite a few of them if you want to work within Kubernetes efficiently. One of these important aspects is “metadata,” namely labels, selectors, and annotations. These three types of metadata each have their role to play when configuring and working with Kubernetes, whether it is stitching multiple resources together or just providing some more context for developers and DevOps engineers.

In this article, you will see some examples of different types of metadata in action and understand how to work with them—adding, editing, and removing them in various ways—as well as some of the benefits they can each provide.

Labels

Labels are a type of metadata in Kubernetes that take on the form of a key-value pair attached to objects such as pods and services. Labels are often used to describe identifying aspects of the object, possibly for use by the user at a later stage. However, like other metadata, labels do not directly change any functionality as they imply no semantics to Kubernetes by default. One of the nice things about labels is that they let you map your own data structures onto objects in a loosely coupled fashion.

For example, your team might have different “release” types, such as “alpha,” “beta,” and “stable.” This would be a solid use case for labels, allowing you to indicate which of these release types a given object falls under. Although selectors can use labels for identification purposes, it is important to remember that they are not unique, as many objects carry the same labels.

You can add labels to your resources in a few different ways. The first and most common way is to add them directly to your config files to set them when the resource is created or updated. To do this, you can specify label values at <terminal inline>metadata.labels<terminal inline> like so:


apiVersion: v1
kind: Pod
metadata:
  name: metadata-demo
  labels:
    environment: demo
    app: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.14.2
      ports:
        - containerPort: 80

The other way to work with labels is via the kubectl CLI tool. This is handy for making small tweaks to your resources, but it is important to remember that the changes will not be reflected back to your config files automatically.

To add a label to an existing resource, you can use the following command:


# this will create a label “group” with a value of “main”
kubectl label pod/metadata-demo group=main

You can also remove the label using this command:


# remove the “group” label from the resource
kubectl label pod/metadata-demo group-

Finally, you can also use the edit command to change your running configurations in a more imperative way using <terminal inline>kubectl edit pod/metadata-demo<terminal inline>. This will open your CLI editor of choice and allow you to add and remove labels and other details. When you save and exit, the changes will apply.

Selectors

As their name suggests, label selectors allow you to identify the objects you have tagged with particular labels. Label selectors can either be equality-based or set-based. Equality-based label selectors work by specifying an exact value that you want to match against. If you provide multiple selectors, all of them must be satisfied to qualify as a match.

Set-based selectors work in a similar fashion, except you can specify multiple values in a single selector, and only one of them needs to match for the object to qualify.

Selectors come in handy for a few different things. Probably the most common usage is for grouping the correct resources for something like a service. Consider the following configuration file where a deployment will create a pod with a particular label, and that label is then used by a service to determine its association:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: metadata-deployment
  labels:
    app: metadata-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: metadata-pod
  template:
    metadata:
      labels:
        app: metadata-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: metadata-service
  labels:
    app: metadata-service
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: metadata-pod

The significant part of this config is the <terminal inline>selector<terminal inline> field for the service, which tells the service which pods it should associate with and send traffic to.

Selectors are also commonly used for more “human” operations via the command-line tool. In clusters with many resources running, it can be beneficial to use the selectors to discriminate and quickly identify the resources you are interested in. For example, suppose you wanted to find all of the resources in the above configuration file. In that case, you could use the aforementioned set-based selectors to see everything with one of the matching labels, like so:


└> kubectl get all -l 'app in (metadata-pod, metadata-deployment, metadata-service)'
NAME READY STATUS RESTARTS AGE
pod/metadata-deployment-5b468f9986-rvqv2 1/1 Running 0 9m18s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/metadata-service ClusterIP 10.152.183.91 >none< 80/TCP 9m10s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/metadata-deployment 1/1 1 1 9m18s

NAME DESIRED CURRENT READY AGE
replicaset.apps/metadata-deployment-5b468f9986 1 1 1 9m18s

If you are only looking for a specific label, the syntax is similar, if simpler:


└> kubectl get deployment -l app=metadata-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
metadata-deployment 1/1 1 1 12m

Annotations

Annotations are another type of metadata you can use in Kubernetes. While labels can be used to identify and select objects, annotations cannot. Their intended use is to store arbitrary, non-identifying information about objects. This data is often used to provide context about objects to the human operators of the system. One good example of how you can use annotations is the a8r projects, which establish a convention for “using annotations to help developers manage Kubernetes services.” This includes annotations such as <terminal inline>a8r.io/description<terminal inline> and <terminal inline>a8r.io/bugs<terminal inline>, among others. These can be used to store an unstructured text description of the service for humans, as well as a link to an external bug-tracker for the service, respectively.

Because annotations have fewer restrictions on them than labels do, you can store special characters not permitted by labels, as well as large, structured values if your use case requires it. This can be seen when you use the <terminal inline>kubectl edit<terminal inline> command. For example, when editing a deployment configuration, you can see that the previous configuration state is stored in an annotation as structured data:


apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"metadata-deployment"},"name":"metadata-deployment","namespace":"default"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"metadata-pod"}},"template":{"metadata":{"labels":{"app":"metadata-pod"}},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx","ports":[{"containerPort":80}]}]}}}}
…

Like labels, you can add annotations in a few ways, namely via config files or the kubectl command line. Say, for example, you wanted to add some of those a8r.io annotations onto the config example above. That might look something like this if you want to do it as config:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: metadata-deployment
  labels:
    app: metadata-deployment
  annotations:
    a8r.io/owner: John Doe
…

Or, it might look like this, if you want to do it via the CLI:


# add an annotation
kubectl annotate deployment/metadata-deployment a8r.io/description="a simple deployment to demonstrate metadata"

# remove an annotation
kubectl annotate deployment-metadata-deployment a8r.io/description-

Finally, there is always the option of using the <terminal inline>kubectl edit<terminal inline> command to alter the configuration on the fly.

While annotations do not inherently imply semantics to the Kubernetes core, it is still possible for them to affect operation in some cases. A good example of this is with the NGINX Ingress controller (among others). The NGINX Ingress controller allows you to add Kubernetes annotations onto your ingress objects to affect their behavior. Most of these map cleanly to the configuration options available in NGINX, and as such, it is a nice way to allow mapping NGINX specific concepts onto your Kubernetes resources. The NGINX Ingress controller is then able to read these annotations and apply them as needed. An example of this is the <terminal inline>nginx.ingress.kubernetes.io/rewrite-target<terminal inline> annotation. Much like the a8r.io annotations discussed above, this NGINX annotation is prefixed with a specific scope to avoid conflicts with other annotations that may be similarly named.

Conclusion

There are multiple types of metadata in Kubernetes, with just as many ways to work with them and even more use cases. Metadata is essential to managing larger deployments and keeping everything organized, as it gives you a way to impose your own organizational model onto the Kubernetes resources in a loosely coupled fashion, without directly implying semantics.

As exemplified in features like human-readable annotations, this can provide a lot of value for other humans interacting with the system, as it guides them through the resources and paints a picture of how things work together. Metadata isn’t only useful for humans though, it also provides Kubernetes with a way to group, organize, and associate resources, allowing for larger and more complex structures to exist than would be reasonably viable without it.

Looking for an out-of-the-box monitoring solution?

With a simple one-line install, ContainIQ allows you to monitor the health of your cluster with pre-built dashboards and easy-to-set alerts.

Article by

Cameron Pavey

Full Stack Developer

Cameron is a Full Stack Developer at Rexlabs, and before that was a Software Engineer at Catapult. He specializes in Laravel, React, and is also knowledgeable in DevOps. Cameron received a Bachelor of Information Technology from Queensland University of Technology. He is currently living and working in Melbourne, Australia.

Read More