587 lines
24 KiB
HTML
587 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>rig — local environment installer</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
background: #0a0e17;
|
|
color: #e8eaf0;
|
|
font-family: 'Inter', sans-serif;
|
|
line-height: 1.6;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
header {
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid #1e2a4a;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 16px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
header h1 {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
letter-spacing: 3px;
|
|
color: #0066ff;
|
|
}
|
|
|
|
header .subtitle {
|
|
font-size: 13px;
|
|
color: #4a5568;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.layout { display: flex; flex: 1; min-height: 0; }
|
|
|
|
nav {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 200px;
|
|
flex-shrink: 0;
|
|
background: #121829;
|
|
border-right: 1px solid #1e2a4a;
|
|
padding: 8px 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
nav a {
|
|
padding: 10px 20px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 12px;
|
|
color: #8892a8;
|
|
text-decoration: none;
|
|
border-left: 2px solid transparent;
|
|
transition: all 0.15s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
nav a:hover { color: #e8eaf0; background: #1a2340; }
|
|
nav a.active { color: #0066ff; border-left-color: #0066ff; background: #0d1a33; }
|
|
|
|
main { flex: 1; overflow: auto; padding: 32px 48px; }
|
|
|
|
.section { display: none; animation: fadeIn 0.2s ease; }
|
|
.section.active { display: block; }
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
|
|
.section h2 {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
color: #8892a8;
|
|
margin-bottom: 8px;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.section > p.lede {
|
|
font-size: 13px;
|
|
color: #4a5568;
|
|
margin-bottom: 24px;
|
|
max-width: 800px;
|
|
}
|
|
|
|
.prose { max-width: 820px; }
|
|
.prose p { font-size: 14px; color: #b4bccf; line-height: 1.7; margin-bottom: 14px; }
|
|
.prose p b { color: #e8eaf0; }
|
|
.prose ul { margin: 0 0 16px 20px; }
|
|
.prose li { font-size: 14px; color: #b4bccf; margin-bottom: 6px; }
|
|
|
|
.prose h3 {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 13px;
|
|
text-transform: uppercase;
|
|
color: #e8eaf0;
|
|
margin: 32px 0 10px;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.prose code, pre code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 12px;
|
|
color: #7ab0ff;
|
|
background: #121829;
|
|
padding: 1px 5px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
pre {
|
|
background: #121829;
|
|
border: 1px solid #1e2a4a;
|
|
padding: 16px;
|
|
overflow: auto;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
pre code { background: none; padding: 0; }
|
|
pre .c { color: #4a5568; }
|
|
pre .k { color: #0066ff; }
|
|
|
|
.graph-container { margin: 16px 0; }
|
|
.graph-container img {
|
|
display: block;
|
|
max-width: 100%;
|
|
background: #0a0e17;
|
|
border: 1px solid #1e2a4a;
|
|
padding: 12px;
|
|
}
|
|
|
|
dl {
|
|
display: grid;
|
|
grid-template-columns: max-content 1fr;
|
|
gap: 10px 24px;
|
|
margin: 16px 0;
|
|
max-width: 820px;
|
|
}
|
|
|
|
dt {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
color: #0066ff;
|
|
font-size: 13px;
|
|
padding-top: 2px;
|
|
}
|
|
|
|
dd { font-size: 14px; color: #b4bccf; line-height: 1.6; }
|
|
|
|
table { border-collapse: collapse; margin: 16px 0; max-width: 820px; }
|
|
th, td {
|
|
text-align: left;
|
|
padding: 7px 16px 7px 0;
|
|
font-size: 13px;
|
|
border-bottom: 1px solid #1e2a4a;
|
|
color: #b4bccf;
|
|
}
|
|
th {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
color: #8892a8;
|
|
letter-spacing: 1px;
|
|
}
|
|
td code { white-space: nowrap; }
|
|
|
|
.note {
|
|
border-left: 2px solid #ffc107;
|
|
background: #17130a;
|
|
padding: 12px 16px;
|
|
margin: 16px 0;
|
|
max-width: 820px;
|
|
}
|
|
.note p { margin: 0; font-size: 13px; color: #b4bccf; }
|
|
.note b { color: #ffc107; }
|
|
|
|
.menu-toggle {
|
|
display: none;
|
|
background: transparent;
|
|
border: 1px solid #1e2a4a;
|
|
color: #8892a8;
|
|
padding: 6px 10px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
line-height: 1;
|
|
margin-left: auto;
|
|
}
|
|
.menu-toggle:hover { background: #1a2340; }
|
|
|
|
.nav-backdrop {
|
|
display: none;
|
|
position: absolute;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 10;
|
|
}
|
|
.layout.nav-open .nav-backdrop { display: block; }
|
|
|
|
@media (max-width: 720px) {
|
|
header { padding: 10px 12px; gap: 8px; }
|
|
header h1 { font-size: 16px; letter-spacing: 1px; }
|
|
header .subtitle { display: none; }
|
|
.menu-toggle { display: inline-block; }
|
|
.layout { position: relative; }
|
|
nav {
|
|
position: absolute; left: 0; top: 0; bottom: 0;
|
|
width: 200px; z-index: 20;
|
|
transform: translateX(-100%);
|
|
transition: transform 0.2s ease;
|
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.5);
|
|
}
|
|
.layout.nav-open nav { transform: translateX(0); }
|
|
main { padding: 16px; }
|
|
.section h2 { font-size: 13px; }
|
|
.prose p, .prose li { font-size: 13px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<h1>RIG</h1>
|
|
<span class="subtitle">local environment installer</span>
|
|
<button class="menu-toggle">☰</button>
|
|
</header>
|
|
|
|
<div class="layout">
|
|
<div class="nav-backdrop"></div>
|
|
<nav>
|
|
<a href="#start">Start here</a>
|
|
<a href="#steps">The steps</a>
|
|
<a href="#install">Installation</a>
|
|
<a href="#environments">Environments</a>
|
|
<a href="#profiles">Profiles</a>
|
|
<a href="#registry">Registry</a>
|
|
<a href="#architecture">Architecture</a>
|
|
<a href="#troubleshooting">Troubleshooting</a>
|
|
</nav>
|
|
|
|
<main>
|
|
|
|
<section class="section" id="start">
|
|
<h2>Start here</h2>
|
|
<p class="lede">A runnable local model of a large, regulated estate — legacy and new side by side.</p>
|
|
<div class="prose">
|
|
<p>rig builds a disposable Kubernetes environment on your machine so you can
|
|
explore how a system fits together without needing access to any of it. Its
|
|
job is <b>onboarding and exploration</b>, not a production replica.</p>
|
|
|
|
<p>Most services in it are deliberately <b>not real</b>. What has to be faithful
|
|
is the topology — the names, the ports, the dependency order, who can reach whom,
|
|
and how it fails. The workloads themselves are noise. This is what makes the
|
|
whole estate fit on a laptop: a real 20-service platform will not fit even once
|
|
on 14 GB, but mocks are about 30 MB each, so three faithful copies do.</p>
|
|
|
|
<h3>The only prerequisite</h3>
|
|
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
|
|
<pre><code><span class="c"># then, in the environment directory:</span>
|
|
make check <span class="c"># is this machine ready? reports, never fixes</span>
|
|
make deps <span class="c"># install the pinned toolchain</span>
|
|
make cluster up <span class="c"># build the cluster for the active profile</span>
|
|
</code></pre>
|
|
<p>Read <code>make check</code> before <code>make deps</code>. It never changes
|
|
anything — it prints what it found and, at the end, the steps it cannot perform
|
|
for you.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="steps">
|
|
<h2>The steps</h2>
|
|
<p class="lede">Start to finish, in order, with what each one actually does.</p>
|
|
<div class="prose">
|
|
|
|
<h3>1 · make check</h3>
|
|
<p>Asks whether this machine is ready. It <b>changes nothing</b> — it
|
|
reports what it found and, at the end, the things only a human can do
|
|
(anything needing <code>sudo</code>, or a Windows-side restart). Read it
|
|
before installing anything; it is faster than discovering the same problems
|
|
one failure at a time.</p>
|
|
<pre><code>make check</code></pre>
|
|
|
|
<h3>2 · make setup</h3>
|
|
<p>Does the preparation that can be automated: installs the pinned
|
|
toolchain if it is missing, checks PATH, Docker, and this environment's
|
|
ports. Every step is independently checked, so running it twice is safe and
|
|
running it half-configured finishes the job.</p>
|
|
<p>It <b>does not stop at the first failure</b>. A setup script that dies at
|
|
step two hides that steps four and five would also have failed, and on an
|
|
unfamiliar machine the complete list is the point. The tail of the output is
|
|
a to-do list of only what is outstanding.</p>
|
|
<pre><code>make setup <span class="c"># host + toolchain</span>
|
|
</code></pre>
|
|
|
|
<h3>3 · make cluster up</h3>
|
|
<p>Builds the cluster for the active profile. It prints what the profile
|
|
locks in <i>before</i> spending the time, because the CNI and the audit
|
|
policy are fixed at creation and cannot be changed afterwards.</p>
|
|
<p>Re-running is safe and, more importantly, <b>convergent</b>: if a first
|
|
attempt was interrupted before the CNI was installed, running it again
|
|
finishes the job rather than reporting "already exists" and leaving every
|
|
node permanently NotReady.</p>
|
|
<pre><code>make cluster up <span class="c"># built-in defaults — no profile needed</span>
|
|
make cluster up PROFILE=client <span class="c"># after copying env.d/client.env.example: three nodes, audit, cached registry</span>
|
|
make cluster reset <span class="c"># destroy and rebuild — the only way to change CNI or audit</span>
|
|
</code></pre>
|
|
|
|
<h3>4 · make docs</h3>
|
|
<p>Serves this page from a throwaway container. Works with no cluster and
|
|
no toolchain, which is deliberate: these pages are the instructions for
|
|
building everything else, so they cannot depend on it.</p>
|
|
<pre><code>make docs</code></pre>
|
|
|
|
<h3>Checking on things</h3>
|
|
<dl>
|
|
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. The usual reason a new one will not start is an old one you forgot about; <code>make cluster free</code> frees them without deleting.</dd>
|
|
<dt>make ports</dt><dd>This environment's port block, and whether each is derived or overridden.</dd>
|
|
<dt>make registry</dt><dd>Which of the four registry modes is active, and where it points.</dd>
|
|
</dl>
|
|
|
|
<h3>Running more than one</h3>
|
|
<p>Copy the directory, rename it, and repeat from step 2. Cluster name,
|
|
context, image tags and the port block all follow the directory name, so
|
|
the second environment collides with nothing and neither one's teardown can
|
|
reach the other.</p>
|
|
<pre><code>cp -r rig ../platform-v2 && cd ../platform-v2
|
|
make setup && make cluster up
|
|
</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="install">
|
|
<h2>Installation</h2>
|
|
<p class="lede">A container installs onto the host and then gets out of the way.</p>
|
|
<div class="graph-container">
|
|
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="Installation flow"></a>
|
|
</div>
|
|
<div class="prose">
|
|
<p>The installer is a container, not a shell script, for a specific reason: a
|
|
stock slim Debian has no <code>curl</code>, no <code>wget</code>, no
|
|
<code>jq</code>, no <code>python3</code> and <b>no CA bundle</b>. A shell
|
|
installer could not make a verified HTTPS request, let alone check one. The
|
|
container carries its own toolchain, so the host needs nothing but Docker.</p>
|
|
|
|
<p>The cluster never runs inside that container. Everything it installs —
|
|
kind, kubectl, tilt, jq — runs natively afterwards, so nothing pays a
|
|
container tax during daily work.</p>
|
|
|
|
<h3>Pinned and verified</h3>
|
|
<p>Every tool is a single binary fetched at a pinned version and checked
|
|
against a published SHA256. Node images are pinned <b>by digest</b>, so
|
|
upgrading kind cannot silently move your Kubernetes version.</p>
|
|
|
|
<h3>Not every machine should get cluster tooling</h3>
|
|
<p>A managed or corporate-issued machine — the kind that holds the access
|
|
you cannot get anywhere else — is not somewhere to install development
|
|
tools by default. So the toolchain comes in two tiers:</p>
|
|
<table>
|
|
<tr><th>tier</th><th>installs</th><th>for</th></tr>
|
|
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
|
|
<tr><td><code>dev</code></td><td>+ kind, tilt</td><td>build clusters and hot-reload into them</td></tr>
|
|
</table>
|
|
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
|
|
make deps <span class="c"># dev, the default</span>
|
|
make setup core <span class="c"># same distinction, via setup</span>
|
|
</code></pre>
|
|
<p>Testing <i>in situ</i> on a managed machine is still possible — install
|
|
the <code>dev</code> tier deliberately when you need it. The point is that
|
|
it should be a decision rather than a side effect of running setup.</p>
|
|
<p>The documentation itself needs neither tier: <code>make docs</code>
|
|
wants only Docker.</p>
|
|
|
|
<h3>Air-gapped</h3>
|
|
<pre><code>make deps-image full <span class="c"># bakes every binary into the image</span>
|
|
docker save …-deps:full | gzip > rig.tgz
|
|
<span class="c"># carry that one file in, then:</span>
|
|
docker load < rig.tgz && make cluster up PROFILE=offline <span class="c"># from env.d/offline.env.example</span>
|
|
</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="environments">
|
|
<h2>Environments</h2>
|
|
<p class="lede">One directory is one environment. Copy it, rename it, run it.</p>
|
|
<div class="graph-container">
|
|
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
|
|
</div>
|
|
<div class="prose">
|
|
<p>Running several versions of a system at once means several clusters on one
|
|
machine, not several machines. Everything that could collide is derived from
|
|
the directory name:</p>
|
|
<dl>
|
|
<dt>cluster + context</dt><dd><code>acmebank/</code> builds <code>acmebank</code> on <code>kind-acmebank</code>.</dd>
|
|
<dt>port block</dt><dd>Ten ports from a hash of the name, in the 20000+ range — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
|
|
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
|
|
</dl>
|
|
<p>Two copies therefore never collide, and neither one's
|
|
<code>make cluster down</code> can touch the other. <code>make ports</code>
|
|
shows the block; <code>make ports persist</code> freezes it into
|
|
<code>ctrl/.env</code> if you want it fixed rather than derived.</p>
|
|
|
|
<h3>Configuration layers</h3>
|
|
<p>Weakest first, later wins: built-in defaults → pinned versions → a
|
|
profile, if you name one → <code>ctrl/.env</code> → the environment. So
|
|
<code>make cluster up PROFILE=<name></code> always beats every file.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="profiles">
|
|
<h2>Profiles</h2>
|
|
<p class="lede">Cluster shape is declared, not baked in.</p>
|
|
<div class="prose">
|
|
<table>
|
|
<tr><th>profile</th><th>nodes</th><th>audit</th><th>registry</th><th>for</th></tr>
|
|
<tr><td><code>minimal</code></td><td>1</td><td>off</td><td>none</td><td>first boot; assumes nothing</td></tr>
|
|
<tr><td><code>client</code></td><td>3</td><td>on</td><td>mirror</td><td>the regulated shape</td></tr>
|
|
<tr><td><code>offline</code></td><td>1</td><td>on</td><td>local</td><td>air-gapped</td></tr>
|
|
</table>
|
|
|
|
<div class="note"><p><b>The audit policy cannot be changed later.</b> It is an
|
|
apiserver flag, fixed when the cluster is created. <code>cluster up</code>
|
|
prints what a profile locks in before spending the time, and
|
|
<code>make cluster reset</code> is the way out.</p></div>
|
|
|
|
<h3>LoadBalancer services</h3>
|
|
<p>Real manifests use <code>type: LoadBalancer</code>, because a real
|
|
cluster has one. On a bare local cluster those Services sit at
|
|
<code>EXTERNAL-IP <pending></code> forever, with no error anywhere —
|
|
the deployment looks healthy and simply is not reachable.</p>
|
|
<p>The <code>metallb</code> addon fixes that, so the same manifests work
|
|
here as upstream and nothing has to be rewritten to NodePort. Its address
|
|
pool is derived from the cluster's Docker network at install time rather
|
|
than hardcoded, because Docker picks that subnet and it differs between
|
|
machines.</p>
|
|
<div class="note"><p><b>Where those addresses are reachable from.</b> The
|
|
pool lives on the Docker bridge, so LoadBalancer IPs work from the Linux
|
|
side — including from inside WSL. A browser on Windows has no route to
|
|
them. Use the ingress host ports for anything you need to open in a
|
|
browser.</p></div>
|
|
|
|
<h3>Networking</h3>
|
|
<p>The cluster uses kind's built-in networking, which <b>does</b> enforce
|
|
standard NetworkPolicy — verified against a no-policy control, not assumed.
|
|
The widely repeated claim that it accepts policies and silently ignores
|
|
them is out of date.</p>
|
|
<p>A pluggable CNI was tried and removed: it only added
|
|
GlobalNetworkPolicy, policy tiers and egress-CIDR rules, none of which are
|
|
needed yet, in exchange for a slower boot and one more thing that has to be
|
|
right at creation time. Worth revisiting only when a policy the built-in
|
|
cannot express actually comes up.</p>
|
|
|
|
<h3>Memory</h3>
|
|
<p>Every cluster is a running container tree whether you are using it or not.
|
|
<code>make cluster list</code> shows what exists and what it costs;
|
|
<code>make cluster free</code> stops the others without deleting them.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="registry">
|
|
<h2>Registry</h2>
|
|
<p class="lede">Local, cached, or straight to the corporate registry.</p>
|
|
<div class="prose">
|
|
<table>
|
|
<tr><th>mode</th><th>what it does</th></tr>
|
|
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
|
|
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
|
|
<tr><td><code>mirror</code></td><td>that container as a <b>pull-through cache</b> of the corporate registry</td></tr>
|
|
<tr><td><code>remote</code></td><td>no local container; pull direct with an imagePullSecret</td></tr>
|
|
</table>
|
|
<p><code>mirror</code> is what a locked-down network actually looks like:
|
|
images originate from the corporate registry, you do not hammer it, and you
|
|
keep working when the connection drops.</p>
|
|
|
|
<div class="note"><p><b>The corporate CA will bite you.</b> A corporate
|
|
registry is usually behind an internal CA, and trust has to reach
|
|
<b>three</b> places: the host Docker daemon, every cluster node's containerd
|
|
(nodes do <i>not</i> inherit host trust), and any in-cluster client. Set
|
|
<code>REGISTRY_CA_FILE</code> and <code>make check</code> reports which is
|
|
still missing. The symptom otherwise is an opaque
|
|
<code>x509: certificate signed by unknown authority</code>.</p></div>
|
|
|
|
<p>Reachability also depends on where you are: if the registry is only
|
|
routable from a managed workspace, <code>mirror</code> and <code>remote</code>
|
|
will not resolve from a laptop at all. That is what <code>local</code> and
|
|
<code>offline</code> are for.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="architecture">
|
|
<h2>Architecture</h2>
|
|
<p class="lede">The estate being modelled.</p>
|
|
<div class="note"><p><b>TODO — placeholder.</b> The diagram below is
|
|
illustrative only: it shows how a mocked dependency, a service under active
|
|
work, and an unreachable remote system sit together. It is not the real
|
|
topology. Replace <code>docs/graphs/03-architecture.dot</code> with the
|
|
extracted platform diagrams, then run <code>make docs graphs</code>.</p></div>
|
|
<div class="graph-container">
|
|
<a href="viewer.html?src=graphs/03-architecture.svg"><img src="graphs/03-architecture.svg" alt="Estate topology (placeholder)"></a>
|
|
</div>
|
|
<div class="prose">
|
|
<p>Each component is one of three things, and switching between them should be
|
|
a one-line change rather than a rewrite:</p>
|
|
<dl>
|
|
<dt>real</dt><dd>Built from source and hot-reloaded. The thing you are actually working on — usually exactly one.</dd>
|
|
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
|
|
<dt>remote</dt><dd>No pod at all: a Service of type ExternalName pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
|
|
</dl>
|
|
<p>The intended end state is that this diagram is <b>generated from the
|
|
running cluster</b> rather than drawn by hand — so it becomes a report of
|
|
what exists instead of a picture of what was once intended.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="troubleshooting">
|
|
<h2>Troubleshooting</h2>
|
|
<p class="lede">The failures that are hard to diagnose from their symptoms.</p>
|
|
<div class="prose">
|
|
<h3>Tilt stops noticing file changes</h3>
|
|
<p>Almost always <code>inotify</code> limits, and it fails <i>silently</i> —
|
|
nothing errors, changes just stop being picked up. Defaults on WSL are far too
|
|
low. <code>make check</code> reports it and prints the fix.</p>
|
|
|
|
<h3>Cluster creation dies halfway with a port error</h3>
|
|
<p>Docker reports <code>failed to bind host port … address already in use</code>
|
|
partway through creating the cluster. Run <code>make check</code> first — it
|
|
checks every port in this environment's block before anything is built.</p>
|
|
|
|
<h3>Every node stays NotReady</h3>
|
|
<p>Usually a cluster created with the default CNI disabled but the real CNI
|
|
never installed — typically an interrupted first run. Just run
|
|
<code>make cluster up</code> again: it converges rather than exiting early, and
|
|
will finish the missing steps.</p>
|
|
|
|
<h3>x509: certificate signed by unknown authority</h3>
|
|
<p>Corporate CA trust has not reached one of the three places it needs to be.
|
|
See <a href="#registry">Registry</a>.</p>
|
|
|
|
<h3>kubectl says the context does not exist</h3>
|
|
<p>The cluster can exist while its context does not — a reset or a switched
|
|
<code>KUBECONFIG</code> loses it. <code>make cluster up</code> detects this and
|
|
re-exports the context.</p>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var layout = document.querySelector('.layout');
|
|
var main = document.querySelector('main');
|
|
|
|
function syncActive() {
|
|
var hash = location.hash.slice(1) || 'start';
|
|
document.querySelectorAll('.section').forEach(function (s) { s.classList.remove('active'); });
|
|
document.querySelectorAll('nav a').forEach(function (a) { a.classList.remove('active'); });
|
|
var section = document.getElementById(hash);
|
|
if (section) section.classList.add('active');
|
|
var link = document.querySelector('nav a[href="#' + hash + '"]');
|
|
if (link) link.classList.add('active');
|
|
if (main) main.scrollTop = 0;
|
|
layout.classList.remove('nav-open');
|
|
}
|
|
|
|
window.addEventListener('hashchange', syncActive);
|
|
window.addEventListener('DOMContentLoaded', syncActive);
|
|
syncActive();
|
|
|
|
document.addEventListener('click', function (e) {
|
|
if (e.target.closest('.menu-toggle') || e.target.closest('.nav-backdrop')) {
|
|
layout.classList.toggle('nav-open');
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|