635 lines
46 KiB
HTML
635 lines
46 KiB
HTML
<title>The Exploded Cluster — a five-course teardown of Kubernetes, OpenShift & Docker</title>
|
|
<style>
|
|
/* ============ THE EXPLODED CLUSTER v2 — five-course 3D teardown ============
|
|
GSAP 3.13 + ScrollTrigger inlined (100% free incl. plugins since 2025).
|
|
Committed single dark world: control-room hologram. Progressive enhancement:
|
|
without JS everything renders assembled + readable; JS adds the 3D theatre. */
|
|
:root {
|
|
--void: #070b14; --panel: #0d1424; --panel-2: #111a2e; --line: #1d2a44;
|
|
--cyan: #3fbaf5; --cyan-dim: #1f6f9c; --magenta: #e879f9; --violet: #8b5cf6;
|
|
--amber: #fbbf24; --green: #34d399; --text: #dbe7f3; --muted: #93a9c2;
|
|
--mono: ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
|
|
--sans: system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
}
|
|
html { scroll-behavior: smooth; }
|
|
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
|
|
body {
|
|
background: var(--void); color: var(--text); font-family: var(--sans);
|
|
line-height: 1.6; overflow-x: hidden; margin: 0;
|
|
}
|
|
#stars { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
|
|
body::before {
|
|
content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
background:
|
|
radial-gradient(ellipse 80% 50% at 50% -10%, rgba(63,186,245,.09), transparent 60%),
|
|
linear-gradient(rgba(63,186,245,.03) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(63,186,245,.03) 1px, transparent 1px);
|
|
background-size: 100% 100%, 44px 44px, 44px 44px;
|
|
}
|
|
main { position: relative; z-index: 1; }
|
|
|
|
#bar {
|
|
position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 60;
|
|
background: linear-gradient(90deg, var(--cyan), var(--magenta));
|
|
transform-origin: 0 50%; transform: scaleX(0);
|
|
}
|
|
|
|
nav { position: fixed; left: 18px; top: 50%; transform: translateY(-50%); z-index: 50;
|
|
display: flex; flex-direction: column; gap: 10px; }
|
|
nav a { display: flex; align-items: center; gap: 9px; color: var(--muted); text-decoration: none;
|
|
font: 600 10px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; }
|
|
nav a::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
|
|
border: 1px solid var(--cyan-dim); transition: background .3s, box-shadow .3s; }
|
|
nav a span { opacity: 0; transform: translateX(-4px); transition: opacity .25s, transform .25s; }
|
|
nav a:hover span, nav a:focus-visible span, nav a.on span { opacity: 1; transform: none; }
|
|
nav a.on { color: var(--cyan); }
|
|
nav a.on::before { background: var(--cyan); box-shadow: 0 0 10px var(--cyan); }
|
|
@media (max-width: 900px) { nav { display: none; } }
|
|
|
|
/* ---------- courses ---------- */
|
|
.course-tag {
|
|
display: inline-flex; align-items: center; gap: 10px;
|
|
font: 700 11px/1 var(--mono); letter-spacing: .3em; text-transform: uppercase;
|
|
color: var(--magenta); border: 1px solid rgba(232,121,249,.35); border-radius: 999px;
|
|
padding: 7px 16px; background: rgba(232,121,249,.06); margin-bottom: 16px;
|
|
}
|
|
.eyebrow { font: 700 11px/1 var(--mono); letter-spacing: .3em; text-transform: uppercase;
|
|
color: var(--cyan); display: flex; align-items: center; gap: 14px; }
|
|
.eyebrow::after { content: ""; height: 1px; flex: 1;
|
|
background: linear-gradient(90deg, var(--cyan-dim), transparent); }
|
|
h2 { font-size: clamp(1.7rem, 4.4vw, 2.7rem); font-weight: 800; letter-spacing: -.015em;
|
|
margin: 14px 0 18px; text-wrap: balance; }
|
|
.copy { max-width: 68ch; color: var(--muted); font-size: 1.02rem; }
|
|
.copy b { color: var(--text); font-weight: 650; }
|
|
section { padding: 12vh 20px; max-width: 1060px; margin: 0 auto; position: relative; }
|
|
|
|
/* term chips + tooltip (unchanged machinery) */
|
|
.term { font: 600 .86em/1 var(--mono); letter-spacing: .02em; color: var(--cyan);
|
|
background: rgba(63,186,245,.09); border: 1px solid rgba(63,186,245,.28); border-radius: 6px;
|
|
padding: 2px 7px 3px; cursor: help; white-space: nowrap; transition: background .2s, box-shadow .2s;
|
|
-webkit-tap-highlight-color: transparent; }
|
|
button.term { font-family: var(--mono); }
|
|
.term:hover, .term:focus-visible { background: rgba(63,186,245,.18); box-shadow: 0 0 14px rgba(63,186,245,.25); outline: none; }
|
|
.term:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
|
|
.term.m { color: var(--magenta); background: rgba(232,121,249,.09); border-color: rgba(232,121,249,.3); }
|
|
.term.m:hover, .term.m:focus-visible { background: rgba(232,121,249,.18); box-shadow: 0 0 14px rgba(232,121,249,.22); }
|
|
.term.v { color: #b39dfa; background: rgba(139,92,246,.12); border-color: rgba(139,92,246,.35); }
|
|
#tip { position: fixed; z-index: 70; max-width: 300px; pointer-events: none;
|
|
background: #0b1322; border: 1px solid var(--cyan-dim); border-radius: 10px;
|
|
padding: 10px 13px; font-size: .84rem; line-height: 1.5; color: var(--text);
|
|
box-shadow: 0 12px 40px rgba(0,0,0,.6), 0 0 22px rgba(63,186,245,.14);
|
|
opacity: 0; transform: translateY(5px); transition: opacity .18s, transform .18s; }
|
|
#tip.show { opacity: 1; transform: none; }
|
|
#tip b { color: var(--cyan); display: block; font: 700 11px/1 var(--mono);
|
|
letter-spacing: .12em; text-transform: uppercase; margin-bottom: 6px; }
|
|
|
|
.phrase { margin: 30px 0 0; padding: 18px 22px; border-radius: 12px;
|
|
border: 1px solid rgba(63,186,245,.3); max-width: 60ch;
|
|
background: linear-gradient(115deg, rgba(63,186,245,.10), rgba(232,121,249,.07)); }
|
|
.phrase small { display: block; font: 700 10px/1 var(--mono); letter-spacing: .26em;
|
|
text-transform: uppercase; color: var(--cyan); margin-bottom: 8px; }
|
|
.phrase q { font-size: 1.12rem; font-weight: 700; color: var(--text); quotes: "“" "”"; }
|
|
.fnote { margin: 14px 0 0; padding: 12px 16px; border-left: 3px solid var(--amber);
|
|
background: rgba(251,191,36,.06); border-radius: 0 10px 10px 0; color: var(--muted);
|
|
font-size: .92rem; max-width: 60ch; }
|
|
.fnote b { color: var(--amber); font: 700 10px/1 var(--mono); letter-spacing: .2em;
|
|
text-transform: uppercase; margin-right: 8px; }
|
|
|
|
/* ---------- COURSE 00: the terminal (pinned intro) ---------- */
|
|
#aperitif { max-width: none; padding: 0; }
|
|
.pinstage { height: 100vh; display: grid; place-items: center; overflow: hidden;
|
|
perspective: 1100px; position: relative; }
|
|
.crt {
|
|
width: min(720px, 92vw); border-radius: 14px; position: relative;
|
|
background: #050910; border: 1px solid #16324a;
|
|
box-shadow: 0 0 0 1px rgba(63,186,245,.12), 0 30px 90px rgba(0,0,0,.7), 0 0 60px rgba(63,186,245,.12);
|
|
transform-style: preserve-3d; will-change: transform;
|
|
}
|
|
.crt-head { display: flex; align-items: center; gap: 8px; padding: 11px 15px;
|
|
border-bottom: 1px solid #14293d; font: 600 11px/1 var(--mono); color: var(--cyan-dim);
|
|
letter-spacing: .12em; }
|
|
.crt-head i { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }
|
|
.crt-head i:nth-child(1){ background:#ff5f57; } .crt-head i:nth-child(2){ background:#febc2e; }
|
|
.crt-head i:nth-child(3){ background:#28c840; }
|
|
.crt-head span { margin-left: 8px; }
|
|
.crt-body { padding: 18px 20px 22px; font: 500 13.5px/1.75 var(--mono); min-height: 340px;
|
|
color: #b7d7ee; white-space: pre-wrap; }
|
|
.crt-body .ln { display: block; min-height: 1.7em; }
|
|
.crt-body .p1 { color: var(--green); } /* prompt */
|
|
.crt-body .cy { color: var(--cyan); }
|
|
.crt-body .mg { color: var(--magenta); }
|
|
.crt-body .dm { color: #5e7a94; }
|
|
.crt-body .ok { color: var(--green); }
|
|
.caret { display: inline-block; width: 8px; height: 15px; background: var(--cyan);
|
|
vertical-align: -2px; animation: blink 1s steps(1) infinite; }
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
.crt::after { /* scanlines */
|
|
content: ""; position: absolute; inset: 0; border-radius: 14px; pointer-events: none;
|
|
background: repeating-linear-gradient(0deg, rgba(63,186,245,.045) 0 1px, transparent 1px 3px);
|
|
mix-blend-mode: screen;
|
|
}
|
|
#skip { position: absolute; right: 22px; bottom: 20px; z-index: 5;
|
|
font: 600 10px/1 var(--mono); letter-spacing: .22em; text-transform: uppercase;
|
|
color: var(--muted); background: transparent; border: 1px solid var(--line);
|
|
border-radius: 999px; padding: 8px 14px; cursor: pointer; }
|
|
#skip:hover, #skip:focus-visible { color: var(--cyan); border-color: var(--cyan-dim); outline: none; }
|
|
.scrollcue { position: absolute; bottom: 26px; left: 50%; transform: translateX(-50%);
|
|
font: 600 11px/1.8 var(--mono); letter-spacing: .24em; text-transform: uppercase;
|
|
color: var(--cyan); opacity: 0; text-align: center; }
|
|
.scrollcue i { display: block; font-style: normal; animation: bob 2s ease-in-out infinite; }
|
|
@keyframes bob { 50% { transform: translateY(7px); } }
|
|
|
|
/* the big title revealed behind the exploding terminal */
|
|
.titlewrap { position: absolute; inset: 0; display: grid; place-content: center;
|
|
text-align: center; pointer-events: none; transform-style: preserve-3d; }
|
|
.bigtitle { font-weight: 800; letter-spacing: -.02em; line-height: .96; margin: 0;
|
|
font-size: clamp(2.6rem, 9vw, 6.6rem); transform-style: preserve-3d; }
|
|
.bigtitle .x1 { display: block; color: var(--text); }
|
|
.bigtitle .x2 { display: block;
|
|
background: linear-gradient(92deg, var(--cyan) 15%, var(--magenta) 85%);
|
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
filter: drop-shadow(0 0 26px rgba(63,186,245,.35)); }
|
|
.bigtitle .ch { display: inline-block; transform-style: preserve-3d; will-change: transform; }
|
|
.menuline { margin-top: 26px; color: var(--muted); font: 600 12px/1.6 var(--mono);
|
|
letter-spacing: .18em; text-transform: uppercase; }
|
|
.menuline b { color: var(--magenta); }
|
|
|
|
/* ---------- shared stage machinery (v2: real depth) ---------- */
|
|
.stage { position: relative; margin: 44px auto 0; border: 1px solid var(--line);
|
|
border-radius: 16px; overflow: hidden; perspective: 1000px;
|
|
background: radial-gradient(60% 90% at 50% 0%, rgba(63,186,245,.05), transparent), var(--panel); }
|
|
.stage .scene { position: absolute; inset: 0; transform-style: preserve-3d; }
|
|
.stage svg.wires { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
.stage svg.wires line { stroke: var(--cyan-dim); stroke-width: 1.5; fill: none;
|
|
vector-effect: non-scaling-stroke; opacity: 0; transition: opacity .8s .9s; }
|
|
.live .stage svg.wires line { opacity: .8; }
|
|
.stage svg.wires .flow { stroke: var(--cyan); stroke-dasharray: 6 10; }
|
|
.live .stage svg.wires .flow { animation: dashflow 1.4s linear infinite; }
|
|
@keyframes dashflow { to { stroke-dashoffset: -16; } }
|
|
|
|
.p { position: absolute; will-change: transform;
|
|
transform: translate(calc(-50% + var(--sx,0px)), calc(-50% + var(--sy,0px)))
|
|
translateZ(var(--sz, -140px)) scale(var(--sc,.82));
|
|
transition: transform 1s cubic-bezier(.22,.9,.3,1); }
|
|
.live .p { transform: translate(-50%,-50%) translateZ(0) scale(1); }
|
|
.live .p:nth-child(2){transition-delay:.06s} .live .p:nth-child(3){transition-delay:.12s}
|
|
.live .p:nth-child(4){transition-delay:.18s} .live .p:nth-child(5){transition-delay:.24s}
|
|
.live .p:nth-child(6){transition-delay:.30s} .live .p:nth-child(7){transition-delay:.36s}
|
|
.live .p:nth-child(8){transition-delay:.42s} .live .p:nth-child(9){transition-delay:.48s}
|
|
|
|
.card { border: 1px solid var(--line); border-radius: 12px; padding: 12px 15px;
|
|
background: linear-gradient(180deg, var(--panel-2), var(--panel));
|
|
box-shadow: 0 10px 30px rgba(0,0,0,.35); text-align: left; min-width: 118px;
|
|
transform-style: preserve-3d; position: relative;
|
|
transform: rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)); transition: transform .18s ease-out; }
|
|
.card::after { /* glare that follows tilt */
|
|
content: ""; position: absolute; inset: 0; border-radius: 12px; pointer-events: none;
|
|
background: radial-gradient(300px 160px at var(--gx,50%) var(--gy,50%), rgba(63,186,245,.13), transparent 70%);
|
|
opacity: 0; transition: opacity .25s; }
|
|
.card:hover::after { opacity: 1; }
|
|
.card h4 { margin: 0; font: 700 12px/1.2 var(--mono); letter-spacing: .06em; color: var(--text);
|
|
transform: translateZ(22px); }
|
|
.card p { margin: 5px 0 0; font-size: .74rem; color: var(--muted); line-height: 1.45;
|
|
max-width: 21ch; transform: translateZ(14px); }
|
|
.card.c { border-color: rgba(63,186,245,.5); box-shadow: 0 0 26px rgba(63,186,245,.14), 0 10px 30px rgba(0,0,0,.35); }
|
|
.card.c h4 { color: var(--cyan); }
|
|
.card.m { border-color: rgba(232,121,249,.5); box-shadow: 0 0 26px rgba(232,121,249,.14), 0 10px 30px rgba(0,0,0,.35); }
|
|
.card.m h4 { color: var(--magenta); }
|
|
.card.v { border-color: rgba(139,92,246,.55); box-shadow: 0 0 26px rgba(139,92,246,.16), 0 10px 30px rgba(0,0,0,.35); }
|
|
.card.v h4 { color: #b39dfa; }
|
|
.card.a { border-color: rgba(251,191,36,.5); } .card.a h4 { color: var(--amber); }
|
|
.card.g { border-color: rgba(52,211,153,.5); } .card.g h4 { color: var(--green); }
|
|
.stage-cap { position: absolute; left: 16px; bottom: 12px; font: 600 10px/1 var(--mono);
|
|
letter-spacing: .2em; text-transform: uppercase; color: var(--cyan-dim); }
|
|
|
|
#st-cluster { height: 420px; } #st-cp { height: 430px; }
|
|
#st-node { height: 430px; } #st-pod { height: 470px; } #st-traffic { height: 380px; }
|
|
|
|
.capsule { width: 300px; height: 190px; border-radius: 100px;
|
|
border: 2px dashed rgba(63,186,245,.5); box-shadow: inset 0 0 40px rgba(63,186,245,.06); }
|
|
.ring { width: 340px; height: 230px; border-radius: 120px;
|
|
border: 1.5px solid rgba(232,121,249,.4); display: grid; place-content: end center;
|
|
padding-bottom: 6px; font: 600 10px/1 var(--mono); letter-spacing: .18em;
|
|
color: var(--magenta); text-transform: uppercase; }
|
|
|
|
/* ---------- COURSE 01: docker 3D layer rise (pinned) ---------- */
|
|
#lay-stage { height: 100vh; display: grid; place-items: center; perspective: 1200px;
|
|
overflow: hidden; position: relative; }
|
|
#laystack { position: relative; width: min(420px, 80vw); height: 380px;
|
|
transform-style: preserve-3d; transform: rotateX(14deg); }
|
|
.layer3d { position: absolute; left: 50%; width: 100%;
|
|
transform: translateX(-50%) translateZ(var(--z, 0px)) translateY(var(--y, 0px));
|
|
border-radius: 12px; padding: 15px 20px; font: 700 13px/1.3 var(--mono); letter-spacing: .05em;
|
|
border: 1px solid rgba(139,92,246,.5); color: #cfc3fb;
|
|
background: linear-gradient(180deg, rgba(139,92,246,.18), rgba(13,20,36,.94));
|
|
box-shadow: 0 18px 44px rgba(0,0,0,.5); will-change: transform, opacity;
|
|
transform-style: preserve-3d; }
|
|
.layer3d small { display: block; font: 400 .7rem/1.4 var(--sans); color: var(--muted);
|
|
letter-spacing: 0; margin-top: 4px; }
|
|
.layer3d.base { border-color: rgba(63,186,245,.55); color: #ace0fb;
|
|
background: linear-gradient(180deg, rgba(63,186,245,.15), rgba(13,20,36,.94)); }
|
|
.layer3d.top { border-color: rgba(52,211,153,.6); color: #a7ecd3;
|
|
background: linear-gradient(180deg, rgba(52,211,153,.16), rgba(13,20,36,.94)); }
|
|
.laycard { position: absolute; will-change: transform, opacity; }
|
|
#lay-cap { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
|
|
font: 600 11px/1 var(--mono); letter-spacing: .26em; text-transform: uppercase;
|
|
color: var(--cyan-dim); white-space: nowrap; }
|
|
|
|
/* ---------- COURSE 05: OCP orbit ring (pinned) ---------- */
|
|
#orbit-stage { height: 100vh; display: grid; place-items: center; perspective: 1300px;
|
|
overflow: hidden; position: relative; }
|
|
#orbit { position: relative; width: 300px; height: 300px; transform-style: preserve-3d;
|
|
will-change: transform; }
|
|
#orbit .sat { position: absolute; left: 50%; top: 50%; width: 178px; margin-left: -89px;
|
|
transform: translateY(-50%) rotateY(var(--a)) translateZ(300px);
|
|
backface-visibility: hidden; }
|
|
#core { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); z-index: 2; }
|
|
#orbit-cap { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
|
|
font: 600 11px/1 var(--mono); letter-spacing: .26em; text-transform: uppercase;
|
|
color: var(--cyan-dim); white-space: nowrap; }
|
|
|
|
.hop { padding: 12px 16px; border-radius: 999px; font: 700 12px/1 var(--mono);
|
|
letter-spacing: .05em; border: 1px solid var(--line); background: var(--panel-2);
|
|
color: var(--text); box-shadow: 0 8px 24px rgba(0,0,0,.4); }
|
|
|
|
.book { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 34px; }
|
|
.book .term { font-size: .86rem; padding: 8px 13px; border-radius: 9px; }
|
|
|
|
footer { text-align: center; padding: 12vh 20px 9vh; color: var(--muted); font-size: .9rem; }
|
|
footer .sig { font: 600 11px/1 var(--mono); letter-spacing: .24em; text-transform: uppercase;
|
|
color: var(--cyan-dim); margin-top: 10px; }
|
|
|
|
@media (max-width: 720px) {
|
|
section { padding: 10vh 14px; }
|
|
.card { min-width: 92px; padding: 9px 11px; }
|
|
.card p { display: none; }
|
|
#st-cluster, #st-cp, #st-node { height: 340px; }
|
|
#st-pod { height: 400px; } #st-traffic { height: 320px; }
|
|
.capsule { width: 220px; height: 150px; } .ring { width: 258px; height: 188px; }
|
|
#orbit .sat { width: 132px; margin-left: -66px; }
|
|
.crt-body { min-height: 300px; font-size: 12px; }
|
|
}
|
|
|
|
/* reduced motion: everything assembled, no pins (JS also respects this via matchMedia) */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.p, .live .p { transition: none; transform: translate(-50%,-50%) translateZ(0) scale(1); }
|
|
.stage svg.wires line { opacity: .8; transition: none; }
|
|
.live .stage svg.wires .flow, .scrollcue i, .caret { animation: none; }
|
|
.card { transition: none; }
|
|
}
|
|
/* no-JS / pre-JS safety: assembled by default; JS flips body.fx on to arm the theatre */
|
|
body:not(.fx) .p { transform: translate(-50%,-50%) translateZ(0) scale(1); transition: none; }
|
|
body:not(.fx) .stage svg.wires line { opacity: .8; }
|
|
body:not(.fx) .scrollcue { opacity: 1; }
|
|
/* …and the hero degrades to a static stack (title above terminal), nothing overlapping */
|
|
body:not(.fx) .pinstage { height: auto; padding: 14vh 20px 8vh; place-items: center;
|
|
grid-template-rows: auto auto auto; gap: 44px; }
|
|
body:not(.fx) .titlewrap { position: static; pointer-events: auto; }
|
|
body:not(.fx) .scrollcue { position: static; transform: none; margin-top: 6px; }
|
|
body:not(.fx) #skip { display: none; }
|
|
</style>
|
|
|
|
<div id="bar" role="presentation"></div>
|
|
<canvas id="stars" aria-hidden="true"></canvas>
|
|
|
|
<nav aria-label="Courses">
|
|
<a href="#aperitif"><span>Aperitif</span></a>
|
|
<a href="#docker"><span>I · Docker</span></a>
|
|
<a href="#machine"><span>II · Machine</span></a>
|
|
<a href="#runtime"><span>III · Runtime</span></a>
|
|
<a href="#traffic"><span>IV · Traffic</span></a>
|
|
<a href="#ocp"><span>V · OpenShift</span></a>
|
|
<a href="#book"><span>Phrasebook</span></a>
|
|
</nav>
|
|
|
|
<main>
|
|
|
|
<!-- ============ COURSE 00 — APERITIF: the terminal ============ -->
|
|
<section id="aperitif" aria-label="Intro: an animated terminal that explodes into the title">
|
|
<div class="pinstage" id="hero-pin">
|
|
<div class="titlewrap" aria-hidden="true">
|
|
<h1 class="bigtitle" id="bigtitle"><span class="x1">THE EXPLODED</span><span class="x2">CLUSTER</span></h1>
|
|
<div class="menuline">a <b>five-course</b> tasting menu · kubernetes · openshift · docker</div>
|
|
</div>
|
|
<div class="crt" id="crt" role="img" aria-label="Terminal: docker build, docker push, kubectl apply, pod becomes Running — but what actually just happened?">
|
|
<div class="crt-head"><i></i><i></i><i></i><span>jonny@edge — bash</span></div>
|
|
<div class="crt-body" id="crtbody"><span class="caret" id="caret"></span></div>
|
|
</div>
|
|
<button id="skip" type="button">skip intro ▸</button>
|
|
<div class="scrollcue" id="cue">scroll to tear it down<i>▼</i></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ COURSE 01 — DOCKER ============ -->
|
|
<section id="docker">
|
|
<div class="course-tag">Course I — entrée</div>
|
|
<div class="eyebrow">Image & Container · Docker</div>
|
|
<h2>A container is a lie the kernel tells a process.</h2>
|
|
<p class="copy">That terminal did more than it admitted. <code>docker build</code> turned each Dockerfile step into an immutable
|
|
<button class="term v" data-def="One filesystem diff per Dockerfile instruction. Layers are content-addressed, cached, and shared between images — order your Dockerfile so the volatile bits come last.">layer</button>,
|
|
stacked by an
|
|
<button class="term v" data-def="OverlayFS: read-only layers stacked, one thin writable layer on top. Files 'change' by being copied up; the lower layers never change.">overlay filesystem</button> —
|
|
the running container adds one thin writable layer that dies with it. Shipping is a push to a
|
|
<button class="term v" data-def="Where images live: Docker Hub, quay.io, a private mirror. Push and pull move only the layers the other side doesn't have.">registry</button>.
|
|
The trap: a
|
|
<button class="term" data-def="A mutable pointer. :latest, :v2, :prod — all can silently move to a different image. Fine for humans, dangerous for production manifests.">tag</button>
|
|
is a pointer that can move under you; a
|
|
<button class="term" data-def="The SHA-256 of the image content itself. @sha256:… names the artefact, not a label on it. Pin production to digests.">digest</button>
|
|
is the artefact itself. At run time, isolation is just the kernel:
|
|
<button class="term v" data-def="Kernel namespaces limit what a process can SEE — its own PID tree, network stack, mounts, hostname.">namespaces</button>
|
|
decide what a process <em>sees</em>,
|
|
<button class="term v" data-def="Control groups limit what a process can USE — CPU, memory, IO. Your pod's resource limits become cgroup values.">cgroups</button>
|
|
decide what it <em>uses</em>. No VM — very confident bookkeeping. Scroll: the image rises out of the deep, layer by layer.</p>
|
|
</section>
|
|
<div id="lay-stage" aria-label="3D image layer stack rising as you scroll">
|
|
<div id="laystack">
|
|
<div class="layer3d base" style="--y: 150px;" data-i="0">FROM ubi9-minimal <small>base OS — shared, cached, content-addressed</small></div>
|
|
<div class="layer3d" style="--y: 75px;" data-i="1">RUN install deps <small>a filesystem diff — cache hit if unchanged</small></div>
|
|
<div class="layer3d" style="--y: 0px;" data-i="2">COPY src/ /app <small>your code — order it late, bust the cache less</small></div>
|
|
<div class="layer3d top" style="--y: -75px;" data-i="3">writable layer <small>the running container — ephemeral, dies with it</small></div>
|
|
<div class="laycard" style="right:-40px; top:-40px;"><div class="card v"><h4>registry</h4><p>push / pull — only missing layers move</p></div></div>
|
|
<div class="laycard" style="left:-56px; bottom:-30px;"><div class="card a"><h4>tag vs digest</h4><p>:latest can move · @sha256 cannot</p></div></div>
|
|
</div>
|
|
<div id="lay-cap">read-only layers below · one writable lie on top</div>
|
|
</div>
|
|
<section aria-label="Docker key phrase">
|
|
<div class="phrase"><small>Say it like you mean it</small><q>Build once, promote by digest.</q></div>
|
|
<div class="fnote"><b>Field note</b>If your deploy "changed" without a code change, a floating tag moved
|
|
under you. Production manifests pin digests; humans read tags.</div>
|
|
</section>
|
|
|
|
<!-- ============ COURSE 02 — THE MACHINE ============ -->
|
|
<section id="machine">
|
|
<div class="course-tag">Course II — the machine</div>
|
|
<div class="eyebrow">The Cluster</div>
|
|
<h2>A cluster is a promise, not a place.</h2>
|
|
<p class="copy">You never tell Kubernetes <em>how</em> to run your app. You describe what you want —
|
|
<button class="term" data-def="You declare the end state (3 replicas, this image, this port) and the system computes the steps. The opposite of imperative scripts.">declarative intent</button> —
|
|
and the cluster works, forever, to make it true. That habit is the
|
|
<button class="term" data-def="Controllers continuously compare what you asked for with what exists, and act to close the gap. Not once — in a loop, for the life of the cluster.">reconciliation loop</button>:
|
|
compare <button class="term" data-def="The spec you submitted — what SHOULD exist. Stored in etcd via the API server.">desired state</button>
|
|
against <button class="term" data-def="What is actually running right now — pods, their health, their nodes.">actual state</button>, fix the difference, repeat.
|
|
Two halves: a <b>control plane</b> that decides, and <b>worker nodes</b> that run. That
|
|
<code class="term" data-def="The CLI. It doesn't start containers — it submits and reads objects through the API server.">kubectl apply</code>
|
|
in the terminal didn't launch anything; it filed paperwork. The machine took it from there.</p>
|
|
|
|
<div class="stage" id="st-cluster">
|
|
<div class="scene">
|
|
<svg class="wires" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
<line class="flow" x1="50" y1="30" x2="22" y2="66"/><line class="flow" x1="50" y1="30" x2="50" y2="66"/>
|
|
<line class="flow" x1="50" y1="30" x2="78" y2="66"/>
|
|
</svg>
|
|
<div class="p" style="left:50%; top:24%; --sx:0px; --sy:110px; --sz:-220px;">
|
|
<div class="card m"><h4>CONTROL PLANE</h4><p>decides — holds truth, schedules, reconciles</p></div></div>
|
|
<div class="p" style="left:22%; top:72%; --sx:180px; --sy:-120px; --sz:-160px;">
|
|
<div class="card c"><h4>NODE 1</h4><p>runs pods</p></div></div>
|
|
<div class="p" style="left:50%; top:72%; --sx:0px; --sy:-120px; --sz:-260px;">
|
|
<div class="card c"><h4>NODE 2</h4><p>runs pods</p></div></div>
|
|
<div class="p" style="left:78%; top:72%; --sx:-180px; --sy:-120px; --sz:-160px;">
|
|
<div class="card c"><h4>NODE 3</h4><p>runs pods</p></div></div>
|
|
</div>
|
|
<span class="stage-cap">decide ▲ / run ▼ — the split that makes everything else possible</span>
|
|
</div>
|
|
<div class="phrase"><small>Say it like you mean it</small><q>Kubernetes doesn't run your app — it reconciles it.</q></div>
|
|
<div class="fnote"><b>Field note</b>If you're SSHing into nodes to "fix" things, you're arm-wrestling the
|
|
reconciler — and it doesn't get tired. Change the desired state instead.</div>
|
|
</section>
|
|
|
|
<section aria-label="Control plane">
|
|
<div class="eyebrow">The Control Plane</div>
|
|
<h2>Four processes and a filing cabinet.</h2>
|
|
<p class="copy">The
|
|
<button class="term" data-def="The single front door. Authenticates, authorises, validates, persists. Every component — kubectl, kubelets, controllers — talks THROUGH it, never around it.">kube-apiserver</button>
|
|
is the only door in the building. Behind it,
|
|
<button class="term" data-def="A distributed key-value store using the Raft consensus algorithm. The cluster's only source of truth. Loses quorum → the cluster loses its memory.">etcd</button>
|
|
is the cluster's memory — only the API server may touch it. The
|
|
<button class="term" data-def="Watches for pods with no node assigned. Filters nodes (can this pod run here?), scores the survivors (where best?), then binds the pod. It never runs anything itself.">scheduler</button>
|
|
picks <em>where</em> each pod lands — filter, score,
|
|
<button class="term" data-def="The scheduler's only write: patching the pod object with a node name. The kubelet on that node sees it and takes over.">bind</button> —
|
|
and never runs a thing. The
|
|
<button class="term" data-def="A bundle of control loops: Deployment controller, ReplicaSet controller, node lifecycle, endpoints… each one reconciles its own object type.">controller-manager</button>
|
|
hosts the loops: a Deployment begets a
|
|
<button class="term" data-def="The controller that keeps N identical pods alive. Deployments manage ReplicaSets to do rolling updates — you rarely touch one directly.">ReplicaSet</button>,
|
|
which begets pods.</p>
|
|
<div class="stage" id="st-cp">
|
|
<div class="scene">
|
|
<svg class="wires" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
<line class="flow" x1="50" y1="50" x2="50" y2="20"/><line x1="24" y1="72" x2="44" y2="55"/>
|
|
<line x1="76" y1="72" x2="56" y2="55"/>
|
|
</svg>
|
|
<div class="p" style="left:50%; top:50%; --sx:0px; --sy:0px; --sz:60px; --sc:.9;">
|
|
<div class="card c"><h4>kube-apiserver</h4><p>the only door — authn, authz, admission, persistence</p></div></div>
|
|
<div class="p" style="left:50%; top:16%; --sx:0px; --sy:120px; --sz:-200px;">
|
|
<div class="card m"><h4>etcd</h4><p>consensus truth — quorum or nothing</p></div></div>
|
|
<div class="p" style="left:22%; top:76%; --sx:190px; --sy:-100px; --sz:-160px;">
|
|
<div class="card"><h4>scheduler</h4><p>filter → score → bind</p></div></div>
|
|
<div class="p" style="left:78%; top:76%; --sx:-190px; --sy:-100px; --sz:-160px;">
|
|
<div class="card"><h4>controller-manager</h4><p>the reconciliation loops</p></div></div>
|
|
</div>
|
|
<span class="stage-cap">everything watches the api-server — nothing talks sideways</span>
|
|
</div>
|
|
<div class="phrase"><small>Say it like you mean it</small><q>etcd is the truth; everything else is a cache.</q></div>
|
|
<div class="fnote"><b>Field note</b>The API server being the bottleneck is the design, not a flaw —
|
|
one authenticated, audited, admission-controlled door beats five convenient ones.</div>
|
|
</section>
|
|
|
|
<!-- ============ COURSE 03 — THE RUNTIME ============ -->
|
|
<section id="runtime">
|
|
<div class="course-tag">Course III — where it runs</div>
|
|
<div class="eyebrow">The Node</div>
|
|
<h2>Where intent becomes a process.</h2>
|
|
<p class="copy">Every node runs a
|
|
<button class="term" data-def="The node agent. Watches the API server for pods bound to its node, drives the container runtime, mounts volumes, runs your probes, reports status back.">kubelet</button> —
|
|
the only component in the whole system that actually starts your container. It speaks
|
|
<button class="term" data-def="Container Runtime Interface — the plug standard between kubelet and runtime. containerd on most clusters; CRI-O on OpenShift.">CRI</button>
|
|
to <b>containerd</b> or <b>CRI-O</b>, which pulls images and launches containers. A
|
|
<button class="term" data-def="Container Network Interface — the plugin that gives each pod its own IP and wires it into the cluster network. Flannel, Calico, Cilium…">CNI plugin</button>
|
|
hands each pod a real IP, and
|
|
<button class="term" data-def="Programs the node's dataplane (iptables/IPVS rules) so Service VIPs actually route to live pods. Modern CNIs like Cilium replace it entirely with eBPF.">kube-proxy</button> —
|
|
or an <button class="term" data-def="Programs run safely inside the kernel. Cilium uses eBPF to replace kube-proxy: service routing, network policy and observability at kernel speed.">eBPF</button>
|
|
dataplane replacing it — makes Service addresses route to real pods.</p>
|
|
<div class="stage" id="st-node">
|
|
<div class="scene">
|
|
<svg class="wires" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
<line class="flow" x1="50" y1="26" x2="28" y2="58"/><line x1="28" y1="66" x2="28" y2="80"/>
|
|
<line class="flow" x1="50" y1="26" x2="72" y2="58"/>
|
|
</svg>
|
|
<div class="p" style="left:50%; top:20%; --sx:0px; --sy:100px; --sz:40px; --sc:.9;">
|
|
<div class="card c"><h4>kubelet</h4><p>node agent — the only thing that starts your container</p></div></div>
|
|
<div class="p" style="left:28%; top:56%; --sx:120px; --sy:-80px; --sz:-180px;">
|
|
<div class="card v"><h4>containerd / CRI-O</h4><p>pulls images, runs containers (CRI)</p></div></div>
|
|
<div class="p" style="left:72%; top:56%; --sx:-120px; --sy:-80px; --sz:-180px;">
|
|
<div class="card"><h4>CNI plugin</h4><p>a real IP per pod</p></div></div>
|
|
<div class="p" style="left:28%; top:84%; --sx:120px; --sy:-140px; --sz:-240px;">
|
|
<div class="card g"><h4>pod ⬤⬤</h4><p>lands here</p></div></div>
|
|
<div class="p" style="left:72%; top:84%; --sx:-120px; --sy:-140px; --sz:-240px;">
|
|
<div class="card"><h4>kube-proxy / eBPF</h4><p>makes Services route</p></div></div>
|
|
</div>
|
|
<span class="stage-cap">kubelet drives · runtime executes · cni wires · proxy routes</span>
|
|
</div>
|
|
<div class="phrase"><small>Say it like you mean it</small><q>The kubelet is the only component that actually starts your container.</q></div>
|
|
<div class="fnote"><b>Field note</b>Node <code>NotReady</code>? Check kubelet ↔ api-server connectivity
|
|
before anything else — a node that can't phone home is presumed lost.</div>
|
|
</section>
|
|
|
|
<section aria-label="The pod">
|
|
<div class="eyebrow">The Pod</div>
|
|
<h2>One IP, shared fate.</h2>
|
|
<p class="copy">A pod isn't a container — it's a jacket around one or more. Everything inside shares a
|
|
<button class="term" data-def="Linux network namespace — the pod's containers share ONE IP and can reach each other on localhost. Held open by a tiny hidden 'pause' container.">network namespace</button>
|
|
(one IP, localhost between friends) and can share
|
|
<button class="term" data-def="Storage mounted into the pod — emptyDir scratch space, ConfigMaps, Secrets, PersistentVolumeClaims.">volumes</button>.
|
|
<button class="term" data-def="Run BEFORE the app containers, one at a time, each to completion. Perfect for wait-for-dependency gates, permission fixes, and refuse-to-start guards.">initContainers</button>
|
|
run first, in order — gatekeepers.
|
|
<button class="term" data-def="A helper container riding in the same pod: log shipper, proxy, config reloader. Shares the pod's network and volumes.">Sidecars</button>
|
|
ride along. Then the probes — three different jobs:
|
|
<button class="term" data-def="Owns warmup. While it runs, liveness and readiness stay out of the way. Generous budget — slow starts are normal.">startup</button> owns warmup,
|
|
<button class="term" data-def="Gates traffic. Failing readiness pulls the pod out of Service endpoints — it does NOT restart it.">readiness</button> gates traffic,
|
|
<button class="term" data-def="Restarts a genuinely hung process. Make it TOLERANT — a self-healing app killed mid-recovery recovers slower.">liveness</button>
|
|
restarts the truly hung. Confusing them is how healthy pods get executed.</p>
|
|
<div class="stage" id="st-pod">
|
|
<div class="scene">
|
|
<div class="p" style="left:50%; top:47%; --sx:0px; --sy:0px; --sz:-80px; --sc:.7;">
|
|
<div class="ring">shared network namespace — one IP</div></div>
|
|
<div class="p" style="left:50%; top:47%; --sx:0px; --sy:0px; --sz:-40px; --sc:.8;">
|
|
<div class="capsule" role="presentation"></div></div>
|
|
<div class="p" style="left:38%; top:44%; --sx:60px; --sy:10px; --sz:30px;">
|
|
<div class="card g"><h4>app container</h4><p>your process</p></div></div>
|
|
<div class="p" style="left:63%; top:52%; --sx:-60px; --sy:-10px; --sz:20px;">
|
|
<div class="card"><h4>sidecar</h4><p>proxy · logs · reload</p></div></div>
|
|
<div class="p" style="left:14%; top:16%; --sx:150px; --sy:110px; --sz:-200px;">
|
|
<div class="card a"><h4>initContainer</h4><p>runs first, to completion — the gatekeeper</p></div></div>
|
|
<div class="p" style="left:85%; top:16%; --sx:-150px; --sy:110px; --sz:-200px;">
|
|
<div class="card"><h4>volumes</h4><p>shared mounts</p></div></div>
|
|
<div class="p" style="left:16%; top:85%; --sx:140px; --sy:-120px; --sz:-160px;">
|
|
<div class="card c"><h4>probes</h4><p>startup → readiness → liveness</p></div></div>
|
|
</div>
|
|
<span class="stage-cap">smallest schedulable unit — the jacket, not the container</span>
|
|
</div>
|
|
<div class="phrase"><small>Say it like you mean it</small><q>containerPort is documentation — the app still has to bind the port itself.</q></div>
|
|
<div class="fnote"><b>Field note</b><code>exit 137</code> with reason <code>Error</code> is the kubelet's
|
|
probe kill; <code>OOMKilled</code> is memory. Same exit code, different crimes — read the reason.</div>
|
|
</section>
|
|
|
|
<!-- ============ COURSE 04 — TRAFFIC ============ -->
|
|
<section id="traffic">
|
|
<div class="course-tag">Course IV — the traffic</div>
|
|
<div class="eyebrow">Services & Ingress</div>
|
|
<h2>Pods are cattle; Services are the brand.</h2>
|
|
<p class="copy">Pods are ephemeral — they die, respawn, change IPs. A
|
|
<button class="term" data-def="A stable virtual IP + DNS name over a set of pods chosen by label selector. The endpoints churn; the Service name never does.">Service</button>
|
|
is the stable name in front of the churn: a
|
|
<button class="term" data-def="The default Service type — a virtual IP reachable only inside the cluster.">ClusterIP</button>
|
|
inside, a
|
|
<button class="term" data-def="A Service exposed on an external IP — in the cloud, a cloud LB; on bare metal, something answering ARP for a VIP (MetalLB, Cilium LB-IPAM).">LoadBalancer</button>
|
|
at the edge, and
|
|
<button class="term" data-def="L7 routing: host/path rules + TLS termination in front of many Services. Gateway API is its successor.">Ingress</button>
|
|
(or the <b>Gateway API</b>) for host- and path-routing above them. Membership is decided by
|
|
<b>readiness</b> — a pod failing readiness silently leaves the pool. That's the feature. It's also the
|
|
first place to look when traffic "disappears".</p>
|
|
<div class="stage" id="st-traffic">
|
|
<div class="scene">
|
|
<svg class="wires" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
<line class="flow" x1="12" y1="50" x2="34" y2="50"/><line class="flow" x1="40" y1="50" x2="62" y2="50"/>
|
|
<line class="flow" x1="68" y1="46" x2="84" y2="28"/><line class="flow" x1="68" y1="54" x2="84" y2="72"/>
|
|
</svg>
|
|
<div class="p" style="left:12%; top:50%; --sx:120px; --sy:0px; --sz:-140px;"><div class="hop">☺ client</div></div>
|
|
<div class="p" style="left:37%; top:50%; --sx:40px; --sy:0px; --sz:-180px;">
|
|
<div class="hop" style="border-color:rgba(232,121,249,.5); color:var(--magenta);">LB / Ingress</div></div>
|
|
<div class="p" style="left:65%; top:50%; --sx:-60px; --sy:0px; --sz:-140px;">
|
|
<div class="hop" style="border-color:rgba(63,186,245,.5); color:var(--cyan);">Service VIP</div></div>
|
|
<div class="p" style="left:88%; top:24%; --sx:-140px; --sy:60px; --sz:-200px;">
|
|
<div class="card g"><h4>pod · ready</h4><p>in the pool</p></div></div>
|
|
<div class="p" style="left:88%; top:76%; --sx:-140px; --sy:-60px; --sz:-200px;">
|
|
<div class="card a"><h4>pod · not ready</h4><p>silently removed</p></div></div>
|
|
</div>
|
|
<span class="stage-cap">readiness decides membership — traffic follows</span>
|
|
</div>
|
|
<div class="phrase"><small>Say it like you mean it</small><q>Never talk to a pod; talk to a Service.</q></div>
|
|
<div class="fnote"><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.</div>
|
|
</section>
|
|
|
|
<!-- ============ COURSE 05 — OPENSHIFT (orbit) ============ -->
|
|
<section id="ocp">
|
|
<div class="course-tag">Course V — dessert</div>
|
|
<div class="eyebrow">OpenShift</div>
|
|
<h2>Kubernetes with opinions — and a security guard.</h2>
|
|
<p class="copy">OpenShift is a <em>distribution</em> of Kubernetes: same engine, opinionated chassis. The doorman is the
|
|
<button class="term m" data-def="Security Context Constraints — OpenShift's pod admission policy. Decides what a pod may BE: which UIDs, capabilities, volumes, host access.">SCC</button>:
|
|
the default,
|
|
<button class="term m" data-def="The default SCC. Non-root, RANDOM UID, capabilities dropped, no host access. Your image must cope with an arbitrary UID — design for GID 0.">restricted-v2</button>,
|
|
runs your container as a <b>random non-root UID</b> — your image has to cope. Workloads that genuinely
|
|
need privilege get a <b>dedicated ServiceAccount</b> bound to a <b>minimal custom SCC</b> — never the stock one.
|
|
<button class="term m" data-def="OpenShift's L7 exposure object — it predates Ingress and is still idiomatic on OCP. HAProxy-backed by default.">Routes</button>
|
|
predate Ingress and still rule here.
|
|
<button class="term m" data-def="A controller + CRDs that manage software the way you'd manage it: install, upgrade, reconfigure, self-heal. OpenShift runs ITSELF as operators.">Operators</button>
|
|
run the platform itself, and
|
|
<button class="term m" data-def="Single-Node OpenShift — the entire control plane and workload on one box. The edge form factor: a shop's back office, a factory floor.">SNO</button>
|
|
puts the whole thing on one box in a shop's back office. The OS underneath is immutable — changed by
|
|
<button class="term m" data-def="Declarative node configuration for immutable RHCOS — files, units, kernel args. Applying one rolls/reboots nodes. No SSH-and-tweak.">MachineConfig</button>,
|
|
not SSH. Scroll — the opinions orbit the core.</p>
|
|
</section>
|
|
<div id="orbit-stage" aria-label="OpenShift components orbiting the Kubernetes core in 3D">
|
|
<div id="core"><div class="card c" style="text-align:center;"><h4>KUBERNETES CORE</h4><p>same engine underneath</p></div></div>
|
|
<div id="orbit">
|
|
<div class="sat" style="--a: 0deg;"><div class="card m"><h4>SCC</h4><p>admission: what a pod may BE</p></div></div>
|
|
<div class="sat" style="--a: 60deg;"><div class="card m"><h4>Routes</h4><p>L7 exposure, pre-Ingress</p></div></div>
|
|
<div class="sat" style="--a: 120deg;"><div class="card m"><h4>Operators / OLM</h4><p>the platform runs itself</p></div></div>
|
|
<div class="sat" style="--a: 180deg;"><div class="card m"><h4>oc + console</h4><p>kubectl, extended</p></div></div>
|
|
<div class="sat" style="--a: 240deg;"><div class="card m"><h4>MachineConfig</h4><p>immutable OS, declared changes</p></div></div>
|
|
<div class="sat" style="--a: 300deg;"><div class="card m"><h4>SNO</h4><p>the whole cluster on one edge box</p></div></div>
|
|
</div>
|
|
<div id="orbit-cap">a ring of opinions around the same core — scroll to spin it</div>
|
|
</div>
|
|
<section aria-label="OpenShift key phrase">
|
|
<div class="phrase"><small>Say it like you mean it</small><q>On OpenShift, admission is the interview — the SCC is the dress code.</q></div>
|
|
<div class="fnote"><b>Field note</b>Deployment stuck at 0/1 with <em>no pod at all</em>? The refusal
|
|
happened above scheduling — read the ReplicaSet events. The error lives a level up.</div>
|
|
</section>
|
|
|
|
<!-- ============ PHRASEBOOK ============ -->
|
|
<section id="book">
|
|
<div class="course-tag">Petits fours</div>
|
|
<div class="eyebrow">Phrasebook</div>
|
|
<h2>Take the words with you.</h2>
|
|
<p class="copy">Hover (or tap) any term — the load-bearing phrases of the whole trade. Use them precisely
|
|
and people will assume, correctly, that you know where the bodies are buried.</p>
|
|
<div class="book">
|
|
<button class="term" data-def="You declare the end state; the system computes the steps.">declarative</button>
|
|
<button class="term" data-def="Compare desired vs actual, act, repeat forever. The heartbeat of Kubernetes.">reconciliation loop</button>
|
|
<button class="term" data-def="The spec you asked for, stored in etcd. The reconciler's target.">desired state</button>
|
|
<button class="term" data-def="The consensus store holding cluster truth. Quorum or nothing.">etcd</button>
|
|
<button class="term" data-def="Filter nodes, score survivors, bind the pod. Runs nothing itself.">scheduling</button>
|
|
<button class="term" data-def="The node agent — the only component that actually starts containers.">kubelet</button>
|
|
<button class="term" data-def="Container Runtime Interface: kubelet ↔ containerd/CRI-O.">CRI</button>
|
|
<button class="term" data-def="Container Network Interface: a real IP per pod.">CNI</button>
|
|
<button class="term" data-def="Kernel-resident programs replacing kube-proxy in modern CNIs like Cilium.">eBPF</button>
|
|
<button class="term" data-def="Smallest schedulable unit — a jacket around containers sharing an IP and volumes.">pod</button>
|
|
<button class="term" data-def="Runs before app containers, in order, to completion. The gatekeeper.">initContainer</button>
|
|
<button class="term" data-def="Startup owns warmup, readiness gates traffic, liveness restarts the hung.">probes</button>
|
|
<button class="term v" data-def="One immutable filesystem diff per Dockerfile step. Cached, shared, content-addressed.">image layer</button>
|
|
<button class="term v" data-def="What a process can SEE (namespaces) vs what it can USE (cgroups). That's a container.">namespaces + cgroups</button>
|
|
<button class="term" data-def="A mutable pointer to an image. :latest can and will move.">tag</button>
|
|
<button class="term" data-def="@sha256:… — the content address of the artefact itself. Pin production to this.">digest</button>
|
|
<button class="term" data-def="A stable VIP + DNS name over churning pods, membership decided by readiness.">Service</button>
|
|
<button class="term" data-def="L7 host/path routing + TLS in front of Services. Gateway API is the successor.">Ingress</button>
|
|
<button class="term" data-def="Git as the desired state; an agent (ArgoCD/Flux) reconciles the cluster to it. kubectl becomes read-only.">GitOps</button>
|
|
<button class="term m" data-def="OpenShift pod admission: what a pod may BE. Default restricted-v2 = random non-root UID.">SCC</button>
|
|
<button class="term m" data-def="OpenShift's L7 exposure object, older than Ingress and still idiomatic.">Route</button>
|
|
<button class="term m" data-def="CRDs + a controller that install, upgrade and heal software autonomously.">Operator</button>
|
|
<button class="term m" data-def="Extend the Kubernetes API with your own object types — the mechanism behind operators.">CRD</button>
|
|
<button class="term m" data-def="Single-Node OpenShift: the whole cluster on one edge box.">SNO</button>
|
|
<button class="term" data-def="A workload that starts only if its preconditions hold — and refuses loudly otherwise.">fail-closed</button>
|
|
<button class="term" data-def="Immutable host OS: nodes are replaced or declaratively reconfigured, never hand-edited.">immutable infrastructure</button>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
<div>Cut from real clusters — every field note above cost somebody a late night.</div>
|
|
<div class="sig">the exploded cluster · five courses · bztmon</div>
|
|
</footer>
|
|
</main>
|
|
|
|
<div id="tip" role="tooltip" aria-hidden="true"></div>
|