Two minutes of setup, then never write YAML from scratch
The problem. Typing a Deployment's YAML by hand takes five minutes and one typo loses the task. Generators (kubectl create ... $do) write it in seconds; a two-minute setup at the start of the exam pays back on every task.
What you need to know already: the speed kit and $do (15.3), imperative vs declarative (15.40), -o jsonpath / custom-columns (15.38), shell aliases and export, nano (1.5).
The shell
On an exam task host k and completion exist already. Check, and add what is missing:
candidate@cka3962:~$ alias k
alias k='kubectl'
candidate@cka3962:~$ echo "$do"
candidate@cka3962:~$ export do="--dry-run=client -o yaml"
candidate@cka3962:~$ export now="--force --grace-period=0"
For your own machines (and killer.sh, KillerCoda) the full kit, at the end of ~/.bashrc:
alias k=kubectl
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
export do="--dry-run=client -o yaml"
export now="--force --grace-period=0"
- The
completeline matters: without it Tab completes nothing afterk, because completion is registered for the wordkubectl, not for your alias. $dorenders the object client-side and prints it; nothing is created.$nowdeletes a pod without waiting its grace period - for pods you are about to replace, never for anything you care about.- Exported variables do not follow you through ssh. On the exam, set
doagain on each task host.
$ k delete pod web-0 $now
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "web-0" force deleted
Generate, then edit
The rule from the plan: never write a manifest from nothing. Every object with a generator starts as generated YAML:
# $do renders each object into a file; nothing is created in the cluster
$ k run web --image=nginx:1.27 --port=80 $do > web.yaml
$ k create deploy api --image=nginx:1.27 --replicas=3 $do > api.yaml
$ k create cronjob report --image=busybox:1.36 --schedule='*/10 * * * *' $do -- sh -c 'date' > cj.yaml
$ k create job once --image=busybox:1.36 $do -- sh -c 'echo hi' > job.yaml
$ k create cm app-config --from-literal=MODE=fast $do > cm.yaml
$ k create secret generic db --from-literal=password=s3cr3t $do > secret.yaml
$ k create role reader --verb=get,list --resource=pods $do > role.yaml
$ k create ingress web --class=nginx --rule="shop.lab/api*=api:80" $do > ing.yaml
$ k create pdb api --selector=app=api --min-available=2 $do > pdb.yaml
$ k create quota team --hard=pods=10,requests.cpu=2 $do > quota.yaml
k expose is the one generator that reads the cluster: it copies the selector from the object you name, so that object has to exist first, $do or not:
$ k expose deploy api --port=80 --target-port=8080 --type=NodePort $do > svc.yaml
Error from server (NotFound): deployments.apps "api" not found
$ k apply -f api.yaml
deployment.apps/api created
$ k expose deploy api --port=80 --target-port=8080 --type=NodePort $do > svc.yaml
Many tasks need no file at all - the generator IS the answer: k create role, k create rolebinding, k expose, k set image, k set resources, k scale, k autoscale, k label, k taint, k create job --from=cronjob/X. Use them straight, no YAML.
Objects without a generator - copy from the docs page instead: PersistentVolume, PersistentVolumeClaim, StorageClass, NetworkPolicy, DaemonSet (generate a Deployment and change the kind, delete replicas/strategy), HTTPRoute/Gateway, anything with affinity or probes (generate the Deployment, then add the block).
$ k create deploy node-agent --image=busybox:1.36 $do > ds.yaml
$ sed -i 's/^kind: Deployment/kind: DaemonSet/; /replicas:/d; /strategy: {}/d' ds.yaml
Changing what exists
| to change | use |
|---|---|
| image | k set image deploy/X CONTAINER=IMAGE |
| requests / limits | k set resources deploy/X --requests=cpu=100m --limits=memory=256Mi |
| env | k set env deploy/X KEY=VALUE |
| replicas | k scale deploy/X --replicas=5 |
| one field, anywhere | k patch (strategic merge) or --type=json with an op |
| several fields | k edit - or k get -o yaml > f.yaml, edit, k apply -f |
| a field the API will not change (pod spec) | k replace --force -f f.yaml (deletes and recreates) |
# an illustration: exam-style task state (the mock tasks build it)
k edit pod web
error: pods "web" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-2190451742.yaml"
error: Edit cancelled, no valid changes were saved.
k replace --force -f /tmp/kubectl-edit-2190451742.yaml
pod "web" deleted
pod/web replaced
Most pod fields are immutable after creation; edit refuses but saves your copy. replace --force with that file is the one-liner way out. On a Deployment you never need it - the template can change.
Editors
The exam hosts have vim and nano. YAML breaks on tabs, so either editor needs two settings:
# an illustration: exam-style task state (the mock tasks build it)
cat ~/.vimrc
set expandtab
set tabstop=2
set shiftwidth=2
cat ~/.nanorc
set tabsize 2
set tabstospaces
The vim you need, and nothing more: i insert, Esc, :wq save and quit, :q! quit without saving, dd delete a line, yy/p copy/paste a line, u undo, /text search, :set paste before pasting a block (stops auto-indent from staircasing it), V + > / < to indent a selected block, :%s/old/new/g.
(simulator) Only nano is simulated here; kubectl edit opens nano. Your vim muscle memory has to come from a real box.
For small, exact changes sed -i beats any editor - especially in manifests on a node:
# an illustration: exam-style task state (the mock tasks build it)
sudo sed -i 's#--kubeconfig=/etc/kubernetes/scheduler.config#--kubeconfig=/etc/kubernetes/scheduler.conf#' /etc/kubernetes/manifests/kube-scheduler.yaml
kubectl explain
Field names without leaving the terminal:
$ k explain pod.spec.containers.livenessProbe --recursive | head -12
KIND: Pod
VERSION: v1
FIELD: livenessProbe <Probe>
DESCRIPTION:
Periodic probe of container liveness.
FIELDS:
exec <ExecAction>
command <[]string>
failureThreshold <integer>
grpc <GRPCAction>
--recursive prints the whole subtree - the shape of nodeAffinity or a CronJob's jobTemplate in one screen. Without it you get one level with descriptions and defaults (k explain job.spec.backoffLimit says 6).
Answers into files
Some tasks want a value written to a file. The output shapers:
# on a task's cluster (the mock tasks build shop)
$ k get pods -n shop -o jsonpath='{.items[*].metadata.name}'
api-5d8f9c7b6-2kq9x web-7c9d8f6b5-8xz2m
$ k get pods -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,NODE:.spec.nodeName --sort-by=.spec.nodeName
$ k top pod -n kube-system --sort-by=memory --no-headers | head -1 | awk '{print $1}'
$ k get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\n"}{end}'
$ k config get-contexts -o name
- jsonpath prints no trailing newline; that is fine for a file a grader trims, but
echo >> fileif the task says "one per line". --sort-bytakes a JSONPath (.metadata.creationTimestamp), not a column name.- After writing any answer file:
catit. The file is what is graded, not the command.
The speed habits, in one list
- The task's context/ssh line first. Always.
- Namespace on every command (
-n NS), ork config set-context --current --namespace=NSfor a task with many commands - and set it back after. - Generator or
$do, never a blank file. - One verify command before moving on:
get,describe,auth can-i,exec ... wget,cat file.