Tools Used in DevOps · Kubernetes

Kubernetes

Kubernetes is the control plane that takes a declarative description of what you want running — this many replicas of this image, reachable at this address — and continuously drives the cluster's actual state to match it, correcting on its own whenever a Pod crashes, a node dies, or someone edits something by hand. Containers & orchestration already named Pods, Deployments, and Services in passing and introduced the shape of that control loop; this page goes deeper into all three, into the reconciliation model that makes them self-healing, and into the actual YAML and kubectl commands you need to ship a workload onto a cluster with confidence. It deliberately does not go as deep as running one: cluster bootstrapping, etcd backup and restore, RBAC design, CNI networking internals, storage provisioning, and version upgrades are cluster-administrator territory, covered end to end on the CKA certification profile. This page is written at the altitude a DevOps engineer actually operates at — someone who deploys onto a cluster someone else keeps alive, not someone who built the cluster.

☺ Explain it like I'm 10

Imagine a thermostat instead of a person in charge of the temperature. You don't tell it "turn the heater on now" — you tell it "I want it to be 70 degrees in here," and it checks the room over and over, forever, quietly switching the heater on or off to close whatever gap it finds, without you ever telling it to check again. Kubernetes is a thermostat for your application: you don't tell it to start a container, you tell it "I want three copies of this image running," and something inside it checks that promise against reality every few seconds. Come back from a coffee break, a copy has died, and a new one is already running — nobody paged anyone to type the command.

🤖Your host for this topic: Recon the Robot — the same reconciler behind infrastructure as code and Ansible, except this time the control loop isn't something you run on a schedule. It's built into the cluster itself, watching every second of every day, whether you're online or not.

What a DevOps engineer needs here, and what belongs to CKA

☺ Like you're 10: You need to know how to move into the apartment and live there comfortably — you don't need to know how the building's plumbing was installed.

Kubernetes has one API surface but two very different jobs sitting behind it, and conflating them is the single most common reason people bounce off this page's subject feeling like they need a semester before they can ship anything. The cluster administrator's job is keeping the cluster itself alive: installing and upgrading the control plane, managing etcd, wiring up the container network interface (CNI) and storage classes, writing RBAC policy, patching nodes. The DevOps engineer's job — the one this page covers — is using a cluster that already exists to run workloads reliably: writing correct manifests, reading what the cluster is telling you when something's wrong, and knowing which command fixes which failure. You can do the second job extremely well without ever having run kubeadm init.

In scope on this pageOut of scope — see CKA
Writing Pod, Deployment, and Service manifests correctlyCluster installation, kubeadm, control-plane component config
Reading the reconciliation loop's status to diagnose a stuck rolloutetcd backup, restore, and disaster recovery
Requests, limits, probes, and the failure modes they causeRBAC design, ServiceAccounts, admission controllers
The kubectl commands you reach for daily to ship and triageCNI plugin internals, NetworkPolicy enforcement, ingress controller install
Comparing Kubernetes to the alternatives you might pick insteadStorageClasses, PersistentVolume provisioning, node maintenance, upgrades

If you're the one who also owns the cluster, or you're studying for the exam that certifies that role, everything in the right-hand column above gets full treatment on the CKA — Certified Kubernetes Administrator profile. If your interest is specifically writing and deploying application manifests — a slightly different exam with a slightly different emphasis — see CKAD instead; it maps onto this page's scope more closely than CKA does. Either way, verify current exam format, domain weights, and pricing on the CNCF's own pages before you commit study time — certification details change more often than the underlying technology does.

The control plane and the reconciliation loop

☺ Like you're 10: A handful of specialist workers each do one job — one writes down what you asked for, one decides which machine runs what, one actually starts it — and they never stop checking their own piece of the promise.

Every Kubernetes cluster splits into two kinds of machines. The control plane makes decisions and never runs your application containers; worker nodes run nothing but your workloads. What "Kubernetes" actually is, mechanically, is a small set of independent processes that only talk to each other through one shared, versioned record of desired and observed state.

How a declared change reaches a running container kubectl apply -f deployment.yaml API server validate · the only door etcd the only state that exists Controllers watch → diff → act, forever Scheduler assigns unscheduled Pods Node kubelet + container runtime pulls image, starts container write / read watch / act unscheduled Pod binding kubelet watches for Pods on its Node This loop never stops — it re-runs the diff continuously, not only when apply is called.
◆ Key idea — level-triggered, not edge-triggered

