Skip to content

Catalog

All 84 stages in one place — filter by difficulty or verification status, or search by stage, description or the concept it teaches. Click any row for the stage's note and the diff the reference solution adds at that stage.

  • 84Stages
  • 84✓ Verified
  • 20Hard
  • 3Courses
What "verified" means: ✓ Verified — the stage has a reference solution checked in that passed stages 1..N cumulatively against a live kind cluster in CI. CI re-verifies every snapshot on each push, so a drifted assertion fails the build rather than the reader.
✓ Stage Level What it teaches
Build your own kubectl 30/30 verified
▤ Primer Background kubectl is not special.▸
✓ 1. Find the cluster Easy Before a client can do anything it has to answer "which server, as whom?", and the answer is never hardcoded.▸
✓ 2. Choose a context Easy A context is a named triple: a cluster, a user, and optionally a namespace.▸
✓ 3. Ask the server what it is Medium This is the first request that leaves your machine, and it deliberately goes through the discovery client rather than a typed one.▸
✓ 4. List the pods Easy The first real read. A typed clientset gives you a method per resource — CoreV1().Pods(ns).List(...) — which returns *corev1.PodList with real Go fields.▸
✓ 5. Choose a namespace Medium Namespace resolution has three steps and the middle one is the trap: --namespace, then whatever the current context carries, then "default".▸
✓ 6. Print a table Medium A table is a user interface, and the property that makes it one is that the columns line up when the names do not.▸
✓ 7. Humanise the age Medium The API stores a creation timestamp; a person reading a hundred rows wants "2d".▸
✓ 8. Print the object as JSON Medium -o json is not "your table, as JSON". It is the API's own object, verbatim — apiVersion, kind, metadata, spec, status, managedFields and all.▸
✓ 9. Print the object as YAML Medium Same object, different serializer — and the way you get there matters.▸
✓ 10. Look everywhere at once Medium -A is not a client-side filter and it is not a loop over namespaces.▸
✓ 11. Ask for one object Easy Get and List are different endpoints with different failure modes, and the difference is the lesson. A List of an empty namespace succeeds and returns nothing.▸
✓ 12. Ask what the server serves Medium This is the request that makes a client general. Nothing in your program knows what a Pod is: the server reports its groups, the versions in each, and the resources in those —…▸
✓ 13. Resolve any spelling Hard po, pod, pods, Pod, pods.v1. — five spellings of one thing, and the API accepts exactly one of them in a URL.▸
✓ 14. Get anything at all Hard This is the largest single step in the course: your program stops being a pod client. The dynamic client speaks in GVRs and unstructured.▸
✓ 15. Wider columns Easy -o wide adds the columns that only make sense for a particular resource: a Pod's node and IP, a Service's cluster IP and ports.▸
✓ 16. Filter by label Medium Labels are the identifying metadata everything in Kubernetes coordinates through: a Service finds its pods by selector, a Deployment owns its ReplicaSets by selector.▸
✓ 17. Filter by field Medium Field selectors look like label selectors and are a different axis.▸
✓ 18. A stable order Easy The API returns items in whatever order the storage layer hands them over.▸
✓ 19. Keep watching Hard A watch is a long-lived HTTP response the server keeps appending to, one JSON event per change: ADDED, MODIFIED, DELETED, BOOKMARK, ERROR.▸
✓ 20. Delete an object Easy The first write, and the lesson is that delete is asynchronous. The call returns when the server has accepted the request and marked the object — not when it is gone.▸
✓ 21. Create from a manifest Hard A manifest is self-describing: it carries apiVersion and kind, which is why kubectl create -f needs no flag saying what is in the file.▸
✓ 22. Apply, not create Hard Apply is not "create if missing, update if present". It is a different claim: these fields should look like this, and I am the one saying so.▸
✓ 23. Two managers, one field Hard When your apply sets a field another manager already owns, the server answers 409 Conflict and changes nothing.▸
✓ 24. Change one field Medium Patch is the smallest write verb: the body names only what changes, so nothing has to be read first and there is no resourceVersion to conflict on.▸
✓ 25. Scale through a subresource Medium /scale is a subresource: a separate endpoint on the same object, with its own tiny schema.▸
✓ 26. Describe one object Medium Describe answers a different question from get, and the difference explains the format.▸
✓ 27. Attach its events Hard Events are the cluster's explanation of why, and the surprise is that they are not part of the object they describe.▸
✓ 28. Stream a container's logs Hard There is no Log object to Get. pods/log is a subresource that returns a stream of bytes, and with --follow it never ends — which is why its client-go signature could not have…▸
✓ 29. Run a command inside Hard Exec needs several independent byte streams at once — stdin, stdout, stderr and a terminal-resize channel — multiplexed over a single connection. Ordinary HTTP/1.▸
✓ 30. Forward a local port Hard Port-forward is the same upgrade as exec put to a different use: a local listener whose connections are tunnelled, one stream pair per forwarded port, through the apiserver to the…▸
Build your own controller 28/28 verified
▤ Primer Background Kubernetes is not a system that does what you tell it.▸
✓ 1. Teach the cluster a new kind Medium A Kubernetes API is not a fixed list. A CustomResourceDefinition is an ordinary object that, once stored, makes the API server serve a whole new resource: /apis/byok8s.▸
✓ 2. Give the kind a schema Medium The schema is not documentation. It is the contract the API server enforces on your behalf: a Website with replicas: "two" is refused before it is ever stored, and a Website with…▸
✓ 3. List, then watch from there Medium A watch is not a feed of everything that ever happened. It is a stream of changes after a point in time, and that point is a resourceVersion.▸
✓ 4. Let an informer keep the cache Medium The list-then-watch you just wrote is what an informer does for you — and then it does the part you did not write: it keeps everything it has seen in a local store, so the…▸
✓ 5. Read from the cache Easy Once the informer holds the objects, asking the API server for them again is pure waste.▸
✓ 6. Queue keys, not objects Medium Handlers must not do work. They run on the informer's thread, and anything slow in one blocks every other event.▸
✓ 7. Reconcile, don't react Medium A reconcile function is handed a key and answers one question: given how the world is right now, what should I do? It does not ask what happened.▸
✓ 8. Create the Deployment Medium Here the controller stops observing and starts acting. A Website is a wish; a Deployment is the thing that grants it.▸
✓ 9. Own what you create Medium The controller never writes deletion code for its children. It writes down who owns what, and the API server's garbage collector does the rest: when the Website goes, everything…▸
✓ 10. Adopt what is already there Medium Sooner or later the controller meets a Deployment with the right name that belongs to nobody: someone created it by hand, or an earlier version of this controller did not set…▸
✓ 11. Put back what drifts Medium Someone runs kubectl scale. The Deployment now says 0 and the Website still says 2.▸
✓ 12. Roll out a spec change Easy Editing the Website changes the wish, and the same repair loop that fixed a hand-scaled Deployment now rolls out a new image.▸
✓ 13. Add the Service Easy One wish, several objects. A Website is not "a Deployment with extra steps" — it is a thing a person wants, and granting it takes a Deployment and a Service, and later possibly…▸
✓ 14. Retry instead of dying Medium A pass fails. Not because your code is wrong — because the spec asks for something the cluster refuses, or a webhook is down, or a quota is full.▸
✓ 15. Reconcile on a timer Medium Nothing has to happen for a Website to stop being satisfied. A hand-run kubectl scale changes the child, and no event about the parent will ever arrive — so a controller that…▸
✓ 16. Let someone pause you Easy At three in the morning, with a bad spec rolling out, the only lever a person has is your controller.▸
✓ 17. Report what you did Medium Spec is the wish. Status is the world. Keeping them apart is not tidiness — the status subresource makes the API server enforce it: a write to the main endpoint cannot change…▸
✓ 18. Say what Ready means Medium A condition is a yes/no question with a reason attached, and the reason is the useful half. "Not ready" is a status light.▸
✓ 19. Clean up before you let go Hard Garbage collection handles anything inside the cluster that carries an owner reference.▸
✓ 20. Lose a race and recover Hard Kubernetes has no locks. Every write carries the resourceVersion the writer read, and the API server rejects it if the object has moved on — HTTP 409, IsConflict.▸
✓ 21. Apply as a field manager Hard Everything so far has been "check, then create or patch". Server-side apply replaces that with one call: you send the object as you want it, the API server records which fields…▸
✓ 22. Watch what you own Medium The timer bounds how long the world can be wrong. Watching your children makes it right almost immediately: someone deletes the Deployment, an event arrives, and the pass that…▸
✓ 23. Leave other people's objects alone Medium A controller that matches objects by name is a controller that will eventually overwrite something it did not create.▸
✓ 24. Record events Medium Status says what is true now. Events say what happened, and they are what kubectl describe shows at the bottom — the first place anyone looks when something is not working.▸
✓ 25. Expose the loop's metrics Easy A controller that is quietly failing looks exactly like a controller with nothing to do.▸
✓ 26. Answer healthz and readyz Easy Once this runs in the cluster, the kubelet decides whether to restart it and whether to send it traffic, and it decides by asking. Two endpoints, two different questions:▸
✓ 27. Elect a leader Hard Run two copies of this controller and they will both reconcile every Website: two writers, conflicting updates, children created twice under different names if you are unlucky.▸
✓ 28. Shut down cleanly Medium A controller is stopped constantly — every rollout, every node drain, every scale-down.▸
Build your own admission webhook 26/26 verified
▤ Primer Background Every write to a Kubernetes cluster takes the same road.▸
✓ 1. Serve HTTPS the API server will trust Medium An admission webhook is called, not calling. Before it can judge anything it has to be a server the API server is willing to talk to — and the API server has exactly one rule…▸
✓ 2. Read an AdmissionReview, answer with one Medium The payload of admission is one kind, AdmissionReview, and it travels in both directions. The API server POSTs one with .request filled in; you reply with one with .▸
✓ 3. Register yourself with the cluster Medium A webhook that nobody has been told about is a web server. What makes it part of admission is a ValidatingWebhookConfiguration: a cluster-scoped object that says which requests to…▸
✓ 4. Reject what breaks the rule Easy Everything so far said yes. A validating webhook earns its name by saying no, and the mechanics of no are smaller than they look: allowed: false, plus a status explaining why.▸
✓ 5. Echo the UID, or the answer is discarded Easy Your program has two ways to answer, and only one of them is admission.▸
✓ 6. Ask only for the objects you judge Medium A rule is the blast radius of your webhook, written down. Four axes decide what the API server diverts to you:▸
✓ 7. Leave other namespaces alone Medium A rule says what kind of object you judge. A namespaceSelector says where — and it is the field that decides whether a mistake in your webhook is an inconvenience or an outage.▸
✓ 8. Narrow by label on the object itself Easy namespaceSelector filters by where an object lives. objectSelector filters by what it is — the labels on the object being admitted.▸
✓ 9. Change an object with a JSON patch Hard A mutating webhook answers the same question with one extra field: not just "may this be written", but "write this instead".▸
✓ 10. Fill in what was left out Medium Defaulting is mutation with a policy attached: instead of refusing an object for what it lacks, you supply it.▸
✓ 11. Add a container nobody asked for Hard Injecting a container is the most useful and most dangerous thing a mutating webhook does.▸
✓ 12. Honour a dry run, change nothing Medium kubectl apply --dry-run=server asks the cluster what would happen. The request runs the whole admission chain, including your webhook, and is thrown away instead of being stored.▸
✓ 13. Decide what happens when you are down Medium failurePolicy answers one question: when the API server cannot get an answer out of you, does the request go through?▸
✓ 14. Answer inside the deadline, or be one Medium Every request your rules match waits for you. That is the whole cost model of admission, and timeoutSeconds is the bound on it:▸
✓ 15. Run again after another webhook has been Hard Mutating webhooks run in sequence, in an order you do not control.▸
✓ 16. Replace the certificate without dropping a request Hard Certificates expire. A webhook whose certificate expires stops being callable, and — depending on the failure policy — either silently stops enforcing or stops the cluster…▸
✓ 17. Say why, where an auditor will read it Easy A verdict answers one person: whoever ran the command. Two other audiences need to know what you decided, and both have a field.▸
✓ 18. Filter with CEL before the request reaches you Medium Rules and selectors filter by shape: which resource, which namespace, which labels.▸
✓ 19. The same rule, with no webhook at all Hard Everything you have built exists because the API server had no way to evaluate a rule itself. Since 1.▸
✓ 20. Never gate the namespace you run in Medium Stage 7 narrowed this webhook to one namespace. This stage is about the one namespace it must never contain: its own.▸
✓ 21. An eviction is not a delete Hard Your rule says resources: ["pods"], and for twenty stages that has been enough. It does not cover an eviction.▸
✓ 22. Judge what is being taken away Medium Stage 21 stopped a drain from taking a protected pod. It left the front door open: kubectl delete pod still works, because the rule covers CREATE and UPDATE and an eviction, and a…▸
✓ 23. Guard every way in Medium Stages 21 and 22 closed the two ways a protected pod could be taken away. Neither stops anyone getting inside it.▸
✓ 24. Let the namespace go Medium Stage 22 taught the webhook to refuse the deletion of a protected pod. It does exactly that — including when nobody meant to delete that pod on its own.▸
✓ 25. The rule, as data Medium Stage 19 moved the owner rule into the API server. It is still a rule written down in code, though: the program builds the policy, so changing what the policy accepts means…▸
✓ 26. Every version of the same thing Medium A rule names API versions. So far that has been harmless: pods have exactly one.▸

