Overview & how to useIkhtisar & cara pakai

This guide turns the 19 practice labs in this repo into an exam-prep handbook. Every topic page covers what the concept is, the exact commands/YAML that pass the lab validators, and the gotchas that cost points. It is written to take you from a comfortable ~85 to a 90+ (and a realistic shot at 100).

Panduan ini mengubah 19 lab latihan di repo ini menjadi buku pegangan persiapan ujian. Setiap halaman topik membahas apa konsepnya, command/YAML persis yang lolos validator lab, dan jebakan yang membuat skor berkurang. Ditulis untuk membawa Anda dari ~85 yang nyaman ke 90+ (dan peluang nyata menuju 100).

The mindsetPola pikir The CKA is performance-based: a grader inspects your cluster's end state. You don't get credit for knowing the answer — only for the resource existing, in the right namespace/context, in a Running state, matching every field. This guide drills that discipline. CKA bersifat performance-based: grader memeriksa end state cluster Anda. Anda tidak dapat nilai karena tahu jawabannya — hanya jika resource benar-benar ada, di namespace/context yang tepat, dalam status Running, dan cocok di setiap field. Panduan ini melatih disiplin tersebut.

How each lab worksCara kerja tiap lab

FileFilePurposeFungsi
LabSetUp.bashProvisions the scenario (creates the broken/partial state).Menyiapkan skenario (membuat kondisi rusak/setengah jadi).
Questions.bashThe task text + a YouTube walkthrough link.Teks soal + link walkthrough YouTube.
SolutionNotes.bashStep-by-step reference solution.Solusi referensi langkah demi langkah.
validate.bashAutomated PASS/FAIL checks — your scoring rubric.Cek otomatis PASS/FAIL — rubrik penilaian Anda.
cleanup.bashTears down resources created by the lab.Menghapus resource yang dibuat lab.
scripts/run-question.sh 5        # setup + show the task
scripts/validate-question.sh 5   # grade your work (PASS/FAIL per check)
scripts/cleanup-question.sh 5    # reset
scripts/validate-question.sh all # grade everything
Read the validatorsBaca validator Open each validate.bash before you start. It tells you the exact field paths and values the grader checks (e.g. HPA's behavior.scaleDown.stabilizationWindowSeconds). On the real exam you won't see them — but training against them builds the "match every field" habit that separates 88 from 100. Buka tiap validate.bash sebelum mulai. File itu memberi tahu path field dan nilai persis yang dicek grader (mis. behavior.scaleDown.stabilizationWindowSeconds pada HPA). Di ujian asli Anda tidak melihatnya — tapi berlatih dengannya membangun kebiasaan "cocokkan setiap field" yang membedakan 88 dari 100.

Exam strategy: how to score 90→100Strategi ujian: cara meraih 90→100

A perfect score is achievable. Most people land 80–97 not because the grader is harsh, but because results-based grading is unforgiving of small deviations and the 2-hour clock punishes perfectionism. Here's how to close the gap.

Skor sempurna memang bisa dicapai. Kebanyakan orang mendapat 80–97 bukan karena grader pelit, tapi karena penilaian berbasis hasil tidak memaafkan penyimpangan kecil dan waktu 2 jam menghukum perfeksionisme. Berikut cara menutup celahnya.

1. The #1 score killer: wrong context / namespace1. Pembunuh skor #1: context / namespace salah

Do this first, every single taskLakukan ini dulu, di setiap soal The exam has multiple clusters. A flawless solution applied to the wrong cluster scores zero. Ujian punya banyak cluster. Solusi sempurna yang diterapkan ke cluster yang salah bernilai nol.
kubectl config use-context <the-context-from-the-task>   # copy-paste it
Same with namespace — creating in default when the task said autoscale fails the check even if the YAML is perfect. Always pass -n <ns> and re-read the task for the namespace. Sama untuk namespace — membuat di default padahal soal meminta autoscale akan gagal walau YAML-nya sempurna. Selalu sertakan -n <ns> dan baca ulang namespace di soal.

2. "Created" ≠ "Running and stable"2. "Dibuat" ≠ "Running dan stabil"

Many checks require the workload to actually be running, not just applied. A Pending, CrashLoopBackOff, or ContainerCreating pod fails even with correct YAML. After every task:

Banyak cek menuntut workload benar-benar berjalan, bukan sekadar di-apply. Pod Pending, CrashLoopBackOff, atau ContainerCreating tetap gagal walau YAML benar. Setelah tiap soal:

kubectl get pods -n <ns>                 # confirm Running, not Pending/Error
kubectl rollout status deploy/<name> -n <ns>
kubectl describe pod <pod> -n <ns>        # if not Running, read Events

3. Partial credit is real — finish what you can3. Nilai parsial itu nyata — selesaikan yang bisa

Each task is weighted and split into sub-checks (look at any validate.bash — Q5 has 7 independent checks). A "mostly right" answer earns a fraction. So:

Tiap soal punya bobot dan terbagi menjadi sub-cek (lihat validate.bash mana pun — Q5 punya 7 cek terpisah). Jawaban "hampir benar" tetap dapat sebagian. Maka:

  • Bank easy points first. Triage: do the quick wins, flag the hard ones (etcd, CNI) for last.Kumpulkan poin mudah dulu. Triase: kerjakan yang cepat, tandai yang sulit (etcd, CNI) untuk terakhir.
  • An unfinished task is the most common reason a strong candidate gets ~85 instead of 100.Soal yang tak selesai adalah alasan paling umum kandidat kuat dapat ~85 alih-alih 100.
  • Even if you can't fully solve a task, get the parts you can — every sub-check counts.Walau tak bisa menyelesaikan penuh, ambil bagian yang bisa — tiap sub-cek dihitung.

4. Persistence for node-level tasks4. Persistensi untuk soal level-node

enable + start, and survive rebootenable + start, dan tahan reboot For service/runtime tasks (cri-dockerd, kubelet, sysctl), the grader may reboot or check enablement. Always systemctl enable --now (not just start), and write sysctl to a file under /etc/sysctl.d/, not just sysctl -w. Untuk soal service/runtime (cri-dockerd, kubelet, sysctl), grader bisa me-reboot atau mengecek enablement. Selalu systemctl enable --now (bukan hanya start), dan tulis sysctl ke file di /etc/sysctl.d/, bukan sekadar sysctl -w.

5. Match every named value exactly5. Cocokkan setiap nilai/nama secara persis

Wrong port name, label selector, tail -f vs -F, an extra field — each costs the fraction tied to that check. Re-read the task and copy names/ports/labels verbatim.

Salah nama port, label selector, tail -f vs -F, field berlebih — masing-masing memakan bagian nilai cek tersebut. Baca ulang soal dan salin nama/port/label apa adanya.

6. Time management6. Manajemen waktu

  • ~15–20 tasks in 120 min → budget ~6 min average. Don't sink 25 min into one 4% task.~15–20 soal dalam 120 mnt → anggarkan rata-rata ~6 mnt. Jangan habiskan 25 mnt untuk satu soal bernilai 4%.
  • Note the question's weight (%) shown in the exam UI; prioritize high-weight tasks.Perhatikan bobot (%) soal di UI ujian; dahulukan soal berbobot tinggi.
  • Use the flag for review feature; come back to hard ones.Pakai fitur flag for review; kembali ke soal sulit nanti.
  • Generate YAML, don't hand-type it (see kubectl power moves).Generate YAML, jangan ketik manual (lihat jurus kubectl).
Allowed resourcesSumber yang diizinkan During the exam you may use kubernetes.io/docs and the GitHub/Blog under those domains. kubectl explain works offline and is faster than browsing — lean on it. Saat ujian Anda boleh memakai kubernetes.io/docs serta GitHub/Blog di domain tersebut. kubectl explain bekerja offline dan lebih cepat dari browsing — andalkan itu.

kubectl power movesJurus kubectl

Speed comes from generating manifests, not writing them by hand. These are the highest-leverage habits.

Kecepatan datang dari meng-generate manifest, bukan mengetiknya manual. Ini kebiasaan dengan dampak terbesar.

Aliases & setup (first 60 seconds)Alias & setup (60 detik pertama)

alias k=kubectl
export do="--dry-run=client -o yaml"   # generate
export now="--force --grace-period=0"  # fast delete
source <(kubectl completion bash)       # tab-completion

Generate skeletons with --dry-run=client -o yamlGenerate kerangka dengan --dry-run=client -o yaml

k create deploy web --image=nginx $do > web.yaml
k create svc nodeport mysvc --tcp=80:80 $do
k run nginx --image=nginx $do
k create cm my-config --from-literal=key=val $do
k create secret tls web-tls --cert=tls.crt --key=tls.key $do
k create priorityclass high --value=999 $do
No generator? Steal an existing objectTak ada generator? Salin object yang ada PVC, NetworkPolicy, HPA-with-behavior, Gateway, etc. have no rich create generator. Either grab an example from the docs, or export a similar live object and edit: PVC, NetworkPolicy, HPA dengan behavior, Gateway, dll. tidak punya generator create yang lengkap. Ambil contoh dari docs, atau ekspor object serupa lalu edit:
k get deploy web -o yaml > web.yaml   # then strip status/metadata noise

Imperative shortcuts that save minutesShortcut imperative yang menghemat menit

k expose deploy web --port=80 --target-port=80 --type=NodePort
k set image deploy/web nginx=nginx:1.27
k set resources deploy/web --requests=cpu=250m,memory=300Mi
k scale deploy/web --replicas=3
k label / k annotate / k taint ...

Edit vs patch vs applyEdit vs patch vs apply

ToolToolUse whenPakai saat
kubectl editFast interactive change to a live object (adding a sidecar, tweaking a field).Perubahan interaktif cepat pada object live (menambah sidecar, tweak satu field).
kubectl patchScripted single-field change, or when the task requires patch.Perubahan satu field via script, atau saat soal mewajibkan patch.
kubectl apply -fDeclarative full manifest you wrote/generated.Manifest lengkap deklaratif yang Anda tulis/generate.

Inspect like the grader doesInspeksi seperti grader

k get hpa apache-server -n autoscale \
  -o jsonpath='{.spec.maxReplicas}'
k get deploy app -o jsonpath='{.spec.template.spec.containers[0].resources}' | python3 -m json.tool

Using docs & kubectl explainPakai docs & kubectl explain

There is no single page with a complete copy-paste YAML for each resource. Kubernetes splits this into a field reference and example pages. Know which to reach for.

Tidak ada satu halaman tunggal berisi YAML lengkap siap-tempel untuk tiap resource. Kubernetes membaginya menjadi referensi field dan halaman contoh. Ketahui kapan memakai yang mana.

The authoritative field referenceReferensi field resmi

  • API Reference — every field of every resource: kubernetes.io/docs/reference/kubernetes-api/. It's a field dictionary, not a ready manifest.API Reference — setiap field tiap resource: kubernetes.io/docs/reference/kubernetes-api/. Ini kamus field, bukan manifest siap pakai.
  • Concepts / Tasks pages — real copy-paste examples (common fields only). Best starting point under exam time.Halaman Concepts / Tasks — contoh copy-paste nyata (hanya field umum). Titik awal terbaik saat ujian.

kubectl explain — the field reference, offline & fasterkubectl explain — referensi field, offline & lebih cepat

k explain pvc                  # top-level fields
k explain pvc.spec             # spec fields
k explain pvc.spec --recursive # entire nested field tree at once
k explain certificate.spec.subject   # drill into a CRD field
WorkflowAlur kerja Generate a skeleton with $do → fill fields you remember → confirm the rest with k explain <res> --recursive. Faster than scrolling the website, and always available. Generate kerangka dengan $do → isi field yang Anda ingat → konfirmasi sisanya dengan k explain <res> --recursive. Lebih cepat dari scroll situs, dan selalu tersedia.

Fast doc navigation habitKebiasaan navigasi docs cepat

Practice finding pages by the docs' own structure so you're quick on exam day:

Latih menemukan halaman lewat struktur docs itu sendiri agar cepat saat ujian:

  • HPA → Tasks → Run Application → HPA Walkthrough
  • PV/PVC → Concepts → Storage → Persistent Volumes
  • Taints → Concepts → Scheduling → Taints and Tolerations
  • Sidecar → Concepts → Workloads → Pods → Sidecar Containers

Sidecars & tail -f vs tail -FSidecar & tail -f vs tail -FLab Q3

A sidecar is a helper container in the same pod as the main app, sharing volumes/network. Classic use: tailing a log file the app writes.

Sidecar adalah container pembantu di pod yang sama dengan app utama, berbagi volume/network. Penggunaan klasik: tail file log yang ditulis aplikasi.

tail -f vs tail -F — which to choosetail -f vs tail -F — pilih yang mana

FlagFlagBehaviorPerilakuExam choicePilihan ujian
-fFollows by descriptor. After log rotation it keeps following the deleted file and misses new logs.Mengikuti via descriptor. Saat log dirotasi, ia tetap mengikuti file lama yang sudah dihapus dan melewatkan log baru.AvoidHindari
-FFollows by name — re-opens the file across rotation/recreation. Survives log rotation.Mengikuti via nama — membuka ulang file saat rotasi/dibuat ulang. Tahan rotasi log.Use thisPakai ini
Rule of thumbPatokan In a long-running sidecar, always use tail -F. Logs rotate in real systems; -f silently goes deaf. Di sidecar yang berjalan lama, selalu pakai tail -F. Log dirotasi di sistem nyata; -f diam-diam jadi tuli.

Two sidecar patternsDua pola sidecar

1. Co-located container (n+1) — what the CKA expects for Deployments1. Container co-located (n+1) — yang diharapkan CKA untuk Deployment

spec:
  template:
    spec:
      volumes:
      - name: logs
        emptyDir: {}
      containers:
      - name: wordpress
        image: wordpress:php8.2-apache
        volumeMounts:
        - {name: logs, mountPath: /var/log}
      - name: sidecar
        image: busybox:stable
        command: ['sh','-c','tail -F /var/log/wordpress.log']
        volumeMounts:
        - {name: logs, mountPath: /var/log}

2. Native sidecar (initContainer + restartPolicy: Always, K8s 1.29+)2. Native sidecar (initContainer + restartPolicy: Always, K8s 1.29+)

Useful for Jobs (a regular sidecar would keep the Job alive forever). For Deployment tasks the exam wants the co-located pattern above.

Berguna untuk Job (sidecar biasa akan membuat Job hidup selamanya). Untuk soal Deployment, ujian ingin pola co-located di atas.

spec:
  template:
    spec:
      initContainers:
      - name: sidecar
        image: busybox:stable
        restartPolicy: Always
        command: ['sh','-c','tail -F /var/log/wordpress.log']

Fastest approach & verificationPendekatan tercepat & verifikasi

k edit deployment wordpress
k rollout status deploy wordpress
POD=$(k get pod -l app=wordpress -o jsonpath='{.items[0].metadata.name}')
k exec $POD -c sidecar -- sh -c 'cat /var/log/wordpress.log | tail'

Resource requests & limitsResource requests & limitsLab Q4 & Q19

The "divide the node fairly across N pods" task. Key insight: only requests matter for scheduling (limits cap usage, requests reserve capacity). Get the math right and stay under limits.

Soal "bagi node merata untuk N pod". Inti: hanya requests yang dipakai scheduler (limits membatasi pemakaian, requests memesan kapasitas). Hitung benar dan tetap di bawah limits.

The formulaRumus

Per-pod requestRequest per pod (allocatable − system overhead) / replicas
Overhead = system pods (kube-proxy, coredns, etc.), typically ~150m CPU / ~250Mi mem on a small node. Always round down so the total stays under allocatable, or pods go Pending. Overhead = pod sistem (kube-proxy, coredns, dll.), biasanya ~150m CPU / ~250Mi mem di node kecil. Selalu bulatkan ke bawah agar total tetap di bawah allocatable, atau pod akan Pending.

Step 1 — find the node's allocatableLangkah 1 — cari allocatable node

k describe node | grep -A6 'Allocatable'
# cpu: 1        -> 1000m
# memory: 1843520Ki -> /1024 ≈ 1800Mi   (Ki→Mi: divide by 1024)

Step 2 — compute & key constraintsLangkah 2 — hitung & batasan kunci

  • Requests must be ≤ limits. If the split exceeds the existing limit, use a value just under the limit.Requests harus ≤ limits. Jika hasil bagi melebihi limit, pakai nilai sedikit di bawah limit.
  • Count containers, not just pods. Q4 has 3 pods × 2 containers (init+main) = 6 — divide by 6 and apply the same values to every container.Hitung container, bukan hanya pod. Q4 punya 3 pod × 2 container (init+main) = 6 — bagi dengan 6 dan pasang nilai sama di tiap container.
# Q19, 1-CPU node, 3 replicas:
#   CPU:  (1000 - 150)/3 ≈ 266m -> use 250m
#   Mem:  (1800 - 250)/3 ≈ 516Mi, but limit is 500Mi -> use 300Mi
# Real exam (3-CPU node): (3000 - 600)/3 = 800m per pod

Step 3 — apply (scale down → edit → scale up)Langkah 3 — terapkan (scale down → edit → scale up)

k scale deploy wordpress --replicas 0
k set resources deploy/wordpress --requests=cpu=250m,memory=300Mi
k scale deploy wordpress --replicas 3
k rollout status deploy wordpress
k get pods -n <ns>
Common mistakeKesalahan umum Setting requests too high → not all replicas fit → some stay Pending → check fails. Setting requests above limits → invalid spec, rejected. Requests terlalu tinggi → tidak semua replica muat → sebagian tetap Pending → cek gagal. Requests di atas limits → spec invalid, ditolak.

kubectl patchLab Q18

When a task says "using only kubectl patch", edit/apply won't be accepted. Know the patch types.

Ketika soal berkata "hanya dengan kubectl patch", edit/apply tidak diterima. Pahami tipe patch.

TypeTipeBehaviorPerilaku
--type=strategicK8s-aware: matches list items by key (e.g. container by name). Safest for containers.Sadar K8s: cocokkan item list via key (mis. container via name). Paling aman untuk container.
--type=mergeRFC 7386 JSON merge: replaces the whole object at that key — must include all sub-fields.JSON merge RFC 7386: mengganti seluruh object di key itu — harus mencantumkan semua sub-field.
--type=jsonRFC 6902 ops (add/replace/remove) with explicit paths — precise array edits.Operasi RFC 6902 (add/replace/remove) dengan path eksplisit — edit array yang presisi.

Update only CPU/memory limits (keep requests, keep replicas)Update hanya limits CPU/memory (pertahankan requests & replicas)

kubectl patch deployment resource-app -n patch-ns --type='strategic' \
  -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx",
       "resources":{"limits":{"cpu":"500m","memory":"512Mi"}}}]}}}}'

