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:
2026-08-17 16:06:11 +10:00
parent a201af9d29
commit b12e435003
20 changed files with 3663 additions and 9 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

+5 -1
View File
@@ -11,12 +11,16 @@ here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd -- "$here"
for f in pilot/arc.tpl.html pilot/manifest-docker.json pilot/manifest-cluster.json \
pilot/manifest-surround.json pilot/manifest-node.json pilot/manifest-pod.json \
pilot/manifest-traffic.json pilot/manifest-orbit.json \
pilot/manifest-gitops.json pilot/manifest-supply.json \
pilot/manifest-helm.json gsap.min.js st.min.js \
assets/dist/course-VI-gitops.webp assets/dist/course-VII-supply-chain.webp \
assets/dist/course-VIII-helm-press.webp assets/dist/appendix-dependency-ledger.webp \
assets/dist/delivery-flow.svg assets/dist/docker-layers.webp \
assets/dist/course-II-cluster.webp; do
assets/dist/course-II-cluster.webp assets/dist/course-00-surround.webp \
assets/dist/course-III-node.webp assets/dist/course-III-pod.webp \
assets/dist/course-IV-traffic.webp assets/dist/course-V-orbit.webp; do
[[ -f "$f" ]] || { printf 'missing source: %s\n' "$f" >&2; exit 1; }
done
+240 -6
View File
@@ -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&#39;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 &middot; 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">=&gt; 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&nbsp;&nbsp;1/1&nbsp;&nbsp;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 &middot; 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
&ensp;<b>Further:</b> <a href="https://kubernetes.io/docs/concepts/architecture/">cluster architecture</a> &middot;
<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 &middot; 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
&ensp;<b>Further:</b> <a href="https://kubernetes.io/docs/concepts/workloads/pods/">pods</a> &middot;
<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 &middot; 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)
&ensp;<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 &middot; 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
&ensp;<b>Further:</b> <a href="https://docs.redhat.com/en/documentation/openshift_container_platform">OpenShift documentation</a> &middot;
<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)));
})();
+10
View File
@@ -20,6 +20,11 @@ const i = tpl.lastIndexOf("<script>");
const j = tpl.lastIndexOf("</script>");
if (i < 0 || j < i) throw new Error("app script block not found");
let app = tpl.slice(i + 8, j)
.replace("__MAN_SURROUND__", man("surround"))
.replace("__MAN_NODE__", man("node"))
.replace("__MAN_POD__", man("pod"))
.replace("__MAN_TRAFFIC__", man("traffic"))
.replace("__MAN_ORBIT__", man("orbit"))
.replace("__MAN_DOCKER__", man("docker"))
.replace("__MAN_CLUSTER__", man("cluster"))
.replace("__MAN_GITOPS__", man("gitops"))
@@ -28,6 +33,11 @@ let app = tpl.slice(i + 8, j)
let body = tpl.slice(0, i) + tpl.slice(j + 9);
const heroes = {
__HERO_SURROUND__: "course-00-surround.webp",
__HERO_NODE__: "course-III-node.webp",
__HERO_POD__: "course-III-pod.webp",
__HERO_TRAFFIC__: "course-IV-traffic.webp",
__HERO_ORBIT__: "course-V-orbit.webp",
__HERO_DOCKER__: "docker-layers.webp",
__HERO_CLUSTER__: "course-II-cluster.webp",
__HERO_GITOPS__: "course-VI-gitops.webp",
+10
View File
@@ -16,11 +16,21 @@ def man(tag):
separators=(",", ":"))
out = (ROOT / "pilot/arc.tpl.html").read_text() \
.replace("__MAN_SURROUND__", man("surround")) \
.replace("__MAN_NODE__", man("node")) \
.replace("__MAN_POD__", man("pod")) \
.replace("__MAN_TRAFFIC__", man("traffic")) \
.replace("__MAN_ORBIT__", man("orbit")) \
.replace("__MAN_DOCKER__", man("docker")) \
.replace("__MAN_CLUSTER__", man("cluster")) \
.replace("__MAN_GITOPS__", man("gitops")) \
.replace("__MAN_SUPPLY__", man("supply")) \
.replace("__MAN_HELM__", man("helm")) \
.replace("__HERO_SURROUND__", b64("course-00-surround.webp")) \
.replace("__HERO_NODE__", b64("course-III-node.webp")) \
.replace("__HERO_POD__", b64("course-III-pod.webp")) \
.replace("__HERO_TRAFFIC__", b64("course-IV-traffic.webp")) \
.replace("__HERO_ORBIT__", b64("course-V-orbit.webp")) \
.replace("__HERO_DOCKER__", b64("docker-layers.webp")) \
.replace("__HERO_CLUSTER__", b64("course-II-cluster.webp")) \
.replace("__HERO_GITOPS__", b64("course-VI-gitops.webp")) \
+601
View File
@@ -0,0 +1,601 @@
{
"scene": "node",
"source": "assets/raw/course-III-node.jpeg",
"parts": [
{
"name": "kubelet",
"title": "kubelet",
"blurb": "The node agent - the only component in the whole system that actually starts your container.",
"z": 0,
"points": [
[
17.33,
29.89
],
[
18.28,
22.52
],
[
18.71,
19.27
],
[
18.79,
18.95
],
[
19.51,
17.27
],
[
21.12,
14.65
],
[
25.04,
11.52
],
[
25.39,
11.26
],
[
25.9,
11.0
],
[
25.99,
11.0
],
[
27.19,
11.91
],
[
27.99,
12.56
],
[
28.62,
13.08
],
[
30.4,
14.52
],
[
30.89,
14.91
],
[
34.56,
22.64
],
[
34.92,
24.03
],
[
34.99,
24.34
],
[
35.14,
25.25
],
[
35.21,
25.86
],
[
35.21,
26.46
],
[
34.99,
34.83
],
[
34.92,
35.15
],
[
34.41,
36.34
],
[
30.15,
40.43
],
[
28.6,
41.6
],
[
28.43,
41.6
],
[
22.84,
39.91
],
[
22.49,
39.65
],
[
18.49,
35.48
],
[
18.13,
34.95
],
[
17.91,
34.61
],
[
17.33,
33.32
]
],
"dx": 10.84,
"dy": 11.97
},
{
"name": "engine",
"title": "containerd / CRI-O",
"blurb": "Speaks CRI: pulls images, launches containers. The engine the kubelet drives.",
"z": 0,
"points": [
[
55.56,
40.75
],
[
56.07,
42.91
],
[
55.85,
54.8
],
[
54.62,
58.67
],
[
52.58,
62.25
],
[
49.92,
64.65
],
[
48.82,
65.56
],
[
46.78,
66.47
],
[
45.1,
65.04
],
[
41.99,
62.18
],
[
34.27,
54.48
],
[
33.03,
52.35
],
[
31.79,
44.03
],
[
31.72,
40.09
],
[
31.94,
37.88
],
[
32.67,
34.56
],
[
33.18,
32.99
],
[
34.69,
29.88
],
[
36.13,
28.58
],
[
36.74,
28.19
],
[
37.5,
27.93
],
[
42.66,
27.28
],
[
44.32,
28.32
],
[
47.1,
30.66
],
[
53.38,
38.02
]
],
"dx": 0.0,
"dy": 0.0
},
{
"name": "cni",
"title": "CNI plugin",
"blurb": "Hands each pod a real IP. The wiring of the whole machine.",
"z": 0,
"points": [
[
53.7,
76.63
],
[
52.65,
74.87
],
[
50.83,
68.61
],
[
50.76,
67.43
],
[
51.56,
61.09
],
[
52.22,
59.42
],
[
54.11,
55.55
],
[
54.69,
54.63
],
[
55.92,
52.86
],
[
57.48,
52.15
],
[
58.9,
51.76
],
[
60.29,
51.5
],
[
63.68,
51.63
],
[
64.8,
51.89
],
[
66.61,
52.63
],
[
67.55,
53.81
],
[
68.5,
55.13
],
[
69.22,
56.32
],
[
71.19,
60.35
],
[
71.48,
61.69
],
[
72.35,
67.96
],
[
70.46,
73.49
],
[
64.94,
78.84
],
[
59.2,
79.23
],
[
58.54,
79.1
]
],
"dx": -10.89,
"dy": -12.01
},
{
"name": "proxy",
"title": "kube-proxy / eBPF",
"blurb": "Makes Service addresses route to real pods - the datapath's switchboard.",
"z": 0,
"points": [
[
69.95,
71.38
],
[
70.68,
69.46
],
[
76.08,
65.17
],
[
76.76,
64.65
],
[
76.85,
64.65
],
[
77.36,
64.91
],
[
77.71,
65.17
],
[
78.34,
66.08
],
[
79.11,
67.77
],
[
82.74,
79.93
],
[
83.32,
82.26
],
[
83.98,
84.98
],
[
84.05,
85.43
],
[
84.12,
86.19
],
[
84.12,
87.57
],
[
83.69,
88.58
],
[
82.96,
89.25
],
[
82.67,
89.5
],
[
81.66,
89.78
],
[
80.39,
89.65
],
[
74.21,
89.0
],
[
68.71,
87.52
],
[
68.28,
87.28
],
[
67.7,
85.97
],
[
67.33,
85.02
],
[
67.33,
84.72
],
[
67.48,
83.83
],
[
68.42,
78.83
],
[
69.44,
73.26
],
[
69.58,
72.6
]
],
"dx": -19.91,
"dy": -20.5
},
{
"name": "pod",
"title": "The pod",
"blurb": "Lands here. Everything else in this scene exists to run it.",
"z": 0,
"points": [
[
52.27,
13.48
],
[
55.2,
25.14
],
[
55.27,
28.0
],
[
54.91,
30.82
],
[
53.7,
33.01
],
[
52.83,
33.92
],
[
51.74,
34.57
],
[
50.78,
34.7
],
[
49.83,
34.44
],
[
48.87,
33.92
],
[
47.51,
32.1
],
[
46.71,
30.8
],
[
43.06,
20.97
],
[
42.4,
18.73
],
[
42.11,
13.82
],
[
42.48,
11.94
],
[
43.12,
10.35
],
[
44.6,
8.66
],
[
45.67,
8.01
],
[
46.89,
7.75
],
[
48.35,
8.4
],
[
49.95,
10.09
],
[
51.44,
12.3
]
],
"dx": -2.19,
"dy": 11.2
}
],
"stagger": 0.3
}
+832
View File
@@ -0,0 +1,832 @@
{
"scene": "orbit",
"source": "assets/raw/course-V-orbit.jpeg",
"parts": [
{
"name": "core",
"title": "The Kubernetes core",
"blurb": "Same engine underneath - everything in orbit is opinion, not replacement.",
"z": 0,
"points": [
[
40.08,
49.98
],
[
40.95,
42.34
],
[
41.53,
40.42
],
[
44.17,
35.13
],
[
45.64,
33.4
],
[
46.34,
33.0
],
[
48.12,
32.33
],
[
50.21,
32.07
],
[
52.9,
32.73
],
[
53.66,
33.0
],
[
54.28,
33.53
],
[
57.6,
37.75
],
[
58.61,
40.97
],
[
59.78,
49.07
],
[
59.85,
50.9
],
[
58.83,
58.37
],
[
58.54,
59.52
],
[
55.88,
64.6
],
[
54.63,
66.2
],
[
51.53,
67.67
],
[
50.38,
68.07
],
[
49.47,
67.93
],
[
45.8,
66.47
],
[
45.2,
66.07
],
[
41.53,
59.74
],
[
40.73,
55.02
]
],
"dx": 0.0,
"dy": 0.0
},
{
"name": "scc",
"title": "SCC",
"blurb": "Admission: what a pod may BE. restricted-v2 runs you as a random non-root UID - your image has to cope.",
"z": 0,
"points": [
[
52.91,
28.07
],
[
48.91,
29.0
],
[
43.57,
28.46
],
[
42.99,
27.13
],
[
42.19,
22.68
],
[
42.11,
18.9
],
[
42.62,
15.27
],
[
43.13,
13.62
],
[
44.44,
10.72
],
[
48.82,
5.93
],
[
49.72,
5.53
],
[
50.7,
5.67
],
[
51.88,
6.07
],
[
56.36,
9.1
],
[
57.89,
12.61
],
[
59.34,
17.03
],
[
59.41,
19.47
],
[
58.54,
21.06
],
[
54.3,
27.13
],
[
53.2,
27.93
]
],
"dx": -0.22,
"dy": 23.11
},
{
"name": "routes",
"title": "Routes",
"blurb": "L7 exposure that predates Ingress - and still rules here.",
"z": 0,
"points": [
[
63.33,
31.51
],
[
63.33,
13.15
],
[
63.41,
12.63
],
[
63.84,
11.46
],
[
64.71,
10.2
],
[
65.21,
9.8
],
[
65.89,
9.27
],
[
66.63,
8.73
],
[
66.9,
8.6
],
[
67.42,
9.0
],
[
68.65,
10.07
],
[
71.1,
12.07
],
[
73.66,
14.15
],
[
74.38,
15.61
],
[
74.96,
17.16
],
[
75.11,
17.7
],
[
75.47,
19.25
],
[
75.69,
20.26
],
[
75.24,
37.8
],
[
74.7,
38.6
],
[
73.59,
39.8
],
[
72.67,
40.2
],
[
72.43,
40.07
],
[
65.27,
33.93
],
[
64.0,
32.6
]
],
"dx": -13.89,
"dy": 19.09
},
{
"name": "operators",
"title": "Operators",
"blurb": "OLM - the platform runs itself. The rings never stop turning.",
"z": 0,
"points": [
[
70.46,
52.73
],
[
70.46,
50.07
],
[
70.53,
49.59
],
[
70.75,
48.32
],
[
70.82,
48.0
],
[
71.62,
46.14
],
[
74.79,
41.27
],
[
76.32,
40.47
],
[
76.87,
40.2
],
[
77.78,
39.93
],
[
78.59,
39.93
],
[
79.67,
40.07
],
[
81.24,
40.6
],
[
81.81,
40.87
],
[
82.49,
41.27
],
[
83.37,
42.2
],
[
84.12,
44.21
],
[
84.27,
44.77
],
[
84.56,
46.34
],
[
84.56,
47.17
],
[
84.05,
61.15
],
[
83.94,
61.8
],
[
83.32,
63.27
],
[
83.01,
63.8
],
[
82.14,
65.0
],
[
80.72,
65.93
],
[
80.46,
66.07
],
[
80.12,
66.07
],
[
79.01,
65.67
],
[
78.5,
65.27
],
[
71.77,
56.68
],
[
70.82,
54.49
]
],
"dx": -20.22,
"dy": -1.54
},
{
"name": "console",
"title": "oc + console",
"blurb": "kubectl, extended - the operator's handheld.",
"z": 0,
"points": [
[
54.69,
83.27
],
[
53.96,
81.7
],
[
53.89,
80.33
],
[
53.89,
80.03
],
[
53.96,
79.73
],
[
54.69,
78.23
],
[
58.76,
71.64
],
[
59.88,
69.4
],
[
61.41,
68.2
],
[
64.26,
66.2
],
[
65.15,
65.67
],
[
65.69,
65.67
],
[
66.5,
65.8
],
[
67.25,
66.33
],
[
69.73,
68.62
],
[
71.11,
70.86
],
[
71.84,
71.93
],
[
72.06,
72.27
],
[
72.35,
72.78
],
[
72.78,
73.75
],
[
72.93,
74.37
],
[
72.93,
75.46
],
[
72.86,
76.07
],
[
72.2,
77.46
],
[
71.77,
78.1
],
[
70.31,
80.12
],
[
67.17,
84.87
],
[
63.61,
87.53
],
[
61.58,
89.27
],
[
61.07,
89.67
],
[
60.81,
89.8
],
[
60.19,
89.8
],
[
59.83,
89.67
],
[
59.64,
89.53
],
[
56.58,
85.86
]
],
"dx": -9.64,
"dy": -19.97
},
{
"name": "machineconfig",
"title": "MachineConfig",
"blurb": "The OS underneath is immutable - changed by declaration, never by SSH.",
"z": 0,
"points": [
[
22.93,
78.51
],
[
22.93,
69.78
],
[
23.51,
68.51
],
[
23.73,
68.18
],
[
28.0,
62.6
],
[
31.84,
59.4
],
[
32.19,
59.13
],
[
32.36,
59.13
],
[
32.88,
59.53
],
[
36.94,
62.87
],
[
37.66,
63.4
],
[
39.06,
65.68
],
[
39.28,
66.06
],
[
39.86,
67.44
],
[
39.86,
72.34
],
[
39.79,
76.62
],
[
39.28,
77.79
],
[
39.06,
78.14
],
[
38.34,
79.26
],
[
34.89,
83.4
],
[
31.83,
85.93
],
[
31.22,
86.47
],
[
30.45,
87.13
],
[
30.1,
87.13
],
[
29.57,
86.73
],
[
28.56,
85.93
],
[
25.47,
82.69
],
[
23.58,
79.95
]
],
"dx": 13.4,
"dy": -16.73
},
{
"name": "sno",
"title": "SNO",
"blurb": "The whole cluster on one box at the edge - same promises, one machine.",
"z": 0,
"points": [
[
24.24,
24.33
],
[
25.54,
21.03
],
[
29.21,
17.53
],
[
29.95,
17.0
],
[
30.94,
16.6
],
[
31.3,
16.6
],
[
31.66,
16.87
],
[
32.48,
17.53
],
[
36.23,
20.91
],
[
36.59,
21.57
],
[
37.17,
23.07
],
[
37.17,
33.35
],
[
37.1,
33.69
],
[
36.67,
34.81
],
[
35.94,
36.66
],
[
31.66,
40.33
],
[
31.48,
40.47
],
[
30.41,
40.87
],
[
29.88,
40.73
],
[
25.91,
37.59
],
[
24.74,
35.62
],
[
24.31,
34.54
],
[
24.24,
33.72
]
],
"dx": 13.85,
"dy": 15.36
}
]
}
+598
View File
@@ -0,0 +1,598 @@
{
"scene": "pod",
"source": "assets/raw/course-III-pod.jpeg",
"parts": [
{
"name": "shell-l",
"title": "The jacket",
"blurb": "A pod is not a container - it is a jacket around one or more, sharing a network namespace.",
"z": 0,
"points": [
[
18.93,
25.51
],
[
19.37,
18.71
],
[
21.26,
12.64
],
[
23.35,
9.44
],
[
26.72,
7.62
],
[
29.25,
7.88
],
[
30.81,
8.66
],
[
35.8,
12.3
],
[
41.24,
18.18
],
[
44.01,
23.92
],
[
44.59,
27.52
],
[
44.37,
30.68
],
[
43.06,
38.42
],
[
40.88,
44.99
],
[
38.28,
47.72
],
[
35.52,
47.72
],
[
33.35,
46.29
],
[
23.22,
38.21
],
[
21.98,
36.33
],
[
20.53,
33.23
],
[
19.29,
28.97
]
],
"dx": 9.1,
"dy": 10.23
},
{
"name": "shell-r",
"title": "One IP, shared fate",
"blurb": "One address for everything inside; localhost between friends; volumes shared.",
"z": 0,
"points": [
[
59.05,
29.58
],
[
59.19,
24.61
],
[
59.63,
22.68
],
[
61.88,
17.94
],
[
65.68,
13.21
],
[
72.69,
7.88
],
[
75.23,
7.88
],
[
77.33,
8.92
],
[
79.4,
11.13
],
[
81.21,
15.58
],
[
82.38,
22.19
],
[
82.16,
27.39
],
[
81.29,
31.03
],
[
78.82,
36.3
],
[
71.07,
43.69
],
[
67.61,
45.9
],
[
65.55,
45.64
],
[
63.93,
44.34
],
[
61.52,
40.91
],
[
60.43,
37.52
],
[
59.41,
32.83
]
],
"dx": -10.39,
"dy": 10.66
},
{
"name": "core",
"title": "The app container",
"blurb": "Your process - the reason the jacket exists.",
"z": 0,
"points": [
[
57.3,
73.63
],
[
41.34,
73.63
],
[
43.35,
28.47
],
[
43.49,
27.05
],
[
44.15,
24.92
],
[
44.58,
23.87
],
[
44.88,
23.3
],
[
46.37,
21.68
],
[
47.62,
21.29
],
[
48.23,
21.16
],
[
51.06,
21.16
],
[
51.75,
21.29
],
[
52.25,
21.42
],
[
52.66,
21.55
],
[
54.83,
22.47
],
[
55.12,
23.0
],
[
55.34,
23.46
],
[
55.78,
24.65
],
[
56.21,
26.08
],
[
56.29,
26.43
],
[
56.51,
27.76
],
[
56.58,
28.58
]
],
"dx": 0.0,
"dy": 0.0
},
{
"name": "sidecar",
"title": "The sidecar",
"blurb": "Rides along: proxy, logs, reload. Same jacket, its own container.",
"z": 0,
"points": [
[
55.18,
67.64
],
[
54.79,
39.65
],
[
55.04,
38.87
],
[
56.01,
37.56
],
[
56.19,
37.43
],
[
56.82,
37.04
],
[
57.87,
36.52
],
[
58.3,
36.39
],
[
59.84,
36.39
],
[
60.27,
36.52
],
[
60.87,
36.78
],
[
61.66,
37.17
],
[
62.01,
37.43
],
[
62.76,
38.48
],
[
63.17,
39.26
],
[
63.28,
39.52
],
[
63.4,
39.91
],
[
63.14,
65.69
],
[
63.03,
66.47
],
[
62.75,
67.64
],
[
62.66,
67.91
],
[
61.89,
69.08
],
[
61.06,
70.12
],
[
60.73,
70.25
],
[
56.87,
69.08
],
[
55.79,
68.69
],
[
55.27,
67.91
]
],
"dx": -4.63,
"dy": -2.57
},
{
"name": "gate",
"title": "initContainer",
"blurb": "Runs first, to completion - the gatekeeper underneath everything.",
"z": 0,
"points": [
[
40.44,
69.49
],
[
58.32,
70.86
],
[
58.76,
71.32
],
[
59.27,
72.35
],
[
59.27,
76.92
],
[
58.83,
77.77
],
[
48.69,
91.34
],
[
48.43,
91.47
],
[
47.99,
91.47
],
[
46.94,
91.34
],
[
39.57,
84.6
],
[
36.74,
80.73
],
[
36.3,
79.82
],
[
36.3,
79.22
],
[
36.95,
75.6
],
[
37.03,
75.3
],
[
37.24,
74.68
],
[
37.68,
73.72
],
[
39.28,
71.25
]
],
"dx": 0.72,
"dy": -7.57
},
{
"name": "discs",
"title": "Volumes",
"blurb": "Shared mounts - the only place anything survives the writable layer's death.",
"z": 0,
"points": [
[
73.95,
71.5
],
[
73.87,
84.4
],
[
73.66,
85.31
],
[
71.92,
87.83
],
[
71.05,
88.35
],
[
70.13,
88.74
],
[
68.95,
89.0
],
[
66.53,
88.87
],
[
64.78,
88.22
],
[
63.9,
87.7
],
[
62.39,
85.14
],
[
62.1,
83.43
],
[
62.17,
71.12
],
[
62.39,
70.24
],
[
63.88,
67.64
],
[
64.96,
66.99
],
[
66.16,
66.6
],
[
69.32,
66.47
],
[
70.32,
66.73
],
[
71.64,
67.38
],
[
72.35,
67.9
],
[
73.66,
70.07
],
[
73.87,
70.98
]
],
"dx": -9.06,
"dy": -14.92
}
]
}
+679
View File
@@ -0,0 +1,679 @@
{
"scene": "surround",
"source": "assets/raw/course-00-surround.jpeg",
"parts": [
{
"name": "frag-1",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
39.79,
24.36
],
[
24.29,
36.92
],
[
24.12,
36.79
],
[
20.82,
32.32
],
[
20.02,
30.61
],
[
19.95,
30.17
],
[
19.95,
29.3
],
[
20.02,
29.02
],
[
24.96,
10.68
],
[
25.33,
9.18
],
[
25.98,
7.51
],
[
26.2,
7.09
],
[
29.03,
5.92
],
[
29.39,
5.79
],
[
32.41,
5.01
],
[
35.97,
4.36
],
[
36.15,
4.36
],
[
36.96,
4.75
],
[
38.18,
6.31
],
[
41.03,
13.84
],
[
41.1,
14.14
],
[
41.17,
14.59
],
[
41.24,
15.19
],
[
41.24,
16.09
],
[
41.17,
17.59
],
[
40.44,
22.6
],
[
40.37,
22.91
],
[
40.23,
23.38
]
],
"dx": -22.34,
"dy": -37.33
},
{
"name": "frag-2",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
78.82,
11.32
],
[
79.76,
19.27
],
[
79.47,
20.96
],
[
78.6,
25.58
],
[
77.29,
31.71
],
[
76.27,
34.18
],
[
73.1,
38.35
],
[
72.43,
39.13
],
[
71.03,
38.87
],
[
68.94,
37.96
],
[
63.26,
28.81
],
[
60.36,
16.47
],
[
61.15,
14.67
],
[
63.99,
10.4
],
[
70.66,
5.92
],
[
72.86,
4.75
],
[
74.12,
5.01
],
[
75.16,
6.05
],
[
77.3,
8.66
]
],
"dx": 23.92,
"dy": -35.63
},
{
"name": "frag-3",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
81.09,
35.74
],
[
81.54,
33.4
],
[
82.29,
32.36
],
[
83.51,
32.09
],
[
84.57,
33.4
],
[
85.92,
35.74
],
[
86.19,
36.65
],
[
86.92,
39.91
],
[
88.7,
58.69
],
[
88.77,
60.51
],
[
87.65,
62.83
],
[
85.99,
64.65
],
[
82.83,
68.43
],
[
82.42,
68.82
],
[
82.0,
68.95
],
[
81.32,
68.69
],
[
79.51,
67.51
],
[
78.59,
66.34
],
[
78.35,
65.43
]
],
"dx": 38.73,
"dy": -0.36
},
{
"name": "frag-4",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
22.02,
62.57
],
[
19.25,
62.31
],
[
18.29,
62.18
],
[
17.25,
61.52
],
[
11.74,
58.23
],
[
10.94,
57.04
],
[
10.06,
55.83
],
[
8.83,
53.01
],
[
8.83,
52.86
],
[
8.97,
52.25
],
[
9.7,
49.32
],
[
10.36,
46.59
],
[
10.94,
44.05
],
[
11.37,
42.08
],
[
11.76,
40.43
],
[
12.97,
39.0
],
[
13.16,
38.87
],
[
14.2,
38.34
],
[
16.1,
37.43
],
[
16.45,
37.3
],
[
17.51,
37.3
],
[
18.22,
37.56
],
[
18.96,
38.08
],
[
23.58,
42.54
],
[
24.09,
43.72
],
[
24.53,
45.17
],
[
24.53,
45.48
],
[
24.17,
58.32
],
[
24.09,
58.65
],
[
23.8,
59.66
],
[
23.28,
61.39
],
[
22.57,
62.31
]
],
"dx": -37.8,
"dy": -2.51
},
{
"name": "frag-5",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
58.97,
94.23
],
[
59.63,
87.99
],
[
60.43,
86.45
],
[
72.75,
66.34
],
[
73.15,
65.82
],
[
74.44,
64.26
],
[
74.6,
64.13
],
[
74.94,
64.13
],
[
76.21,
65.82
],
[
76.32,
66.08
],
[
76.42,
66.69
],
[
76.49,
67.5
],
[
76.49,
68.44
],
[
76.42,
69.21
],
[
74.53,
78.28
],
[
74.46,
78.62
],
[
73.87,
80.04
],
[
73.51,
80.82
],
[
67.19,
88.87
],
[
61.45,
96.16
],
[
60.41,
96.81
],
[
60.33,
96.81
],
[
59.56,
95.98
],
[
58.97,
94.67
]
],
"dx": 20.61,
"dy": 33.81
},
{
"name": "frag-6",
"title": "",
"blurb": "",
"z": 0,
"points": [
[
34.31,
94.86
],
[
25.75,
88.87
],
[
23.95,
87.0
],
[
23.51,
85.9
],
[
23.07,
84.7
],
[
23.0,
84.38
],
[
22.42,
81.2
],
[
20.97,
73.32
],
[
20.97,
72.87
],
[
21.55,
71.59
],
[
22.93,
69.29
],
[
23.07,
69.09
],
[
23.29,
68.86
],
[
24.06,
68.16
],
[
27.64,
66.99
],
[
29.15,
66.73
],
[
29.23,
66.73
],
[
34.41,
69.33
],
[
34.92,
69.59
],
[
35.13,
69.73
],
[
35.65,
71.1
],
[
36.96,
76.04
],
[
37.39,
77.63
],
[
37.46,
77.94
],
[
38.34,
83.44
],
[
38.34,
84.04
],
[
37.06,
92.25
],
[
36.85,
92.64
],
[
36.34,
93.56
],
[
35.5,
94.73
],
[
35.32,
94.86
]
],
"dx": -22.51,
"dy": 31.74
}
],
"reverse": true
}
+671
View File
@@ -0,0 +1,671 @@
{
"scene": "traffic",
"source": "assets/raw/course-IV-traffic.jpeg",
"parts": [
{
"name": "client",
"title": "The client",
"blurb": "Knows one stable name. Never a pod.",
"z": 0,
"points": [
[
22.57,
20.71
],
[
21.98,
23.54
],
[
21.18,
25.13
],
[
20.04,
25.93
],
[
19.41,
26.33
],
[
18.61,
26.47
],
[
17.83,
26.47
],
[
17.23,
26.33
],
[
16.31,
25.93
],
[
15.88,
25.67
],
[
15.53,
25.4
],
[
14.35,
23.73
],
[
13.92,
21.94
],
[
13.77,
21.15
],
[
13.55,
19.81
],
[
13.48,
18.9
],
[
13.41,
17.63
],
[
13.55,
15.62
],
[
14.21,
12.16
],
[
15.23,
10.6
],
[
16.03,
9.93
],
[
16.84,
9.53
],
[
17.63,
9.27
],
[
18.8,
9.27
],
[
19.09,
9.4
],
[
19.81,
9.8
],
[
20.45,
10.2
],
[
20.79,
10.47
],
[
22.28,
13.34
],
[
22.49,
14.53
],
[
22.64,
15.83
],
[
22.71,
17.09
],
[
22.71,
18.17
]
],
"dx": 18.88,
"dy": 17.42
},
{
"name": "lb",
"title": "LoadBalancer",
"blurb": "The edge - divides incoming flow before anything else sees it.",
"z": 0,
"points": [
[
19.29,
24.08
],
[
26.55,
16.47
],
[
27.85,
15.53
],
[
28.89,
15.8
],
[
29.33,
16.2
],
[
36.81,
29.42
],
[
37.32,
30.64
],
[
37.25,
32.31
],
[
35.14,
38.07
],
[
33.61,
40.42
],
[
33.03,
41.0
],
[
31.16,
41.8
],
[
28.66,
42.33
],
[
17.48,
33.98
],
[
16.75,
32.74
],
[
16.53,
31.08
],
[
16.61,
29.28
],
[
17.77,
26.68
]
],
"dx": 13.92,
"dy": 10.93
},
{
"name": "ingress",
"title": "Ingress / Gateway",
"blurb": "Host- and path-routing above the Services - the open doorframe.",
"z": 0,
"points": [
[
47.67,
14.2
],
[
47.91,
46.2
],
[
47.75,
47.13
],
[
47.65,
47.4
],
[
46.73,
48.73
],
[
46.47,
49.0
],
[
46.04,
49.4
],
[
44.01,
51.27
],
[
38.43,
56.6
],
[
37.8,
57.13
],
[
37.64,
57.13
],
[
37.32,
56.87
],
[
36.75,
56.33
],
[
36.0,
55.13
],
[
35.28,
31.31
],
[
35.28,
22.81
],
[
35.36,
22.13
],
[
35.43,
21.77
],
[
35.57,
21.22
],
[
36.37,
20.07
],
[
36.66,
19.8
],
[
37.13,
19.4
],
[
38.69,
18.07
],
[
44.74,
12.47
],
[
45.3,
11.93
],
[
45.46,
11.8
],
[
45.78,
11.8
],
[
46.12,
12.07
],
[
46.79,
12.73
],
[
47.05,
13.0
],
[
47.57,
13.8
]
],
"dx": 5.89,
"dy": 8.42
},
{
"name": "prism",
"title": "The Service",
"blurb": "The stable name in front of the churn - a virtual IP that outlives every pod behind it.",
"z": 0,
"points": [
[
46.91,
58.09
],
[
46.91,
41.92
],
[
47.64,
39.86
],
[
48.18,
39.27
],
[
50.41,
37.0
],
[
51.6,
35.67
],
[
52.3,
34.87
],
[
52.56,
34.87
],
[
53.27,
35.67
],
[
55.73,
38.33
],
[
56.68,
39.27
],
[
57.23,
39.84
],
[
57.96,
41.91
],
[
57.96,
58.1
],
[
57.24,
59.13
],
[
56.43,
59.93
],
[
55.01,
61.4
],
[
52.56,
64.07
],
[
52.39,
64.2
],
[
49.2,
60.73
],
[
48.14,
59.67
],
[
47.73,
59.27
]
],
"dx": 0.0,
"dy": 0.0
},
{
"name": "ready",
"title": "Pod - ready",
"blurb": "Passing readiness: in the pool, receiving traffic.",
"z": 0,
"points": [
[
72.06,
57.64
],
[
72.42,
62.82
],
[
72.06,
64.38
],
[
71.04,
66.97
],
[
70.1,
68.74
],
[
64.94,
74.47
],
[
62.61,
75.93
],
[
60.78,
76.47
],
[
59.02,
76.33
],
[
57.08,
75.21
],
[
55.7,
72.32
],
[
55.34,
69.17
],
[
55.78,
65.94
],
[
56.43,
62.01
],
[
56.72,
61.04
],
[
58.25,
57.7
],
[
62.75,
52.87
],
[
66.21,
50.33
],
[
68.19,
50.07
],
[
69.45,
50.6
],
[
70.75,
52.24
],
[
71.33,
53.87
]
],
"dx": -6.34,
"dy": -7.73
},
{
"name": "notready",
"title": "Pod - not ready",
"blurb": "Failing readiness: silently removed. That is the feature - and the first place to look.",
"z": 0,
"points": [
[
67.48,
83.79
],
[
67.62,
80.2
],
[
68.2,
77.99
],
[
69.37,
75.48
],
[
70.31,
73.95
],
[
77.1,
67.27
],
[
78.65,
66.2
],
[
80.23,
66.07
],
[
81.04,
66.33
],
[
81.7,
66.73
],
[
83.25,
69.93
],
[
83.76,
71.91
],
[
83.9,
73.17
],
[
83.83,
76.6
],
[
83.54,
78.0
],
[
82.16,
81.39
],
[
81.36,
82.65
],
[
74.57,
89.8
],
[
73.08,
90.87
],
[
71.94,
91.13
],
[
70.96,
91.0
],
[
70.03,
90.6
],
[
69.37,
90.0
],
[
68.06,
86.87
],
[
67.7,
85.39
]
],
"dx": -9.32,
"dy": -11.61
}
],
"stagger": 0.22
}
+16 -1
View File
@@ -62,6 +62,11 @@ def emit(name, tag, spec):
for part in spec["parts"]:
xs = np.concatenate([comps[i]["xs"] for i in part["comps"]])
ys = np.concatenate([comps[i]["ys"] for i in part["comps"]])
if "clip" in part:
x0, y0, x1, y1 = part["clip"]
W2, H2 = W, lum.shape[0]
keep = (xs >= x0*W2) & (xs < x1*W2) & (ys >= y0*H2) & (ys < y1*H2)
xs, ys = xs[keep], ys[keep]
pts = np.stack([xs, ys], 1).astype(np.float64)
hull = ConvexHull(pts)
poly = pts[hull.vertices]
@@ -77,6 +82,9 @@ def emit(name, tag, spec):
z=part.get("z", 0),
points=[[round(float(x) / W * 100, 2), round(float(y) / h * 100, 2)] for x, y in poly],
cx=round(float(np.mean(xs)) / W * 100, 2), cy=round(float(np.mean(ys)) / h * 100, 2)))
if "anchor_xy" in spec:
ax, ay = spec["anchor_xy"]
else:
ax, ay = next((p["cx"], p["cy"]) for p in parts if p["name"] == spec["anchor"])
for p in parts:
nest = spec.get("nest_overrides", {}).get(p["name"], spec.get("nest", 0.72))
@@ -86,6 +94,8 @@ def emit(name, tag, spec):
man = dict(scene=tag, source=f"assets/raw/{name}", parts=parts)
if "stagger" in spec:
man["stagger"] = spec["stagger"]
if spec.get("reverse"):
man["reverse"] = True
out = ROOT / "pilot" / f"manifest-{tag}.json"
out.write_text(json.dumps(man, indent=1))
print(f"manifest-{tag}.json: {len(parts)} parts ->",
@@ -94,7 +104,12 @@ def emit(name, tag, spec):
if __name__ == "__main__":
mode, scenes_json, outdir = sys.argv[1], sys.argv[2], sys.argv[3]
scenes = json.loads(pathlib.Path(scenes_json).read_text()) if mode == "emit" else None
heroes = [("course-II-cluster.jpeg", "cluster"),
heroes = [("course-00-surround.jpeg", "surround"),
("course-III-node.jpeg", "node"),
("course-III-pod.jpeg", "pod"),
("course-IV-traffic.jpeg", "traffic"),
("course-V-orbit.jpeg", "orbit"),
("course-II-cluster.jpeg", "cluster"),
("course-VI-gitops.jpeg", "gitops"),
("course-VII-supply-chain.jpeg", "supply"),
("course-VIII-helm-press.jpeg", "helm")]