Lab 01 — OCI Registry Engine + AKS Scheduler
Phase: 07 — Containers, Docker, ACR & AKS Internals | Difficulty: ⭐⭐⭐☆☆ | Time: 4–5 hours
A container image is not a binary — it is an ordered stack of content-addressed layers plus a config, named by a
sha256digest, listed by a manifest, and pointed at by a mutable tag. Get that one model right and ACR's whole behaviour — dedup, layer-reuse pulls, garbage collection, geo-replication — falls out as arithmetic on digests. This lab makes you build the registry, then build the AKS scheduler that bin-packs pods onto nodes and tells you, with numbers, why a pod isPending.
What you build
digest(data)—"sha256:" + hexdigest; the content-addressing primitive everything else stands on (same bytes → same name; one flipped bit → a new name).Registry— a content-addressable OCI registry:put_blob(store-by-digest with dedup),put_manifest(store config+layers, return a content-addressed manifest digest),tag(mutable pointer → digest),resolve(tag or digest → manifest digest),pull(ref, client_has=…)(return only the missing blobs — layer reuse),blob_count.garbage_collect(registry)— mark-and-sweep from the tags: a shared layer survives while any tagged image references it; untagging reclaims only an image's exclusive blobs. Returns the swept digest set.replicate(src, dst, name)— ACR geo-replication: copy a tag's manifest + only the blobs the destination is missing.schedule(pods, nodes)— an AKS-style first-fit bin-packer: place each pod (by name order) on the first node whose remaining cpu and mem satisfy its requests and whose taints are all tolerated; decrement capacity as pods land; a pod that fits nowhere isPending(None).
Key concepts
| Concept | What to understand |
|---|---|
| Content addressing | a blob's name is the hash of its bytes → dedup and tamper-evidence are free |
| Digest vs tag | digests are immutable; tags are mutable pointers → pin prod by digest |
| Manifest closure | the set of blobs (config + layers) you must possess to run an image |
| Pull = set difference | download = manifest's blobs minus what the client already cached |
| GC reachability | a blob is garbage iff no tagged manifest reaches it; shared layers survive |
| Replication = missing-only | copy the small manifest always, the big blobs only if absent |
| Schedule by requests | placement uses requests (the floor), not limits (the ceiling) |
Pending is arithmetic | unschedulable = requests exceed remaining capacity, or no taint tolerated |
Files
| File | Purpose |
|---|---|
lab.py | skeleton with # TODO markers and signatures/docstrings in place |
solution.py | complete reference; python solution.py runs a worked example |
test_lab.py | the proof — run it red, make it green |
requirements.txt | pytest only (pure stdlib otherwise) |
Run
pip install -r requirements.txt
pytest test_lab.py -v # against your lab.py
LAB_MODULE=solution pytest test_lab.py -v # against the reference
python solution.py # worked example
Success criteria
- All tests pass against your implementation (
pytest test_lab.py -v). - You can explain why
test_shared_base_layer_stored_onceasserts 5 blobs, not 6 — the base layer two images share is stored exactly once because blobs are keyed by content. - You can explain why
test_gc_shared_layer_survives_until_last_tag_goneonly reclaims the base after both referencing tags are gone — GC reachability is computed from the tags. - You can explain why
test_schedule_is_deterministic_by_pod_nameproduces the same result regardless of input order, and whyworker/zetalands asPending. - Given a
docker pullthat "downloads almost nothing," you can say why (cached base layers), and given aPendingpod you can name the two possible causes (capacity, taints).
How this maps to real Azure
| Lab construct | Real Azure / OCI mechanism |
|---|---|
digest() | the OCI image-spec sha256: content descriptor digest |
Registry blob/manifest store | an ACR repository's data plane (the OCI distribution-spec /v2/ API) |
tag is a mutable pointer | myacr.azurecr.io/app:latest drifts; production pins app@sha256:… |
pull(client_has=…) | docker pull skipping already-cached layers ("Already exists") |
garbage_collect() | ACR untagged-manifest GC / az acr manifest list-metadata + acr purge task |
replicate() | ACR geo-replication (Premium SKU) copying manifests/blobs to paired regions |
Node / Pod / schedule() | the kube-scheduler filter step (PodFitsResources, TaintToleration) on AKS node pools |
Pod is Pending | kubectl describe pod → 0/N nodes are available: Insufficient cpu / had untolerated taint |
What the lab simplifies (know these for the interview): real manifests are JSON with
media types and sizes (and a manifest list / image index for multi-arch); ACR GC also
honours soft-delete retention and never deletes a blob mid-pull; the kube-scheduler runs a
scoring phase (least-allocated, spread, affinity) after the filter step we model, and
also weighs nodeSelector, affinity/anti-affinity, topology spread, and pod priority /
preemption. Our first-fit is the filter step, deterministic and good enough to teach the
mechanism.
Extensions
- Add a manifest list (multi-arch index): a manifest whose "layers" are themselves
manifest digests, one per
os/arch; makepullselect by platform. - Add content trust: sign a manifest digest (HMAC over the digest with a key) and make
pullrefuse an untrusted/altered manifest — model Docker Content Trust / Notation. - Add soft-delete:
untagmoves a manifest to a tombstone with a retention window;garbage_collectonly sweeps tombstones past their window;restorebrings one back. - Add a scheduler scoring pass: among nodes that pass the filter, pick least-allocated (spread) or most-allocated (bin-pack to scale down) and show the placement differs.
- Add pod priority + preemption: a high-priority
Pendingpod evicts a lower-priority pod to make room; show the evicted pod becomesPending.
Resume / interview bullets
- Built a content-addressable OCI registry (digests, manifests, mutable tags) with cross-image layer deduplication, a missing-blob pull protocol, mark-and-sweep garbage collection, and missing-only geo-replication — the ACR data plane modelled.
- Implemented an AKS-style bin-packing scheduler (requests, taints/tolerations, capacity
accounting) that explains
Pendingpods as requests-vs-capacity arithmetic.