A controller doesn't act on the event "a Pod died." It acts on the current gap between desired and observed state, re-evaluated from scratch on every pass. Miss a notification, restart a controller, lose a network connection for ten seconds — none of it matters, because the next reconcile pass reads current reality fresh and closes whatever gap it finds, regardless of how it got there. This is the entire reason Kubernetes self-heals without anyone building retry logic: there's nothing to retry, only a diff to recompute.

The core objects: Pod, Deployment, Service

☺ Like you're 10: A Pod holds your container, a Deployment keeps the right number of Pods running and rolls out new versions, and a Service hands out one steady address so nobody has to track which Pod moved where.

Pod is the smallest deployable unit — a wrapper around one or more containers sharing one network namespace (one IP) and, optionally, storage volumes. You almost never write a bare Pod for a real workload: Pods are disposable by design, and something needs to notice when one dies and create a replacement. That something is a Deployment, which owns a ReplicaSet, which owns the Pods — a Deployment you write directly, a ReplicaSet you almost never touch by hand, and Pods you mostly only read. A Service then gives that shifting set of Pods one address that doesn't change as individual Pods come and go.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-api
  labels: { app: checkout-api }
spec:
  replicas: 3
  selector:
    matchLabels: { app: checkout-api }        # MUST match template.metadata.labels below
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # up to 1 extra Pod above `replicas` during a rollout
      maxUnavailable: 0    # never drop below `replicas` Ready Pods while rolling
  template:
    metadata:
      labels: { app: checkout-api }
    spec:
      containers:
        - name: checkout-api
          image: registry.internal/checkout-api:1.4.2   # pin a digest or a never-reused tag in prod
          ports:
            - containerPort: 3000
          env:
            - name: LOG_LEVEL
              valueFrom:
                configMapKeyRef: { name: checkout-config, key: logLevel }
          resources:
            requests: { cpu: 100m, memory: 128Mi }   # what the scheduler reserves for this Pod
            limits:   { memory: 256Mi }               # exceed this and the container is OOMKilled
          readinessProbe:                             # fail this → removed from Service, NOT restarted
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:                              # fail this → container is killed and restarted
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 15
            periodSeconds: 20
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: checkout-api
spec:
  selector: { app: checkout-api }    # every label here must match a Pod's labels, or Endpoints is empty
  ports:
    - port: 80
      targetPort: 3000
  type: ClusterIP                    # NodePort or LoadBalancer expose it beyond the cluster

Two fields are worth reading twice because they're the source of most first-week mistakes. A Deployment's spec.selector.matchLabels must match its own spec.template.metadata.labels — Kubernetes rejects an update that changes the selector on an existing Deployment, because changing what a Deployment considers "its" Pods retroactively is not a safe operation. And a Service's spec.selector is matched against Pod labels independently, by string equality — it has no relationship to the Deployment at all beyond both happening to use the same labels; a Service will happily route to Pods from three different Deployments, or to zero Pods, based purely on whether labels line up.

Beyond these three, a DevOps engineer runs into a handful of other objects constantly and a longer tail rarely — the constant ones are worth naming even though most get their own fuller treatment elsewhere in this course:

ObjectWhat it's forHow often you touch it
NamespaceA logical partition of the cluster — names, RBAC scope, and resource quotas are all per-namespaceConstantly, as a -n flag or a manifest field
ConfigMapNon-secret configuration injected as env vars or mounted filesConstantly
SecretSame shape as a ConfigMap, base64-encoded (not encrypted) at rest by defaultConstantly — see the warning below
IngressHTTP(S) routing into the cluster from outside, handled by an ingress controller (NGINX, etc.)Occasionally, per public-facing service
HorizontalPodAutoscalerAdjusts a Deployment's replicas automatically based on CPU/memory or a custom metricOccasionally, for variable-load services
StatefulSet, DaemonSet, PersistentVolumeClaim, NetworkPolicy, RBAC objectsStable identity/storage, one-per-node agents, storage requests, traffic policy, access controlRarely as an application deployer — mostly platform/cluster-admin territory, full depth on CKA
⚠ A Secret is not encrypted by default