Or from a file (clearer for complex patches):

Atau dari file (lebih jelas untuk patch kompleks):

cat > patch.yaml <<'EOF'
spec:
  template:
    spec:
      containers:
      - name: nginx
        resources:
          limits: {cpu: 500m, memory: 512Mi}
EOF
kubectl patch deployment resource-app -n patch-ns --patch-file=patch.yaml

JSON patch for arrays (precise path)JSON patch untuk array (path presisi)

kubectl patch deploy x --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--flag"}]'

PriorityClassLab Q7

PriorityClasses let the scheduler preempt lower-priority pods. The Q7 trick: the new class value must be exactly one less than the highest existing user-defined class.

PriorityClass memungkinkan scheduler melakukan preemption pod berprioritas lebih rendah. Trik Q7: nilai class baru harus tepat satu kurang dari class user-defined tertinggi.

Find the highest existing value, then create just below itCari nilai tertinggi yang ada, lalu buat tepat di bawahnya

k get priorityclass        # note the highest USER-defined value
k create priorityclass high-priority --value=999 --description="high priority"
Watch outHati-hati Ignore the built-in system-* classes when finding "the highest" — they're huge (2e9) and reserved. "One less" means relative to the highest user-defined class. Abaikan class bawaan system-* saat mencari "tertinggi" — nilainya besar (2e9) dan reserved. "Satu kurang" berarti relatif terhadap class user-defined tertinggi.

