the complete menu: aperitif CRT terminal + courses III node/pod, IV traffic, V orbit - five animation personalities, clip-split and reverse slice modes
This commit is contained in:
+240
-6
@@ -10,8 +10,13 @@
|
||||
<nav class="toc" aria-label="index">
|
||||
<p class="kt">Index</p>
|
||||
<ol>
|
||||
<li><a href="#c0"><b>Course 00</b><span>Aperitif - three commands, and what actually just happened</span></a></li>
|
||||
<li><a href="#c1"><b>Course I</b><span>Docker - an image is a stack of frozen diffs</span></a></li>
|
||||
<li><a href="#c2"><b>Course II</b><span>Kubernetes - a cluster is a promise, not a place</span></a></li>
|
||||
<li><a href="#c3a"><b>Course III</b><span>The node - where intent becomes a process</span></a></li>
|
||||
<li><a href="#c3b"><b>Course III</b><span>The pod - one IP, shared fate</span></a></li>
|
||||
<li><a href="#c4"><b>Course IV</b><span>The traffic - pods are cattle, Services are the brand</span></a></li>
|
||||
<li><a href="#c5"><b>Course V</b><span>OpenShift - Kubernetes with opinions, and a security guard</span></a></li>
|
||||
<li><a href="#c6"><b>Course VI</b><span>GitOps - nobody deploys anything; the cluster syncs itself</span></a></li>
|
||||
<li><a href="#c7"><b>Course VII</b><span>The image supply chain - a tag is a promise, a digest is a fact</span></a></li>
|
||||
<li><a href="#c8"><b>Course VIII</b><span>Helm - a chart is a function; the values file is the cluster speaking</span></a></li>
|
||||
@@ -26,13 +31,50 @@
|
||||
<p>The Exploded Cluster teaches how modern container platforms work by taking them apart -
|
||||
literally. Each course is one machine drawn as a single exploded illustration, sliced into its
|
||||
real components and wired to your scroll, so the architecture moves while the words explain it.
|
||||
Start at Course I with what an image actually is; finish knowing how a change travels from a
|
||||
Start with the aperitif's three terminal commands; finish knowing how a change travels from a
|
||||
git commit to a running, secret-fed, digest-pinned workload on a fleet. Every claim comes from
|
||||
running these systems for real, and the library at the end links only to official
|
||||
documentation.</p>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============ COURSE 00 ============ -->
|
||||
<section class="course" id="c0" data-scene="surround" aria-labelledby="c0h">
|
||||
<header class="chead">
|
||||
<p class="eyebrow">Course 00 · Aperitif</p>
|
||||
<h2 id="c0h" class="thesis">Three commands.<br><em>What actually just happened?</em></h2>
|
||||
<p class="lede">The whole ceremony of shipping software fits in four lines of terminal. They
|
||||
work on your first day and stay mysterious for years. Scroll - the shell comes off first.</p>
|
||||
</header>
|
||||
<div class="pin">
|
||||
<div class="viewport solo">
|
||||
<figure class="scene">
|
||||
<img class="flat" src="__HERO_SURROUND__"
|
||||
alt="Six dark armour fragments with neon seams framing a large empty centre - the
|
||||
casing of a machine caught the instant before it comes apart.">
|
||||
<div class="crt" role="img" aria-label="A terminal transcript: docker build, docker push,
|
||||
kubectl apply, kubectl get pods showing Running - then the question: but what
|
||||
actually just happened?">
|
||||
<p class="line">$ docker build -t shop/api:1.4.2 .</p>
|
||||
<p class="line ok">=> exported image sha256:9f2c41...</p>
|
||||
<p class="line">$ docker push shop/api:1.4.2</p>
|
||||
<p class="line">$ kubectl apply -f deploy.yaml</p>
|
||||
<p class="line ok">deployment.apps/api created</p>
|
||||
<p class="line">$ kubectl get pods</p>
|
||||
<p class="line ok">api-7d4b9c6-x2k4f 1/1 Running</p>
|
||||
<p class="line q">...but what actually just happened?</p>
|
||||
</div>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="bench"></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<p>An image got <b>built</b> - of what, exactly? <b>Pushed</b> - to where, and what travelled?
|
||||
<b>Applied</b> - which is not the same as launched. <b>Running</b> - according to whom?
|
||||
Every course below takes one of those words apart. The armour is already loose.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE I ============ -->
|
||||
<section class="course" id="c1" data-scene="docker" aria-labelledby="c1h">
|
||||
<header class="chead">
|
||||
@@ -126,6 +168,171 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE III - THE NODE ============ -->
|
||||
<section class="course" id="c3a" data-scene="node" aria-labelledby="c3ah">
|
||||
<header class="chead">
|
||||
<p class="eyebrow">Course III · The node</p>
|
||||
<h2 id="c3ah" class="thesis">Where intent<br><em>becomes a process.</em></h2>
|
||||
<p class="lede">Everything so far was decision. This is the machine where a pod stops being
|
||||
paperwork and starts being a process - and the chain assembles link by link as you scroll.</p>
|
||||
</header>
|
||||
<div class="pin">
|
||||
<div class="viewport">
|
||||
<figure class="scene">
|
||||
<img class="flat" src="__HERO_NODE__"
|
||||
alt="A diagonal chain of node machinery: a visor-lit kubelet module, a layered
|
||||
container-runtime engine, a ported CNI ring, a magenta routing prism - and a
|
||||
small glowing pod capsule descending toward the engine.">
|
||||
</figure>
|
||||
<ol class="legend"></ol>
|
||||
</div>
|
||||
<div class="bench"></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<h3>One component starts containers. One.</h3>
|
||||
<p>Every node runs a <b>kubelet</b> - and in the entire system it is the only component that
|
||||
actually starts your container. It speaks CRI to <b>containerd</b> or CRI-O, which pulls the
|
||||
image (through the mirror of Course VII) and launches it. A <b>CNI plugin</b> hands the pod
|
||||
a real IP, and <b>kube-proxy</b> - or an eBPF datapath replacing it - makes Service
|
||||
addresses route to real pods. The kubelet drives, the runtime executes, the CNI wires, the
|
||||
proxy routes.</p>
|
||||
<p class="sayit">The kubelet is the only component that actually starts your container.</p>
|
||||
<p class="fieldnote"><b>Field note.</b> Node NotReady? Check kubelet-to-API-server
|
||||
connectivity before anything else - a node that cannot phone home is presumed lost.</p>
|
||||
<p class="closeline">The control plane never touches your workload. It writes intent; the
|
||||
kubelet reads it and makes it flesh. Authority and execution meet exactly here, nowhere
|
||||
else.</p>
|
||||
<p class="reads"><b>Pre-reads:</b> C-II
|
||||
 <b>Further:</b> <a href="https://kubernetes.io/docs/concepts/architecture/">cluster architecture</a> ·