A Kubernetes Secret stores its value base64-encoded, which is an encoding, not encryption — anyone with API access to read that Secret (or a copy of etcd) can decode it in one command. Treat a raw Secret object the way you'd treat a plaintext file with restricted permissions, not the way you'd treat something actually encrypted at rest. For anything beyond a low-stakes dev cluster, inject real secrets from an external store instead of hand-writing them into manifests — see secrets & credential management and HashiCorp Vault.

Day-to-day kubectl commands

☺ Like you're 10: A handful of commands cover almost everything: ship it, look at it, read what it's saying, and undo it if it's wrong.

# ship and inspect
$ kubectl apply -f deployment.yaml -f service.yaml   # create or update — idempotent, use this over `create`
$ kubectl get deployments,pods,svc -n checkout -o wide
$ kubectl describe pod checkout-api-7d8f9c-xk2p9      # the single most useful triage command — read the Events at the bottom
$ kubectl get endpoints checkout-api                  # empty list = Service selector matches no Pod, full stop

# logs and a shell
$ kubectl logs -f checkout-api-7d8f9c-xk2p9           # follow live logs
$ kubectl logs checkout-api-7d8f9c-xk2p9 --previous   # logs from the container BEFORE its last restart — the CrashLoopBackOff command
$ kubectl exec -it checkout-api-7d8f9c-xk2p9 -- sh    # a shell inside the running container

# rollouts
$ kubectl rollout status deployment/checkout-api      # blocks until the rollout finishes or fails
$ kubectl rollout history deployment/checkout-api     # every revision, with --revision=N for detail
$ kubectl rollout undo deployment/checkout-api         # back to the previous revision
$ kubectl rollout undo deployment/checkout-api --to-revision=3

# scale, debug, and clean up
$ kubectl scale deployment/checkout-api --replicas=5
$ kubectl top pods -n checkout                        # live CPU/memory, needs metrics-server installed
$ kubectl get events -n checkout --sort-by=.lastTimestamp   # cluster-wide, chronological — the first thing to check
$ kubectl port-forward svc/checkout-api 8080:80        # reach a ClusterIP Service from your laptop, no Ingress needed
$ kubectl delete pod checkout-api-7d8f9c-xk2p9         # deletes ONE Pod; the ReplicaSet immediately creates a replacement
$ kubectl explain deployment.spec.strategy.rollingUpdate  # field-level docs from the API itself — works offline

kubectl delete pod is worth calling out on its own: deleting a Pod that a ReplicaSet owns doesn't remove capacity, it just forces a fresh one — the fastest "have you tried turning it off and on again" available, and safe precisely because the reconciliation loop notices the gap and closes it within seconds.

Gotchas and failure modes

☺ Like you're 10: Almost every real incident is one of a handful of shapes — a container that won't stay up, a Pod nobody will run, or a Service pointing at nothing.

CrashLoopBackOff and ImagePullBackOff

CrashLoopBackOff means the container starts and exits immediately, repeatedly, and the kubelet is backing off between restart attempts (capped around five minutes). It's almost always an application-level problem — a missing environment variable, a bad startup command, a dependency that isn't reachable yet — not a Kubernetes problem, and kubectl logs --previous is the fastest way to see the exit before the next restart wipes it. ImagePullBackOff / ErrImagePull means the kubelet can't fetch the image at all: a typo'd tag, an image that was never pushed, or a private registry the Node has no imagePullSecret for. kubectl describe pod's Events section names the exact reason for both.

Pending: the Pod the scheduler won't place

A Pod stuck Pending has never been scheduled to a Node at all — no container has started, because the scheduler couldn't find a Node that satisfies it. The three usual causes are insufficient CPU or memory across every Node relative to the Pod's resources.requests, a nodeSelector or affinity rule that no Node's labels satisfy, and a taint on every eligible Node with no matching toleration on the Pod. kubectl describe pod again has the answer, in a line reading something like "0/6 nodes are available: 6 Insufficient memory."

Readiness vs. liveness: two probes, two very different consequences

A failing readinessProbe removes the Pod from every Service's Endpoints — traffic stops routing to it — but the container keeps running untouched. A failing livenessProbe kills the container and restarts it. Confuse the two, or set a livenessProbe's initialDelaySeconds too aggressively for a slow-starting app, and you get a container killed mid-boot in a loop that looks exactly like CrashLoopBackOff but is actually self-inflicted by the probe config — a startupProbe exists specifically to give slow-starting containers room before liveness checks begin. The opposite failure is quieter and more dangerous: a readinessProbe that fails forever due to a misconfigured path leaves a Pod running, consuming resources, showing as healthy in most dashboards, and permanently receiving zero traffic — kubectl get endpoints is what actually surfaces it.

