07

Pods: Imperative vs Declarative & YAML

Run it in the public monorepo

This course is built around the public HelixWorks Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.

Source: app/Dockerfile

FROM nginx:1.29-alpine

COPY index.html /usr/share/nginx/html/index.html

EXPOSE 80

Code to reality

Declared intent
Package the first web workload with one repeatable runtime and HTTP port.
Interpreter
The Docker builder executes each instruction and records an immutable image layer.
Software effect
The image contains the HelixWorks page at the nginx document root and declares port 80.
Hardware effect
A running container consumes host CPU, memory, storage layers, and a network namespace.
Observable evidence
docker image inspect and an HTTP request to the running container identify the image and response.

Key terms

TermMeaning
PodSmallest deployable unit (one or more containers)
ImperativeDo-this-now commands (kubectl run/create)
DeclarativeApply desired YAML and let Kubernetes converge
ManifestA YAML/JSON object definition
apiVersion/kind/metadata/specThe four required top-level YAML fields
--dry-run=clientBuild YAML without creating anything
kubectl applyDeclarative create/update from a manifest

Problem & solution

The pod is the unit you actually deploy, but there are two very different ways to create resources: quick imperative commands versus versionable declarative YAML. Choosing wrong leads to unrepeatable, undocumented infrastructure.

Solution: Define workloads declaratively in YAML and apply them, so the cluster reconciles to your desired state (prefer declarative over imperative one-offs).

The analogy

You can run a dock two ways. You can stand on the pier barking each step at a dockworker, fast for a one-off, but nobody records what you asked or repeats it tomorrow. Or you can file a standing work order describing the end state you want, and the port keeps fulfilling it on its own, restoring it whenever reality drifts. Kubernetes mirrors this: imperative kubectl commands are the shouted steps, while a declarative YAML manifest is the standing order that Kubernetes continuously reconciles toward.

Where this fits in the cluster

The same cluster entities appear in every day's notes; the diagram below shows where this day's topic fits.

Graph legend — each node maps to the nginx pod this day creates:

Graph nodeMaps toWhat it does
api-server / etcd / scheduler / controller-mgrthe control-plane componentsAccept, store, schedule, and reconcile the pod
Worker nodea node running kubelet, kube-proxy, runtimeHosts the nginx pod
Pod nginxkind: Pod, metadata.name: nginxThe smallest deployable unit
container nginx:1.27spec.containers[0].image: nginx:1.27The web server process inside the pod

What is a Pod?

The smallest deployable unit in Kubernetes. A pod wraps one (or more) containers that share network (same IP) and storage.

Graph legend — each node maps to containers sharing one pod's network/storage:

Graph nodeMaps toWhat it does
Pod - shared IP and shared volumesa single kind: PodOne network namespace and shared volumes for its containers
container nginx:1.27the main nginx:1.27 containerServes the app on the pod IP
sidecar - log shipperan optional second containerReads nginx logs over the shared volume
localhostthe shared pod networkLets the two containers reach each other on 127.0.0.1
  • You usually run ONE main container per pod.
  • Pods are ephemeral: they get replaced, not repaired.

Two ways to manage objects

Kubernetes accepts both styles: you can fire off imperative commands or hand it a declarative file describing the end state.

   IMPERATIVE  = tell K8s the exact COMMANDS to run (how)
   DECLARATIVE = give K8s a YAML of the desired STATE (what)
ImperativeDeclarative
Stylekubectl run/create ...kubectl apply -f file.yaml
Best forquick tests, exam speedreal/version-controlled infra
Repeatablehard to trackyes (GitOps-friendly)
Idempotentnoyes (re-apply = no-op)

Imperative examples

Quick one-off commands that create and inspect a pod without writing any YAML.

kubectl run nginx --image=nginx              # create a pod fast
kubectl get pods
kubectl get pods -o wide                      # node + pod IP
kubectl describe pod nginx                     # events & details
kubectl delete pod nginx

Declarative: a Pod YAML

Here the desired state lives in a file you version-control and apply — the preferred way for real infrastructure.

pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: web
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      ports:
        - containerPort: 80
kubectl apply -f pod.yaml      # create or update to match file
kubectl get pod nginx -o yaml  # full live spec
kubectl delete -f pod.yaml

The 4 required top-level YAML fields

Every Kubernetes manifest needs these four keys at the top.

   apiVersion:  which API group/version  (e.g. v1, apps/v1)
   kind:        object type              (Pod, Deployment...)
   metadata:    name, labels, namespace
   spec:        desired state of the object

Pro tip: generate YAML instead of hand-writing (exam gold)

Let kubectl write the boilerplate for you, then tweak the file instead of typing YAML from scratch.

# --dry-run=client builds the YAML WITHOUT creating anything
kubectl run nginx --image=nginx \
  --dry-run=client -o yaml > pod.yaml

Debugging a pod

When a pod misbehaves, these three commands surface events, logs, and a shell inside the container.

kubectl describe pod nginx       # see Events at the bottom
kubectl logs nginx               # container stdout/stderr
kubectl exec -it nginx -- bash   # shell inside

Imperative -> Declarative mental model

A handy workflow: generate a manifest from an imperative command, then manage it declaratively from then on.

Graph legend — each node maps to the generate-then-apply workflow for the nginx pod:

Graph nodeMaps toWhat it does
kubectl run nginx ... --dry-run=client -o yamlthe imperative generate commandPrints a pod manifest without creating anything
pod.yamlthe saved manifestVersion-controlled desired state for the nginx pod
kubectl apply -f pod.yamlthe declarative applySubmits the manifest to the cluster
cluster state in etcdthe stored objectThe reconciled desired state

End-to-end flow

Imperative and declarative paths both converge on the same reconcile flow that ends in a running pod.

Graph legend — each node maps to the path both styles take to a running nginx pod:

Graph nodeMaps toWhat it does
kubectl run nginx imperativekubectl run nginx --image=nginx:1.27One-off imperative creation
kubectl apply -f pod.yaml declarativekubectl applyDeclarative creation from the manifest
API serverkube-apiserverValidates and admits either request
Store in etcdetcd writePersists the desired pod
Scheduler picks nodekube-schedulerBinds the pod to a node
kubelet on nodethe node kubeletDrives the runtime for the bound pod
Runtime pulls nginx:1.27containerd/CRI-OPulls the image and starts the container
Pod nginx Runningthe live podFinal reconciled state

Key takeaways

  • Pod = smallest unit; containers in it share IP + storage.
  • Use imperative for speed, declarative for real infra.
  • --dry-run=client -o yaml generates manifests fast (use it in the exam).

Checklist

  • [ ] Created a pod imperatively and declaratively
  • [ ] Generated YAML with --dry-run=client -o yaml
  • [ ] Used describe, logs, exec to inspect a pod