Attach it to the workload (patch)Pasang ke workload (patch)

kubectl patch deployment busybox-logger -n priority \
  -p '{"spec":{"template":{"spec":{"priorityClassName":"high-priority"}}}}'
k describe deploy busybox-logger -n priority | grep -i "Priority Class"

priorityClassName goes in the pod template spec, not the deployment spec.

priorityClassName diletakkan di pod template spec, bukan di spec deployment.

HorizontalPodAutoscalerLab Q5

HPA scales replicas based on metrics. Requires metrics-server and CPU requests set on target pods (utilization is % of request).

HPA menskalakan replica berdasarkan metrik. Butuh metrics-server dan CPU requests di pod target (utilization adalah % dari request).

Full manifest (incl. the field everyone forgets)Manifest lengkap (termasuk field yang sering dilupakan)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: apache-server, namespace: autoscale}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: apache-deployment
  minReplicas: 1
  maxReplicas: 4
  metrics:
  - type: Resource
    resource:
      name: cpu
      target: {type: Utilization, averageUtilization: 50}
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 30   # <-- the easily-missed check
Where points leakDi mana poin bocor The imperative kubectl autoscale can set min/max/CPU% but not the behavior block. If the task asks for a stabilization window, write YAML. Validator checks .spec.behavior.scaleDown.stabilizationWindowSeconds exactly. Perintah kubectl autoscale bisa set min/max/CPU% tapi tidak blok behavior. Jika soal meminta stabilization window, tulis YAML. Validator mengecek .spec.behavior.scaleDown.stabilizationWindowSeconds persis.
k get hpa apache-server -n autoscale     # TARGETS should show a % (not <unknown>)

