Kubernetes Cookbook

Kubernetes Cookbook

Overview

  • Study notes from two books:
    • Kubernetes Cookbook by Sébastien Goasguen & Michael Hausenblas (O’Reilly, 2018): ~70 recipes in Problem → Solution → Discussion form.
    • Kubernetes Best Practices by Brendan Burns, Eddie Villalba, Dave Strebel & Lachlan Evenson (O’Reilly, 2019): distilled into one checklist note, Best Practices.
  • Everything is modernised to Kubernetes 1.31+. The Cookbook was written for Kubernetes ~1.7–1.10, so plenty has changed. Look for the Changed since the book callouts.
  • Each recipe is condensed into Problem / Solution / Discussion / Gotchas, with its source recipe number.
  • These notes build on my existing Kubernetes notes rather than repeat them:

Biggest Changes Since 2018 (cheat sheet)

Then (book)Now
kubectl run creates a Deploymentkubectl run = one Pod. Use kubectl create deployment
extensions/v1beta1, apps/v1beta1/2apps/v1 (with a required selector)
Ingress extensions/v1beta1, serviceNamenetworking.k8s.io/v1, pathType, ingressClassName, + Gateway API
CronJob batch/v2alpha1batch/v1 (+ timeZone)
HPA autoscaling/v1 + Heapsterautoscaling/v2 + metrics-server
PodSecurityPolicyPod Security Admission (namespace labels)
Helm 2 + TillerHelm 3, no Tiller, OCI charts
hand-rolled overlaysKustomize built into kubectl (apply -k)
client-side applykubectl apply --server-side
Docker Engine as node runtimecontainerd / CRI-O (dockershim removed 1.24)
SA token Secrets auto-createdShort-lived projected tokens, kubectl create token
CRD v1beta1 (no validation, defaults or subresources)apiextensions.k8s.io/v1 with schema, CEL rules, conversion
exec with curl to debugkubectl debug with ephemeral containers
Katacoda, minikube + VirtualBox, ACS, kubicorn, kubelessKillercoda, kind / minikube (Docker) / k3d, AKS, Cluster API, Knative

Study Order

  1. Getting Started & Local Clusters: get a kind or minikube cluster running.
  2. Learning kubectl: get comfortable with get/describe/explain/apply/delete.
  3. API, Namespaces & Labels: how manifests and metadata work.
  4. Creating & Modifying Workloads: Deployments, rollouts, Jobs, CronJobs, DaemonSets, StatefulSets.
  5. Services & Ingress: networking and exposure.
  6. Volumes & Configuration: ConfigMaps, Secrets, PVCs.
  7. Monitoring & Logging: probes, logs, metrics.
  8. Scaling: HPA, VPA, node autoscaling.
  9. Security: ServiceAccounts, RBAC, Pod Security, NetworkPolicy, Secrets in Git.
  10. Managing Manifests: Helm, Kustomize, server-side apply, GitOps.
  11. Maintenance & Troubleshooting: debugging, drain, etcd.
  12. Extending Kubernetes: CRDs, operators, admission policies.
  13. Best Practices: the production checklist that ties it all together.

All Recipes

NoteRecipeOne-liner
Getting Started & Local Clusters1.1Try K8s in a browser sandbox (Killercoda)
Getting Started & Local Clusters1.2Install kubectl (brew / dl.k8s.io), keep within ±1 minor
Getting Started & Local Clusters1.3 / 1.4minikube start, addons, docker-env, image load
Getting Started & Local Clusters1.5First app: create deployment + expose
Getting Started & Local Clusters1.6GUI: minikube dashboard → Headlamp / k9s
Getting Started & Local Clusters2.1 / 2.2kubeadm init/join + containerd + a CNI
Getting Started & Local Clusters2.3 – 2.6Release binaries, static Pods, kubelet under systemd
Getting Started & Local Clusters2.7 / 2.8Managed clusters: GKE, AKS (was ACS), EKS
Learning kubectl3.1get, short names, -A, api-resources
Learning kubectl3.2delete by name/label/file, cascades, finalizers
Learning kubectl3.3-w, watch, rollout status, kubectl wait
Learning kubectl3.4edit, set, patch, scale, and why edits drift
Learning kubectl3.5kubectl explain for field docs
Creating & Modifying Workloads4.1Deployment from the CLI
Creating & Modifying Workloads4.2apply -f file/dir/URL
Creating & Modifying Workloads4.3A Pod manifest from scratch
Creating & Modifying Workloads4.4A Deployment manifest (apps/v1)
Creating & Modifying Workloads4.5Rolling update, history, undo, restart
Services & Ingress5.1Expose a Deployment with a Service
Services & Ingress5.2Verify Service DNS
Services & Ingress5.3Change Service type (NodePort, LoadBalancer)
Services & Ingress5.4Install an Ingress controller
Services & Ingress5.5Ingress networking.k8s.io/v1 (+ Gateway API extra)
API, Namespaces & Labels6.1Discover API groups and versions
API, Namespaces & Labels6.2Anatomy of a manifest
API, Namespaces & Labels6.3Namespaces against name collisions
API, Namespaces & Labels6.4ResourceQuota + LimitRange
API, Namespaces & Labels6.5 / 6.6Labels and selectors
API, Namespaces & Labels6.7Annotations and change-cause
Creating & Modifying Workloads7.1Job (with TTL cleanup)
Creating & Modifying Workloads7.2CronJob batch/v1
Creating & Modifying Workloads7.3DaemonSet per node
Creating & Modifying Workloads7.4StatefulSet + headless Service
Creating & Modifying Workloads7.5Init containers (+ native sidecars)
Volumes & Configuration8.1Share files via emptyDir
Volumes & Configuration8.2Secrets (and why base64 ≠ encryption)
Volumes & Configuration8.3ConfigMaps as env vars or files
Volumes & Configuration8.4Static PV + PVC
Volumes & Configuration8.5 / 8.6Dynamic provisioning with StorageClasses
Scaling9.1kubectl scale
Scaling9.2 / 9.3Node autoscaling (Cluster Autoscaler, Karpenter)
Scaling9.4HPA autoscaling/v2 (+ VPA extra)
Security10.1ServiceAccounts and bound tokens
Security10.2kubectl auth can-i, inspect roles
Security10.3Role + RoleBinding (least privilege)
Security10.4securityContext + Pod Security Admission (+ NetworkPolicy extra)
Monitoring & Logging11.1kubectl logs (incl. --previous)
Monitoring & Logging11.2 – 11.4Liveness, readiness, startup probes
Monitoring & Logging11.5metrics-server + kubectl top (was Heapster)
Monitoring & Logging11.6Prometheus via kube-prometheus-stack
Monitoring & Logging11.7Central logging (Fluent Bit → Loki/OpenSearch)
Maintenance & Troubleshooting12.1kubectl autocompletion
Services & Ingress12.2Relabel a Pod out of a Service
Services & Ingress12.3Reach a ClusterIP Service (port-forward, proxy)
Maintenance & Troubleshooting12.4Parse status with jsonpath and kubectl wait
Maintenance & Troubleshooting12.5Debug Pods (statuses table, kubectl debug)
Maintenance & Troubleshooting12.6cluster-info dump snapshot
Maintenance & Troubleshooting12.7Add worker nodes (kubelet, containerd, kube-proxy)
Maintenance & Troubleshooting12.8cordon/drain/uncordon + PDBs
Maintenance & Troubleshooting12.9etcd v3 snapshots
Extending Kubernetes13.1 / 13.2Build K8s from source (make WHAT=)
Extending Kubernetes13.3Python client
Extending Kubernetes13.4CRDs apiextensions.k8s.io/v1 (+ operators, admission extras)
Managing Manifests14.1Install Helm 3 (no Tiller)
Managing Manifests14.2Install charts, upgrade, rollback
Managing Manifests14.3Create and package your own chart
Managing Manifests14.4Compose → K8s with kompose (+ Kustomize, SSA, GitOps extras)
Extending Kubernetes14.5Clusters as code (kubicorn → Cluster API/eksctl)
Security14.6Encrypted Secrets in Git (Sealed Secrets, ESO, SOPS)
Extending Kubernetes14.7Functions (kubeless → Knative/OpenFaaS/KEDA)
Best PracticesBP Ch. 1–17The production checklist

Summary

  • 12 topic notes + 1 Best Practices checklist, covering every Cookbook recipe (1.1–14.7) with modern APIs and tools.
  • Start with local clusters and kubectl, then workloads → networking → config → observability → scaling → security → packaging → ops → extending.
  • Rule of thumb throughout: declarative YAML in Git, current API versions, least privilege, and probes + resources on everything.
Link to original

Kubernetes

Kubernetes

Kubernetes

  • Kubernetes is not a programming language, it is a container orchestration tool.
  • Kubernetes also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.
  • It groups containers that make up an application into logical units for easy management and discovery.
Link to original

Best Practices

Best Practices

Overview

  • A distillation of Kubernetes Best Practices (Burns, Villalba, Strebel & Evenson, O’Reilly 2019) into checklists, updated for Kubernetes 1.31+. The Cookbook tells you how. This book tells you what good looks like.
  • Each section cites its chapter (“BP Ch. n”). Deeper how-tos are linked to the Cookbook notes.
  • Back to the hub: Kubernetes Cookbook · Basics: Kubernetes, Deployments, Services, ConfigMap

Changed since the book (2019):

  • PodSecurityPolicy (all of Ch. 10’s first half) was removed in 1.25 → Pod Security Admission.
  • Helm 2/Tiller advice is obsolete → Helm 3.
  • PodPresets never left alpha and were removed.
  • KubeFed (federation) was archived → GitOps fleets (Argo CD ApplicationSets, Flux), Cluster API.
  • The TTL controller is GA.
  • Gatekeeper is still around, now joined by Kyverno and built-in ValidatingAdmissionPolicy.

1. Deploying a Service (BP Ch. 1)

  • Deployments for almost everything stateless, Services for a stable address, and an Ingress/Gateway for HTTP with TLS and routing.
  • Images:
    • Build from trusted bases or from scratch/distroless. Supply-chain attacks are real, so sign images (cosign) and scan them.
    • Treat tags as immutable: v1.0.1-bfeda01f (semver + git SHA). Never :latest in prod. Pinning a digest (@sha256:…) is even stricter.
  • Parameterise per environment with Helm or Kustomize (Managing Manifests), not copy-paste.

A “Production-Ready” Deployment, All in One

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
    app.kubernetes.io/version: "1.4.2"
    app.kubernetes.io/part-of: shop
    app.kubernetes.io/managed-by: argocd
spec:
  replicas: 3                                   # drop this if an HPA owns scaling
  revisionHistoryLimit: 5
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
        app.kubernetes.io/version: "1.4.2"
    spec:
      serviceAccountName: web
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      topologySpreadConstraints:                # spread across zones
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: web
      terminationGracePeriodSeconds: 30
      containers:
        - name: web
          image: registry.example.com/shop/web:1.4.2-bfeda01f
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: web-config
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              memory: 256Mi                     # memory limit = request → no OOM surprises from overcommit
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /livez
              port: http
            periodSeconds: 10
            failureThreshold: 3
          lifecycle:
            preStop:
              sleep:
                seconds: 5                      # let endpoints update before shutdown
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]

2. Developer Workflows (BP Ch. 2)

  • Design for three phases: onboarding → developing → testing.
  • One shared dev cluster (namespace per developer) usually beats a cluster each. Give each person their own identity + namespace + ResourceQuota.
  • Reap stale namespaces automatically (TTL labels, cleanup jobs), because developers forget.
  • Provide shared cluster services (logging, monitoring, maybe databases via Helm) for everyone.
  • Inner-loop tools: Skaffold, Tilt, DevSpace, kind load docker-image (Getting Started & Local Clusters).

3. Monitoring, Logging, Alerting (BP Ch. 3)

  • Monitor infrastructure with USE (utilisation, saturation, errors) and services with RED (rate, errors, duration).
  • Black-box checks catch symptoms, white-box instrumentation explains them.
  • Prometheus-style, label-rich time series. Watch cardinality.
  • Logs: stdout + a DaemonSet shipper (not a sidecar per Pod). Keep hot logs ~30–45 days, then archive cheaply.
  • Alert on SLO-impacting symptoms, fight alert fatigue, and keep improving alerts.
  • Details: Monitoring & Logging

4. Configuration, Secrets, RBAC (BP Ch. 4)

  • ConfigMaps/Secrets:
    • Mount as volumes for hot reload (not subPath).
    • Env vars need a restart to pick up changes.
    • Easiest rule: any config change = a new rollout. Use hashed names (Kustomize configMapGenerator) or a Helm checksum/config annotation.
    • Referenced ConfigMaps/Secrets must exist first (or be optional). A missing configMapKeyRef key blocks Pod start, while invalid keys in envFrom are skipped with an InvalidVariableNames event.
  • Secrets:
    • Encrypt at rest (KMS).
    • Keep the source of truth in an external vault (External Secrets, CSI driver).
    • Put imagePullSecrets on the ServiceAccount, not in every Pod spec.
  • RBAC:
    • Most apps need no RBAC at all. Disable token automount for them.
    • Apps that call the API get a dedicated ServiceAccount + least-privilege Role.
    • Humans via OIDC/SSO groups. Just-in-time elevation for SREs.
    • Separate ServiceAccounts for CI/CD.
    • Avoid granting list/watch on Secrets.
  • Details: Volumes & Configuration, Security

5. CI/CD (BP Ch. 5)

  • Fast, reliable builds and tests, because quick feedback = productive devs.
  • Pipelines as code, versioned with the app.
  • Multi-stage builds for small images with a small attack surface.
  • Tags traceable to a commit/build ID. No latest.
  • Start CD with rolling updates, then graduate to blue/green and canary (Argo Rollouts, Flagger, Gateway API weights).
  • Test client-connection draining and DB schema migrations (expand → migrate → contract) as part of rollouts.
  • Test in production carefully: chaos experiments with a small blast radius and good monitoring.
  • Today: CI builds, signs and pushes the image, then bumps Git. GitOps (Argo CD/Flux) deploys (Managing Manifests).

