Skip to content

Manifests and kubectl

Scope: the grammar of every Kubernetes object — four fields that never change — and kubectl, the one tool you drive it all with.

Every object has the same four fields

A manifest is a YAML document describing one object. Whatever the object, the top level is:

apiVersion: apps/v1      # which API, which version
kind: Deployment         # what kind of object this is
metadata:                # identity: name, labels
  name: hello-web
spec:                    # the desired state — the part that differs per kind
  ...
  • apiVersion + kind together say what this is: v1 + Pod, apps/v1 + Deployment, v1 + Service. The API server rejects a pairing it doesn't know.
  • metadata is identity: a name (unique among objects of this kind in the namespace), plus labels — key/value tags (app: hello-web) that other objects use to select this one. Labels look like decoration and are actually the cluster's connective tissue: Services find pods by label, Deployments count pods by label.
  • spec is the desired state proper. Everything past this point is kind-specific, and it's the only part you really write.

Namespaces divide one cluster into named compartments (default is where things land when you don't say). Objects are usually namespaced; names only need to be unique within one.

A real manifest, generated by kubectl itself

You don't have to write YAML from memory: kubectl will draft it. This is actual output, produced while writing this guide (client-side, no cluster involved):

$ kubectl create deployment hello-web --image=nginx:alpine \
      --replicas=2 --dry-run=client -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: hello-web
  name: hello-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: hello-web
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
        resources: {}
status: {}

Two teaching points hide in it. The template section is a pod description embedded in the Deployment — the thing being replicated. And the creationTimestamp: null / status: {} lines are dry-run printing artifacts: real objects get real timestamps, and status is written by the cluster, never by you. Delete those two lines when you save a generated draft as your manifest.

The working verbs

(Documented — these need a cluster.) Life with a manifest is a short loop:

kubectl apply -f deployment.yaml    # create or update to match the file
kubectl get deployments             # what exists, and is it as desired?
kubectl describe deployment hello-web   # the object + recent events
kubectl delete -f deployment.yaml   # remove what the file declares

apply is the heart of the declarative style: the file is the truth, and applying it again after an edit converges the cluster to the file. The opposite style — imperative commands like kubectl scale or kubectl set image — changes the live object without changing any file. Both work; mixing them carelessly doesn't, because the next apply will quietly undo whatever the imperative command did that the file doesn't know about. Pick files as the source of truth; use imperative commands for experiments and emergencies.

Check

On any machine with kubectl installed — no cluster required — the generator above should work as shown; it's how the draft for this guide's example Deployment started. To validate a hand-written file's shape without a cluster, a schema checker such as kubeconform does it offline; every manifest shipped in examples/ passed exactly that check (see the README's note).