|
||||
<a href="https://kubernetes.io/docs/concepts/architecture/#node-components">node components</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE III - THE POD ============ -->
|
||||
<section class="course" id="c3b" data-scene="pod" aria-labelledby="c3bh">
|
||||
<header class="chead">
|
||||
<p class="eyebrow">Course III · The pod</p>
|
||||
<h2 id="c3bh" class="thesis">One IP,<br><em>shared fate.</em></h2>
|
||||
<p class="lede">A pod is not a container - it is a jacket around one or more. Scroll, and the
|
||||
capsule opens like a clamshell: shells apart, contents on display.</p>
|
||||
</header>
|
||||
<div class="pin">
|
||||
<div class="viewport">
|
||||
<figure class="scene">
|
||||
<img class="flat" src="__HERO_POD__"
|
||||
alt="A pod capsule blown open: two frosted shell halves floating apart, a cyan app
|
||||
container column standing on an amber-lit init gate, a smaller magenta sidecar
|
||||
beside it, and a stack of translucent volume discs.">
|
||||
</figure>
|
||||
<ol class="legend"></ol>
|
||||
</div>
|
||||
<div class="bench"></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<h3>The jacket, not the container</h3>
|
||||
<p>Everything inside the jacket shares a <b>network namespace</b>: one IP, localhost between
|
||||
friends, volumes mountable by all. <b>initContainers</b> run first, in order, to completion -
|
||||
gatekeepers under everything. <b>Sidecars</b> ride along with their own containers and their
|
||||
own jobs: proxy, logs, reload.</p>
|
||||
<h3>Three probes, three different jobs</h3>
|
||||
<p><b>startup</b> owns warm-up, <b>readiness</b> gates traffic, <b>liveness</b> restarts the
|
||||
truly hung. Confusing them is how healthy pods get executed - a slow start killed by an
|
||||
impatient liveness probe looks exactly like a crash.</p>
|
||||
<p class="sayit">containerPort is documentation - the app still has to bind the port itself.</p>
|
||||
<p class="fieldnote"><b>Field note.</b> Exit 137 with reason Error is the kubelet's probe
|
||||
kill; OOMKilled is memory. Same exit code, different crimes - read the reason.</p>
|
||||
<p class="closeline">The pod is the smallest schedulable unit - the jacket, never the
|
||||
container. Once that distinction lands, half of Kubernetes networking stops being
|
||||
mysterious.</p>
|
||||
<p class="reads"><b>Pre-reads:</b> C-III's node scene above
|
||||
 <b>Further:</b> <a href="https://kubernetes.io/docs/concepts/workloads/pods/">pods</a> ·