6. Versioning, Releases, Rollouts (BP Ch. 6)

  • Version the app as a whole separately from each container and each Deployment spec, because they change independently.
  • Put release name/number labels on objects that match your CD system’s release IDs, for traceability and easy rollback targeting.
  • Bundle things that must roll back together into one Helm chart/release. Use hooks for ordered steps (e.g. a pre-upgrade DB backup Job).
  • Keep release channels simple: stable / canary / alpha.

7. Global Distribution & Staged Rollouts (BP Ch. 7)

  • Replicate images to registries near every region.
  • Shift testing left (integration and replay tests) so only confident releases start rolling.
  • Roll out canary region first, then low-risk regions, then the rest, with health gates between waves.
  • Write down and rehearse rollback. Nobody remembers runbooks mid-incident.

8. Resource Management (BP Ch. 8)

  • Requests on every container (scheduling + autoscaling depend on them) and memory limits on every container. CPU limits are debated, since they cause throttling. Many teams set CPU requests only.
  • QoS: Guaranteed (req = limit) for critical workloads, Burstable for most, never BestEffort in prod.
  • ResourceQuota per namespace/team + LimitRange defaults (API, Namespaces & Labels).
  • High availability:
    • Pod anti-affinity / topologySpreadConstraints across zones.
    • PodDisruptionBudgets for drains and upgrades.
  • Special hardware: taint GPU or special nodes and add tolerations only where needed. Use nodeSelector/affinity to target them. Node pools for mixed workload shapes.
  • Right-size nodes before production (cost vs. performance).
  • Scaling: start manual until you know the workload profile. HPA for spiky workloads. VPA recommendations for sizing. Cluster Autoscaler/Karpenter with an eye on spin-up time. Details: Scaling

9. Networking & Service Mesh (BP Ch. 9)

  • CNI:
    • Pick for features: NetworkPolicy, eBPF, encryption, multi-cluster. Cilium and Calico are common.
    • Make sure your cloud supports it.
    • Plan Pod CIDR and IP space generously (default max 110 Pods per node).
    • Check that your observability and security tools work with it.
  • Services: keep most ClusterIP. Expose only what must be public, and for HTTP use one Ingress/Gateway with TLS.
  • Standardise on one ingress controller (annotations aren’t portable), run it HA, and scale on metrics. API-gateway-style controllers suit API-heavy traffic.
  • NetworkPolicy: start with ingress rules, add a default-deny per namespace, open explicitly with labels, and keep a permissive test namespace for figuring out flows. Details: Security
  • Service mesh: only adopt for concrete needs (mTLS, retries, traffic shifting, observability). Weigh the operational cost. Sometimes your CNI gives you mTLS already. Today’s lighter options: Istio ambient mode (no sidecars) and Linkerd.

10. Pod & Container Security (BP Ch. 10)

  • Enforce pod hardening with Pod Security Admission (baseline everywhere, restricted where possible) instead of PSP.
  • RuntimeClass sandboxes (gVisor, Kata) for untrusted workloads. Isolation ≠ secure multi-tenancy, since the API is still shared. Mixed runtimes complicate debugging and portability.
  • Runtime threat detection: Falco, Tetragon.

11. Policy & Governance (BP Ch. 11)

  • Use a policy engine for org rules: ValidatingAdmissionPolicy (CEL), Kyverno or OPA Gatekeeper. Examples: allowed registries, required labels, no :latest, required probes and resources.
  • Enforce at the Pod level (the last hop before runtime). The user then sees the error on the ReplicaSet/events, not on kubectl apply. Use warn/audit modes to give early feedback.
  • Scope constraints tightly (kinds, namespaces, labels). Don’t replicate Secrets into the policy engine’s cache.
  • Run policies in audit mode first, then enforce.

12. Multiple Clusters (BP Ch. 12)

  • Use multiple clusters for blast radius, compliance (PCI, HIPAA), hard multi-tenancy, regions, and special workloads (HPC/GPU).
  • Have a data replication strategy before going multi-region. Stateless is easy, data is hard.
  • GitOps for fleet consistency (Argo CD ApplicationSets, Flux). Cluster API for lifecycle. Global load balancing between clusters.
  • Plan cross-cluster discovery/networking (mesh, Cilium ClusterMesh, MCS API) and make CD multi-cluster aware.

13. Integrating External Services (BP Ch. 13)

  • Calling out:
    • Selector-less Service + manual EndpointSlice for fixed IPs.
    • ExternalName (DNS CNAME) for a stable hostname, e.g. a managed DB.
    • A controller that syncs endpoints if addresses change.
  • Exposing in: an internal LoadBalancer (cloud annotation) or NodePort for on-prem consumers.
  • Cluster to cluster: export from one cluster, import as a selector-less Service in the other.

14. Machine Learning Workloads (BP Ch. 14)

  • Taint GPU nodes (nvidia.com/gpu:NoSchedule) and autoscale GPU pools to zero when idle. GPUs are the expensive bottleneck, so keep them saturated.
  • Separate, tainted node pools for ML in mixed clusters. Monitor GPU, CPU, network and storage together.
  • Today: NVIDIA GPU Operator, Kueue for batch/job queueing, Kubeflow/Ray (KubeRay) for training, and Dynamic Resource Allocation (DRA) for flexible accelerators.

15. Building Platforms (BP Ch. 15)

  • Use admission controllers to validate or mutate (inject sidecars, defaults) and kubectl plugins (krew) to extend the UX.
  • Design for users’ growth: an easy platform that traps users is a failure. Keep an escape hatch to plain Kubernetes, and support plain container images and normal Service discovery.

16. State & Stateful Apps (BP Ch. 16)

  • Use volumes mainly to share data between containers (emptyDir). hostPath only for node agents. Logs go to stdout, not a disk.
  • Default StorageClass on, with WaitForFirstConsumer and zone-aware topology (keep the Pod and its disk in the same zone).
  • Ask first: can a managed DB service do this instead? How hard is it to make the app stateless?
  • Back up volumes with CSI VolumeSnapshots or Velero, since Kubernetes doesn’t do it for you. Use Retain reclaim for sensitive or forensic data.
  • StatefulSets:
    • Create the headless Service yourself.
    • Ordinal identity doesn’t always need PVs.
    • On a dead node, Pods sit in Terminating/Unknown. Force-delete only as a last resort, and only when the node can’t come back.
    • Use preStop hooks for graceful leader hand-off.
  • Prefer a mature operator for complex data systems (see Extending Kubernetes).

17. Admission Control & Authorization (BP Ch. 17)

  • Mutating runs before validating. Any rejection rejects the whole request.
  • Don’t let two mutating webhooks edit the same field. Add a validating check after mutation.
  • Choose failurePolicy deliberately, keep webhooks fast (30 s max), scope with namespaceSelector, skip kube-system, lock webhook configs with RBAC, and don’t send Secrets to webhooks.
  • Authorization: use RBAC only. ABAC needs file syncing and API server restarts. Authorization webhooks are risky single points of failure.

