delivery arc VI-VIII: course briefs (GitOps, supply chain, Helm contexts) + NB2 scene prompts 09-11
This commit is contained in:
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user