|
||||
<a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">the three probes</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE IV ============ -->
|
||||
<section class="course" id="c4" data-scene="traffic" aria-labelledby="c4h">
|
||||
<header class="chead">
|
||||
<p class="eyebrow">Course IV · The traffic</p>
|
||||
<h2 id="c4h" class="thesis">Pods are cattle.<br><em>Services are the brand.</em></h2>
|
||||
<p class="lede">Pods die, respawn and change addresses - and traffic still arrives. Scroll,
|
||||
and the delivery route assembles checkpoint by checkpoint; watch what happens to the pod
|
||||
that stops answering.</p>
|
||||
</header>
|
||||
<div class="pin">
|
||||
<div class="viewport">
|
||||
<figure class="scene">
|
||||
<img class="flat" src="__HERO_TRAFFIC__"
|
||||
alt="A delivery route in the void: a glowing client orb, a fanned load-balancer
|
||||
wedge, an open ingress doorframe, a Service prism with a bright core, a lit
|
||||
ready pod - and a dark unlit pod fallen out of the line.">
|
||||
</figure>
|
||||
<ol class="legend"></ol>
|
||||
</div>
|
||||
<div class="bench"></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<h3>The stable name in front of the churn</h3>
|
||||
<p>A <b>Service</b> is the fixed point: a ClusterIP inside, a LoadBalancer at the edge,
|
||||
Ingress or the Gateway API doing host- and path-routing above. Clients hold the name; the
|
||||
pods behind it come and go without anyone being told.</p>
|
||||
<h3>Readiness decides membership</h3>
|
||||
<p>A pod failing its readiness probe <b>silently leaves the pool</b>. No error, no event at
|
||||
the client - traffic simply stops arriving. That is the feature: broken instances remove
|
||||
themselves. It is also the first place to look when traffic "disappears".</p>
|
||||
<p class="sayit">Never talk to a pod; talk to a Service.</p>
|
||||
<p class="fieldnote"><b>Field note.</b> "The network is broken" after a deploy is usually
|
||||
readiness telling the truth about your app - not the network lying about your packets.</p>
|
||||
<p class="closeline">The dark pod in the scene is not an error state - it is the system
|
||||
working. Membership is earned per probe, continuously, forever.</p>
|
||||
<p class="reads"><b>Pre-reads:</b> C-III's pod scene (readiness lives there)
|
||||
 <b>Further:</b> <a href="https://kubernetes.io/docs/concepts/services-networking/">Services and networking</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE V ============ -->