If TARGETS shows <unknown>: metrics-server isn't ready or pods lack CPU requests.

Jika TARGETS menampilkan <unknown>: metrics-server belum siap atau pod tak punya CPU requests.

PersistentVolume / PVC bindingLab Q1

A PVC binds to a PV when access mode, size, and storageClass are compatible. Q1 scenario: reuse an existing retained PV after the workload was deleted.

PVC mengikat PV jika access mode, ukuran, dan storageClass cocok. Skenario Q1: pakai ulang PV retained setelah workload dihapus.

Two ways to bind to a specific PVDua cara mengikat ke PV tertentu

apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: mariadb, namespace: mariadb}
spec:
  accessModes: [ReadWriteOnce]
  volumeName: mariadb-pv      # (A) bind by name explicitly — most reliable
  resources:
    requests: {storage: 250Mi}
  storageClassName: ""        # (B) if not using volumeName, must MATCH the PV's class
Binding logicLogika binding
  • Use volumeName → binds to that exact PV; storageClassName can be blank.Pakai volumeName → mengikat ke PV itu persis; storageClassName boleh kosong.
  • No volumeNamestorageClassName must equal the PV's class, plus access mode + size must fit.Tanpa volumeNamestorageClassName harus sama dengan class PV, plus access mode + size harus pas.
  • Check the PV's class: k get pv. Names vary: standard, local-path, etc.Cek class PV: k get pv. Namanya beragam: standard, local-path, dll.

