ch4/planpalto-helm/docs/render-pipeline.html

468 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<title>planpalto-helm — render pipeline</title>
<style>
:root {
--paper: #E7EAEE;
--paper-2: #DDE1E7;
--rule: #A4ADB8;
--ink: #242A31;
--ink-dim: #5C6672;
--accent: #1F5FA8;
--accent-weak: rgba(31, 95, 168, 0.14);
--grid: rgba(164, 173, 184, 0.14);
--font-display: "IBM Plex Mono", ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Consolas, monospace;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Consolas, monospace;
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--paper: #161A1F; --paper-2: #1D232A; --rule: #39424D;
--ink: #D6DBE1; --ink-dim: #828C98; --accent: #4C93E0;
--accent-weak: rgba(76, 147, 224, 0.18);
--grid: rgba(57, 66, 77, 0.22);
}
}
:root[data-theme="dark"] {
--paper: #161A1F; --paper-2: #1D232A; --rule: #39424D;
--ink: #D6DBE1; --ink-dim: #828C98; --accent: #4C93E0;
--accent-weak: rgba(76, 147, 224, 0.18);
--grid: rgba(57, 66, 77, 0.22);
}
:root[data-theme="light"] {
--paper: #E7EAEE; --paper-2: #DDE1E7; --rule: #A4ADB8;
--ink: #242A31; --ink-dim: #5C6672; --accent: #1F5FA8;
--accent-weak: rgba(31, 95, 168, 0.14);
--grid: rgba(164, 173, 184, 0.14);
}
* { box-sizing: border-box; border-radius: 0; }
body {
margin: 0; padding: 20px 12px;
background: var(--paper); color: var(--ink);
font-family: var(--font-body); font-size: 14px; line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* Double-rule drafting frame */
.frame-outer { max-width: 1100px; margin: 0 auto; border: 2px solid var(--ink); padding: 6px; }
.frame-inner {
border: 1px solid var(--ink); padding: 24px;
background-color: var(--paper);
background-image:
repeating-linear-gradient(to right, var(--grid) 0 1px, transparent 1px 24px),
repeating-linear-gradient(to bottom, var(--grid) 0 1px, transparent 1px 24px);
}
.mono { font-family: var(--font-mono); }
.disp { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; }
.dim { color: var(--ink-dim); }
.acc { color: var(--accent); }
.ms { font-family: var(--font-mono); font-size: 11px; line-height: 1.35; }
.sep { border: 0; border-top: 1px solid var(--rule); margin: 20px 0; }
/* ── (a) title / context strip ── */
.strip { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; flex-wrap: wrap; }
.strip h1 { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; font-size: 15px; margin: 0 0 4px; text-wrap: balance; }
.strip .sub { font-size: 14px; color: var(--ink-dim); max-width: 60ch; margin: 0; }
.legend { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; }
.legend .item { display: flex; align-items: center; gap: 6px; }
.sw-acc { width: 22px; height: 0; border-top: 2px solid var(--accent); }
.sw-ink { width: 22px; height: 0; border-top: 1px solid var(--ink); }
.stamp { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border: 1px solid var(--ink); font-family: var(--font-display); font-size: 11px; flex: none; }
.secthead { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; font-size: 12px; margin: 0 0 12px; }
.secthead .st-sub { font-family: var(--font-body); text-transform: none; letter-spacing: 0; color: var(--ink-dim); font-size: 12px; margin-left: 10px; }
/* ── (b→h) pipeline spine ── */
.spine { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; position: relative; }
.stage { padding: 0 12px 0 0; position: relative; }
.stage:not(:last-child)::after {
/* dimension arrow between stages */
content: ""; position: absolute; top: 9px; right: 4px; width: 20px; height: 0;
border-top: 1.5px solid var(--accent);
}
.stage:not(:last-child) .flow-note {
position: absolute; top: -6px; right: 2px; color: var(--accent); font-size: 10px;
}
.stage-hd { display: flex; gap: 8px; align-items: flex-start; }
.stage-stamp { width: 20px; height: 20px; border: 1px solid var(--ink); display: flex; align-items: center; justify-content: center; font-family: var(--font-display); font-size: 11px; flex: none; }
.stage-name { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; }
.stage-desc { font-size: 11px; color: var(--ink-dim); margin-top: 6px; padding-left: 0; line-height: 1.4; }
.stage-desc code { font-family: var(--font-mono); color: var(--ink); }
/* recessed panels / wells */
.well { background: var(--paper-2); border: 1px solid var(--rule); border-top: 1px solid var(--ink); padding: 14px; }
.well-hd { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-size: 11px; margin: 0 0 10px; }
.subgrid { display: grid; grid-template-columns: 1.1fr 1fr; gap: 16px; margin-top: 14px; }
.filetree { font-family: var(--font-mono); font-size: 12px; line-height: 1.55; white-space: pre; }
.filetree .an { color: var(--ink-dim); }
/* command callout */
.cmd-wrap { margin-top: 14px; }
.cmd-lead { color: var(--accent); font-size: 10px; font-family: var(--font-mono); margin-bottom: 4px; }
.cmd-box { position: relative; border: 1px solid var(--ink); background: var(--paper); padding: 10px 12px; font-family: var(--font-mono); font-size: 13px; overflow-x: auto; }
.cmd-box::before { content: ""; position: absolute; left: -8px; top: 50%; width: 8px; height: 0; border-top: 1px solid var(--accent); }
.cmd-note { font-family: var(--font-mono); font-size: 11px; color: var(--ink-dim); margin-top: 6px; }
/* render well 3 columns */
.render3 { display: grid; grid-template-columns: 1fr 1.15fr 1fr; gap: 0; }
.render3 .rcol { padding: 0 16px; border-left: 1px solid var(--rule); position: relative; }
.render3 .rcol:first-child { border-left: 0; padding-left: 0; }
.render3 .rcol:last-child { padding-right: 0; }
.render3 .rcol::after { content: attr(data-cross); position: absolute; top: 24px; right: -6px; z-index: 2; color: var(--accent); font-size: 9px; font-family: var(--font-mono); background: var(--paper-2); padding: 0 2px; }
.render3 .rcol:last-child::after { content: none; }
.rcol .rhd { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; margin: 0 0 8px; }
.rcol pre { font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5; margin: 0; white-space: pre-wrap; word-break: break-word; }
.mech { list-style: none; margin: 0; padding: 0; }
.mech li { font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5; margin-bottom: 6px; }
.mech .tok { color: var(--ink); }
.mech .gl { color: var(--ink-dim); }
.twin { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 14px; }
.chain { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: 11.5px; }
.chain .bx { border: 1px solid var(--rule); padding: 3px 7px; }
.chain .ar { color: var(--accent); }
.addon-row { display: grid; grid-template-columns: auto auto 1fr; gap: 8px; align-items: baseline; font-family: var(--font-mono); font-size: 11.5px; padding: 5px 0; border-top: 1px solid var(--rule); }
.addon-row:first-of-type { border-top: 0; }
.addon-row .ar { color: var(--accent); }
.addon-row .obj { color: var(--ink); }
.addon-row .gl { color: var(--ink-dim); }
/* ── (e) BOM table ── */
.bom { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 13px; }
.bom th, .bom td { text-align: left; padding: 7px 10px; border: 1px solid var(--rule); vertical-align: top; }
.bom thead th { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; color: var(--ink-dim); background: var(--paper-2); }
.bom tbody tr:nth-child(even) td { background: var(--paper-2); }
.bom .note { color: var(--accent); font-size: 11px; }
.bom .note-dim { color: var(--ink-dim); font-size: 11px; }
.bom .sum td { background: var(--accent-weak); border-top: 1px solid var(--accent); border-bottom: 1px solid var(--accent); color: var(--accent); font-size: 12.5px; }
.bom-wrap { overflow-x: auto; }
/* ── lower band ── */
.lower { display: grid; grid-template-columns: 62fr 38fr; gap: 24px; align-items: start; }
.detail { border: 1px solid var(--ink); padding: 16px 16px 14px; position: relative; }
.detail-tab { position: absolute; top: -1px; left: -1px; border: 1px solid var(--ink); background: var(--paper); font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-size: 11px; padding: 3px 8px; }
.detail-cap { font-size: 11.5px; color: var(--ink-dim); margin: 22px 0 16px; line-height: 1.45; }
.detail-cap code { font-family: var(--font-mono); color: var(--ink); }
/* DETAIL A fan */
.fan { position: relative; }
.fan svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.fan-grid { display: grid; grid-template-columns: minmax(150px,1.1fr) 78px minmax(210px,1.5fr); gap: 0; align-items: stretch; }
.lanes { display: flex; flex-direction: column; gap: 8px; }
.srcbox { border: 1px solid var(--rule); padding: 6px 8px; min-height: 44px; display: flex; flex-direction: column; justify-content: center; }
.srcbox .nm { font-family: var(--font-mono); font-size: 12.5px; }
.srcbox .tags { margin-top: 3px; display: flex; gap: 8px; flex-wrap: wrap; }
.srcbox .tag { font-family: var(--font-mono); font-size: 10.5px; color: var(--accent); }
.bracket-col { display: flex; align-items: center; justify-content: center; position: relative; }
.bracket-lbl { font-family: var(--font-mono); font-size: 10px; color: var(--accent); writing-mode: vertical-rl; text-orientation: mixed; letter-spacing: 0.04em; }
.outlanes { display: flex; flex-direction: column; gap: 8px; }
.oline { min-height: 44px; display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; }
.objbox { border: 1px solid var(--rule); padding: 4px 7px; font-family: var(--font-mono); font-size: 11.5px; white-space: nowrap; }
.objbox.dep { border-color: var(--ink); }
.gate { font-family: var(--font-mono); font-size: 10px; color: var(--ink-dim); white-space: nowrap; }
.gate::before { content: "├ "; color: var(--accent); }
.fan-recon { font-family: var(--font-mono); font-size: 11px; color: var(--ink-dim); margin-top: 14px; line-height: 1.45; }
.fan-recon code { color: var(--ink); }
.fan-x5 { position: absolute; font-family: var(--font-mono); font-size: 10px; color: var(--accent); }
/* DETAIL B multi-release */
.rel-cols { display: flex; gap: 48px; justify-content: flex-start; }
.rel-col { flex: 1; min-width: 0; }
.rel-title { font-family: var(--font-mono); font-size: 12px; color: var(--accent); text-align: center; margin-bottom: 6px; }
.rel-stack { border: 1px solid var(--rule); }
.rel-cell { border-top: 1px solid var(--rule); padding: 7px 9px; font-family: var(--font-mono); font-size: 11.5px; line-height: 1.4; }
.rel-cell:first-child { border-top: 0; }
.rel-cell .v { color: var(--accent); }
.rel-note { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-dim); margin-top: 14px; line-height: 1.55; white-space: pre-wrap; }
.rel-note .v { color: var(--accent); }
/* ── title block ── */
.tblock-wrap { display: flex; justify-content: flex-end; margin-top: 20px; }
.tblock { width: 340px; border: 1px solid var(--ink); }
.tblock .tb-row { display: grid; grid-template-columns: 128px 1fr; border-top: 1px solid var(--rule); }
.tblock .tb-row:first-child { border-top: 0; }
.tblock .tb-k { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: 10px; color: var(--ink-dim); padding: 5px 8px; border-right: 1px solid var(--rule); }
.tblock .tb-v { font-family: var(--font-mono); font-size: 11px; padding: 5px 8px; }
.tblock .tb-v.v-acc { color: var(--accent); }
@media (max-width: 959px) {
.spine { grid-template-columns: repeat(3, 1fr); row-gap: 20px; }
.stage:nth-child(3)::after { content: none; }
.render3 { grid-template-columns: 1fr; }
.render3 .rcol { border-left: 0; padding: 12px 0 0; border-top: 1px solid var(--rule); }
.render3 .rcol:first-child { border-top: 0; padding-top: 0; }
.render3 .rcol::after { content: none; }
}
@media (max-width: 719px) {
.strip { flex-direction: column; }
.spine { grid-template-columns: 1fr; }
.stage::after { content: none !important; }
.stage { border-top: 1px solid var(--rule); padding-top: 12px; }
.stage:first-child { border-top: 0; padding-top: 0; }
.subgrid, .twin, .lower { grid-template-columns: 1fr; }
.fan-grid { grid-template-columns: 1fr; gap: 10px; }
.fan svg { display: none; }
.bracket-col { justify-content: flex-start; }
.bracket-lbl { writing-mode: horizontal-tb; }
.tblock { width: 100%; }
}
@media (prefers-reduced-motion: no-preference) {
.draw { stroke-dasharray: var(--len); stroke-dashoffset: var(--len); animation: draw 0.9s ease-out forwards; }
.fan .draw { animation-duration: 0.7s; }
@keyframes draw { to { stroke-dashoffset: 0; } }
}
</style>
<div class="frame-outer"><div class="frame-inner">
<!-- (a) title / context strip -->
<div class="strip">
<div>
<h1>planpalto-helm — render pipeline</h1>
<p class="sub">How <span class="mono">helm install</span> turns the chart on disk into running objects on the fakhri-k8 EKS cluster.</p>
</div>
<div class="legend">
<div class="item"><span class="sw-acc"></span><span class="ms dim">live / rendered value</span></div>
<div class="item"><span class="sw-ink"></span><span class="ms dim">static linework</span></div>
<div class="item"><span class="stamp">N</span><span class="ms dim">pipeline stage</span></div>
</div>
</div>
<hr class="sep">
<!-- (b→h) pipeline spine -->
<p class="secthead">Render pipeline — 6 stages</p>
<div class="spine">
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">1</span><span class="stage-name">Inputs</span></div>
<p class="stage-desc"><code>Chart.yaml</code> · <code>values.yaml</code> · <code>templates/*</code> · <code>_helpers.tpl</code> · optional <code>-f envs/a.yaml</code></p>
<span class="flow-note">paths</span>
</div>
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">2</span><span class="stage-name">Command</span></div>
<p class="stage-desc">You run one <code>helm</code> command. Helm reads the chart and any override file.</p>
<span class="flow-note">flags+values</span>
</div>
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">3</span><span class="stage-name">Render</span></div>
<p class="stage-desc">Go templating merges values + <code>.Release.*</code>, walks <code>templates/</code>, emits one multi-doc YAML.</p>
<span class="flow-note">YAML</span>
</div>
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">4</span><span class="stage-name">Submit</span></div>
<p class="stage-desc">Manifest sent to the k8s API server. Helm records the revision in a Secret.</p>
<span class="flow-note">objects</span>
</div>
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">5</span><span class="stage-name">Reconcile</span></div>
<p class="stage-desc">Controllers act: Deployment → ReplicaSet → Pod. Scheduler places Pods on EKS nodes.</p>
<span class="flow-note">pods</span>
</div>
<div class="stage">
<div class="stage-hd"><span class="stage-stamp">6</span><span class="stage-name">Add-ons</span></div>
<p class="stage-desc">ESO, AWS Load Balancer Controller, and metrics-server finish the job.</p>
</div>
</div>
<!-- (b) inputs + (c) command -->
<div class="subgrid">
<div class="well">
<p class="well-hd">1 · Inputs on disk</p>
<div class="filetree">planpalto-helm/
├─ Chart.yaml
├─ values.yaml <span class="an">apps: map (5 entries) + deps + global</span>
├─ templates/
│ ├─ namespace.yaml
│ ├─ apps.yaml <span class="acc">{{ range .Values.apps }}</span>
│ ├─ deps.yaml <span class="an">redis, nats</span>
│ ├─ external-secrets.yaml
│ ├─ ingress.yaml
│ └─ seed-job.yaml
└─ _helpers.tpl <span class="an">planpal.namespace / .storeName / .image</span>
+ override: <span class="acc">-f envs/a.yaml</span></div>
</div>
<div>
<div class="cmd-wrap">
<p class="cmd-lead">2 · command ↓</p>
<div class="cmd-box"><span>helm upgrade --install </span><span class="acc">planpal</span><span> ./planpalto-helm -f </span><span class="acc">envs/a.yaml</span></div>
<p class="cmd-note">upgrade --install = create if absent, else new revision.</p>
</div>
</div>
</div>
<!-- (d) render well -->
<div class="well" style="margin-top:16px">
<p class="well-hd">3 · Render — Go template engine (client-side, in Helm)</p>
<div class="render3">
<div class="rcol" data-cross="merge">
<p class="rhd">Merged values</p>
<pre>values.yaml
+ <span class="acc">-f envs/a.yaml</span> (override)
+ <span class="acc">.Release.Name planpal</span>
+ <span class="acc">.Release.Namespace</span></pre>
</div>
<div class="rcol" data-cross="walk templates/">
<p class="rhd">Go template engine</p>
<ul class="mech">
<li><span class="tok">{{ .Values.x }}</span> <span class="gl">substitute</span></li>
<li><span class="tok">{{ if }} / {{ with }}</span> <span class="gl">conditional / scope</span></li>
<li><span class="tok">{{ range }}</span> <span class="gl">loop over apps map</span></li>
<li><span class="tok">{{ include "planpal.namespace" . }}</span> <span class="gl">named template from _helpers.tpl</span></li>
<li><span class="tok">| default | toJson | toString</span> <span class="gl">pipes</span></li>
</ul>
</div>
<div class="rcol">
<p class="rhd">Multi-doc YAML</p>
<pre>---
kind: Namespace
...
---
kind: Deployment
metadata:
name: <span class="acc">planpal-backend</span>
...
--- <span class="dim">(× many docs)</span></pre>
</div>
</div>
</div>
<!-- (g) reconcile + (h) add-ons -->
<div class="twin">
<div class="well">
<p class="well-hd">5 · Reconcile</p>
<div class="chain">
<span class="bx">Deployment</span><span class="ar"></span>
<span class="bx">ReplicaSet</span><span class="ar"></span>
<span class="bx">Pod</span><span class="ar"></span>
<span class="bx">scheduler</span><span class="ar"></span>
<span class="bx">EKS node</span>
</div>
<p class="cmd-note">Controllers reconcile the declared state. HPA scales replicas via metrics-server.</p>
</div>
<div class="well">
<p class="well-hd">6 · Cluster add-ons</p>
<div class="addon-row"><span class="obj">ExternalSecret</span><span class="ar">▶ ESO</span><span class="gl">reads AWS Secrets Manager → real k8s Secret</span></div>
<div class="addon-row"><span class="obj">Ingress</span><span class="ar">▶ ALB ctrl</span><span class="gl">provisions an ALB</span></div>
<div class="addon-row"><span class="obj">HPA</span><span class="ar">▶ metrics-server</span><span class="gl">feeds CPU/mem → scale</span></div>
</div>
</div>
<hr class="sep">
<!-- (e) BOM table -->
<p class="secthead">Template → object map<span class="st-sub">bill of materials · rendered from the chart, exact counts</span></p>
<div class="bom-wrap">
<table class="bom">
<thead><tr><th style="width:32%">Source file</th><th>Rendered objects</th></tr></thead>
<tbody>
<tr><td>templates/namespace.yaml</td><td>1 Namespace · 1 ServiceAccount <span class="dim">planpal-backend</span></td></tr>
<tr><td>templates/apps.yaml</td><td>5 Deployment · 2 Service · 5 HPA · 2 PDB &nbsp;<span class="note">← one {{ range }} loop, see DETAIL A</span></td></tr>
<tr><td>templates/deps.yaml</td><td>2 Deployment <span class="dim">redis, nats</span> · 2 Service &nbsp;<span class="note-dim">(these 2 also count toward the 7 Deployment total)</span></td></tr>
<tr><td>templates/external-secrets.yaml</td><td>1 ClusterSecretStore · 4 ExternalSecret</td></tr>
<tr><td>templates/ingress.yaml</td><td>1 Ingress <span class="dim">(ALB)</span></td></tr>
<tr><td>templates/seed-job.yaml</td><td>1 Job <span class="dim">seed-admin (runs once)</span></td></tr>
<tr class="sum"><td>Σ total</td><td>7 Deployment · 4 Service · 5 HPA · 2 PDB · 1 Ingress · 1 Job · 1 Namespace · 1 ServiceAccount · 1 ClusterSecretStore · 4 ExternalSecret</td></tr>
</tbody>
</table>
</div>
<hr class="sep">
<!-- lower band: DETAIL A | DETAIL B -->
<div class="lower">
<!-- (f) DETAIL A -->
<div class="detail">
<span class="detail-tab">Detail A</span>
<p class="detail-cap">One <code>{{ range .Values.apps }}</code> over a 5-entry map builds 5 Deployments. Conditionals add the extras: a Service <code>if service:</code>, an HPA <code>if hpa:</code>, a PDB <code>if pdb:</code>.</p>
<div class="fan">
<svg viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
<defs>
<marker id="ah" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="var(--accent)"/>
</marker>
</defs>
<!-- fan: single origin at bracket right-mid (x~40.5), 5 lines to lane mids -->
<line class="draw" style="--len:60" x1="40.5" y1="50" x2="52" y2="10" stroke="var(--accent)" stroke-width="0.5" marker-end="url(#ah)"/>
<line class="draw" style="--len:55" x1="40.5" y1="50" x2="52" y2="30" stroke="var(--accent)" stroke-width="0.5" marker-end="url(#ah)"/>
<line class="draw" style="--len:50" x1="40.5" y1="50" x2="52" y2="50" stroke="var(--accent)" stroke-width="0.5" marker-end="url(#ah)"/>
<line class="draw" style="--len:55" x1="40.5" y1="50" x2="52" y2="70" stroke="var(--accent)" stroke-width="0.5" marker-end="url(#ah)"/>
<line class="draw" style="--len:60" x1="40.5" y1="50" x2="52" y2="90" stroke="var(--accent)" stroke-width="0.5" marker-end="url(#ah)"/>
</svg>
<div class="fan-grid">
<!-- Zone L: apps map -->
<div class="lanes">
<div class="srcbox"><span class="nm">backend</span><span class="tags"><span class="tag">service:</span><span class="tag">hpa:</span><span class="tag">pdb:</span></span></div>
<div class="srcbox"><span class="nm">frontend</span><span class="tags"><span class="tag">service:</span><span class="tag">hpa:</span><span class="tag">pdb:</span></span></div>
<div class="srcbox"><span class="nm">schedule-worker</span><span class="tags"><span class="tag">hpa:</span></span></div>
<div class="srcbox"><span class="nm">notification-worker</span><span class="tags"><span class="tag">hpa:</span></span></div>
<div class="srcbox"><span class="nm">ai-worker</span><span class="tags"><span class="tag">hpa:</span></span></div>
</div>
<!-- Zone C: range bracket -->
<div class="bracket-col">
<span class="bracket-lbl">{{ range .Values.apps }} &nbsp;×5</span>
</div>
<!-- Zone R: emitted objects -->
<div class="outlanes">
<div class="oline"><span class="objbox dep">Deployment</span><span class="gate">if service:</span><span class="objbox">Service</span><span class="gate">if hpa:</span><span class="objbox">HPA</span><span class="gate">if pdb:</span><span class="objbox">PDB</span></div>
<div class="oline"><span class="objbox dep">Deployment</span><span class="gate">if service:</span><span class="objbox">Service</span><span class="gate">if hpa:</span><span class="objbox">HPA</span><span class="gate">if pdb:</span><span class="objbox">PDB</span></div>
<div class="oline"><span class="objbox dep">Deployment</span><span class="gate">if hpa:</span><span class="objbox">HPA</span></div>
<div class="oline"><span class="objbox dep">Deployment</span><span class="gate">if hpa:</span><span class="objbox">HPA</span></div>
<div class="oline"><span class="objbox dep">Deployment</span><span class="gate">if hpa:</span><span class="objbox">HPA</span></div>
</div>
</div>
</div>
<p class="fan-recon"><code>apps.yaml → 5 Deployment · 2 Service · 5 HPA · 2 PDB.</code> The redis + nats Deployments come from <code>deps.yaml</code>, not this loop — they are the other 2 in the total of 7.</p>
</div>
<!-- (i) DETAIL B -->
<div class="detail">
<span class="detail-tab">Detail B</span>
<p class="detail-cap">Each <code>helm install</code> lands in its own namespace + ALB + ClusterSecretStore, all defaulting to <code>.Release.Name</code> via <code>_helpers.tpl</code>.</p>
<div class="rel-cols">
<div class="rel-col">
<p class="rel-title">planpal-a</p>
<div class="rel-stack">
<div class="rel-cell">Namespace<br><span class="v">planpal-a</span></div>
<div class="rel-cell">Ingress → ALB<br><span class="dim">(own LB)</span></div>
<div class="rel-cell">ClusterSecretStore<br><span class="v">planpal-a-aws-planpal</span></div>
</div>
</div>
<div class="rel-col">
<p class="rel-title">planpal-b</p>
<div class="rel-stack">
<div class="rel-cell">Namespace<br><span class="v">planpal-b</span></div>
<div class="rel-cell">Ingress → ALB<br><span class="dim">(own LB)</span></div>
<div class="rel-cell">ClusterSecretStore<br><span class="v">planpal-b-aws-planpal</span></div>
</div>
</div>
</div>
<p class="rel-note">planpal.namespace = default .Release.Name → <span class="v">planpal-a</span> / <span class="v">planpal-b</span>
planpal.storeName = per-release CSS
The empty gutter is the isolation: no
connector crosses it. Two releases share
nothing on the cluster.</p>
</div>
</div>
<!-- title block -->
<div class="tblock-wrap">
<div class="tblock">
<div class="tb-row"><div class="tb-k">Chart</div><div class="tb-v">planpalto-helm</div></div>
<div class="tb-row"><div class="tb-k">Release</div><div class="tb-v v-acc">planpal</div></div>
<div class="tb-row"><div class="tb-k">Cluster</div><div class="tb-v">fakhri-k8 (EKS)</div></div>
<div class="tb-row"><div class="tb-k">Release state</div><div class="tb-v">Secret sh.helm.release.v1</div></div>
<div class="tb-row"><div class="tb-k">Scale</div><div class="tb-v">NTS</div></div>
<div class="tb-row"><div class="tb-k">Sheet</div><div class="tb-v">1 of 1</div></div>
<div class="tb-row"><div class="tb-k">Date</div><div class="tb-v">2026-09-01</div></div>
</div>
</div>
</div></div>