|
||||
<section class="course" id="c5" data-scene="orbit" aria-labelledby="c5h">
|
||||
<header class="chead">
|
||||
<p class="eyebrow">Course V · OpenShift</p>
|
||||
<h2 id="c5h" class="thesis">Kubernetes with opinions -<br><em>and a security guard.</em></h2>
|
||||
<p class="lede">OpenShift is a distribution of Kubernetes: same engine, opinionated chassis.
|
||||
Scroll, and the opinions bloom outward from the core they orbit.</p>
|
||||
</header>
|
||||
<div class="pin">
|
||||
<div class="viewport">
|
||||
<figure class="scene">
|
||||
<img class="flat" src="__HERO_ORBIT__"
|
||||
alt="A glowing geodesic core ringed by six satellites: a magenta admission shield,
|
||||
an open route arch, interlocking operator rings, a handheld console, an
|
||||
amber-lit stack of machine-config plates and a compact single-node box.">
|
||||
</figure>
|
||||
<ol class="legend"></ol>
|
||||
</div>
|
||||
<div class="bench"></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<h3>The doorman interviews every pod</h3>
|
||||
<p>The <b>SCC</b> is admission deciding what a pod may BE. The default, restricted-v2, runs
|
||||
your container as a <b>random non-root UID</b> - your image has to cope. Workloads that
|
||||
genuinely need privilege get a dedicated ServiceAccount bound to a minimal custom SCC,
|
||||
never the stock one.</p>
|
||||
<h3>The platform runs itself</h3>
|
||||
<p><b>Routes</b> predate Ingress and still rule here. <b>Operators</b> run the platform's own
|
||||
machinery. The OS underneath is immutable - changed by <b>MachineConfig</b>, never by SSH.
|
||||
And <b>SNO</b> puts the entire promise on one box at the edge. At fleet scale the labels
|
||||
from Course VI decide which of these boxes runs what.</p>
|
||||
<p class="sayit">On OpenShift, admission is the interview - the SCC is the dress code.</p>
|
||||
<p class="fieldnote"><b>Field note.</b> Deployment stuck at 0/1 with no pod at all? The
|
||||
refusal happened above scheduling - read the ReplicaSet events. The error lives a level
|
||||
up.</p>
|
||||
<p class="closeline">Everything in the ring is opinion wrapped around the same core you
|
||||
already know. Learn the core once; the opinions are just house rules.</p>
|
||||
<p class="reads"><b>Pre-reads:</b> C-II
|
||||
 <b>Further:</b> <a href="https://docs.redhat.com/en/documentation/openshift_container_platform">OpenShift documentation</a> ·