The rest of the ladder

Planned courses, in the order they build on each other. They have no stages here yet — the full stage outline for each lives in CURRICULUM.md.

the client

  • Build your own diff and safe apply Server-side dry-run, a three-way diff, and a change you can read before it lands.

control loops

  • Build your own scheduler Filter, score, bind — then priority and preemption.

the API plane

  • Build your own kube-apiserver REST over etcd, watch, server-side apply, RBAC — driven by real kubectl.

the node plane

  • Build your own container runtime Image layers, namespaces, cgroups, and the CRI a kubelet can drive.
  • Build your own kubelet Register a node, run the pods assigned to it, report what happened.
  • Build your own CNI plugin veth pairs, IPAM, routes, and an overlay across nodes.
  • Build your own kube-proxy Services and EndpointSlices turned into a working data plane.

the ecosystem

  • Build your own Helm Charts, values, releases, and a three-way-merge upgrade.
  • Build your own kustomize Bases, overlays, patches, and generated names that stay wired up.
  • Build your own CSI driver Provision a volume, stage it, publish it into a pod.
  • Build your own metrics server and HPA Scrape the kubelet, serve metrics.k8s.io, then do the scaling maths.
  • Build your own cluster bootstrap PKI, static pods, bootstrap tokens — a control plane from nothing.

    ▶ Run it

    From the repo root: — it verifies every stage up to this one, so passing means nothing earlier broke.