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) andstatus(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 inapiVersion:. - 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 proxyorkubectl get --raw. Deployments areapps/v1, notextensions/v1beta1. The wholeextensionsgroup was removed in 1.16 and 1.22. Check old YAML withkubectl 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.yamlto 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 clashapiVersion: 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), andkube-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
ResourceQuotacaps object counts and total compute. ALimitRangegives 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.selectoris immutable inapps/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-causefills the CHANGE-CAUSE column inkubectl rollout history. - Gotchas: You can’t select by annotation.
Changed since the book:
--recordis deprecated, so setkubernetes.io/change-causeexplicitly (as above) after each change.
Source: K8s Cookbook, Recipe 6.7
Summary
apiVersion= group/version. Core isv1, most workloads areapps/v1. Deprecated betas get removed, so keep manifests current.- Every object =
apiVersion+kind+metadata+spec(+statusfrom the system). - Namespaces scope names. Quotas and LimitRanges cap usage per namespace.
- Labels select (Services, Deployments, kubectl
-l), annotations describe.