delivery arc VI-VIII: course briefs (GitOps, supply chain, Helm contexts) + NB2 scene prompts 09-11

This commit is contained in:
2026-08-17 12:44:44 +10:00
parent 5cce8c5cd3
commit 7caa9d5342
6 changed files with 391 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
# Course VI - GitOps: ArgoCD, the GitHub linkage, and the one thing git never holds
**Series position:** first course of the delivery arc (VI-VIII). The main menu taught the
machine; this arc teaches how software REACHES it.
**Safety gate:** public site, real name - technologies only, zero employer/industry framing.
Claims source from [[argocd-stacks]], [[gitops-iac-rigor]], [[eso-infisical-secrets]]
(generalised to the Azure Key Vault worked example), [[authentik-oidc]] not needed here.
## Title (thesis)
**Nobody deploys anything. The cluster syncs itself.**
## Hook - the uncomfortable default
The mental model everyone arrives with: a person (or a pipeline) with credentials pushes
manifests at the cluster. Deploy = an action someone performs, kubectl apply = the verb.
The inversion: in GitOps nothing is pushed. A repository holds the desired state, an agent
inside the cluster watches it, and the cluster pulls its own future from git. kubectl is
for looking, git is for changing.
## Move 1 - The loop you already know, one level up
Course II taught the reconciliation loop: desired vs actual, fix the difference, repeat.
ArgoCD is the same habit applied to DELIVERY. An Application names a repo + path + revision
(the GitHub linkage: watch this branch of this repository); the controller renders what it
finds there, compares it against the live cluster, and shows the diff. Sync = make actual
match desired. The deploy button is a git commit; the audit log is git log; code review IS
change control.
Say it like you mean it: **kubectl apply is for archaeology, not for change.**
## Move 2 - Pull, not push - the security inversion
The load-bearing detail everyone misses: the cluster PULLS. No CI system, no laptop, no
GitHub Action holds a credential that can touch the cluster. The agent inside the cluster
holds a read-only deploy key to the repo - the trust arrow points OUT. Compromise the build
system and you can propose a change (a commit, which is visible); you cannot reach into
production. Drift works the same way: hand-edit a live object and the controller flags it
OutOfSync - with selfHeal on, it puts it back; with prune on, what leaves git leaves the
cluster. Rollback is `git revert`, which is why commit hygiene is an operational skill.
FIELD NOTE (amber): fighting the reconciler by SSH/kubectl is arm-wrestling a machine that
does not get tired (Course II's note, now with a face). The fix is always upstream, in git.
## Move 3 - The one thing git never holds
Git holds everything - manifests, charts, config, the whole desired state - except secrets.
A secret in git is published, forever, to everyone who ever clones. So the pattern splits
the reference from the value: git carries an ExternalSecret manifest naming a LOGICAL key
("this app needs db-password from the vault"); a vault - Azure Key Vault as the worked
example, any ESO-supported store works - holds the value; an operator inside the cluster
exchanges the reference for the value at runtime and keeps it synced. Rotation happens in
the vault, never as a commit.
Say it like you mean it: **git holds the shape of the secret, the vault holds the secret.**
FIELD NOTE: the day a real credential lands in a commit, it is spent - history rewrites do
not un-publish it. Rotate first, tidy git second.
## Close
Delivery stops being an event and becomes a property: the cluster is always converging on
what the repository says. The question "who deployed this?" becomes "who merged this?" -
and that question always has an answer.
## Legend terms (DOM layer)
Application (repo + path + revision) / desired vs actual / sync / drift / selfHeal /
prune / pull model / deploy key / ExternalSecret / vault reference / rotation
## Scene concept (hero + slices, the v3 mechanism)
Exploded chain, upper-left to lower-right: a faceted crystal SLAB etched with branching
lattice (the repository) -> a compare-loop ENGINE with two counter-rotating ring motifs
(ArgoCD: desired vs actual) -> a stack of thin rendered SHEETS (manifests) -> the cluster
CORE (Course II's command slab, familiar silhouette) -> off-axis, drifting away dimmed: a
small GHOST SHARD (the hand-edit being reverted). Separate armoured VAULT BOX floating
above the chain, apart from everything (the secret store - deliberately not in line).
6 parts, no connectors. Slice plan: collapse packs chain into the engine; vault stays
apart even collapsed (it is never absorbed into the pipeline - the visual argument).
+75
View File
@@ -0,0 +1,75 @@
# Course VII - The image supply chain: registries, mirrors, tags and digests
**Series position:** second course of the delivery arc. Course I taught what an image IS;
this course teaches how one travels - and how to say its name precisely.
**Safety gate:** public site - technologies only. Claims source from [[zot-registry]],
[[homelab-pull-through-registry]], [[work-container-image-tagging]] (generalised: the
calendar-rev pattern taught as "a human tag scheme", no corp framing), Azure Container
Registry as the generic managed-registry example, OpenShift IDMS behaviour from public
Red Hat docs.
## Title (thesis)
**A tag is a promise. A digest is a fact.**
## Hook - the uncomfortable default
`myapp:latest` feels like a name. It is not - it is a sticky note. A tag is a mutable
pointer anyone with push rights can peel off one image and press onto another, and
nothing anywhere records that it moved. The digest - sha256 of the content - is the
image's actual name: same bytes, same digest, forever. Most outages in this territory
begin with two machines both holding "the same tag" and running different code.
## Move 1 - Say the name properly
Three ways to name an image, in rising order of honesty: `:latest` (a moving target),
`:1.4.2` (a promise somebody keeps, until they re-push it), `name:1.4.2@sha256:...`
(a fact - the tag stays for human eyes, the digest does the pulling). Content-addressing
is the same idea git uses for commits: the name IS the bytes. Pin by digest and "what is
running?" has exactly one answer.
Say it like you mean it: **the tag is for reading, the digest is for pulling.**
FIELD NOTE (amber): `:latest` is how two nodes run different code from one manifest -
the second node pulled an hour later, after a re-push. Nobody changed the YAML.
## Move 2 - Why a fleet pulls once
Between the build and the node sits the registry chain. Upstream, a managed registry
(Azure Container Registry as the worked example) holds what CI built. In front of the
cluster sits a MIRROR - a pull-through cache like zot: the fleet asks the mirror, the
mirror asks upstream once, everything after is local. Three reasons this exists: rate
limits and egress cost (a hundred nodes should not make a hundred internet pulls),
availability (the fleet boots even when upstream is down or the site is disconnected),
and control (one place to gate, scan and audit what enters). OpenShift formalises the
re-route with image mirror rules - and carries a sharp edge: its digest-mirror rules
rewrite DIGEST pulls only, so a by-tag pull silently skips the mirror. The pinning habit
from Move 1 is what makes the mirror actually catch the traffic.
FIELD NOTE: a mirror can also serve a STALE tag->digest mapping while upstream moved the
tag. Pin by digest and the whole chain becomes content-addressed - the mirror cannot be
wrong about a hash.
## Move 3 - Build once, promote by copy
The anti-pattern: rebuild the image for each environment ("the prod build"). Every
rebuild is a different artefact - different digest, different bytes, untested by the
stages before it. The discipline: build ONCE, then PROMOTE the same digest through
environments by copying (skopeo copy, registry to registry) - dev proves the exact bytes
prod will run. Human-readable tags ride along (a calendar-plus-revision scheme reads well:
month, year, revision), but the digest is the through-line; promotion never changes it.
Say it like you mean it: **if the digest changed, it is not a promotion - it is a new
candidate.**
## Close
Everything in this course is one idea wearing three coats: names that can move are
convenient exactly until they move. Address content by what it IS, let humans keep their
readable labels, and the supply chain stops being a chain of trust and becomes a chain
of arithmetic.
## Legend terms (DOM layer)
tag / digest / content-addressing / pull-through mirror / disconnected pull / rate limit /
image mirror rule (digest-only rewrite) / build-once / promotion / skopeo copy
## Scene concept (hero + slices, the v3 mechanism)
Exploded left-to-right journey: the layered image CUBE from Course I (familiar - four
thin slabs, tight formation, reads as one unit now) -> upstream registry TOWER (tall
managed vault, cool white-cyan) -> the mirror WAY-STATION (squat wide cache block, warm
cyan, visibly closer to the cluster side) -> the node/cluster CORE. Floating above the
journey, two seal plates apart from each other: a GHOST RIBBON plate (the tag - faint,
translucent, edges blurring) and an ENGRAVED SEAL plate (the digest - dense, crisp,
crystalline). 6 parts, generous void, no connectors. Slice plan: collapse draws the
journey into the mirror block (everything local); the two seals collapse toward each
other but never merge - ghost stays ghost.
+74
View File
@@ -0,0 +1,74 @@
# Course VIII - Helm: Go templates, values contexts, one chart for a fleet
**Series position:** third course of the delivery arc. VI taught how change travels,
VII how images are named; this course teaches how ONE definition serves MANY clusters.
**Safety gate:** public site - technologies only. Claims source from [[helm-charts]],
[[yaml-manifest-style]], [[kustomize-inflate-helm]] (the ArgoCD render caveat), the
one-value-many-enforcers post (live on the blog - cross-link it), Helm/Go public docs.
## Title (thesis)
**A chart is a function. The values file is the cluster speaking.**
## Hook - the uncomfortable default
The default way to run one app on five clusters is five copies of the YAML, and the
default result is five slightly different apps - each copy drifting on its own schedule,
each "quick fix" applied to some of them. The inversion: stop copying outputs and ship
the FUNCTION instead. A Helm chart is a template with holes; each cluster supplies one
small values file that fills them; the rendered manifests are build artefacts nobody
hand-edits, ever.
## Move 1 - It's Go under the braces
Helm templates are Go text/template: `{{ .Values.camera.address }}` is a pipeline
walking a values object, `_helpers.tpl` holds named template functions (labels, names)
every manifest calls, and `include`/`toYaml`/`default` compose them. The mental shift:
you are not writing YAML, you are writing a program whose OUTPUT is YAML - so render
locally (`helm template`), read the output, and lint what came out, not what went in.
Say it like you mean it: **review the render, not just the template.**
FIELD NOTE (amber): ArgoCD deploys charts by running `helm template` - a pure render.
Anything that needs a live cluster or an install lifecycle (`lookup`, hooks) is silently
dead there. A chart that works under `helm install` can behave differently under GitOps -
render the way your deployer renders.
## Move 2 - Contexts: the cluster's whole voice is one small file
The fleet pattern: the chart owns EVERYTHING structural - resources, probes, security
contexts, policy - and each cluster owns one values file (its context): names, addresses,
sizes, feature flags. The context is deliberately values-only; the moment a context
carries its own manifests, there are two owners for one object and they will disagree.
One value can feed many rendered artefacts - an address appearing in the app config, the
network attachment and two policies renders from ONE field, so the copies cannot diverge
(the blog post "Consistency by construction beats consistency by discipline" is this
move, lived).
Say it like you mean it: **the chart owns the shape; the context owns the numbers.**
## Move 3 - Make the template refuse
A template that renders whatever it is given just moves the failure downstream. The
grown-up chart carries a values.schema.json: required fields, types, enums - and a
cluster whose context omits a required value FAILS AT RENDER TIME with a message naming
the field, in the pipeline, before anything ships. Same discipline inside the template:
`required "camera.address is required" .Values.camera.address` beats a silent empty
string that becomes a policy pointed at nothing.
FIELD NOTE: the failure you want is the render that refuses - it costs a red pipeline.
The failure you get otherwise is enforcement aimed at an address nothing lives at,
months later, reporting healthy the whole time.
## Close
Fleet consistency is not a discipline problem, it is a construction problem. Ship one
function, let each cluster speak one small file of facts, and make the function refuse
to run on missing facts. Divergence stops being something you police and becomes
something the tooling cannot express.
## Legend terms (DOM layer)
chart / template / Go text-template / pipeline / _helpers.tpl / values / context /
values.schema.json / required / render (helm template) / one source many renders
## Scene concept (hero + slices, the v3 mechanism)
Exploded press-works, centre-out: an engraved PRESS PLATE (the chart - dark slab, its
face carrying an abstract lattice of rectangular voids, the "holes" clearly empty) ->
three small VALUES CRYSTALS (per-cluster contexts - compact faceted gems, cyan, one
subtly larger) feeding toward it from the left -> on the right, three thin RENDERED
SHEETS fanned apart, identical framework but each carrying a different accent pattern
(same shape, different numbers) -> below, a GATE WEDGE (the schema - amber-white edge,
Course III's gatekeeper silhouette re-used) with one dim REJECTED SHEET stopped at it.
7 parts, generous void, no connectors. Slice plan: collapse draws crystals + sheets into
the press (one machine at rest); the rejected sheet collapses BEHIND the gate, never
past it - the visual argument for Move 3.