⚠ Service selector mismatches fail silently

A typo'd label in either a Deployment's Pod template or a Service's selectorapp: checkout-api versus app: checkout_api, say — produces no error anywhere. The Deployment reports Pods Running and Ready. The Service reports Ready to whatever's watching it. Requests just time out, because the Service has zero Endpoints and nothing announces that fact except kubectl get endpoints <svc> coming back empty. This is the single fastest first check for "the Pods look fine but nothing can reach them."

OOMKilled, throttling, and skipping requests/limits entirely

Exceed a container's memory limit and the kernel kills it (SIGKILL, reported as OOMKilled in kubectl describe pod) — memory can't be throttled the way CPU can, only reclaimed by force. Exceed a CPU limit and the container is throttled, not killed, which shows up as mysterious latency rather than a restart. Skip requests entirely and the Pod gets Kubernetes' BestEffort quality-of-service class — first in line for eviction the moment its Node comes under memory pressure, regardless of how much memory the container is actually using.

A rollout that hangs forever

If new Pods from a rollout never pass their readinessProbe — a bad image, a config error only the new version hits — a rolling update with the default maxUnavailable: 25% just stalls: old Pods keep serving, new ones sit un-Ready, and kubectl rollout status never returns. Nothing is actually broken from a user's perspective yet, which is exactly why it's easy to miss; kubectl rollout undo is the fix, and practicing the diagnosis-to-rollback path on a real stuck rollout is the whole point of Drill — Roll Back a Bad Deploy.

Manual edits get reconciled away

kubectl edit deployment/checkout-api or a hand-patched live object works — for exactly as long as nothing re-applies the manifest that doesn't match your edit. Under GitOps with Argo CD that can be minutes; even without GitOps, the next ordinary kubectl apply from CI silently overwrites the hand edit. Fix the source manifest and let it flow through the pipeline, the same discipline covered generally in infrastructure as code — a live cluster edit is a debugging tool, never a fix.

✎ Try it

On a disposable local cluster (kind or minikube): apply the Deployment and Service above, then kubectl get endpoints checkout-api and confirm it lists Pod IPs. Now break it on purpose — edit the Service's selector to a label that doesn't exist, re-apply, and watch kubectl get endpoints go empty while kubectl get pods keeps reporting everything Running. That gap between "Pods are fine" and "nothing can reach them" is the single most common real-world Kubernetes incident, and now you've seen it caused on purpose instead of at 2 a.m.

Kubernetes vs. its alternatives

☺ Like you're 10: Other tools solve the same "keep my containers running" problem with a smaller rulebook — the trade is almost always less power for less to learn.

OptionModelBest whenCosts you
KubernetesDeclarative, extensible API; self-hosted or managed (EKS, GKE, AKS)You need portability across clouds, the ecosystem (Helm, Argo CD, service meshes), or scale a simpler scheduler can't handleReal operational depth — the gap this page deliberately leaves for CKA
Docker SwarmBuilt into the Docker Engine; a much smaller APIA small team wants basic orchestration with almost no learning curveA far smaller ecosystem, and few engineers are actively deepening Swarm skills anymore
HashiCorp NomadA single lightweight binary scheduling containers, VMs, and raw batch jobs alikeYou're already in the HashiCorp stack, or need non-container workloads on one schedulerSmaller ecosystem, fewer managed offerings, fewer engineers who already know it
Amazon ECS / FargateAWS-native scheduler; Fargate removes node management entirelySingle-cloud on AWS, wanting less operational surface than a self-run Kubernetes clusterNot portable outside AWS; a narrower ecosystem than Kubernetes'
Cloud Run / managed serverless containersFully managed, scale-to-zero, no cluster concept at allStateless, request-driven services where you don't want to think about nodesLeast control — no DaemonSets, limited sidecar support, a poor fit for long-running background workers