Then point the deployment at the PVC & confirm stableArahkan deployment ke PVC & pastikan stabil

# in deployment: volumes[].persistentVolumeClaim.claimName: mariadb
k apply -f mariadb-deploy.yaml
k get pvc mariadb -n mariadb        # STATUS: Bound
k get pv mariadb-pv                 # STATUS: Bound to mariadb/mariadb
k get pods -n mariadb               # Running & stable
Retained PV reusePakai ulang PV Retained A Retain PV keeps a stale claimRef after its PVC is deleted and won't rebind until it's cleared: PV Retain menyimpan claimRef basi setelah PVC-nya dihapus dan tidak akan rebind sampai dibersihkan:
k patch pv mariadb-pv --type=json -p '[{"op":"remove","path":"/spec/claimRef"}]'

StorageClassLab Q14

StorageClasses define dynamic provisioning. Q14: create one (non-default), then make it the only default.

StorageClass mendefinisikan provisioning dinamis. Q14: buat satu (non-default), lalu jadikan satu-satunya default.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer   # delays binding until a pod uses the PVC

Flip the default — and unset the old onePindahkan default — dan lepas yang lama

k patch storageclass local-storage \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
k patch storageclass local-path \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
k get sc        # exactly ONE should show (default)
"Only default" matters"Hanya satu default" itu penting If two classes are marked default, provisioning is ambiguous and the check fails. Always unset the previous default. Note the spelling: WaitForFirstConsumer (the task text's "WaitForFirstCustomer" is a typo). Jika dua class ditandai default, provisioning ambigu dan cek gagal. Selalu lepas default sebelumnya. Perhatikan ejaan: WaitForFirstConsumer (teks soal "WaitForFirstCustomer" adalah typo).

Services & NodePortLab Q16

NodePort exposes a service on a static port (30000–32767) on every node. Q16 also wants a named container port and a fixed nodePort.

NodePort mengekspos service di port statis (30000–32767) di tiap node. Q16 juga meminta container port bernama dan nodePort tetap.

1. Add a named port to the deployment1. Tambahkan named port ke deployment

kubectl patch deployment nodeport-deployment -n relative -p '{
 "spec":{"template":{"spec":{"containers":[{"name":"nginx",
  "ports":[{"name":"http","containerPort":80,"protocol":"TCP"}]}]}}}}'

2. NodePort Service on 300802. Service NodePort di 30080

apiVersion: v1
kind: Service
metadata: {name: nodeport-service, namespace: relative}
spec:
  type: NodePort
  selector: {app: nodeport-deployment}    # must match pod labels!
  ports:
  - {port: 80, targetPort: 80, protocol: TCP, nodePort: 30080}
k get svc nodeport-service -n relative -o wide
curl http://<nodeIP>:30080
Selector mismatch = no endpointsSelector tak cocok = endpoint kosong The Service selector must match the pod labels exactly, or k get endpoints is empty and curl fails. Service selector harus cocok persis dengan label pod, atau k get endpoints kosong dan curl gagal.

IngressLab Q12

Ingress routes external HTTP(S) to services by host/path. Needs an ingress controller running to actually serve traffic.

Ingress mengarahkan HTTP(S) eksternal ke service via host/path. Butuh ingress controller yang berjalan untuk benar-benar melayani traffic.

Expose the service, then create the IngressEkspos service, lalu buat Ingress

k expose deploy echo -n echo-sound --name echo-service \
  --type NodePort --port 8080 --target-port 8080
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: echo, namespace: echo-sound}
spec:
  rules:
  - host: example.org
    http:
      paths:
      - path: /echo
        pathType: Prefix
        backend:
          service:
            name: echo-service
            port: {number: 8080}
Make curl-by-host workAgar curl-by-host bekerja If verification uses curl http://example.org/echo, map the host to the node IP: echo "<nodeIP> example.org" | sudo tee -a /etc/hosts. Use the right pathType (Prefix vs Exact) and the service port number, not the nodePort. Jika verifikasi pakai curl http://example.org/echo, petakan host ke IP node: echo "<nodeIP> example.org" | sudo tee -a /etc/hosts. Pakai pathType yang tepat (Prefix vs Exact) dan nomor service port, bukan nodePort.

Gateway APILab Q11

The successor to Ingress: roles split across GatewayClass (infra) → Gateway (listeners/TLS) → HTTPRoute (routing). Q11 migrates an existing Ingress to Gateway API, keeping the HTTPS config.

Penerus Ingress: peran terbagi pada GatewayClass (infra) → Gateway (listener/TLS) → HTTPRoute (routing). Q11 memigrasi Ingress yang ada ke Gateway API, mempertahankan konfigurasi HTTPS.

Map Ingress → Gateway APIPemetaan Ingress → Gateway API

IngressGateway API
tls.secretNameGateway listener tls.certificateRefs
rules.hostGateway listener hostname + HTTPRoute hostnames
paths.backend.serviceHTTPRoute rules.backendRefs

Gateway (TLS terminate on 443)Gateway (terminasi TLS di 443)

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: web-gateway}
spec:
  gatewayClassName: nginx-class
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: gateway.web.k8s.local
    tls:
      mode: Terminate
      certificateRefs:
      - {kind: Secret, name: web-tls}

HTTPRoute (routing rules)HTTPRoute (aturan routing)

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: web-route}
spec:
  parentRefs: [{name: web-gateway}]
  hostnames: ["gateway.web.k8s.local"]
  rules:
  - matches: [{path: {type: PathPrefix, value: /}}]
    backendRefs: [{name: web-service, port: 80}]

Inspect the source first: k describe ingress web and k describe secret web-tls to copy host/secret/backend exactly.

Inspeksi sumber dulu: k describe ingress web dan k describe secret web-tls untuk menyalin host/secret/backend persis.

NetworkPolicyLab Q13

NetworkPolicies are deny-by-default once a pod is selected: a pod with any policy only allows what's explicitly permitted. Q13 asks you to pick the least-permissive policy that still allows frontend→backend.

