Using K8s Label Selectors in Go without doing it wrong

John Montroy
Coding Kubernetes
Published in
4 min readJan 24, 2021
This is the good stuff.

Labels are how we group objects together in Kubernetes. Label selectors are how we use those labels to retrieve our grouped objects. There are a bazillion articles out there on the subject, so if you’re not familiar, start there.

Instead, our focus here is: how do I use Kubernetes libraries to work with labels via label selectors?

If you’re just looking for quick help, the Cheatsheet section will give you top-level summary info. Otherwise, read on.

Important: all Go Playground examples here might need to be run a few times for the dependencies to load in. Go Playground has an unforgiving timeout.

Cheatsheet

There are two Go types for working with label selectors: the labels.Selector interface, and the LabelSelector struct. They are different.

  1. labels.Selector is used for filtering client reads. It’s like the client-go equivalent of kubectl label ... -l service=backend. Use labels.NewSelector + labels.NewRequirement to construct something the client-go can query with.
  2. The LabelSelector struct is used as part of individual Resources as the API type. You’d write this struct in your object’s YAML before applying. So you might get this from a read (as part of a returned object, e.g. Deployment), but don’t use this struct for client reads.
  3. You can convert from a LabelSelector (what you get from a read) to a label.Selector (what you use to read) with LabelSelectorAsSelector . But if you’re doing this, you’re either doing something Controller-esque (if so, fine) OR you should just be using labels.Selector directly.

Using labels.Selector

The labels.Selector interface is located in the apimachinery repo under pkg/labels/selector.go. It’s used to read and query Kubernetes Objects via their labels.

You should use it because:

  1. It provides standard validation for requirements, keys, and values.
  2. It’s what Kubernetes libraries uses, so it’s battle-tested to the extreme.

Initialize a new selector with labels.NewSelector() , and then add labels.Requirements as desired. Think of each Requirement as akin to the normal kubectl label operations you might use for equality-based and set-based label selection (Go Playground):

apimachinery gives you a few additional perks out of the box:

  1. Requirement validation (e.g. Equals shouldn’t have two values).
  2. Key validation (e.g. can’t lead with characters like - or _ )
  3. Value validation (e.g. label values can’t exceed 63 characters)

(Note: these key / value validations are widely used and worth perusing in full.)

apimachinery also provides helpful additional functionality (Go Playground for both):

  • labels.Parse allows you to use the same kubectl label syntax you might already be familiar with.
  • labels.ValidatedSelectorFromSet is a validated convenience for the simplest label selection use case: give me all objects that match this label set exactly, AND’d together. Pass a Set object (under the hood, just map[string]string like labels themselves), it validates client-side (early errors, no extra load on the API Server), and gives you a Selector you can use to query:

What about LabelSelector?

LabelSelector is an API type. That means it’s used as part of various Kubernetes Resource Specs. It’s not for client usage or library code, it’s part of the API, just like Pods and ConfigMaps.

This fact is reflected by where it lives: in apimachinery, but under pkg/apis/v1/meta/types.go. Indeed, comments in that file indicate:

The package contains two categories of types:

— external (serialized) types that lack their own version (e.g TypeMeta)

— internal (never-serialized) types that are needed by several different api groups, and so live here, to avoid duplication and/or import loops (e.g. LabelSelector).

So when you create an Object, you’re probably going to write out a LabelSelector struct in YAML, and when you retrieve Objects from the API Server using labels.Selector as above, your returned Objects might contain the LabelSelector struct as a field. Modified example from the docs:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
<snip>

That spec.selector is a LabelSelector struct object behind the scenes. The Deployment controller will use it (converting it to labels.Selector , more below) to query the Deployment’s Pods.

If this all sounds very obvious to you, good. The key point to understand here is the difference between normal library code and API types. labels.Selector is library code used to read and filter Kubernetes Objects. LabelSelector is part of the API type you get back from those reads.

Converting between the two

Say you use a labels.Selector to query Deployments . You get back matching Deployment Objects, all of which have their own LabelSelector data at spec.selector. Maybe you want to now use those LabelSelector objects to query the Pods programmatically. So we need to convert from LabelSelector to labels.Selector (API type -> library type for querying).

LabelsSelectorAsSelector is your ticket here (r.i.p. Go Playground, not a chance on these dependencies):

Why do we need to do this? Well, controllers do it all the time! In order for the Deployment controller to get all Pods it owns, it will:

  1. Get the LabelSelector API type from a Deployment at spec.selector .
  2. Convert it to a labels.Selector using LabelsSelectorAsSelector .
  3. Query Pods using the newly-converted labels.Selector.

See for yourself in the Deployment Controller, reproduced and shortened here:

This also serves as a great example of everything we’ve discussed here, including actually passing a labels.Selector to a Pod client and querying away!

Conclusions

labels.Selector is library code for filtering reads. LabelSelector is an API type used in other Objects like Deployments and StatefulSets. If you’re writing Go code, you probably want to work with labels.Selector, unless you’re writing controller code that needs to work with some Resource’s selector field!

--

--