The practical rule most teams land on: reach for a managed Kubernetes offering (EKS/GKE/AKS) once you need portability, a mature ecosystem, or genuine multi-service scale, and reach for something narrower — ECS, Cloud Run, Nomad — when a single cloud and a simpler operational model outweigh Kubernetes' extensibility. Very few teams that adopt Kubernetes for a handful of services actually needed its full feature surface on day one; see the DevOps toolchain for where it sits relative to everything else on a pipeline.

🎬 At the Ship-It Guild
🦫

Benny the Beaver: Rollout's been "in progress" for ten minutes. Old Pods still serving, new ones just... sitting there.

🤖

Recon the Robot: Because I never lie about it, Benny — the new Pods aren't Ready, so I won't take away the old ones. That's maxUnavailable doing exactly its job.

🦊

Foxy: So why aren't the new ones Ready? The image built fine.

🤖

Recon the Robot: Built fine, started fine — failing its readinessProbe on a healthcheck path that changed in this release. I only know Ready or not Ready. I don't know why.

👺

Gizmo: Just kubectl edit the old Pods to keep them around longer and quietly bump replicas on the side. Nobody has to know the rollout's stuck. 🤑

🐢

Timmy the Turtle: Or read the Events, fix the probe path in the manifest, and let Recon roll forward properly. A hand-edit just gets reconciled away the next time this deploys anyway.

🦫

Benny the Beaver: rollout undo, fix the probe in the YAML, redeploy. Ninety seconds, and it's actually fixed instead of hidden.

Going further: CKA, CKAD, and the rest of the certification track

☺ Like you're 10: This page gets you deploying confidently — three deeper exams exist for the people who own the cluster, build apps on it full-time, or secure it.

Everything above is deliberately the DevOps-engineer slice of a much larger subject. Three vendor-neutral certifications from the Cloud Native Computing Foundation cover the deeper layers, each with a different center of gravity, and it's worth knowing which one actually matches what you're trying to get better at before you commit study hours to any of them:

Also worth knowing about: KCNA — Kubernetes and Cloud Native Associate is a lighter-weight, no-prerequisite entry point across the whole cloud-native landscape, not Kubernetes hands-on depth specifically — a reasonable starting point if all three exams above feel premature. All four exam formats, current pricing, and domain weightings change more often than the underlying platform does; verify specifics on the CNCF's own certification pages before scheduling. For the canonical technical reference behind everything on this page, kubernetes.io/docs is the primary source, and it's worth reading directly rather than through a summary once you're past this introduction. Practice the objects and commands on this page against a real (if small) deployment in Capstone Part 3 — Deployment Strategy, and see how a chart packages the manifests above for reuse in Helm.

✓ Checkpoint

1. Name the three core objects covered on this page and, in one sentence each, what each one owns. 2. Why does a Kubernetes controller keep working correctly even if it misses an update event entirely? 3. A Pod is stuck in Pending. What are the three usual causes, and which command names the exact one? 4. What's the difference in consequence between a failing readinessProbe and a failing livenessProbe? 5. A Service's Endpoints list is empty even though its Pods show Running and Ready. What's almost certainly wrong? 6. Which certification most closely matches this page's own scope, and which one covers everything this page called out of scope?

Check your answers
  1. Pod — the smallest deployable unit, wrapping one or more containers sharing a network namespace. Deployment — declares a desired replica count and image, and owns a ReplicaSet that in turn owns the Pods. Service — a stable network address routed to whichever Pods currently match its label selector.
  2. Because reconciliation is level-triggered, not edge-triggered: every pass recomputes the gap between desired and observed state from scratch, rather than reacting to the specific event that changed. Missing one notification changes nothing — the next pass finds the same gap and closes it regardless.
  3. Insufficient CPU or memory across every Node relative to the Pod's resources.requests; a nodeSelector/affinity rule no Node satisfies; a taint on every eligible Node with no matching toleration on the Pod. kubectl describe pod names the exact reason in its Events section.
  4. A failing readinessProbe removes the Pod from Service Endpoints (it stops receiving traffic) but leaves the container running untouched. A failing livenessProbe kills the container and restarts it.
  5. A label mismatch between the Service's selector and the Pods' labels — a typo in either one produces zero Endpoints with no error anywhere else; Pods and the Service both report healthy while nothing can actually reach them.
  6. CKAD matches this page's application-deployment scope most closely; CKA covers cluster installation, etcd, RBAC, networking, and storage — everything this page explicitly left out.