|
||||
<a href="https://www.redhat.com/en/technologies/cloud-computing/openshift">Red Hat OpenShift</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ COURSE VI ============ -->
|
||||
<section class="course" id="c6" data-scene="gitops" aria-labelledby="c6h">
|
||||
<header class="chead">
|
||||
@@ -516,6 +723,18 @@ b{color:var(--ink)} .mono{font-family:var(--mono);font-size:.9em;color:var(--cya
|
||||
.legend .t{font-size:.92rem;font-weight:600;margin:0 0 .22rem;letter-spacing:-.01em}
|
||||
.legend .b{font-size:.76rem;line-height:1.5;color:var(--dim);margin:0}
|
||||
|
||||
.viewport.solo{grid-template-columns:1fr;max-width:980px}
|
||||
.crt{position:absolute;inset:22% 26%;display:flex;flex-direction:column;justify-content:center;
|
||||
gap:.35rem;padding:clamp(10px,2.5vw,26px);border:1px solid var(--line);border-radius:10px;
|
||||
background:rgba(5,8,16,0.88);box-shadow:0 0 40px rgba(63,186,245,0.07) inset}
|
||||
.crt .line{font-family:var(--mono);font-size:clamp(.55rem,1.35vw,.85rem);line-height:1.5;
|
||||
color:var(--ink);margin:0;white-space:nowrap;overflow:hidden}
|
||||
.crt .line.ok{color:var(--dim)}
|
||||
.crt .line.q{color:var(--magenta);margin-top:.5rem}
|
||||
body.fx .crt .line{opacity:0;transform:translateY(4px);transition:opacity .3s ease,transform .3s ease}
|
||||
body.fx .crt .line.on{opacity:1;transform:none}
|
||||
@media (max-width:700px){.crt{inset:16% 12%}}
|
||||
|
||||
.bench{width:min(720px,100%);margin:16px auto 0;text-align:center}
|
||||
.bench input{width:100%;accent-color:var(--cyan)}
|
||||
.bench .hud{font-family:var(--mono);font-size:.66rem;letter-spacing:.06em;color:var(--dim);margin:.45rem 0 0}
|
||||
@@ -562,7 +781,7 @@ body:not(.fx) .bench{display:none}
|
||||
<script>
|
||||
(() => {
|
||||
"use strict";
|
||||
const SCENES = { docker: __MAN_DOCKER__, cluster: __MAN_CLUSTER__, gitops: __MAN_GITOPS__, supply: __MAN_SUPPLY__, helm: __MAN_HELM__ };
|
||||
const SCENES = { surround: __MAN_SURROUND__, docker: __MAN_DOCKER__, cluster: __MAN_CLUSTER__, node: __MAN_NODE__, pod: __MAN_POD__, traffic: __MAN_TRAFFIC__, orbit: __MAN_ORBIT__, gitops: __MAN_GITOPS__, supply: __MAN_SUPPLY__, helm: __MAN_HELM__ };
|
||||
const RM = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const units = [];
|
||||
|
||||
@@ -571,7 +790,7 @@ body:not(.fx) .bench{display:none}
|
||||
const scene = sec.querySelector(".scene");
|
||||
const flat = scene.querySelector(".flat");
|
||||
const legend = sec.querySelector(".legend");
|
||||
man.parts.forEach(p => {
|
||||
if (legend) man.parts.forEach(p => {
|
||||
const li = document.createElement("li");
|
||||
li.innerHTML = '<p class="t">' + p.title + '</p><p class="b">' + p.blurb + '</p>';
|
||||
legend.appendChild(li);
|
||||
@@ -594,15 +813,18 @@ body:not(.fx) .bench{display:none}
|
||||
return d;
|
||||
});
|
||||
u.flat.style.visibility = "hidden";
|
||||
const lis = [...u.legend.children];
|
||||
const lis = u.legend ? [...u.legend.children] : [];
|
||||
const n = lis.length;
|
||||
u.apply = t => {
|
||||
// stagger > 0 = cascade: parts complete their travel one after another in list order
|
||||
const sg = u.man.stagger || 0;
|
||||
u.man.parts.forEach((p, i) => {
|
||||
const pt = sg ? Math.min(1, Math.max(0, t * (1 + sg * (n - 1)) - sg * i)) : t;
|
||||
const k = u.man.parts.length;
|
||||
const pt = sg ? Math.min(1, Math.max(0, t * (1 + sg * (k - 1)) - sg * i)) : t;
|
||||
// reverse scenes sit assembled at rest and FLY APART as the scrub advances
|
||||
const e = u.man.reverse ? (1 - pt) : pt;
|
||||
u.slabs[i].style.transform =
|
||||
"translate3d(" + (p.dx * (1 - pt)).toFixed(3) + "%," + (p.dy * (1 - pt)).toFixed(3) + "%,0)";
|
||||
"translate3d(" + (p.dx * (1 - e)).toFixed(3) + "%," + (p.dy * (1 - e)).toFixed(3) + "%,0)";
|
||||
});
|
||||
u.scene.style.transform = "scale(" + (0.95 + 0.05 * t).toFixed(4) + ")";
|
||||
// the side list lights in reading order, top to bottom, as the scrub advances
|
||||
@@ -654,6 +876,18 @@ body:not(.fx) .bench{display:none}
|
||||
});
|
||||
if (!RM) ScrollTrigger.refresh();
|
||||
};
|
||||
// the aperitif terminal types its transcript once, when it first enters view
|
||||
document.querySelectorAll(".crt").forEach(crt => {
|
||||
const lines = [...crt.querySelectorAll(".line")];
|
||||
if (RM) { lines.forEach(l => l.classList.add("on")); return; }
|
||||
const io = new IntersectionObserver(es => {
|
||||
if (!es.some(x => x.isIntersecting)) return;
|
||||
io.disconnect();
|
||||
lines.forEach((l, i) => setTimeout(() => l.classList.add("on"), 350 * i + 200));
|
||||
}, { threshold: 0.4 });
|
||||
io.observe(crt);
|
||||
});
|
||||
|
||||
Promise.all(units.map(u => u.flat.decode ? u.flat.decode().catch(() => {}) : Promise.resolve()))
|
||||
.then(() => requestAnimationFrame(() => requestAnimationFrame(boot)));
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user