Overview

  • Chapter 6 of the Kubernetes Cookbook: how the API is organised, what every manifest has in common, and the metadata you organise everything with (namespaces, quotas, labels, annotations).
  • Back to the hub: Kubernetes Cookbook · Related: Learning kubectl, Kubectl
  • Key idea: every object has spec (what you want) and status (what is). Controllers spend their whole lives making status match spec.

Recipes

Recipe 6.1 — Discover the API Endpoints

  • Problem: You want to see which API groups, versions and resources your cluster serves.
  • Solution:
kubectl api-versions                  # apps/v1, batch/v1, networking.k8s.io/v1, …
kubectl api-resources -o wide         # kind, group, namespaced?, verbs
kubectl get --raw /apis/apps/v1 | jq '.resources[].name'
 
# or browse with plain curl through an authenticated local proxy
kubectl proxy --port=8001 &
curl -s localhost:8001/apis | jq '.groups[].name'
  • Discussion: The core group lives at /api/v1 (Pods, Services, ConfigMaps, Secrets). Everything else is under /apis/<group>/<version>. That pair is exactly what you write in apiVersion:.
  • Gotchas: Versions mature as v1alpha1 → v1beta1 → v1. Beta APIs are off by default for new APIs since 1.24, and deprecated versions really do get removed (see the note below).

Changed since the book:

No more unauthenticated port 8080, so use kubectl proxy or kubectl get --raw. Deployments are apps/v1, not extensions/v1beta1. The whole extensions group was removed in 1.16 and 1.22. Check old YAML with kubectl convert (plugin) or the pluto tool.

Source: K8s Cookbook, Recipe 6.1

Recipe 6.2 — The Shape of Every Manifest

  • Problem: You want to write manifests from scratch, not just generate them.
  • Solution: Four top-level keys, every time:
apiVersion: v1          # group/version (core group has no prefix)
kind: Pod               # the type
metadata:               # name, namespace, labels, annotations
  name: mypod
  labels:
    app: demo
spec:                   # desired state; shape depends on kind
  containers:
    - name: app
      image: nginx:1.27
# status: written by the system, never by you
  • Discussion: Several objects can share one file, separated by ---. JSON works too, since YAML is a superset.
  • Gotchas: Tabs aren’t allowed in YAML, and indentation errors produce baffling messages. Use kubectl apply --dry-run=server -f x.yaml to have the API server validate a file without creating anything.

Source: K8s Cookbook, Recipe 6.2

Recipe 6.3 — Namespaces to Avoid Name Collisions

  • Problem: Two teams both want a Deployment called web.
  • Solution:
kubectl create namespace team-a
kubectl create deployment web --image=nginx -n team-a
kubectl create deployment web --image=nginx -n team-b   # no clash
apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    pod-security.kubernetes.io/enforce: baseline   # Pod Security Admission, see the Security note
  • Discussion: Names must be unique per kind, per namespace. Built-in namespaces are default, kube-system (cluster components), kube-public (world-readable), and kube-node-lease (node heartbeats).
  • Gotchas: Some kinds are cluster-scoped and ignore -n: Nodes, PersistentVolumes, StorageClasses, ClusterRoles, CRDs and Namespaces themselves. Namespaces are a soft boundary. They only isolate anything when you add RBAC, quotas and NetworkPolicies.

Source: K8s Cookbook, Recipe 6.3

Recipe 6.4 — Quotas Within a Namespace

  • Problem: One namespace shouldn’t eat the whole cluster.
  • Solution: A ResourceQuota caps object counts and total compute. A LimitRange gives defaults to containers that forget their requests and limits.
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    pods: "20"
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.memory: 16Gi
    persistentvolumeclaims: "5"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: team-a
spec:
  limits:
    - type: Container
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      default:
        memory: 256Mi
  • Discussion: Check usage with kubectl describe quota -n team-a.
  • Gotchas: Once a quota covers requests.cpu, every Pod must declare a CPU request or it’s rejected. That’s why you pair it with a LimitRange that fills in defaults.

Source: K8s Cookbook, Recipe 6.4 · Best Practices book, Ch. 8

Recipe 6.5 / 6.6 — Labels and Label Queries

  • Problem: You want to tag objects and find them again (and so do Services and Deployments).
  • Solution:
kubectl label pods foobar tier=frontend
kubectl label pods foobar tier=backend --overwrite
kubectl label pods foobar tier-                    # remove a label
kubectl get pods --show-labels
kubectl get pods -l app=cockroachdb                # equality
kubectl get pods -l 'env in (production,staging)'  # set-based
kubectl get pods -L app,env                        # labels as columns
  • Discussion: Labels are free-form key/value pairs that Kubernetes doesn’t interpret. But selectors are how Services find their Pods and how Deployments own their ReplicaSets, so labels are load-bearing.
  • Recommended keys (app.kubernetes.io/*): name, instance, version, component, part-of, managed-by.
  • Gotchas: A Deployment’s spec.selector is immutable in apps/v1. Pick it carefully, and don’t put the version in it.

Source: K8s Cookbook, Recipes 6.5 & 6.6

Recipe 6.7 — Annotations

  • Problem: You want to attach non-identifying data: build info, tool config, a reason for change.
  • Solution:
kubectl annotate deploy web kubernetes.io/change-cause="bump to nginx 1.27"
kubectl annotate pod foobar description='used by automation' --overwrite
  • Discussion: Labels are for selecting, annotations are for describing. Tools love annotations (Ingress controllers, Prometheus scrape hints, Helm). kubernetes.io/change-cause fills the CHANGE-CAUSE column in kubectl rollout history.
  • Gotchas: You can’t select by annotation.

Changed since the book:

--record is deprecated, so set kubernetes.io/change-cause explicitly (as above) after each change.

Source: K8s Cookbook, Recipe 6.7


Summary

  • apiVersion = group/version. Core is v1, most workloads are apps/v1. Deprecated betas get removed, so keep manifests current.
  • Every object = apiVersion + kind + metadata + spec (+ status from the system).
  • Namespaces scope names. Quotas and LimitRanges cap usage per namespace.
  • Labels select (Services, Deployments, kubectl -l), annotations describe.