NetworkPolicy bersifat deny-by-default begitu sebuah pod dipilih: pod dengan policy hanya mengizinkan yang eksplisit diperbolehkan. Q13 meminta Anda memilih policy paling tidak permisif yang tetap mengizinkan frontend→backend.

How to choose "least permissive"Cara memilih "paling tidak permisif"

  • Reject policies that allow all ingress ({} empty from), or allow extra IP blocks/namespaces beyond what's needed.Tolak policy yang mengizinkan semua ingress ({} from kosong), atau mengizinkan IP block/namespace tambahan di luar kebutuhan.
  • Prefer the one scoping to the specific source via podSelector + namespaceSelector matching the real labels.Pilih yang menyempit ke sumber spesifik via podSelector + namespaceSelector yang cocok dengan label sebenarnya.
cat /root/network-policies/*.yaml          # compare candidates
k get pods -n frontend --show-labels        # confirm the label the policy targets
k apply -f /root/network-policies/network-policy-3.yaml   # least-permissive match

Anatomy to read carefullyAnatomi yang harus dibaca cermat

spec:
  podSelector: {matchLabels: {app: backend}}   # WHO this policy protects
  policyTypes: [Ingress]
  ingress:
  - from:
    - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: frontend}}
      podSelector: {matchLabels: {app: frontend}}   # WHO is allowed in
from[] item semanticsSemantik item from[] namespaceSelector + podSelector in the same list item = AND (frontend ns AND frontend pods). As separate items = OR (much more permissive). This distinction is exactly what "least permissive" tests. namespaceSelector + podSelector dalam item list yang sama = AND (ns frontend DAN pod frontend). Sebagai item terpisah = OR (jauh lebih permisif). Perbedaan inilah yang diuji "least permissive".

Verify connectivityVerifikasi konektivitas

k exec -n frontend <frontend-pod> -- curl --connect-timeout 5 \
  backend-service.backend.svc.cluster.local:80

CNI install (Calico/Flannel)Instalasi CNI (Calico/Flannel)Lab Q8

Q8: install a CNI that provides pod networking and NetworkPolicy enforcement. Flannel alone does not enforce NetworkPolicy — choose Calico.

Q8: pasang CNI yang menyediakan jaringan pod dan enforcement NetworkPolicy. Flannel saja tidak meng-enforce NetworkPolicy — pilih Calico.

Calico via the operatorCalico via operator

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/tigera-operator.yaml

# CIDR must match the cluster's podSubnet:
kubectl -n kube-system get cm kubeadm-config -o yaml | grep podSubnet
wget https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/custom-resources.yaml
# edit the ipPools CIDR to match, then:
kubectl apply -f custom-resources.yaml

Verify it's healthyVerifikasi sehat

k get tigerastatus              # all True
k get pods -n calico-system     # Running
k get pods -n kube-system | grep coredns   # CoreDNS leaves Pending once CNI is up
Why CoreDNS was PendingMengapa CoreDNS Pending Without a CNI, CoreDNS can't get a pod IP and stays Pending/ContainerCreating. Installing the CNI with the correct podCIDR is what unblocks it. Mismatched CIDR = networking silently broken. Tanpa CNI, CoreDNS tak bisa dapat IP pod dan tetap Pending/ContainerCreating. Memasang CNI dengan podCIDR yang benar adalah yang membukanya. CIDR tidak cocok = jaringan rusak diam-diam.
Flannel vs CalicoFlannel vs Calico Requirement #2 is "support network policy enforcement." Flannel doesn't; Calico does. Pick Calico to satisfy all three requirements. Syarat #2: "mendukung enforcement network policy." Flannel tidak; Calico iya. Pilih Calico agar semua tiga syarat terpenuhi.

TLS config (nginx ConfigMap)Konfigurasi TLS (ConfigMap nginx)Lab Q17

Q17: restrict an nginx deployment to TLSv1.3 by editing its ConfigMap, then prove it with curl.

Q17: batasi deployment nginx ke TLSv1.3 dengan mengedit ConfigMap-nya, lalu buktikan dengan curl.

1. Edit the ConfigMap — remove TLSv1.21. Edit ConfigMap — hapus TLSv1.2

k edit cm -n nginx-static nginx-config
# in ssl_protocols: leave only TLSv1.3 (remove TLSv1.2)
Config changes need a restartPerubahan config butuh restart Pods load ConfigMaps at start. After editing, the old setting still serves until you restart: k rollout restart -n nginx-static deployment nginx-static. Forgetting this is why "v1.2 still works" after the edit. Pod memuat ConfigMap saat start. Setelah edit, setting lama masih melayani sampai Anda restart: k rollout restart -n nginx-static deployment nginx-static. Lupa ini sebabnya "v1.2 masih jalan" pasca edit.

2. Map the service IP in /etc/hosts2. Petakan IP service di /etc/hosts

k get svc -n nginx-static                       # grab the ClusterIP
echo '<svc-ip> ckaquestion.k8s.local' | sudo tee -a /etc/hosts

3. Verify3. Verifikasi

curl -vk --tls-max 1.2 https://ckaquestion.k8s.local   # should FAIL
curl -vk --tlsv1.3     https://ckaquestion.k8s.local   # should WORK

Taints & TolerationsLab Q10

A taint repels pods from a node; a matching toleration lets a pod ignore it. Taint ≠ scheduling guarantee — a toleration allows but doesn't force placement (use nodeSelector/affinity for that).

Taint menolak pod dari sebuah node; toleration yang cocok membuat pod mengabaikannya. Taint ≠ jaminan penjadwalan — toleration mengizinkan tapi tidak memaksa penempatan (pakai nodeSelector/affinity untuk itu).

Taint the nodeTaint node

kubectl taint nodes node01 PERMISSION=granted:NoSchedule
# effects: NoSchedule | PreferNoSchedule | NoExecute

Tolerate it on the podToleransikan di pod

spec:
  containers:
  - {name: nginx, image: nginx}
  tolerations:
  - key: PERMISSION
    operator: Equal
    value: granted
    effect: NoSchedule
Match exactlyCocokkan persis The toleration's key/value/effect must match the taint. operator: Exists matches any value; operator: Equal requires value too. A typo here leaves the pod Pending. key/value/effect di toleration harus cocok dengan taint. operator: Exists cocok dengan semua nilai; operator: Equal butuh value juga. Typo di sini membuat pod tetap Pending.
k describe pod <pod> | grep -A3 Events   # "node(s) had untolerated taint"

Helm & ArgoCDLab Q2

What is Helm?Apa itu Helm?

Helm is the Kubernetes package manager. A chart is a templated bundle of manifests; values customize it. helm install renders the chart and applies it; helm template renders it to YAML without touching the cluster — ideal for inspection/GitOps.

Helm adalah package manager Kubernetes. Sebuah chart adalah bundel manifest bertemplate; values menyesuaikannya. helm install me-render chart dan menerapkannya; helm template me-render ke YAML tanpa menyentuh cluster — ideal untuk inspeksi/GitOps.

Core commandsPerintah inti

helm repo add argocd https://argoproj.github.io/argo-helm
helm repo update
helm search repo argocd/argo-cd --versions      # list chart versions
helm show values argocd/argo-cd --version 7.7.3 # see configurable values

Generate a template to verify chart modificationsGenerate template untuk verifikasi modifikasi chart

helm template argocd argocd/argo-cd --version 7.7.3 \
  --set crds.install=false --namespace argocd > /root/argo-helm.yaml
cat /root/argo-helm.yaml      # confirm no CRDs rendered
The --skip-crds trapJebakan --skip-crds --skip-crds does not reliably stop CRD install for every chart. argo-cd ships CRDs as regular templates gated by a chart value. Find it and disable it: --skip-crds tidak selalu mencegah instalasi CRD untuk semua chart. argo-cd mengirim CRD sebagai template biasa yang dikontrol oleh sebuah chart value. Temukan dan nonaktifkan:
helm show values argocd/argo-cd --version 7.7.3 | grep -i crd -C 4
# then: --set crds.install=false
install vs templateinstall vs template Use helm template when the task says "generate/save the manifest" — it renders locally, doesn't deploy, and is the way to verify what your value overrides actually produce before applying. Pakai helm template bila soal berkata "generate/simpan manifest" — ia render lokal, tidak mendeploy, dan cara untuk memverifikasi hasil override values sebelum apply.

CRDs & kubectl explainLab Q6

CustomResourceDefinitions extend the API with new kinds (e.g. cert-manager's Certificate). Q6: list them and extract a field's docs.

CustomResourceDefinitions memperluas API dengan kind baru (mis. Certificate milik cert-manager). Q6: daftarkan dan ekstrak dokumentasi sebuah field.

List CRDsDaftarkan CRD

kubectl get crd | grep cert-manager | tee /root/resources.yaml

Extract docs for a specific fieldEkstrak dokumentasi field tertentu

kubectl explain certificate.spec.subject | tee /root/subject.yaml
kubectl explain certificate.spec --recursive   # whole spec tree
Why this mattersKenapa penting kubectl explain works on CRDs too once they're installed — your offline, authoritative field reference for any resource. The task accepts any output format kubectl supports, so explain output is fine to save directly. kubectl explain juga bekerja pada CRD setelah terpasang — referensi field offline & otoritatif untuk resource apa saja. Soal menerima format output apa pun yang didukung kubectl, jadi output explain aman disimpan langsung.

cri-dockerd / container runtimecri-dockerd / container runtimeLab Q9

Node-level task: install a runtime shim package, enable the service, and set kernel networking sysctls. This is where persistence rules apply.

Soal level-node: pasang paket shim runtime, enable service, dan set sysctl jaringan kernel. Di sinilah aturan persistensi berlaku.

Install & enableInstall & enable

sudo dpkg -i cri-dockerd.deb
sudo systemctl enable --now cri-docker.service   # enable = survives reboot
sudo systemctl status cri-docker.service

Set sysctls persistently (not just runtime)Set sysctl secara persisten (bukan hanya runtime)

sudo tee /etc/sysctl.d/kube.conf >/dev/null <<'EOF'
net.bridge.bridge-nf-call-iptables=1
net.ipv6.conf.all.forwarding=1
net.ipv4.ip_forward=1
net.netfilter.nf_conntrack_max=131072
EOF
sudo sysctl --system     # apply now
Two persistence trapsDua jebakan persistensi
  • systemctl start without enable → lost on reboot → check fails.systemctl start tanpa enable → hilang saat reboot → cek gagal.
  • sysctl -w only sets runtime → lost on reboot. Write to /etc/sysctl.d/ and run sysctl --system.sysctl -w hanya set runtime → hilang saat reboot. Tulis ke /etc/sysctl.d/ dan jalankan sysctl --system.

Control-plane troubleshooting (etcd)Troubleshooting control-plane (etcd)Lab Q15

Q15: after a "migration", kube-apiserver points at etcd's peer port 2380 instead of the client port 2379, so the API server won't start. Classic static-pod debugging.

Q15: setelah "migrasi", kube-apiserver menunjuk port peer 2380 etcd alih-alih port client 2379, sehingga API server gagal start. Debugging static-pod klasik.

etcd portsPort etcd 2379 = client API (what kube-apiserver talks to). 2380 = peer (etcd-to-etcd only). The fix is 2380 → 2379. 2379 = client API (yang dipakai kube-apiserver). 2380 = peer (etcd-ke-etcd saja). Perbaikannya 2380 → 2379.

DiagnoseDiagnosa

sudo crictl ps -a | grep kube-apiserver        # crashing/restarting
journalctl -u kubelet | tail
sudo grep etcd-servers /etc/kubernetes/manifests/kube-apiserver.yaml

Fix the static pod manifestPerbaiki manifest static pod

sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
#   --etcd-servers=https://127.0.0.1:2379   (was :2380)
# Save. kubelet auto-restarts the static pod — wait ~30–60s.

Confirm recoveryKonfirmasi pulih

k get nodes                                    # API server responding
k get pods -n kube-system | grep -E 'apiserver|etcd|scheduler|controller'
Static pod workflowAlur kerja static pod Files in /etc/kubernetes/manifests/ are watched by kubelet — edit + save and the pod restarts automatically (no kubectl apply). Back up before editing: sudo cp kube-apiserver.yaml /root/kas.bak. If kubectl is down, fix the manifest directly on the node. File di /etc/kubernetes/manifests/ diawasi kubelet — edit + save dan static pod otomatis restart (tidak perlu kubectl apply). Backup dulu: sudo cp kube-apiserver.yaml /root/kas.bak. Jika kubectl mati, perbaiki manifest langsung di node.

Cheat sheetCheat sheet

Generate fastGenerate cepat

export do="--dry-run=client -o yaml"
k create deploy web --image=nginx $do
k run pod --image=nginx $do
k expose deploy web --port=80 --type=NodePort
k create svc nodeport s --tcp=80:80 $do
k create cm c --from-literal=k=v $do
k create secret tls web-tls --cert=tls.crt --key=tls.key $do
k create priorityclass p --value=999 $do
k set resources deploy/web --requests=cpu=250m,memory=300Mi
k autoscale deploy web --min=1 --max=4 --cpu-percent=50

Inspect / verifyInspeksi / verifikasi

k get <res> -n <ns> -o wide
k get <res> -o jsonpath='{.spec.<field>}'
k describe <res> <name> -n <ns>        # read Events when not Running
k explain <res>.spec --recursive
k get endpoints <svc> -n <ns>          # empty = selector mismatch
k rollout status deploy/<name> -n <ns>

Patch

k patch deploy d -p '{"spec":{"template":{"spec":{"priorityClassName":"high"}}}}'
k patch sc local-storage -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
k patch pv v --type=json -p '[{"op":"remove","path":"/spec/claimRef"}]'

Node / runtimeNode / runtime

k describe node | grep -A6 Allocatable
k taint nodes node01 key=val:NoSchedule
sudo systemctl enable --now <svc>
sudo crictl ps -a ; sudo crictl logs <id>
ls /etc/kubernetes/manifests/         # static pods

Gotchas checklist (the point-leakers)Checklist jebakan (pembocor poin)

Before submitting each task, confirm:Sebelum submit tiap soal, pastikan:
CheckCekWhyKenapa
Switched kubectl config use-context?Sudah kubectl config use-context?Right answer, wrong cluster = 0 points.Jawaban benar, cluster salah = 0 poin.
Correct -n <namespace>?Sudah -n <namespace> yang tepat?Default-namespace resources fail the check.Resource di namespace default gagal cek.
Pods actually Running (not Pending/CrashLoop)?Pod benar-benar Running (bukan Pending/CrashLoop)?"Stable/running" checks are common.Cek "stabil/running" sangat umum.
Names/ports/labels match the task verbatim?Nama/port/label cocok soal persis?Grader compares exact values.Grader membandingkan nilai persis.
Service selector matches pod labels?Service selector cocok label pod?No endpoints → curl fails (Q16).Tanpa endpoint → curl gagal (Q16).
tail -F notbukan -f?Survives log rotation (Q3).Tahan rotasi log (Q3).
HPA behavior.scaleDown.stabilizationWindowSeconds set?HPA behavior.scaleDown.stabilizationWindowSeconds sudah di-set?Imperative cmd can't set it (Q5).Perintah imperative tak bisa set ini (Q5).
Requests ≤ limits, total under allocatable?Requests ≤ limits, total di bawah allocatable?Else Pending or rejected (Q4/Q19).Kalau tidak: Pending atau ditolak (Q4/Q19).
Only ONE default StorageClass?Hanya SATU default StorageClass?Unset the old default (Q14).Lepas default lama (Q14).
ConfigMap change followed by rollout restart?Perubahan ConfigMap diikuti rollout restart?Pods cache config at start (Q17).Pod cache config saat start (Q17).
systemctl enable (not just start) + sysctl in /etc/sysctl.d/?systemctl enable (bukan hanya start) + sysctl di /etc/sysctl.d/?Must survive reboot (Q9).Harus tahan reboot (Q9).
CNI = Calico when NetworkPolicy required; podCIDR matches?CNI = Calico jika NetworkPolicy diminta; podCIDR cocok?Flannel won't enforce policy (Q8).Flannel tidak meng-enforce policy (Q8).
NetworkPolicy from[]: AND vs OR semantics correct?Semantik from[] NetworkPolicy: AND vs OR sudah benar?Separate items = far too permissive (Q13).Item terpisah = jauh terlalu permisif (Q13).
etcd client port 2379 (not peer 2380)?Port client etcd 2379 (bukan peer 2380)?API server won't start (Q15).API server gagal start (Q15).
Helm CRDs disabled via chart value, not --skip-crds?CRD Helm dimatikan via chart value, bukan --skip-crds?--skip-crds is unreliable (Q2).--skip-crds tidak andal (Q2).
Retained PV's stale claimRef cleared before rebind?claimRef basi pada PV Retained dibersihkan sebelum rebind?Won't bind otherwise (Q1).Tidak akan bind tanpa itu (Q1).
Final habitKebiasaan akhir After every task, run a kubectl get/describe that mirrors what the grader checks. The validate.bash scripts in this repo are your training rubric — internalize "exact match + Running state + right context" and 90+ becomes routine. Setelah tiap soal, jalankan kubectl get/describe yang mencerminkan apa yang dicek grader. Skrip validate.bash di repo ini adalah rubrik latihan Anda — internalisasi "cocok persis + status Running + context tepat" dan 90+ jadi rutin.