Stop Writing Kubernetes YAML by Hand
Stop Writing Kubernetes YAML by Hand
There is no reason to type a Pod manifest from memory. kubectl
already knows the schema, and it will write the file for you:
1kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx-pod.yaml
--dry-run=client means "render the object, do not send it
anywhere." -o yaml prints it instead of describing it. You get a
valid manifest, correct apiVersion, correct nesting, in one
command.
The reason this matters is not typing speed. It is that the parts
people get wrong from memory are exactly the parts the generator gets
right every time: whether it is apps/v1 or batch/v1 or
networking.k8s.io/v1, and how many levels down
spec.template.spec.containers actually sits. Those mistakes produce
error messages that send you to the docs for five minutes. The
generator does not make them.
The Rest of the Generators
kubectl run is the one everyone knows. The others cover most of
what you write day to day:
1# Deployment
2kubectl create deployment web --image=nginx --replicas=3 \
3 --dry-run=client -o yaml
4
5# Service (or expose an existing resource)
6kubectl create service clusterip web --tcp=80:8080 --dry-run=client -o yaml
7kubectl expose deployment web --port=80 --target-port=8080 \
8 --dry-run=client -o yaml
9
10# Jobs and CronJobs
11kubectl create job backup --image=alpine --dry-run=client -o yaml
12kubectl create cronjob backup --image=alpine --schedule="0 3 * * *" \
13 --dry-run=client -o yaml
14
15# ConfigMaps and Secrets
16kubectl create configmap app-config --from-file=./config/ \
17 --dry-run=client -o yaml
18kubectl create secret generic db-creds --from-literal=user=app \
19 --dry-run=client -o yaml
RBAC is where this pays for itself most. Role and binding YAML is
tedious and easy to get subtly wrong:
1kubectl create role reader --verb=get,list,watch --resource=pods \
2 --dry-run=client -o yaml
3kubectl create rolebinding reader-binding --role=reader \
4 --serviceaccount=default:myapp --dry-run=client -o yaml
Because you type this constantly, it is worth an alias:
1export do='--dry-run=client -o yaml'
2kubectl create deployment web --image=nginx $do > deploy.yaml
Client vs Server: The Part Worth Knowing
--dry-run=client renders locally. It never contacts the cluster,
so it validates nothing beyond kubectl's own schema knowledge.
--dry-run=server sends the object to the API server, which runs it
through validation and admission control and returns the object as it
would have been persisted — then discards it. Nothing is created.
That difference is useful twice. It catches things client-side
rendering cannot: an invalid field, a CRD that does not exist, an
admission webhook that would reject your object, a policy controller
that would deny it. And it shows you every field the API server
defaults in, which is how you find out what your manifest actually
becomes rather than what you wrote.
It also makes a good CI gate on manifests you already have:
1kubectl apply -f manifests/ --dry-run=server
Validates the whole directory against the real cluster, changes
nothing. Run it on every pull request that touches manifests.
Clean Up the Output
Generated manifests carry cruft that should not be committed:
creationTimestamp: null, an empty status: {}, empty
resources: {}. Strip them. They are noise in review and some of
them are meaningless in a manifest you hand-maintain.
More importantly, the generator gives you a skeleton, not a
production manifest. It will not add resource requests and limits,
liveness and readiness probes, a securityContext that drops
capabilities and runs non-root, or the labels your tooling expects.
Those are the parts that need thought, and they are exactly the parts
worth spending your attention on instead of remembering YAML nesting.
That is the actual argument. The generator handles the mechanical
part correctly and instantly, which frees you to think about the part
that decides whether the workload is any good.
The Takeaway
Never type a manifest from memory. Generate the skeleton with
--dry-run=client -o yaml, validate against the real cluster with
--dry-run=server, delete the generated cruft, and put your effort
into limits, probes, and security context.