Summary

  • Every workload: immutable image tag, requests + memory limit, readiness + liveness (+ startup), labels (app.kubernetes.io/*), non-root restricted securityContext, ≥ 2 replicas spread across zones, a PDB.
  • Every namespace: quota + LimitRange, Pod Security labels, default-deny NetworkPolicy, least-privilege RBAC.
  • Every change: through Git and CI/CD (GitOps), traceable tags/labels, rolling or canary rollouts, rehearsed rollback.
  • Every cluster: metrics + logs + symptom-based alerts, policy engine in audit then enforce, backups of etcd/objects/volumes.
Link to original

Getting Started & Local Clusters

Getting Started & Local Clusters

Overview

  • Chapters 1 and 2 of the Kubernetes Cookbook: how to get a cluster to play with, from a browser sandbox to a laptop cluster to a cloud-managed one.
  • Back to the hub: Kubernetes Cookbook · Big picture: Kubernetes · CLI basics: Kubectl
  • Rule of thumb in 2026: learn on a local cluster (kind, minikube, k3d), deploy to a managed one (GKE, EKS, AKS). Hand-built clusters (kubeadm) are for learning the internals or for on-prem.

Changed since the book (2018 → Kubernetes 1.31+)

  • Katacoda shut down in 2022, so use Killercoda or Play with Kubernetes instead.
  • minikube no longer needs VirtualBox. It defaults to the Docker driver (or podman, qemu, vfkit on macOS).
  • Docker Engine is no longer the node runtime. dockershim was removed in 1.24, and nodes now run containerd or CRI-O. Images built with Docker still run fine because they’re standard OCI images. See docker.
  • kubectl run now creates a single Pod, not a Deployment (since 1.18). Use kubectl create deployment.

Choosing a Local Cluster

ToolRuns asBest forStart command
kindNodes are Docker containersCI, multi-node tests, throwaway clusterskind create cluster
minikubeVM or containerLearning, built-in add-ons (ingress, metrics-server, dashboard)minikube start
k3dk3s in DockerVery light, fast startupk3d cluster create dev
Docker Desktop / OrbStack / Rancher DesktopBuilt into the desktop app”Just a tick-box”Settings → enable Kubernetes

Recipes

Recipe 1.1 — Try Kubernetes Without Installing Anything

  • Problem: You want to poke at Kubernetes with zero setup.
  • Solution: Use a browser sandbox: the interactive tutorials on kubernetes.io, Killercoda, or Play with Kubernetes. Sessions are temporary (roughly an hour).
  • Gotchas: Nothing persists, so treat it as a scratchpad.

Source: K8s Cookbook, Recipe 1.1

Recipe 1.2 — Install kubectl

  • Problem: You need the CLI to talk to any cluster.
  • Solution:
# macOS
brew install kubectl
 
# Linux (official binary)
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -m 0755 kubectl /usr/local/bin/kubectl
 
kubectl version --client
  • Discussion: Keep kubectl within ±1 minor version of the API server (the “version skew” policy).
  • Gotchas: Docker Desktop also ships a kubectl. Run which -a kubectl if versions look odd.

Changed since the book:

The download URL moved from storage.googleapis.com/kubernetes-release to dl.k8s.io.

Source: K8s Cookbook, Recipe 1.2

Recipe 1.3 / 1.4 — Run and Use minikube Locally

  • Problem: You want a real single-node (or multi-node) cluster on your laptop.
  • Solution:
brew install minikube
minikube start --cpus=4 --memory=8g --kubernetes-version=stable
kubectl get nodes                  # context "minikube" is selected for you
 
minikube ip | ssh | stop | delete  # everyday sub-commands
minikube addons list               # ingress, metrics-server, dashboard…
eval $(minikube docker-env)        # build images straight into minikube's runtime
minikube image load myapp:dev      # …or push a local image into the cluster
  • Discussion: docker-env (or minikube image load) saves you from pushing images to a registry during dev. With kind, the equivalent is kind load docker-image myapp:dev.
  • Gotchas: If you use :latest with a locally loaded image, set imagePullPolicy: IfNotPresent. Otherwise Kubernetes tries to pull from Docker Hub and fails.

Changed since the book:

minikube ≥ 1.x picks the Docker driver automatically, and --memory accepts units (8g). You can also run multi-node with minikube start --nodes 3.

Source: K8s Cookbook, Recipes 1.3 & 1.4

Recipe 1.5 — Start Your First Application

  • Problem: The cluster is up and you want something running and reachable.
  • Solution:
kubectl create deployment ghost --image=ghost:5 --port=2368
kubectl expose deployment ghost --port=2368 --type=NodePort
kubectl get pods -w
minikube service ghost          # opens the NodePort in your browser
# (kind/k3d alternative) kubectl port-forward deploy/ghost 2368:2368
  • Discussion: create deployment and expose are imperative generators. They write the YAML for you. For anything you keep, generate the YAML and commit it: kubectl create deployment ghost --image=ghost:5 --dry-run=client -o yaml > ghost.yaml.
  • Gotchas: Old blog posts say kubectl run ghost … creates a Deployment. It doesn’t anymore, so nothing would recreate that Pod if it died. See Deployments.

Changed since the book:

kubectl run = bare Pod only. --dry-run must now be --dry-run=client (or =server).

Source: K8s Cookbook, Recipe 1.5

Recipe 1.6 — A GUI for the Cluster

  • Problem: You want a point-and-click view of Deployments, ReplicaSets and Pods.
  • Solution: minikube dashboard still works for a quick look. For a maintained desktop/web UI use Headlamp (a Kubernetes SIG UI project). Terminal fans use k9s.
  • Discussion: A GUI is great for seeing the Deployment → ReplicaSet → Pod chain from Deployments. Create things with YAML, not clicks.

Changed since the book:

The upstream Kubernetes Dashboard project has been archived. Headlamp is now the suggested replacement. Never expose a dashboard publicly with a cluster-admin token.

Source: K8s Cookbook, Recipe 1.6

Recipe 2.1 / 2.2 — Bootstrap a Cluster with kubeadm

  • Problem: You want a “real” multi-machine cluster on your own VMs or bare metal.
  • Solution (on every node):
    1. Install a container runtime (containerd), and turn swap off (or configure kubelet’s swap support).
    2. Add the pkgs.k8s.io repo for your minor version and install kubelet kubeadm kubectl.
    3. On the control plane: sudo kubeadm init --pod-network-cidr=10.244.0.0/16.
    4. Copy /etc/kubernetes/admin.conf to ~/.kube/config.
    5. Install a CNI plugin (Cilium, Calico or Flannel). Nodes stay NotReady until you do.
    6. On each worker, run the printed command: kubeadm join <cp-ip>:6443 --token <t> --discovery-token-ca-cert-hash sha256:<h>.
  • Discussion: kubeadm is the standard building block that many installers wrap. It runs the control plane as static Pods (see 2.6).
  • Gotchas: Tokens expire after 24h. Make a new one with kubeadm token create --print-join-command.

Changed since the book:

apt.kubernetes.io / packages.cloud.google.com are gone, so use pkgs.k8s.io. Don’t apt-get install docker.io as the runtime. Weave Net is discontinued, so pick Cilium, Calico or Flannel. kubeadm is GA, no longer “beta”.

Source: K8s Cookbook, Recipes 2.1 & 2.2

Recipe 2.3 – 2.6 — Release Binaries, hyperkube and systemd

  • Problem: You want to understand what a cluster is made of, binary by binary.
  • Solution / what’s still true:
    • Release binaries live at https://dl.k8s.io/<version>/…. Always verify the published SHA256, and the signatures, which releases now carry (cosign).
    • The kubelet is the only component that has to be a systemd service. kubeadm writes /lib/systemd/system/kubelet.service plus a drop-in at /etc/systemd/system/kubelet.service.d/10-kubeadm.conf.
    • The kubelet watches /etc/kubernetes/manifests/ and runs any Pod YAML it finds there as a static Pod. That’s how etcd, kube-apiserver, kube-controller-manager and kube-scheduler run on kubeadm clusters: Kubernetes runs itself.
  • Gotchas: Static Pods show up in kubectl get pods -n kube-system as mirror pods. Deleting them via kubectl does nothing; edit the file on the node instead.

Changed since the book:

The hyperkube all-in-one image was removed (1.19). Images moved from gcr.io/google_containers / k8s.gcr.io to registry.k8s.io. The insecure API port (--insecure-port=8080) no longer exists. For the “learn every piece by hand” route, see Kubernetes the Hard Way.

Source: K8s Cookbook, Recipes 2.3 – 2.6

Recipe 2.7 / 2.8 — Managed Clusters (GKE, AKS, EKS)

  • Problem: You want a production-grade control plane without running it yourself.
  • Solution:
# Google GKE (Autopilot = Google also manages nodes)
gcloud container clusters create-auto demo --region=europe-west1
gcloud container clusters get-credentials demo --region=europe-west1
 
# Azure AKS
az group create -n k8s -l northeurope
az aks create -g k8s -n demo --node-count 2 --generate-ssh-keys
az aks get-credentials -g k8s -n demo
 
# AWS EKS
eksctl create cluster --name demo --region eu-west-1 --nodes 2
  • Discussion: The control plane (API server, etcd) is invisible to you, so you can’t SSH to it or touch etcd. You pay per cluster-hour plus nodes.
  • Gotchas: Delete when done (gcloud container clusters delete, az group delete -n k8s, eksctl delete cluster). Forgotten clusters are a classic surprise bill.

Changed since the book:

Azure ACS was retired, so az acs … is now az aks …. GKE gained Autopilot. For EKS, eksctl is the usual CLI.

Source: K8s Cookbook, Recipes 2.7 & 2.8


Summary

  • Local: kind / minikube / k3d. Cloud: GKE / AKS / EKS. DIY: kubeadm + containerd + a CNI.
  • kubectl run = Pod, kubectl create deployment = Deployment. Generate YAML with --dry-run=client -o yaml.
  • Control-plane components on kubeadm clusters are static Pods in /etc/kubernetes/manifests.
  • Nodes run containerd/CRI-O, not Docker, but your Docker-built images work unchanged.
Link to original

Learning kubectl

Learning kubectl

Overview

  • Chapter 3 of the Kubernetes Cookbook: the everyday kubectl verbs (get, delete, watch, edit, explain).
  • My basics are already in Kubectl (deploying images, get/edit/delete, port-forward, proxy). This note adds the power-user layer on top.
  • Back to the hub: Kubernetes Cookbook

The Mental Model

  • Verb + resource type + name: kubectl get deployment web, kubectl delete svc web.
  • Every command hits the API server. kubectl is just a fancy REST client (see API, Namespaces & Labels).
  • Context & namespace decide where you’re pointing:
kubectl config get-contexts
kubectl config use-context kind-dev
kubectl config set-context --current --namespace=my-app   # stop typing -n every time

Recipes

Recipe 3.1 — Listing Resources

  • Problem: You want to see what’s running.
  • Solution:
kubectl get pods                       # current namespace
kubectl get pods -A                    # all namespaces
kubectl get svc,deploy                 # several kinds at once
kubectl get deploy web -o wide         # extra columns
kubectl get pods -o yaml | less        # the full objects
kubectl api-resources                  # every kind + short name + namespaced?
  • Short names worth memorising:
ShortKindShortKind
popodsdeploydeployments
svcservicesrsreplicasets
cmconfigmapsdsdaemonsets
nsnamespacesstsstatefulsets
nonodesingingresses
pv / pvcpersistent volumes / claimshpahorizontal pod autoscalers
saserviceaccountsnetpolnetworkpolicies
  • Gotchas: kubectl get all is not all. It skips ConfigMaps, Secrets, Ingresses, PVCs, CRDs and more.

Source: K8s Cookbook, Recipe 3.1

Recipe 3.2 — Deleting Resources

  • Problem: You want to clean up.
  • Solution:
kubectl delete deploy web
kubectl delete svc,deploy -l app=niceone      # by label
kubectl delete -f app.yaml                     # whatever the file created
kubectl delete ns my-app                       # nukes everything inside it
kubectl delete pod stuck --grace-period=0 --force   # last resort
  • Discussion: Delete the owner, not the children. Deleting a Pod owned by a Deployment just makes a new one appear (that’s the self-healing from Pods). Owners cascade-delete their dependents via ownerReferences and garbage collection (--cascade=background|foreground|orphan).
  • Gotchas: Force-deleting a StatefulSet Pod can leave two Pods with the same identity if the node comes back. A namespace stuck in Terminating almost always means a finalizer is waiting on a controller that’s gone.

Source: K8s Cookbook, Recipe 3.2

Recipe 3.3 — Watching Changes

  • Problem: You want a live view while things roll out.
  • Solution: kubectl get pods -w (streams changes), or watch -n1 kubectl get pods (redraws the screen). kubectl rollout status deploy/web blocks until a rollout finishes. kubectl wait --for=condition=Ready pod -l app=web --timeout=60s is the scripting-friendly version.
  • Gotchas: -w prints a new line per change rather than redrawing. Use watch or k9s for a dashboard feel.

Source: K8s Cookbook, Recipe 3.3

Recipe 3.4 — Editing Resources

  • Problem: You need to change a live object.
  • Solution:
KUBE_EDITOR="code --wait" kubectl edit deploy web     # opens live YAML
kubectl set image deploy/web web=nginx:1.27            # targeted shortcuts
kubectl scale deploy/web --replicas=3
kubectl patch deploy web -p '{"spec":{"replicas":2}}'
  • Discussion: edit is great for experiments, but the change isn’t in Git. The next kubectl apply -f of your file will quietly undo it (“drift”). Prefer editing the file and re-applying. More on that in Managing Manifests.
  • Gotchas: Not every edit triggers a rollout. Only changes under .spec.template do (see Creating & Modifying Workloads).

Changed since the book:

The book’s example starts with kubectl run nginx and then edits deploy/nginx. Today run creates a Pod, so there is no deploy to edit. Use kubectl create deployment nginx --image=nginx.

Source: K8s Cookbook, Recipe 3.4

Recipe 3.5 — kubectl explain

  • Problem: What does this field mean, what type is it, and is it required?
  • Solution:
kubectl explain deployment.spec.strategy
kubectl explain pod.spec.containers.livenessProbe --recursive | less
kubectl explain ingress --api-version=networking.k8s.io/v1
  • Discussion: The docs come from the API server’s OpenAPI schema, so they match your cluster’s version and include CRDs.
  • Gotchas: It needs a reachable cluster, because the schema is served by the API server.

Source: K8s Cookbook, Recipe 3.5


Bonus: Modern kubectl Tricks (not in the book)

  • Generate YAML instead of writing it: kubectl create deploy web --image=nginx --dry-run=client -o yaml > web.yaml
  • Diff before apply: kubectl diff -f web.yaml
  • Server-side apply: kubectl apply --server-side -f web.yaml (see Managing Manifests)
  • Events, sorted: kubectl events --for pod/web-123 or kubectl get events --sort-by=.lastTimestamp
  • JSONPath output: kubectl get pods -o jsonpath='{.items[*].status.podIP}'
  • Custom columns: kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName
  • Plugins via krew: kubectl krew install ctx ns neat tree. Then kubectl ctx / kubectl ns switch context and namespace fast.
  • Autocomplete: source <(kubectl completion zsh), and alias k=kubectl plus compdef k=kubectl (see Maintenance & Troubleshooting).

Summary

  • get / describe / explain to look, apply / edit / set / patch / scale to change, delete to remove.
  • Use short names and -A, -l, -o yaml|wide|jsonpath.
  • Delete owners, not Pods. Watch out for finalizers.
  • Imperative edits drift from Git, so prefer edit file → kubectl diff → kubectl apply.
Link to original

API, Namespaces & Labels

API, Namespaces & Labels

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.
Link to original

Creating & Modifying Workloads

Creating & Modifying Workloads

Overview

  • Chapters 4 and 7 of the Kubernetes Cookbook: Pods and Deployments (long-running apps), then the specialised controllers for Jobs, CronJobs, DaemonSets, StatefulSets and init containers.
  • My basics: Pods (ephemeral, unique IP), Deployments (ReplicaSets, thrashing, CrashLoopBackOff). This note covers the how plus the other workload types.
  • Back to the hub: Kubernetes Cookbook

Which Controller Do I Want?

Workload shapeControllerExample
Long-running, identical, stateless replicasDeploymentweb API, frontend
Run to completion onceJobDB migration, batch convert
Run to completion on a scheduleCronJobnightly backup
Exactly one per nodeDaemonSetlog shipper, node exporter, CNI agent
Stable identity + own disk per replicaStatefulSetPostgres, Kafka, CockroachDB
Just one throwaway containerbare Podkubectl run tmp --rm -it … debugging

Changed since the book:

All these controllers are GA in apps/v1 (Deployment, DaemonSet, StatefulSet, ReplicaSet) or batch/v1 (Job, CronJob). extensions/v1beta1, apps/v1beta1/2 and batch/v2alpha1 are gone. In apps/v1, spec.selector is required and must match the template labels.


Recipes — Fundamental Workloads (Ch. 4)

Recipe 4.1 — Create a Deployment from the CLI

  • Problem: Quickly launch a long-running app.
  • Solution:
kubectl create deployment ghost --image=ghost:5 --port=2368 --replicas=2
kubectl create deployment mysql --image=mysql:8.4
kubectl set env deployment/mysql MYSQL_ROOT_PASSWORD=root   # demo only, use a Secret for real
kubectl expose deployment ghost --port=2368                 # Service in one line
kubectl run myshell --image=busybox:1.36 -it --rm --restart=Never -- sh   # one-off Pod
  • Gotchas: create deployment has no --env, so add env vars with kubectl set env or in YAML.

Changed since the book:

Every kubectl run … in the book that creates a Deployment (--replicas, --expose, --requests) no longer works that way. run now makes one Pod, and most of those flags were removed.

Source: K8s Cookbook, Recipe 4.1

Recipe 4.2 — Create Objects from Manifests

  • Problem: You want the object’s properties written down, not typed.
  • Solution: kubectl apply -f file.yaml, -f dir/, -f https://…/x.yaml, or -k overlay/ for kustomize.
  • Discussion: create -f fails if the object exists. apply is idempotent (create-or-update), so it’s the one to use in scripts and GitOps.

Source: K8s Cookbook, Recipe 4.2

Recipe 4.3 — Write a Pod Manifest from Scratch

  • Problem: Understand the minimal Pod.
  • Solution:
apiVersion: v1
kind: Pod
metadata:
  name: oreilly
  labels:
    app: oreilly
spec:
  containers:
    - name: web
      image: nginx:1.27
      ports:
        - containerPort: 80
    - name: cache          # a second container shares network + volumes
      image: redis:7
  • Discussion: Containers in one Pod share localhost and can share volumes. That’s what enables sidecars and adapters.
  • Gotchas: Don’t run bare Pods in real life. Nothing restarts them if the node dies. Wrap them in a Deployment.

Source: K8s Cookbook, Recipe 4.3

Recipe 4.4 — Launch a Deployment from a Manifest

  • Problem: Full control over how an app is launched and supervised.
  • Solution:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fancyapp
  labels:
    app: fancy
spec:
  replicas: 5
  selector:
    matchLabels:
      app: fancy
  template:
    metadata:
      labels:
        app: fancy
        env: development
    spec:
      containers:
        - name: sise
          image: mhausenblas/simpleservice:0.5.0
          ports:
            - containerPort: 9876
          env:
            - name: SIMPLE_SERVICE_VERSION
              value: "0.9"
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              memory: 128Mi
  • Discussion: Deployment → ReplicaSet → Pods (the picture in Deployments). Each template change creates a new ReplicaSet, and the old ones are kept (revisionHistoryLimit, default 10) so you can roll back.
  • Gotchas: Delete the Deployment, not its Pods. Always set requests (the scheduler needs them) and a memory limit (Best Practices).

Changed since the book:

extensions/v1beta1 → apps/v1 with a mandatory selector. To generate this skeleton use kubectl create deployment fancyapp --image=nginx --dry-run=client -o yaml. The book’s plain --dry-run is gone.

Source: K8s Cookbook, Recipe 4.4

Recipe 4.5 — Update (and Roll Back) a Deployment

  • Problem: Roll out a new version safely, and undo it if it’s broken.
  • Solution:
kubectl set image deployment/sise sise=mhausenblas/simpleservice:0.5.0
kubectl annotate deployment/sise kubernetes.io/change-cause="image 0.5.0"
kubectl rollout status deployment/sise
kubectl rollout history deployment/sise
kubectl rollout undo deployment/sise --to-revision=2
kubectl rollout restart deployment/sise     # re-create Pods, same spec (e.g. after a ConfigMap change)
kubectl rollout pause|resume deployment/sise
  • Tune the rollout (in the Deployment spec):
spec:
  strategy:
    type: RollingUpdate        # or Recreate (kill all, then start: has downtime)
    rollingUpdate:
      maxSurge: 25%            # extra Pods allowed during rollout
      maxUnavailable: 0        # never drop below desired count
  minReadySeconds: 10          # Pod must stay Ready this long to count
  progressDeadlineSeconds: 600 # mark rollout as failed after this
  • Discussion: Only changes to .spec.template (image, env, ports, resources, annotations on the template) trigger a rollout. Scaling doesn’t.
  • Gotchas: Rolling updates are only as safe as your readiness probe. Without one, Kubernetes sends traffic to Pods that aren’t ready (see Monitoring & Logging). For canary or blue-green, use Argo Rollouts / Flagger or a second Deployment behind the same Service.

Changed since the book:

--record is deprecated, so use the change-cause annotation. kubectl rollout restart (added 1.15) is the clean way to “bounce” a Deployment.

Source: K8s Cookbook, Recipe 4.5


Recipes — Specialised Workloads (Ch. 7)

Recipe 7.1 — Run a Batch Job

  • Problem: Run something to completion: a migration, a backup, a conversion.
  • Solution:
apiVersion: batch/v1
kind: Job
metadata:
  name: counter
spec:
  completions: 1
  parallelism: 1
  backoffLimit: 3                # retries before the Job is marked failed
  activeDeadlineSeconds: 300     # hard timeout
  ttlSecondsAfterFinished: 600   # auto-clean the finished Job + Pods
  template:
    spec:
      restartPolicy: Never       # Jobs need Never or OnFailure
      containers:
        - name: counter
          image: busybox:1.36
          command: ["sh", "-c", "for i in 1 2 3; do echo $i; done"]
kubectl apply -f job.yaml
kubectl wait --for=condition=complete job/counter --timeout=120s
kubectl logs job/counter
  • Discussion: Raise completions/parallelism for work queues. completionMode: Indexed gives each Pod a JOB_COMPLETION_INDEX.
  • Gotchas: Without ttlSecondsAfterFinished, finished Jobs and their Pods pile up forever.

Changed since the book:

The TTL-after-finished controller is GA, so the manual cleanup the book mentions isn’t needed.

Source: K8s Cookbook, Recipe 7.1

Recipe 7.2 — Run a Task on a Schedule (CronJob)

  • Problem: Run a Job every hour, night or week.
  • Solution:
apiVersion: batch/v1
kind: CronJob
metadata:
  name: hourly-date
spec:
  schedule: "0 * * * *"
  timeZone: "Europe/London"        # GA since 1.27
  concurrencyPolicy: Forbid        # skip if the previous run is still going
  startingDeadlineSeconds: 120
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: date
              image: busybox:1.36
              command: ["sh", "-c", "date"]
  • Discussion: Trigger a run by hand with kubectl create job --from=cronjob/hourly-date manual-1. Suspend with kubectl patch cronjob hourly-date -p '{"spec":{"suspend":true}}'.
  • Gotchas: Jobs can occasionally run twice (or be skipped), so make them idempotent.

Changed since the book:

batch/v2alpha1 → batch/v1 (GA in 1.21), plus the timeZone field.

Source: K8s Cookbook, Recipe 7.2

Recipe 7.3 — One Pod per Node (DaemonSet)

  • Problem: Run an agent (log collector, monitoring exporter) on every node.
  • Solution:
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      tolerations:
        - operator: Exists           # also run on tainted/control-plane nodes
      containers:
        - name: fluent-bit
          image: cr.fluentbit.io/fluent/fluent-bit:3.1
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
  • Discussion: New nodes get a Pod automatically. Limit to some nodes with nodeSelector or affinity.
  • Gotchas: kubectl drain needs --ignore-daemonsets, because DaemonSet Pods aren’t evicted.

Changed since the book:

extensions/v1beta1 → apps/v1. The book’s fluentd-elasticsearch image is long gone. Fluent Bit is now the usual node log agent.

Source: K8s Cookbook, Recipe 7.3

Recipe 7.4 — Stateful and Leader/Follower Apps (StatefulSet)

  • Problem: Your replicas aren’t interchangeable. They need stable names and their own disks (“pets, not cattle”).
  • Solution:
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  clusterIP: None              # headless: gives each Pod a DNS name
  selector:
    app: db
  ports:
    - port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db
  replicas: 3
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
  • Discussion: Pods are named db-0, db-1, db-2 and reachable at db-0.db.<ns>.svc.cluster.local. They’re created in order and deleted in reverse, and each keeps its own PVC across restarts.
  • Gotchas: You must create the headless Service yourself. Deleting the StatefulSet doesn’t delete the PVCs (by design, unless you set persistentVolumeClaimRetentionPolicy). StatefulSet ≠ replication: Postgres doesn’t magically become a cluster. For real databases use an operator (CloudNativePG, Strimzi for Kafka) or a managed DB. See Extending Kubernetes.

Changed since the book:

apps/v1beta1 → apps/v1. Added persistentVolumeClaimRetentionPolicy and minReadySeconds. The book’s CockroachDB example URL in kubernetes/kubernetes/examples no longer exists.

Source: K8s Cookbook, Recipe 7.4 · Best Practices book, Ch. 16

Recipe 7.5 — Control Startup Order (Init Containers)

  • Problem: Your Pod shouldn’t start until a dependency (a backend, a DB) is reachable or a setup step has run.
  • Solution:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      initContainers:
        - name: checkbackend
          image: busybox:1.36
          command: ["sh", "-c", "until nslookup backend; do echo waiting; sleep 2; done"]
      containers:
        - name: webserver
          image: nginx:1.27
          ports:
            - containerPort: 80
  • Discussion: Init containers run sequentially, to completion, before the app containers start. Debug them with kubectl logs <pod> -c checkbackend.
  • Native sidecars: an init container with restartPolicy: Always starts first and keeps running alongside the app. Use it for proxies or log shippers that must start before the app and stop after it.
  • Gotchas: Prefer apps that retry their dependencies over hard ordering, because dependencies can go away after startup too.

Changed since the book:

apps/v1beta1 → apps/v1. Native sidecar containers were added (beta, on by default in 1.29, GA in 1.33).

Source: K8s Cookbook, Recipe 7.5


Summary

  • Deployment for stateless, Job/CronJob for run-to-completion, DaemonSet per node, StatefulSet for identity + storage.
  • Everything is apps/v1 or batch/v1 now, with a required selector that matches the template labels.
  • Rollouts trigger on .spec.template changes. Use rollout status/history/undo/restart, and record why with change-cause.
  • Clean Jobs up with ttlSecondsAfterFinished, and make CronJobs idempotent.
  • Init containers gate startup. Native sidecars (restartPolicy: Always) run alongside.
Link to original

Services & Ingress

Services & Ingress

Overview

  • Chapter 5 of the Kubernetes Cookbook (plus Recipes 12.2 and 12.3, which are service tricks): giving Pods a stable address, DNS names, service types, and getting HTTP traffic in from outside.
  • My basics: Services (the four service types, L4 vs L7) and Ingress (my app-ingress.yaml with host-based rules). This note adds the recipes, debugging and the modern pieces (IngressClass, Gateway API).
  • Back to the hub: Kubernetes Cookbook

How a Service Actually Works

  1. You create a Service with a selector (app: web).
  2. The control plane finds matching Ready Pods and writes their IPs into EndpointSlices.
  3. kube-proxy on every node programs iptables/IPVS/nftables, so the Service’s virtual ClusterIP forwards to one of those Pod IPs. Some CNIs (Cilium) replace kube-proxy with eBPF.
  4. CoreDNS gives it a name: web.<namespace>.svc.cluster.local.

Changed since the book:

Endpoints are now tracked by EndpointSlices (kubectl get endpointslices). The old Endpoints API is deprecated (since 1.33) but still readable. kube-dns was replaced by CoreDNS. kube-proxy gained an nftables mode.


Recipes

Recipe 5.1 — Create a Service to Expose Your App

  • Problem: Pods come and go (see Pods), but clients need one stable address.
  • Solution:
kubectl create deployment nginx --image=nginx:1.27
kubectl expose deployment nginx --port=80 --target-port=80
kubectl get svc nginx
kubectl get endpointslices -l kubernetes.io/service-name=nginx
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP           # default
  selector:
    app: nginx              # must match the Pod labels
  ports:
    - name: http
      port: 80              # the Service's port
      targetPort: 80        # the container's port (number or named port)
  • Discussion: Deployments and Services both use labels but do different jobs. The Deployment keeps Pods alive, the Service routes to them.
  • Gotchas: Service “doesn’t work”? Check the endpoints first. Empty endpoints mean the selector doesn’t match, or no Pod is Ready.

Changed since the book:

kubectl create deployment labels Pods app=nginx. The book’s kubectl run labelled them run=nginx, so old Service YAML with run: selectors won’t match.

Source: K8s Cookbook, Recipe 5.1

Recipe 5.2 — Verify a Service’s DNS Entry

  • Problem: Is cluster DNS resolving your Service?
  • Solution:
kubectl run dnstest --image=busybox:1.36 -it --rm --restart=Never -- nslookup nginx
# full name: nginx.default.svc.cluster.local
  • Discussion: From the same namespace, nginx resolves. From another namespace use nginx.<ns>. The answer should be the Service’s ClusterIP (headless Services return Pod IPs instead).
  • Gotchas: Pin busybox to a known tag, since some busybox builds have a flaky nslookup. registry.k8s.io/e2e-test-images/agnhost or nicolaka/netshoot are nicer debugging images.

Source: K8s Cookbook, Recipe 5.2

Recipe 5.3 — Change a Service’s Type

  • Problem: You started with ClusterIP but now need NodePort or a cloud LoadBalancer.
  • Solution:
kubectl patch svc webserver -p '{"spec":{"type":"NodePort"}}'
kubectl get svc webserver        # PORT(S) shows 80:3xxxx/TCP
# or edit the YAML (type: LoadBalancer) and kubectl apply
  • Discussion: NodePort opens a port (30000–32767) on every node. LoadBalancer asks the cloud for an external LB, which on-prem or locally needs MetalLB, minikube tunnel or cloud-provider-kind.
  • Gotchas: Each LoadBalancer Service = one paid cloud LB. For many HTTP apps, put one Ingress/Gateway in front instead.

Source: K8s Cookbook, Recipe 5.3

Recipe 5.4 — Deploy an Ingress Controller

  • Problem: Ingress objects do nothing until a controller watches them.
  • Solution:
minikube addons enable ingress                       # minikube
# anywhere else: install a controller with Helm, e.g. Traefik, HAProxy,
# Contour, Kong, Envoy Gateway, or your cloud's controller (GKE, AWS LB Controller, AGIC)
kubectl get ingressclass
  • Discussion: Each controller registers an IngressClass. Your Ingress points at one with ingressClassName.
  • Gotchas: Controller-specific annotations (rewrites, auth, rate limits) aren’t portable, so standardise on one controller (Best Practices).

Changed since the book:

The community ingress-nginx project was retired in 2026 (no more fixes). New setups should pick another controller or move to Gateway API (below). My existing Ingress note’s nginx.ingress.kubernetes.io/* annotations are specific to ingress-nginx.

Source: K8s Cookbook, Recipe 5.4

Recipe 5.5 — Make Services Reachable from Outside (Ingress)

  • Problem: Route http://<host>/web to the nginx Service.
  • Solution:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-public
spec:
  ingressClassName: nginx          # whatever `kubectl get ingressclass` shows
  tls:
    - hosts: ["demo.example.com"]
      secretName: demo-tls         # kubectl create secret tls demo-tls --cert=… --key=…
  rules:
    - host: demo.example.com
      http:
        paths:
          - path: /web
            pathType: Prefix       # Prefix | Exact | ImplementationSpecific
            backend:
              service:
                name: nginx
                port:
                  number: 80
  • Discussion: The controller watches Ingress objects and reconfigures its proxy. Use cert-manager to get and renew TLS certificates (Let’s Encrypt) automatically.
  • Gotchas: Forgetting ingressClassName means no controller may pick the Ingress up (unless one is marked as the default class).

Changed since the book:

extensions/v1beta1 → networking.k8s.io/v1. serviceName/servicePort became service.name/service.port.number. pathType is required. The kubernetes.io/ingress.class annotation was replaced by ingressClassName.

Source: K8s Cookbook, Recipe 5.5

Modern Extra — Gateway API (Ingress’s successor)

  • Why: Ingress only covers basic HTTP routing, and everything else lives in vendor annotations. Gateway API (gateway.networking.k8s.io/v1, GA) splits responsibilities: the platform team owns the Gateway, app teams own their HTTPRoutes. It has first-class header matching, traffic splitting and TCP/gRPC routes.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
spec:
  parentRefs:
    - name: public-gateway         # a Gateway managed by the platform team
  hostnames: ["demo.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /web
      backendRefs:
        - name: nginx
          port: 80
          weight: 90
        - name: nginx-canary       # built-in canary split
          port: 80
          weight: 10
  • Gotchas: The Gateway API CRDs plus a controller (Envoy Gateway, Istio, Cilium, Traefik, NGINX Gateway Fabric, cloud implementations) must be installed first.

Recipe 12.2 — Take One Pod Out of a Service (for Inspection)

  • Problem: One replica is misbehaving. You want it out of rotation but still alive to debug.
  • Solution: Change its label so the Service selector (and the ReplicaSet) no longer match:
kubectl label pod nginx-7c5d-abcde app=quarantine --overwrite
  • Discussion: The Service drops it from its endpoints, and the ReplicaSet notices it’s “missing” and starts a replacement. The quarantined Pod keeps running for you to exec into. Delete it when done.

Source: K8s Cookbook, Recipe 12.2

Recipe 12.3 — Reach a ClusterIP Service from Your Laptop

  • Problem: Test an internal Service without exposing it.
  • Solution:
kubectl port-forward svc/nginx 8080:80           # simplest
curl localhost:8080
 
kubectl proxy &                                   # via the API server
curl localhost:8001/api/v1/namespaces/default/services/nginx:80/proxy/

Changed since the book:

The old proxy path /api/v1/proxy/namespaces/... was removed. It’s now /api/v1/namespaces/<ns>/services/<name>[:port]/proxy/.

Source: K8s Cookbook, Recipe 12.3


Summary

  • A Service = selector → EndpointSlices of Ready Pods → a virtual IP plus a DNS name (svc.ns.svc.cluster.local).
  • Debug Services by checking endpoints and labels first.
  • ClusterIP (internal) → NodePort (node port) → LoadBalancer (cloud LB). For HTTP, use one Ingress or Gateway.
  • Ingress needs a controller and networking.k8s.io/v1 with ingressClassName + pathType. Gateway API is the future.
  • port-forward for quick local access. Relabel a Pod to pull it out of rotation.
Link to original

Volumes & Configuration

Volumes & Configuration

Overview

  • Chapter 8 of the Kubernetes Cookbook: sharing files between containers, feeding config and secrets into Pods, and storage that outlives the Pod (PV, PVC, StorageClass).
  • My basics: ConfigMap (decouple config from images, and ConfigMaps aren’t secure). Remember from Pods: anything written inside a container dies with it.
  • Back to the hub: Kubernetes Cookbook

Volume Types at a Glance

TypeLives as long asUse it for
emptyDirthe Podscratch space, sharing files between containers
configMap / secret / projectedthe Pod (data from API objects)config files, credentials
hostPaththe nodenode agents only (log shippers). Avoid for apps.
persistentVolumeClaimindependently of Podsdatabases, uploads, anything you can’t lose
ephemeral (generic)the Pod, but provisioned by a CSI driverbig scratch disks

Changed since the book:

Storage is now done by CSI drivers. The in-tree cloud plugins (awsElasticBlockStore, gcePersistentDisk, azureDisk…) were migrated or removed. gitRepo volumes are deprecated (use an init container that clones instead).


Recipes

Recipe 8.1 — Share Data Between Containers (emptyDir)

  • Problem: Two containers in one Pod need to exchange files.
  • Solution:
apiVersion: v1
kind: Pod
metadata:
  name: sharevol
spec:
  containers:
    - name: c1
      image: busybox:1.36
      command: ["sh", "-c", "echo hello > /xchange/data; sleep 3600"]
      volumeMounts:
        - name: xchange
          mountPath: /xchange
    - name: c2
      image: busybox:1.36
      command: ["sh", "-c", "sleep 5; cat /data/data; sleep 3600"]
      volumeMounts:
        - name: xchange
          mountPath: /data
  volumes:
    - name: xchange
      emptyDir: {}              # add medium: Memory for a tmpfs
  • Discussion: This is the basis of the sidecar/adapter patterns. One container writes, another ships or transforms.
  • Gotchas: Data is gone when the Pod is deleted or moves node. Set sizeLimit so a runaway writer can’t fill the node’s disk.

Source: K8s Cookbook, Recipe 8.1

Recipe 8.2 — Pass an API Key with a Secret

  • Problem: Give an app a credential without putting it in the image or plain YAML.
  • Solution:
kubectl create secret generic pp --from-literal=passphrase='open sesame'
kubectl get secret pp -o jsonpath='{.data.passphrase}' | base64 -d   # it's only base64!
apiVersion: v1
kind: Pod
metadata:
  name: ppconsumer
spec:
  containers:
    - name: shell
      image: busybox:1.36
      command: ["sh", "-c", "cat /tmp/access/passphrase; sleep 3600"]
      volumeMounts:
        - name: passphrase
          mountPath: /tmp/access
          readOnly: true
      env:
        - name: PASSPHRASE                 # alternative: as an env var
          valueFrom:
            secretKeyRef:
              name: pp
              key: passphrase
  volumes:
    - name: passphrase
      secret:
        secretName: pp
        defaultMode: 0400
  • Discussion: Secret types: generic (Opaque), docker-registry (image pull credentials) and tls (cert + key, used by Ingress). Max size is 1 MiB. Secrets are namespaced.
  • Gotchas: Secrets are base64, not encrypted. Anyone with get secret RBAC, or access to etcd backups, can read them. Do three things:
    1. Turn on encryption at rest (EncryptionConfiguration with a KMS v2 provider). Managed clouds often do this for you.
    2. Lock down RBAC on secrets (list and watch reveal all of them).
    3. Keep real secrets in a vault and sync them in (see Security).
  • Prefer volume mounts to env vars. Env vars leak into crash dumps and kubectl describe, and they don’t update when the Secret changes.

Changed since the book:

The flag is now --encryption-provider-config (no longer “experimental”), with KMS v2 GA. ServiceAccount token Secrets are no longer auto-created (since 1.24). See Security.

Source: K8s Cookbook, Recipe 8.2 · Best Practices book, Ch. 4

Recipe 8.3 — Provide Configuration Data (ConfigMap)

  • Problem: Configure an app without rebuilding the image (ConfigMap).
  • Solution:
kubectl create configmap siseconfig --from-literal=siseversion=0.9
kubectl create configmap configfile --from-file=example.cfg
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cmapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cmapp
  template:
    metadata:
      labels:
        app: cmapp
    spec:
      containers:
        - name: sise
          image: mhausenblas/simpleservice:0.5.0
          envFrom:
            - configMapRef:
                name: siseconfig          # every key becomes an env var
          volumeMounts:
            - name: cfg
              mountPath: /etc/app         # every key becomes a file
      volumes:
        - name: cfg
          configMap:
            name: configfile
  • Discussion:
    • Env vars are read once at start. A ConfigMap change needs a kubectl rollout restart.
    • Volume files refresh automatically (after up to about a minute), so an app that watches its config file can hot-reload.
    • subPath mounts never refresh.
  • Gotchas: The ConfigMap must exist before the Pod starts (or mark it optional: true). Mark stable config immutable: true. That protects it from accidental edits and saves API server watch load.
  • Trick: Kustomize’s configMapGenerator and Helm’s checksum/config annotation change the Pod template whenever config changes. That triggers a rollout automatically (see Managing Manifests).

Changed since the book:

extensions/v1beta1 → apps/v1. Immutable ConfigMaps and Secrets are GA (1.21).

Source: K8s Cookbook, Recipe 8.3 · Best Practices book, Ch. 4

Recipe 8.4 — Use a PersistentVolume (static provisioning)

  • Problem: Data must survive Pod restarts.
  • Solution: The admin creates a PV (the actual disk). The developer creates a PVC (a request for a disk). Kubernetes binds them, and the Pod mounts the PVC.
apiVersion: v1
kind: PersistentVolume
metadata:
  name: hostpathpv
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /tmp/pvdata                 # fine for minikube/kind, never in prod
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  storageClassName: manual
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 200Mi
# in the Pod template:
      volumes:
        - name: html
          persistentVolumeClaim:
            claimName: mypvc
  • Access modes:
ModeMeaning
ReadWriteOnce (RWO)read-write by one node (several Pods on that node can share it)
ReadWriteOncePod (RWOP)read-write by one Pod only (GA 1.29)
ReadOnlyMany (ROX)read-only by many nodes
ReadWriteMany (RWX)read-write by many nodes. Needs NFS, CephFS, EFS, Azure Files…
  • Gotchas: PVs are cluster-scoped, PVCs are namespaced. A PVC stuck in Pending means no PV or StorageClass matches its size, mode or class (kubectl describe pvc tells you why).

Source: K8s Cookbook, Recipe 8.4

Recipe 8.5 / 8.6 — Dynamic Provisioning with StorageClasses

  • Problem: You don’t want an admin hand-making PVs for every claim.
  • Solution: Just create the PVC. The default StorageClass provisions a matching PV on demand.
kubectl get storageclass          # the one marked (default) is used when none is named
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: pd.csi.storage.gke.io      # ebs.csi.aws.com, disk.csi.azure.com, rancher.io/local-path…
parameters:
  type: pd-ssd
reclaimPolicy: Retain                    # default is Delete!
volumeBindingMode: WaitForFirstConsumer  # create the disk in the zone where the Pod lands
allowVolumeExpansion: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  storageClassName: fast-ssd
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
  • Discussion: minikube’s default class (standard, provisioner k8s.io/minikube-hostpath) and kind’s (standard, local-path) are fine for learning. On cloud you get CSI-backed disks. Grow a volume by editing the PVC’s storage (if allowVolumeExpansion). Back up with VolumeSnapshots or Velero.
  • Gotchas:
    • reclaimPolicy: Delete (the default for dynamic PVs) destroys the disk when the PVC is deleted. Use Retain for anything precious.
    • Use WaitForFirstConsumer, or you’ll get “volume in zone A, Pod in zone B” (Best Practices).
    • Block disks are zonal, so a Pod can’t follow its disk to another zone.

Changed since the book:

GKE’s default class now uses the PD CSI driver (standard-rwo), not kubernetes.io/gce-pd. The blog-post walkthrough the book links to is outdated.

Source: K8s Cookbook, Recipes 8.5 & 8.6 · Best Practices book, Ch. 16


Summary

  • emptyDir for scratch and sharing, ConfigMap/Secret for config, PVC for anything that must survive.
  • ConfigMap and Secret as env vars = restart to pick up changes. As volume = auto-refresh (except subPath).
  • Secrets are base64, not encrypted: enable encryption at rest, tighten RBAC, use an external secret store.
  • PV (disk) ↔ PVC (request) ↔ StorageClass (on-demand provisioning). Watch reclaimPolicy and WaitForFirstConsumer.
Link to original

Monitoring & Logging

Monitoring & Logging

Overview

  • Chapter 11 of the Kubernetes Cookbook: container logs, liveness/readiness probes, resource metrics, Prometheus and centralised logging. Plus the Best Practices book’s advice on what to monitor and how to alert.
  • Related: Deployments (CrashLoopBackOff is often a liveness probe killing a slow starter), Maintenance & Troubleshooting. Back to the hub: Kubernetes Cookbook

Who Cares About What

  • Admins: Are nodes healthy? Is the cluster full? Are teams near quota?
  • Developers: Is my app healthy? Are requests and limits right? Why did it restart?

Recipes

Recipe 11.1 — Read a Container’s Logs

  • Problem: See what the app printed.
  • Solution:
kubectl logs pod/web-abc                      # single-container Pod
kubectl logs web-abc -c sidecar               # pick a container
kubectl logs deploy/web                       # some Pod of the Deployment
kubectl logs -l app=web --all-containers --prefix --max-log-requests=10
kubectl logs web-abc -f --since=10m --timestamps
kubectl logs web-abc --previous               # the crashed container before the restart ← gold for CrashLoopBackOff
  • Discussion: Kubernetes only keeps what the container writes to stdout/stderr, rotated on the node. When the Pod is deleted, its logs go too, which is why you want central logging (11.7).
  • Gotchas: For multi-Pod tailing use stern (stern web) or kubectl logs -l … -f.

Source: K8s Cookbook, Recipe 11.1

Recipe 11.2 – 11.4 — Liveness, Readiness (and Startup) Probes

  • Problem: Restart wedged apps automatically, and only send traffic to Pods that are actually ready.
  • Solution:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webserver
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webserver
  template:
    metadata:
      labels:
        app: webserver
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
          startupProbe:              # gives slow starters time; other probes wait for it
            httpGet:
              path: /
              port: 80
            periodSeconds: 5
            failureThreshold: 30     # up to 150 s to start
          readinessProbe:            # fail → removed from Service endpoints (no restart)
            httpGet:
              path: /
              port: 80
            periodSeconds: 5
            failureThreshold: 3
          livenessProbe:             # fail → container is restarted
            httpGet:
              path: /
              port: 80
            periodSeconds: 10
            failureThreshold: 3
            timeoutSeconds: 2
  • The three probes:
ProbeQuestionOn failure
startupProbeHas it finished booting?Keep waiting, then restart if it never starts. Other probes are paused until it passes.
readinessProbeCan it take traffic right now?Pulled out of Service endpoints. Not restarted.
livenessProbeIs it hopelessly stuck?Container restarted by the kubelet.
  • Probe mechanisms: httpGet, tcpSocket, exec (a command’s exit code), grpc (GA 1.27).
  • Discussion: Readiness makes rolling updates and scaling safe. Liveness rescues deadlocks. The kubelet runs the probes, and a Pod is Ready only when all its containers are Ready.
  • Gotchas:
    • Liveness and readiness should usually differ. Readiness can check dependencies (a DB). Liveness should check only the process itself. Otherwise a DB blip restarts every Pod at once, a self-inflicted outage.
    • Too-aggressive liveness on a slow-booting app = endless restarts (CrashLoopBackOff from Deployments). Use a startupProbe instead of a huge initialDelaySeconds.
    • The book’s snippet says initialDelaySecond. The real field is initialDelaySeconds.

Changed since the book:

startupProbe (GA 1.20) and grpc probes (GA 1.27) were added, and extensions/v1beta1 → apps/v1.

Source: K8s Cookbook, Recipes 11.2, 11.3 & 11.4 · Best Practices book, Ch. 1

Recipe 11.5 — Resource Metrics with kubectl top

  • Problem: kubectl top errors out. You want CPU and memory usage per Pod and node.
  • Solution:
minikube addons enable metrics-server          # minikube
# elsewhere:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# (kind/local: add --kubelet-insecure-tls to the metrics-server args)
 
kubectl top nodes
kubectl top pods -A --sort-by=memory
kubectl top pod web-abc --containers
  • Discussion: metrics-server keeps only current usage in memory (no history) and serves the metrics.k8s.io API that HPA and kubectl top use. For history and dashboards you need Prometheus (11.6).

Changed since the book:

Heapster + InfluxDB + Grafana add-on → metrics-server. Heapster was retired in 2018 and removed from everything.

Source: K8s Cookbook, Recipe 11.5

Recipe 11.6 — Prometheus for Cluster and App Metrics

  • Problem: Query and alert on system and app metrics in one place.
  • Solution: Don’t hand-write the ConfigMap, RBAC and Deployment like the book did. Install the kube-prometheus-stack chart: Prometheus Operator, Prometheus, Alertmanager, Grafana, node-exporter, kube-state-metrics, plus ready-made dashboards and alerts.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install mon prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
kubectl port-forward -n monitoring svc/mon-grafana 3000:80
# Tell the Prometheus Operator to scrape your app's /metrics via its Service
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: web
  labels:
    release: mon            # must match the operator's selector
spec:
  selector:
    matchLabels:
      app: web
  endpoints:
    - port: http
      path: /metrics
      interval: 30s
  • Metric sources:
    • cAdvisor (inside the kubelet): container CPU and memory.
    • kube-state-metrics: object state, e.g. “replicas desired vs available”, “Pod restarts”.
    • node-exporter: node OS metrics.
    • your app: instrument it with a Prometheus client or OpenTelemetry.
  • What to watch (Best Practices Ch. 3):
    • USE for infrastructure: Utilisation, Saturation, Errors.
    • RED for services: Rate, Errors, Duration.
    • The four golden signals: latency, traffic, errors, saturation.
  • Gotchas: Watch label cardinality. A label like user_id explodes Prometheus’s memory.

Changed since the book:

The book’s config targeted 1.7-specific kubelet paths and bound Prometheus to cluster-admin. Use the operator chart’s scoped RBAC instead. Managed Prometheus (GKE, Amazon AMP, Azure Monitor) is a common alternative.

Source: K8s Cookbook, Recipe 11.6 · Best Practices book, Ch. 3

Recipe 11.7 — Centralised Logging

  • Problem: Search logs from all Pods, including dead ones, in one place.
  • Solution (modern stack): a node-level agent as a DaemonSet (Fluent Bit, Vector or the OpenTelemetry Collector) reads /var/log/containers/* and ships to a backend: Loki + Grafana (cheap, label-indexed), OpenSearch/Elasticsearch + Dashboards/Kibana (full-text), or a cloud service.
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki -n logging --create-namespace      # configure for single-binary/dev
helm install alloy grafana/alloy -n logging                       # or fluent-bit / promtail-style agent
  • Best Practices:
    • Log to stdout in a structured format (JSON).
    • Prefer the DaemonSet agent over a log-shipper sidecar in every Pod (cheaper).
    • Keep hot logs about 30–45 days and archive older ones to cheap storage.
    • Correlate logs with metrics and traces (OpenTelemetry).
  • Alerting: Alert on symptoms that hurt users or SLOs, not on every blip, because alert fatigue makes people ignore pages.

Changed since the book:

EFK on minikube with Kibana’s default kibana/changeme login is purely historical. Fluentd → Fluent Bit is the common agent today, and Loki is popular with Grafana. Everything installs via Helm.

Source: K8s Cookbook, Recipe 11.7 · Best Practices book, Ch. 3


Summary

  • kubectl logs (with -c, -l, -f, --previous) for one-off looks. A DaemonSet agent → Loki/OpenSearch for history.
  • startup = booted? readiness = take traffic? liveness = restart me? Keep liveness simple and dependency-free.
  • metrics-server powers kubectl top and HPA. Prometheus (kube-prometheus-stack) for history, dashboards and alerts.
  • Monitor USE/RED/golden signals, and alert on user-facing symptoms.
Link to original

Scaling

Scaling

Overview

Three Kinds of Scaling

ScalerChangesDriven by
HPA (Horizontal Pod Autoscaler)number of replicasCPU/memory, custom or external metrics
VPA (Vertical Pod Autoscaler)requests/limits of containersobserved usage history
Cluster Autoscaler / Karpenternumber of nodesPods stuck Pending for lack of room, or empty nodes
  • They stack. HPA adds Pods, and if they don’t fit, the node autoscaler adds nodes.
  • Everything depends on resource requests. The HPA computes utilisation relative to requests, and node autoscalers decide “doesn’t fit” based on requests.

Recipes

Recipe 9.1 — Scale a Deployment Manually

  • Problem: Load dropped and five replicas is too many.
  • Solution:
kubectl scale deployment fancyapp --replicas=3
kubectl get deploy fancyapp
  • Gotchas: If an HPA manages the Deployment, it will overwrite your manual number. And if replicas: is in Git and your GitOps tool re-applies it, it fights the HPA. Leave replicas out of the manifest when an HPA owns it.

Source: K8s Cookbook, Recipe 9.1

Recipe 9.2 / 9.3 — Autoscale the Cluster’s Nodes

  • Problem: Add nodes when Pods can’t be scheduled, and remove them when they’re idle.
  • Solution (managed clouds):
# GKE (Autopilot does this for you automatically)
gcloud container clusters update demo --enable-autoscaling \
  --node-pool=default-pool --min-nodes=1 --max-nodes=5 --region=europe-west1
 
# AKS
az aks update -g k8s -n demo --enable-cluster-autoscaler --min-count 1 --max-count 5
 
# EKS: Karpenter (provisions right-sized nodes directly), or Cluster Autoscaler + ASGs
  • Discussion:
    • Cluster Autoscaler grows and shrinks node groups when Pods are Pending for lack of capacity, or nodes are underused.
    • Karpenter (AWS-born, now also on Azure) skips node groups. It picks an instance type that fits the pending Pods.
    • Test it by scaling a Deployment past what the current nodes can hold, then kubectl get nodes -w.
  • Gotchas:
    • Scale-down evicts Pods, so protect critical apps with a PodDisruptionBudget.
    • Pods with local storage or safe-to-evict: "false" block scale-down.
    • New nodes take minutes, so HPA plus slack capacity beats relying on node scaling for sudden spikes.
    • Watch your cloud quota for max-nodes.

Changed since the book:

The AWS recipe used a Helm 2 chart (Tiller). Today it’s Karpenter, or the Cluster Autoscaler chart via Helm 3. GKE’s gcloud beta is no longer needed.

Source: K8s Cookbook, Recipes 9.2 & 9.3 · Best Practices book, Ch. 8

Recipe 9.4 — Horizontal Pod Autoscaling

  • Problem: Automatically add and remove replicas with load.
  • Solution:
    1. Make sure metrics-server is running (kubectl top pods works). On minikube: minikube addons enable metrics-server.
    2. Give the target CPU requests.
    3. Create the HPA.
kubectl create deployment appserver --image=registry.k8s.io/hpa-example --port=80
kubectl set resources deployment appserver --requests=cpu=200m
kubectl expose deployment appserver --port=80
kubectl autoscale deployment appserver --cpu-percent=40 --min=1 --max=5
kubectl get hpa -w
 
# load generator in another terminal
kubectl run -it --rm loadgen --image=busybox:1.36 --restart=Never -- \
  sh -c "while true; do wget -q -O- http://appserver; done"

Same thing declaratively, with autoscaling/v2:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: appserver
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: appserver
  minReplicas: 1
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 40       # % of the CPU *request*
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # wait 5 min before shrinking (avoid flapping)
  • Discussion: Roughly, desired = ceil(current × currentMetric / target). The controller checks every ~15 s. autoscaling/v2 also supports memory, custom metrics (Prometheus Adapter: requests/s, queue depth) and external metrics. KEDA extends this to event sources (Kafka lag, SQS depth, cron) and can scale to zero.
  • Gotchas:
    • TARGETS <unknown> means no metrics-server, or no CPU request on the containers.
    • CPU is often a poor proxy for load. App-level metrics (RPS, latency, queue length) scale better, as the book also hints.

Changed since the book:

Heapster is gone → metrics-server (the metrics.k8s.io API). HPA API is autoscaling/v2 (GA 1.23) with a behavior block. kubectl run --requests --expose no longer exists. The hpa-example image moved to registry.k8s.io.

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

Modern Extra — Vertical Pod Autoscaler (VPA)

  • What: Watches real usage and recommends (or applies) better requests/limits. The book skipped it (“still under development, Jan 2018”). It’s now a mature add-on (not built in, so install it from kubernetes/autoscaler).
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: appserver-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: appserver
  updatePolicy:
    updateMode: "Off"        # just recommend; check with kubectl describe vpa
  • Gotchas: Don’t let VPA (in Auto mode) and an HPA both act on CPU/memory for the same workload, because they fight. A common combo is VPA Off (for sizing advice) + HPA (for scaling).
  • In-place Pod resize (resizePolicy, beta and on by default since 1.33) lets requests and limits change without restarting the Pod, so VPA no longer has to evict.

Summary

  • Manual: kubectl scale. Automatic Pods: HPA (autoscaling/v2) fed by metrics-server. Sizing: VPA. Nodes: Cluster Autoscaler / Karpenter.
  • No resource requests = no sensible autoscaling of any kind.
  • Remove replicas from Git when an HPA owns it. Add behavior.scaleDown stabilisation to stop flapping.
  • Use PodDisruptionBudgets so node scale-down doesn’t take out all replicas at once.
Link to original

Security

Security

Overview

  • Chapter 10 of the Kubernetes Cookbook (ServiceAccounts, RBAC, security contexts), plus Recipe 14.6 (encrypted secrets in Git) and the modern pieces the book predates: Pod Security Admission, NetworkPolicy and admission policies.
  • Related: Volumes & Configuration (Secrets), Best Practices (RBAC, policy, multi-tenancy). Back to the hub: Kubernetes Cookbook

Who Can Do What: the Request Path

  1. Authentication: who are you? A user (via OIDC, cloud IAM or client certs; Kubernetes has no User object) or a ServiceAccount (an app identity, namespaced).
  2. Authorization: are you allowed? RBAC.
  3. Admission: is this object acceptable, and should it be tweaked? Pod Security Admission, ValidatingAdmissionPolicy, webhooks (Kyverno, Gatekeeper).

Recipes

Recipe 10.1 — Give an App Its Own Identity (ServiceAccount)

  • Problem: An app talks to the Kubernetes API and needs a fine-grained identity.
  • Solution:
kubectl create serviceaccount myappsa
kubectl create token myappsa --duration=1h      # mint a short-lived token by hand
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  serviceAccountName: myappsa
  automountServiceAccountToken: true     # set false for apps that never call the API
  containers:
    - name: main
      image: busybox:1.36
      command: ["sleep", "3600"]
  • Discussion: The token is mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. The identity string is system:serviceaccount:<ns>:<name>. Every namespace has a default SA, which Pods get if you don’t specify one.
  • Gotchas: Most apps never call the Kubernetes API. Turn the token off for them (automountServiceAccountToken: false), because a leaked token is a foothold.

Changed since the book:

Tokens are now bound, projected, short-lived (auto-rotated, about 1h, tied to the Pod). Since 1.24 no long-lived token Secret is created per ServiceAccount, so the book’s kubectl describe secret myappsa-token-… step no longer exists. Use kubectl create token. Cloud workload identity (GKE Workload Identity, EKS Pod Identity/IRSA, AKS Workload Identity) maps SAs to cloud IAM.

Source: K8s Cookbook, Recipe 10.1

Recipe 10.2 — See What Access You Have

  • Problem: Can I (or this ServiceAccount) do X?
  • Solution:
kubectl auth can-i create deployments -n team-a
kubectl auth can-i list secrets --as=system:serviceaccount:sec:myappsa -n sec
kubectl auth can-i --list -n sec
kubectl auth whoami                         # who does the API server think I am?
kubectl get roles,rolebindings -n sec
kubectl get clusterroles | grep -v system:  # admin, edit, view, cluster-admin
kubectl describe clusterrole view
  • Discussion: RBAC’s moving parts:
    • Subject: a user, group or ServiceAccount.
    • Role / ClusterRole: a list of verbs on resources (get list watch create update patch delete).
    • RoleBinding / ClusterRoleBinding: glue a role to subjects.
  • Gotchas: The book’s tip of binding cluster-admin to kube-system:default to make the dashboard work is a terrible idea. It gives every Pod using that SA full control of the cluster. Never do it.

Changed since the book:

RBAC is on by default everywhere (minikube included, no --extra-config needed). kubectl auth whoami is new (GA 1.28).

Source: K8s Cookbook, Recipe 10.2

Recipe 10.3 — Control Access to Resources (Role + RoleBinding)

  • Problem: Let an app read Pods in its namespace and nothing else.
  • Solution:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: podreader
  namespace: sec
rules:
  - apiGroups: [""]            # "" = core group
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: podreaderbinding
  namespace: sec
subjects:
  - kind: ServiceAccount
    name: myappsa
    namespace: sec
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: podreader
# imperative equivalent
kubectl create role podreader --verb=get,list,watch --resource=pods -n sec
kubectl create rolebinding podreaderbinding --role=podreader --serviceaccount=sec:myappsa -n sec
  • Which combo?
WantUse
Namespaced resources in one namespaceRole + RoleBinding
Same permissions reused in many namespacesClusterRole + RoleBinding (per namespace)
Cluster-scoped things (nodes, PVs) or all namespacesClusterRole + ClusterRoleBinding
  • Gotchas:
    • list/watch on Secrets = read all secrets in that namespace.
    • create pods is effectively “run as any SA in the namespace”.
    • roleRef is immutable, so delete and recreate the binding to change it.

Changed since the book:

rbac.authorization.k8s.io/v1beta1 → v1. The book’s --role=sec:podreader should just be --role=podreader.

Source: K8s Cookbook, Recipe 10.3 · Best Practices book, Ch. 4 & 17

Recipe 10.4 — Secure Pods (securityContext + Pod Security Admission)

  • Problem: Run as non-root, drop privileges, and enforce that cluster-wide.
  • Solution, part 1: harden the Pod (this passes the restricted profile):
apiVersion: v1
kind: Pod
metadata:
  name: secpod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 5000
    fsGroup: 5000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: shell
      image: busybox:1.36
      command: ["sleep", "3600"]
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
  • Solution, part 2: enforce it per namespace with Pod Security Admission (built in, label-driven):
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted   # reject violators
    pod-security.kubernetes.io/warn: restricted      # warn on kubectl apply
    pod-security.kubernetes.io/audit: restricted     # log to audit
  • Discussion: Three profiles: privileged (anything goes, for system namespaces), baseline (blocks the known escalations: hostPath, privileged, hostNetwork…) and restricted (the hardened list above). Try a label in dry-run first: kubectl label --dry-run=server --overwrite ns payments pod-security.kubernetes.io/enforce=restricted prints which existing Pods would violate it.
  • Gotchas: Many public images run as root and break under restricted. Use non-root images (distroless, -nonroot tags) or set runAsUser. PSA only covers Pods. For custom rules (“images must come from our registry”, “every Deployment needs an owner label”) use ValidatingAdmissionPolicy (CEL, built in, GA 1.30), Kyverno or OPA Gatekeeper.

Changed since the book:

PodSecurityPolicy (PSP) was removed in 1.25. The book’s PSP pointer (and the Best Practices book’s whole PSP section) is obsolete, and Pod Security Admission is the replacement. seccompProfile became a proper field (it used to be an annotation).

Source: K8s Cookbook, Recipe 10.4 · Best Practices book, Ch. 10 & 11

Modern Extra — NetworkPolicy (Pod Firewall)

  • Problem: By default every Pod can talk to every Pod. You want default-deny plus explicit allows.
  • Solution:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: payments
spec:
  podSelector: {}               # all Pods in the namespace
  policyTypes: ["Ingress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress   # auto-label on every namespace
      ports:
        - protocol: TCP
          port: 8080
  • Gotchas: Policies are only enforced if your CNI supports them (Calico, Cilium yes, plain Flannel no). Rules are additive, with no “deny” rules. If you add egress default-deny, remember to allow DNS (UDP/TCP 53 to kube-system) or everything breaks mysteriously. Start with ingress rules only (Best Practices book, Ch. 9).

Recipe 14.6 — Encrypted Secrets in Git

  • Problem: You want everything in Git (GitOps), including Secrets, without leaking them.
  • Solution options:
    • Sealed Secrets: encrypt with the controller’s public key. Only the in-cluster controller can decrypt.

      kubectl create secret generic db --from-literal=password=s3cr3t \
        --dry-run=client -o yaml | kubeseal --format yaml > db-sealed.yaml
      kubectl apply -f db-sealed.yaml      # controller creates the real Secret
    • External Secrets Operator: Secrets live in AWS Secrets Manager, GCP Secret Manager, Azure Key Vault or HashiCorp Vault. An ExternalSecret CR syncs them in, so nothing secret is in Git at all.

    • SOPS (with age or KMS keys): encrypt values in YAML files. Flux and Argo CD plugins decrypt at deploy time.

    • Secrets Store CSI Driver: mount secrets from the vault as files without creating a Secret object.

  • Gotchas: A SealedSecret is bound to its namespace and name by default. Back up the controller’s private key, or you can’t decrypt after a cluster rebuild.

Changed since the book:

--dry-run → --dry-run=client, kubeseal --format yaml, and install via Helm. The v1alpha1 sealed-secrets API is current but the project is much more mature. ESO didn’t exist in 2018.

Source: K8s Cookbook, Recipe 14.6 · Best Practices book, Ch. 4


Summary

  • Identity: ServiceAccounts with short-lived projected tokens (kubectl create token). Disable automount when an app doesn’t need the API.
  • Authorization: RBAC v1, least privilege, and check with kubectl auth can-i. Never hand out cluster-admin casually.
  • Pod hardening: runAsNonRoot, drop ALL capabilities, no privilege escalation, RuntimeDefault seccomp. Enforce with Pod Security Admission labels (PSP is gone).
  • Network: default-deny NetworkPolicies + explicit allows (needs a policy-capable CNI).
  • Secrets in Git: Sealed Secrets / External Secrets / SOPS, never plain base64.
Link to original

Managing Manifests

Managing Manifests

Overview

  • Chapter 14 (the “Ecosystem”) of the Kubernetes Cookbook: Helm (Recipes 14.1–14.3) and kompose (14.4). Plus what’s become standard since: Kustomize (built into kubectl), server-side apply and GitOps.
  • The problem all of these solve: raw YAML copied per environment (dev/staging/prod) drifts and duplicates. You want one source with small, controlled differences.
  • Back to the hub: Kubernetes Cookbook · Related: Best Practices (CI/CD, versioning)

Helm vs. Kustomize at a Glance

HelmKustomize
ApproachGo templates + values.yamlPatches/overlays on plain YAML
Packaging & sharingYes: charts, repos, OCI registriesNo (just directories, or Git URLs)
Release tracking / rollbackYes (helm history, helm rollback)No (use Git, or GitOps)
Learning curveTemplates can get gnarlyPlain YAML, easy to read
Built into kubectlNoYes (kubectl apply -k)
Typical useInstalling third-party software, distributing your appPer-environment tweaks of your own manifests
  • They combine fine: Argo CD and Flux can render a Helm chart and then apply Kustomize patches on top.

Recipes

Recipe 14.1 — Install Helm

  • Problem: You don’t want to hand-write manifests for common software.
  • Solution:
brew install helm
helm version
  • Discussion: A chart = templated manifests + default values + metadata. Installing one creates a release. Helm 3 stores release state as Secrets in the release’s namespace and uses your kubeconfig permissions.

Changed since the book:

Helm 3 has no Tiller. There’s no helm init, and no server component running with cluster-admin in kube-system (that was a big security hole). The Best Practices book’s “one Tiller per namespace” workaround is obsolete too.

Source: K8s Cookbook, Recipe 14.1

Recipe 14.2 — Install Applications with Helm

  • Problem: Find a chart and deploy it.
  • Solution:
helm search hub redis                                 # searches Artifact Hub
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm show values prometheus-community/kube-prometheus-stack > values.yaml   # see the knobs
helm install mon prometheus-community/kube-prometheus-stack \
  -n monitoring --create-namespace -f my-values.yaml
helm install cache oci://registry.example.com/charts/redis --version 1.2.3  # charts from OCI registries
 
helm list -A
helm upgrade --install mon prometheus-community/kube-prometheus-stack -n monitoring -f my-values.yaml
helm history mon -n monitoring
helm rollback mon 1 -n monitoring
helm uninstall mon -n monitoring
  • Gotchas:
    • Pin --version in anything automated.
    • helm upgrade with a missing -f silently resets values to defaults. Keep your values files in Git.
    • Uninstall keeps PVCs created by StatefulSets.

Changed since the book:

The release name is now required (helm install <name> <chart>, or --generate-name). helm search is now helm search repo / helm search hub. The old stable repo at storage.googleapis.com/kubernetes-charts is dead, so find charts on Artifact Hub. Charts can live in OCI registries.

Source: K8s Cookbook, Recipe 14.2

Recipe 14.3 — Create Your Own Chart

  • Problem: Package your multi-manifest app so it’s installable with parameters.
  • Solution:
helm create mychart          # Chart.yaml, values.yaml, templates/, charts/
helm lint mychart
helm template demo mychart -f values-prod.yaml | less    # render locally: no cluster needed
helm install demo ./mychart --dry-run=server             # validate against the cluster
helm package mychart                                     # → mychart-0.1.0.tgz
helm push mychart-0.1.0.tgz oci://registry.example.com/charts
# templates/deployment.yaml (excerpt)
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}  # restart on config change
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
  • Discussion: Bump version (the chart’s version) and appVersion (your app’s version) in Chart.yaml separately. Bundle components that must upgrade and roll back together into one chart (Best Practices, Ch. 6). Hooks (helm.sh/hook: pre-upgrade) run Jobs such as DB backups or migrations at the right moment.
  • Gotchas: Template files aren’t valid YAML until rendered, so lint with helm lint / helm template, not a YAML linter.

Changed since the book:

No more ~/.helm/repository/local or helm serve. Share charts via OCI registries (GHCR, ECR, ACR, Harbor) or a static chart repo.

Source: K8s Cookbook, Recipe 14.3 · Best Practices book, Ch. 4 & 6

Modern Extra — Kustomize (built into kubectl)

  • Problem: Same app, three environments, small differences. You don’t want templates.
  • Solution: a base/ plus one overlays/<env>/ per environment.
app/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    └── prod/
        ├── kustomization.yaml
        └── replicas-patch.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
labels:
  - pairs:
      app.kubernetes.io/name: web
    includeSelectors: true
configMapGenerator:
  - name: web-config
    literals:
      - LOG_LEVEL=info
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prod
resources:
  - ../../base
images:
  - name: nginx
    newTag: "1.27.2"
patches:
  - path: replicas-patch.yaml
configMapGenerator:
  - name: web-config
    behavior: merge
    literals:
      - LOG_LEVEL=warn
kubectl kustomize overlays/prod          # render and inspect
kubectl diff -k overlays/prod            # compare with the live cluster
kubectl apply -k overlays/prod
  • Discussion: configMapGenerator adds a content hash to the ConfigMap name (web-config-7f9k2…) and rewrites references. So changing config automatically rolls the Deployment, the trick the Best Practices book recommends for ConfigMap changes.
  • Gotchas: kubectl’s bundled kustomize can lag behind the standalone kustomize CLI. Check kubectl version (“Kustomize Version”).

Modern Extra — kubectl apply --server-side

  • What: The API server (not kubectl) merges your manifest and tracks which manager owns which field (managedFields).
  • Why:
    • No more giant last-applied-configuration annotation (which breaks on objects over 256 KB, like big CRDs).
    • Conflicts are explicit: if the HPA owns replicas and you try to set it, you get an error instead of a silent fight.
    • Several tools can safely manage different fields of the same object.
kubectl apply --server-side --field-manager=ci-pipeline -f manifests/
kubectl apply --server-side --force-conflicts -f crds/     # take ownership deliberately

Recipe 14.4 — Convert Docker Compose to Kubernetes (kompose)

  • Problem: You have a compose.yaml and want Kubernetes manifests.
  • Solution:
brew install kompose
kompose convert -f compose.yaml -o k8s/     # writes Deployments + Services
  • Discussion: kompose is a starting point, not a finished product. Review the result and add probes, resources, Secrets, PVCs and an Ingress by hand. Volumes and networks rarely translate cleanly.
  • Gotchas: Compose’s depends_on has no direct equivalent. Use readiness probes, init containers or retry logic (see Creating & Modifying Workloads).

Changed since the book:

Prefer kompose convert → review → commit → kubectl apply over kompose up, so the result lives in Git. Modern Compose files no longer need the version: key.

Source: K8s Cookbook, Recipe 14.4

Modern Extra — GitOps (the destination)

  • Idea: Git is the source of truth. A controller in the cluster (Argo CD or Flux) continuously pulls and applies it, and shows or undoes drift. CI builds and pushes images and bumps a tag in Git. CD = the GitOps controller syncing.
  • Why: auditable (every change is a commit), easy rollback (git revert), no cluster credentials in CI, and consistent multi-cluster rollouts (Best Practices, Ch. 5 & 12).

Summary

  • Helm 3 (no Tiller) to install and package apps: repo add → install/upgrade --install → history/rollback. Pin versions and keep values in Git.
  • Kustomize (kubectl apply -k) for per-environment overlays of plain YAML. Hashed configMapGenerator gives automatic rollouts on config change.
  • Server-side apply for explicit field ownership and conflict detection.
  • kompose gives you a first draft from Compose. GitOps (Argo CD/Flux) is where it all ends up.
Link to original

Maintenance & Troubleshooting

Maintenance & Troubleshooting

Overview

  • Chapter 12 of the Kubernetes Cookbook: kubectl autocompletion, parsing status, debugging Pods, cluster snapshots, adding nodes, drain/cordon, and etcd. (Recipes 12.2 and 12.3 are service tricks and live in Services & Ingress.)
  • Plus the modern debugging toolkit the book predates: kubectl debug, ephemeral containers and kubectl events.
  • Back to the hub: Kubernetes Cookbook · Related: Monitoring & Logging, Learning kubectl

Recipes

Recipe 12.1 — kubectl Autocompletion

  • Problem: Typing kubectl get deployment my-long-name is tedious.
  • Solution (zsh, the macOS default):
# ~/.zshrc
source <(kubectl completion zsh)
alias k=kubectl
compdef k=kubectl
  • Gotchas: Bash needs the bash-completion package first. Completion also completes resource names from the live cluster.

Source: K8s Cookbook, Recipe 12.1

Recipe 12.4 — Parse Resource Status (for scripts and CI)

  • Problem: React to an object’s state in a script or pipeline.
  • Solution:
kubectl get pod jump -o jsonpath='{.status.qosClass}'           # Guaranteed | Burstable | BestEffort
kubectl get pod jump -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
kubectl get pods -o jsonpath='{.items[*].spec.containers[*].image}'
kubectl get deploy web -o json | jq '.status'
 
# Better than polling: block until a condition is true
kubectl wait --for=condition=Available deploy/web --timeout=120s
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/jump
kubectl rollout status deploy/web --timeout=2m                  # non-zero exit if it fails → fails the CI step
  • Discussion: Built-in JSONPath or go-template output means you don’t need jq, but jq is nicer for complex queries.
  • QoS classes decide who gets evicted first under memory pressure:
    • BestEffort: no requests or limits. Evicted first.
    • Burstable: some requests or limits.
    • Guaranteed: requests = limits for CPU and memory. Evicted last.

Source: K8s Cookbook, Recipe 12.4

Recipe 12.5 — Debug Pods

  • Problem: A Pod won’t start, or keeps dying.
  • Solution: Run an OODA loop. Observe, Orient (list hypotheses), Decide (pick one), Act (test it), repeat. Observe in this order:
kubectl get pod web-abc -o wide                    # STATUS, RESTARTS, NODE
kubectl describe pod web-abc                       # Events at the bottom ← start here
kubectl events --for pod/web-abc                   # (or) kubectl get events --sort-by=.lastTimestamp
kubectl logs web-abc --previous                    # why did the last container die?
kubectl get pod web-abc -o jsonpath='{.status.containerStatuses[*].lastState}'
  • Read the status:
StatusUsual causeLook at
PendingNo node fits (requests too big, taints, PVC unbound, quota)describe → Events (FailedScheduling)
ImagePullBackOff / ErrImagePullTypo in image/tag, private registry without imagePullSecrets, rate limitdescribe → Events
CrashLoopBackOffApp exits on start: bad config, missing env/Secret, liveness too aggressivelogs --previous, exit code
OOMKilled (exit 137)Memory limit too low or a leaklastState.terminated.reason, kubectl top
CreateContainerConfigErrorReferenced ConfigMap/Secret or key missingdescribe
Running but 0/1 ReadyReadiness probe failingdescribe → probe events
Terminating foreverFinalizers, or node unreachableget -o yaml → metadata.finalizers
  • Get inside: use kubectl debug. It attaches an ephemeral debug container that works even for distroless images with no shell:
kubectl exec -it web-abc -- sh                                   # if the image has a shell
kubectl debug -it web-abc --image=busybox:1.36 --target=web      # share the app container's process namespace
kubectl debug web-abc -it --copy-to=web-debug --container=web -- sh   # debug a *copy* (e.g. CrashLoop) with a shell
kubectl debug node/worker-1 -it --image=ubuntu                   # a shell on the node (host fs at /host)
kubectl run tmp --rm -it --image=nicolaka/netshoot --restart=Never -- bash   # network toolbox Pod
  • Pre-flight checklist (from the book, still true):
    • Is the manifest valid? (kubectl apply --dry-run=server)
    • Does the image run locally with docker run?
    • Can nodes pull from the registry?
    • Is DNS working (see Services & Ingress)?
    • Are there enough resources, and are your requests and limits sane?

Changed since the book:

kubectl debug and ephemeral containers (GA 1.25) replace “install curl into the running container”. kubectl events (GA 1.28) gives sorted, filterable events. Validate against your real cluster with --dry-run=server. The book pointed to a JSON schema site.

Source: K8s Cookbook, Recipe 12.5

Recipe 12.6 — Snapshot the Whole Cluster State

  • Problem: Capture everything for an audit, a post-mortem, or a support ticket.
  • Solution:
kubectl cluster-info dump --all-namespaces --output-directory=./cluster-state-$(date +%F)
  • Discussion: You get objects as JSON plus Pod logs, per namespace. For restorable backups (objects + volumes) use Velero. For “what’s in Git vs. live”, GitOps tools show drift.
  • Gotchas: The dump contains Secrets and logs, so treat it as sensitive.

Source: K8s Cookbook, Recipe 12.6

Recipe 12.7 — Add Worker Nodes

  • Problem: The cluster needs more capacity.

  • Solution: Provision a machine and install the three node components:

    1. kubelet: the node agent. It registers the node, runs and probes Pods, mounts volumes, and reports status.
    2. A container runtime speaking CRI: containerd or CRI-O.
    3. kube-proxy: programs Service routing. It’s optional if your CNI replaces it, e.g. Cilium.

    Then run kubeadm join … (see Getting Started & Local Clusters). On managed clouds, just grow the node pool, or let the autoscaler do it (Scaling).

  • Discussion: A Node object is only a record. The node counts once its kubelet reports healthy (kubectl get nodes → Ready). Debug runtimes with crictl on the node, since docker ps won’t show Kubernetes containers anymore.

Changed since the book:

The Docker Engine runtime was removed in 1.24 (dockershim), so containerd or CRI-O it is.

Source: K8s Cookbook, Recipe 12.7

Recipe 12.8 — Drain a Node for Maintenance

  • Problem: Patch, reboot or upgrade a node without dropping traffic.
  • Solution:
kubectl cordon worker-1                     # no new Pods land here
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data --timeout=5m
# … do the maintenance …
kubectl uncordon worker-1                   # schedulable again
  • Protect apps during drains with a PodDisruptionBudget:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2          # or maxUnavailable: 1
  selector:
    matchLabels:
      app: web
  • Discussion: drain = cordon + evict every Pod, respecting PDBs and graceful termination. DaemonSet Pods stay. Bare Pods (no controller) need --force and are gone forever.
  • Gotchas: A PDB with minAvailable equal to the replica count (e.g. 1 of 1) blocks drains forever. Run ≥ 2 replicas for anything you drain around.

Changed since the book:

--delete-local-data → --delete-emptydir-data. PDBs are policy/v1 (GA 1.21).

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

Recipe 12.9 — Manage etcd

  • Problem: Back up (or inspect) the cluster’s source of truth.
  • Solution (self-managed / kubeadm control plane):
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /backup/etcd-$(date +%F).db
 
etcdutl snapshot status /backup/etcd-2026-01-01.db -w table
  • Discussion: Only the API server talks to etcd. Everything else goes through the API. Keys look like /registry/<resource>/<ns>/<name>, stored as protobuf (not readable JSON). Back up regularly and test restores (etcdutl snapshot restore).
  • Gotchas: On managed services (GKE, EKS, AKS) etcd is hidden and backed up for you. Back up your objects with Velero or Git instead.

Changed since the book:

The book’s curl …:2379/v2/keys uses the etcd v2 API, which is gone. Kubernetes uses etcd v3 only, over TLS. Snapshot status/restore moved from etcdctl to etcdutl.

Source: K8s Cookbook, Recipe 12.9


Summary

  • Debug order: get -o wide → describe (Events!) → logs --previous → kubectl debug.
  • Know the statuses: Pending (scheduling), ImagePullBackOff (image), CrashLoopBackOff (app/config/probe), OOMKilled (memory).
  • Script with jsonpath, kubectl wait and rollout status, not sleep 30.
  • Maintenance: cordon → drain --ignore-daemonsets --delete-emptydir-data → uncordon, with PDBs and ≥ 2 replicas.
  • Nodes run kubelet + containerd + kube-proxy, so use crictl on the node. Back up etcd with etcdctl snapshot save (v3 only).
Link to original

Extending Kubernetes

Extending Kubernetes

Overview

  • Chapter 13 of the Kubernetes Cookbook (building Kubernetes, the Python client, CRDs), plus Recipes 14.5 (kubicorn) and 14.7 (kubeless) from the Ecosystem chapter. Then the modern picture: operators, admission control and platforms (Best Practices Ch. 15–17).
  • Big idea: Kubernetes is a database of desired state + control loops. Extending it = adding new kinds of desired state (CRDs) + new control loops (controllers/operators).
  • Back to the hub: Kubernetes Cookbook

Recipes

Recipe 13.1 / 13.2 — Build Kubernetes from Source

  • Problem: You want your own binaries (to patch a bug or test a PR).
  • Solution:
git clone https://github.com/kubernetes/kubernetes && cd kubernetes
make WHAT=cmd/kubectl          # one component → _output/bin/kubectl
make                           # everything (needs Go at the version in .go-version)
make quick-release             # containerised build (needs Docker with ≥ 8 GB RAM)
  • Discussion: Few people need this. It’s for contributors. To test a local build, kind build node-image packages your source into a kind node image.

Changed since the book:

hyperkube is gone, so make hyperkube no longer exists. Per-component targets use make WHAT=cmd/<component>.

Source: K8s Cookbook, Recipes 13.1 & 13.2

Recipe 13.3 — Talk to the API from Python

  • Problem: Script against the cluster in Python.
  • Solution:
from kubernetes import client, config
 
config.load_kube_config()            # in-cluster: config.load_incluster_config()
core = client.CoreV1Api()
apps = client.AppsV1Api()
 
for pod in core.list_pod_for_all_namespaces().items:
    print(pod.metadata.namespace, pod.metadata.name, pod.status.phase)
 
apps.patch_namespaced_deployment_scale(
    "web", "default", {"spec": {"replicas": 3}}
)
  • Discussion: The client is generated from the OpenAPI spec, with one class per API group/version: CoreV1Api for core, AppsV1Api for Deployments, BatchV1Api, NetworkingV1Api, CustomObjectsApi for CRDs. Its version tracks Kubernetes minors.
  • Gotchas: In a Pod, the client uses the Pod’s ServiceAccount, so give it a minimal Role (Security). Use watch.Watch() instead of polling. Go’s client-go is the reference client.

Changed since the book:

Deployments are AppsV1Api, not ExtensionsV1beta1Api, and the module is no longer “incubator”.

Source: K8s Cookbook, Recipe 13.3

Recipe 13.4 — Extend the API with a CustomResourceDefinition

  • Problem: None of the built-in kinds fit your workload, and you want kubectl get functions to just work.
  • Solution:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: functions.example.com          # must be <plural>.<group>
spec:
  group: example.com
  scope: Namespaced
  names:
    kind: Function
    plural: functions
    singular: function
    shortNames: ["fn"]
  versions:
    - name: v1
      served: true
      storage: true
      schema:                           # a structural schema is REQUIRED in v1
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: ["code"]
              properties:
                code:
                  type: string
                ram:
                  type: string
                  default: "128Mi"      # defaulting is supported
      additionalPrinterColumns:
        - name: RAM
          type: string
          jsonPath: .spec.ram
      subresources:
        status: {}
apiVersion: example.com/v1
kind: Function
metadata:
  name: myfaas
spec:
  code: "http://src.example.com/myfaas.js"
  ram: 100Mi
kubectl apply -f functions-crd.yaml
kubectl apply -f myfaas.yaml
kubectl get fn
kubectl explain function.spec
  • Discussion: A CRD alone just stores data in etcd. To make something happen, you need a controller that watches Function objects and reconciles real resources (Deployments, Services) to match. Controller + CRD + domain knowledge = an operator.
  • Gotchas:
    • Deleting a CRD deletes every instance of it (cascade).
    • Changing a CRD’s schema across versions needs a conversion webhook.
    • Keep CRD installs separate from the CRs that use them (CRDs first).

Changed since the book:

apiextensions.k8s.io/v1beta1 was removed in 1.22, so use v1. The book’s example also had bugs: the name must be functions.example.com, and scope belongs under spec, not names. Every limitation the book lists is now solved: multiple versions with conversion webhooks, defaulting, schema validation, the status/scale subresources, plus CEL validation rules (x-kubernetes-validations).

Source: K8s Cookbook, Recipe 13.4

Modern Extra — Operators

  • What: A controller that encodes an ops runbook (install, upgrade, back up, fail over) for one piece of software, driven by its CRDs.
  • Examples: Prometheus Operator, cert-manager, CloudNativePG (Postgres), Strimzi (Kafka), Argo CD, External Secrets.
  • Build your own: Kubebuilder or Operator SDK (Go, controller-runtime). Kopf for Python. Find existing operators on OperatorHub or Artifact Hub.
  • Rules of thumb (Best Practices Ch. 12 & 16):
    • Before running a complex stateful system yourself, check whether a mature operator, or a managed service, exists.
    • Reconcile loops must be idempotent and level-triggered: “make it so”, not “do this once”.

Modern Extra — Admission Control (Best Practices Ch. 17)

  • Mutating webhooks change objects (inject sidecars, add defaults). Validating ones accept or reject them. Mutating always runs before validating.
  • Built-in, no webhook needed:
    • ValidatingAdmissionPolicy (CEL, GA 1.30).
    • MutatingAdmissionPolicy (CEL, newer).
    • Pod Security Admission (Security).
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-team-label
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
      message: "Deployments must carry a 'team' label"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: require-team-label
spec:
  policyName: require-team-label
  validationActions: ["Deny"]
  matchResources:
    namespaceSelector:
      matchExpressions:
        - key: kubernetes.io/metadata.name
          operator: NotIn
          values: ["kube-system"]
  • Webhook best practices:
    • Scope webhooks with a namespaceSelector, and exclude kube-system.
    • Choose failurePolicy deliberately: Fail can lock out the whole cluster if the webhook is down, while Ignore silently skips policy.
    • Keep webhooks fast (30 s timeout max), with no external DB lookups.
    • Never have two mutating webhooks touch the same field.
    • Lock webhook configurations down with RBAC, because creating one is effectively root.

Recipe 14.5 — Create Cloud Clusters Declaratively (was: kubicorn)

  • Problem: Create and manage clusters as code.
  • Solution today: eksctl (EKS), Terraform/OpenTofu modules, Crossplane, or Cluster API (Kubernetes-style CRDs that create clusters on any cloud). kops is still around for self-managed AWS clusters.

Changed since the book:

kubicorn was abandoned and its repo archived. Cluster API carries its “clusters as Kubernetes objects” idea forward.

Source: K8s Cookbook, Recipe 14.5

Recipe 14.7 — Functions on Kubernetes (was: kubeless)

  • Problem: Deploy functions (HTTP- or event-triggered) without writing Deployments.
  • Solution today: Knative Serving (request-driven, scale to zero, revisions and traffic splitting) + Knative Eventing. Or OpenFaaS. For “scale my normal Deployment on events, down to zero”, use KEDA (see Scaling).

Changed since the book:

kubeless was archived (2021), and its Python 2.7 runtime is long dead. It was a nice example of CRD + controller, though: a Function CR got turned into a ConfigMap (code) + Deployment + Service. That’s exactly the operator pattern above.

Source: K8s Cookbook, Recipe 14.7


Summary

  • Kubernetes = desired-state store + control loops. Extend with CRDs (apiextensions.k8s.io/v1, structural schema) + controllers = operators.
  • Script with the official clients (Python: CoreV1Api, AppsV1Api, CustomObjectsApi). Build with Kubebuilder or Operator SDK.
  • Enforce rules at admission time: ValidatingAdmissionPolicy (CEL) first, webhooks (Kyverno, Gatekeeper) when you need more.
  • Book-era tools replaced: kubicorn → Cluster API/eksctl/Terraform, kubeless → Knative/OpenFaaS/KEDA.
Link to original

Notes in this canvas