design: gates 3 and 4 — one file, and two flags I had not thought of

Setting --node-ip alone would have been wrong in two ways I only found by
looking. servicelb publishes the node's address, so the traefik service and
every single ingress currently carry the public IP and would have started
carrying 10.0.0.2. And the API certificate's SAN list has no 10.0.0.2 in it, so
regenerating it while the kubeconfig points at the public address is how you
lock yourself out. The line now carries --node-external-ip and --tls-san
alongside.

Two risks I had assumed turned out not to exist: alloy scrapes pods only, never
the kubelet or the node role, and cert-manager solves HTTP01 over the public
name. Neither cares what the node advertises.

The install step runs with SKIP_START on purpose. The whole risk of this
undertaking is what the script writes into that unit, so it gets read before
anything starts rather than trusted.
This commit is contained in:
Thore Cimbal
2026-08-21 12:00:00 +00:00
parent 1d0a8b0120
commit 3e55a42ea6
3 changed files with 100 additions and 2 deletions
+1 -1
View File
@@ -88,7 +88,7 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md).
| Design | Gate | Title |
|---|---|---|
| [2026-08-22-k3s-patch-1-34-10](docs/design/2026-08-22-k3s-patch-1-34-10.md) | gate-2 | Design: k3s v1.34.6 → v1.34.10 — die mitgelieferten Komponenten nachziehen |
| [2026-08-22-k3s-patch-1-34-10](docs/design/2026-08-22-k3s-patch-1-34-10.md) | gate-4 | Design: k3s v1.34.6 → v1.34.10 — die mitgelieferten Komponenten nachziehen |
## ADRs (27)
+96 -1
View File
@@ -1,6 +1,6 @@
---
type: design
status: gate-2
status: gate-4
date: 2026-08-22
size: L
related:
@@ -267,3 +267,98 @@ repariert." Begründung, Beleg und die verworfenen Optionen B und C gehören
festgehalten — sonst schreibt sie der nächste Mensch aus guter Absicht zurück.
> **STOP — wartet auf Gate-2-Freigabe.**
## Gate 3 + 4 — Program Design und Slices
Zusammengelegt: Es gibt **eine** zu ändernde Datei und eine Kommandofolge; ein
getrenntes Gate 4 hätte dieselbe Liste zweimal.
### Die einzige Datei
`/etc/systemd/system/k3s.service` — nichts sonst. Kein Manifest, kein
Helm-Wert, keine Zeile in einem Repo.
⚠️ **Und das ist selbst ein Befund.** Die gesamte Konfiguration dieses Clusters
steht in einer Unit auf einem Host und in **keinem** Repository. Genau die
Lücke, die [#0110](../issues/0110-haertung-lebt-auf-dem-host-das-repo-weiss-nichts-davon.md)
für den Betriebs-Stack beschreibt — hier trifft sie den Cluster selbst.
**Neue ExecStart-Zeile:**
```
/usr/local/bin/k3s server \
--node-ip=10.0.0.2 \
--node-external-ip=49.13.132.245 \
--tls-san=49.13.132.245
```
Drei Flags, jedes mit einem Grund:
| Flag | warum |
|---|---|
| `--node-ip=10.0.0.2` | Der Knoten kündigt heute seine **öffentliche** Adresse als `InternalIP` an. Der vSwitch liegt auf `enp7s0`, die Route zu CFGMON geht darüber. |
| `--node-external-ip=49.13.132.245` | ⚠️ **Sonst veröffentlicht `servicelb` die private Adresse.** Der Traefik-Dienst trägt heute `49.13.132.245`, und **jeder** Ingress veröffentlicht sie. Ohne dieses Flag stünde dort `10.0.0.2`. |
| `--tls-san=49.13.132.245` | ⚠️ Die SANs des API-Zertifikats sind heute `49.13.132.245`, die öffentliche IPv6, `10.43.0.1`, `127.0.0.1`, `localhost`, `matrix`. **`10.0.0.2` fehlt.** Beim Adresswechsel wird das Zertifikat neu erzeugt; ohne dieses Flag kann die öffentliche Adresse herausfallen — und unsere Kubeconfig zeigt genau dorthin. |
**Nicht** in der Zeile: `--disable=traefik`, `--disable=servicelb`. Sie werden
gestrichen, nicht repariert — der Ingress bleibt vorerst k3s-verwaltet.
### Was der Adresswechsel NICHT anfasst — geprüft, nicht angenommen
| | |
|---|---|
| **Alloy** | scrapt ausschließlich `role = "pod"`, keinen Kubelet und keine Knoten-Rolle. Unberührt. |
| **cert-manager** | HTTP01 über die IngressClass `traefik`; Let's Encrypt kommt über den öffentlichen Namen, der Weg ändert sich nicht. |
| **NetworkPolicies** | verweisen auf `kube-system` per Namensraum-Label, nicht auf Adressen. |
| **Prometheus remote-write** | geht aus dem Cluster nach `10.0.0.3` — Gegenrichtung, unberührt. |
| **Flux** | spricht `kubernetes.default` im Cluster an. |
⚠️ **Was sich ändert und in die Abnahme gehört:** Der Knoten verliert seine
öffentliche **IPv6** als `InternalIP` (`--node-ip` setzt IPv4). Die Pod-CIDRs
sind ohnehin IPv4-only (in #0088 gemessen), aber die Serie `kube_node_info`
ändert sich — und damit möglicherweise Panels, die daran hängen.
### Ablauf
⚠️ **Ein Stopp, nicht zwei.** Der Datenbank-Abzug verlangt einen gestoppten
Dienst (eine offene sqlite-Datei ist kein konsistenter Abzug). Also wird
gestoppt, gesichert, aufgerüstet und gestartet — der Ausfall beginnt mit der
Sicherung, nicht mit dem Upgrade.
| Slice | Was | Prüfung danach |
|---|---|---|
| **0** | **Vorher-Bild festhalten:** Pod-Liste, `kubectl get nodes -o wide`, SAN-Liste, Antwort von `axion1337.chat` und `wiki.axion1337.chat`, CVE-Zahlen. | Datei liegt vor; ohne sie ist „danach ist alles wie vorher" nicht belegbar. |
| **1** | `systemctl stop k3s`; `cp /usr/local/bin/k3s /root/k3s-v1.34.6.bak`; `cp /etc/systemd/system/k3s.service /root/k3s.service.bak`; `cp -a /var/lib/rancher/k3s/server/db /root/db-vor-1.34.10/` | Alle drei existieren, `db` ist ~60 MB. ⚠️ **Ohne diesen Beleg wird nicht weitergemacht.** |
| **2** | Installationsskript mit `INSTALL_K3S_VERSION=v1.34.10+k3s1`, `INSTALL_K3S_EXEC="server --node-ip=10.0.0.2 --node-external-ip=49.13.132.245 --tls-san=49.13.132.245"` und **`INSTALL_K3S_SKIP_START=true`** | ⚠️ **Nicht starten.** Erst die geschriebene Unit **lesen** und gegen die obige Zeile halten — genau hier liegt das Risiko dieses Vorhabens. |
| **3** | `systemctl start k3s` | Innerhalb ~70 s: Knoten `Ready`, Fassung `v1.34.10+k3s1`, alle Pods `Running`. |
| **4** | Abnahme gegen das Vorher-Bild | Ingress von außen, Anmeldung, SANs, Knoten-Adressen, sechs Komponenten-Fassungen. |
| **5** | Nacharbeit: die sechs `rancher/*`-Einträge aus `entscheidungen.json` entfernen, ADR schreiben, Ledger schließen | `cve_critical_offen` = 0, `cve_entscheidungen_ohne_befund` = 0. |
### Grenzen — DO NOT CHANGE
- **Kein `--disable`.** Nicht in dieser Zeile, nicht „schon mal vorbereitet".
- **Keine Minor-Sprünge.** Ziel ist exakt `v1.34.10+k3s1`.
- **Keine `HelmChartConfig`.** Traefik bleibt auf k3s-Vorgaben.
- **Kein Anfassen der Arbeitslasten.** Wer während des Fensters „schnell noch"
etwas ausrollt, macht die Abnahme wertlos.
### Wackeligste Annahmen
1. **Dass das Installationsskript `INSTALL_K3S_EXEC` so übernimmt, wie erwartet.**
Deshalb Slice 2 mit `SKIP_START` und Lesen statt Vertrauen. Wenn die Unit
anders aussieht: anhalten, nicht korrigieren und weiterlaufen.
2. **Dass ein Patch-Rückschritt trägt.** Innerhalb derselben Minor-Reihe ist er
unterstützt; sicher ist erst der Datenbank-Abzug daneben.
3. **Dass 70 Sekunden reichen.** Gemessen am Neustart vom 2026-08-01 — der war
allerdings **ohne** Fassungswechsel und ohne neue Images. Diesmal müssen
sechs Images gezogen werden. ⚠️ Realistisch also länger; die Images können in
Slice 0 vorgezogen werden (`crictl pull`), dann bleibt es bei der Größenordnung.
### Neue ADRs
**Einer**, nach dem Sprung zu schreiben: „Der Ingress bleibt vorerst
k3s-verwaltet; die drei nie wirksamen Flags werden gestrichen, `--node-ip`
wird bewusst gesetzt." Mit Beleg, warum die Flags vier Monate lang nichts taten
— sonst schreibt der Nächste sie aus guter Absicht zurück.
> **STOP — wartet auf Gate-3/4-Freigabe.**
@@ -16,6 +16,7 @@ related:
|---|---|---|---|---|
| 1 | bf2d87a | sorb | DONE | Umfang, sechs Abnahmekriterien, zwei offene Entscheidungen (Ausfallzeit, Traefik-Chart-Major); ⚠️ korrigiert die eigene Aussage „grosses Vorhaben" — es ist ein Patch auf der eigenen Minor-Reihe. |
| 2 | 1d0d5f1 | sorb | DONE_WITH_CONCERNS | Fassungen gepinnt, Traefik-Major gerendert (identisch), Ausfallzeit aus der Historie gemessen (~70 s); ⚠️ Fund: drei k3s-Flags sind seit der Installation unwirksam, und das Installationsskript schreibt die Unit neu. |
| 3+4 | PENDING | sorb | DONE | Eine Datei, sechs Slices; ExecStart um `--node-external-ip` und `--tls-san` ergaenzt, weil `--node-ip` allein die veroeffentlichte Adresse und das API-Zertifikat mitreisst. |
## Slices
@@ -34,6 +35,8 @@ related:
| die genauen Fassungen der Release, statt die neuesten Tags zu nehmen | `k3s-images.txt` der Release nennt jedes Image mit Tag — klipper-helm v0.13.3-build20260727, coredns 1.14.6, local-path v0.0.36 | reused: das Herausgeber-Verzeichnis; die Korrektur kostet 29 HIGH gegenueber meiner Schaetzung und wird VOR der Arbeit im Kriterium nachgezogen | |
| das ECHTE laufende Traefik-Chart, statt upstream als Stellvertreter zu nehmen | k3s legt die Chart-Tarballs unter `/var/lib/rancher/k3s/server/static/charts/` ab; von dort geholt und mit den echten `HelmChart`-Werten gerendert | reused: der laufende Bestand als Vergleichsseite — Objekte, Dienste, Ports, Selektoren, RBAC und Argumente sind identisch | |
| wie lange ein k3s-Neustart wirklich kostet, statt „einige Minuten" zu schaetzen | der Neustart vom 2026-08-01 steht in den Container-Startzeiten: 19:37:06 Dienst, 19:38:14 letzter Container | reused: die eigene Historie als Messung — ~70 s statt einer Vermutung, und kein Pod-Objekt wurde neu erzeugt | |
| was der Adresswechsel sonst anfasst, statt nur an die Kubeconfig zu denken | `servicelb` veroeffentlicht die Knoten-Adresse: der Traefik-Dienst UND jeder Ingress tragen heute 49.13.132.245 — ohne `--node-external-ip` stuende dort kuenftig 10.0.0.2 | built: die ExecStart-Zeile traegt jetzt drei Flags statt einem; der Fund kam aus dem Nachsehen, nicht aus dem Nachdenken | |
| ob Alloy oder cert-manager an der Knoten-Adresse haengen | Alloy scrapt ausschliesslich `role = "pod"`, kein Kubelet, keine Knoten-Rolle; cert-manager loest HTTP01 ueber den oeffentlichen Namen | reused: die eigene Konfiguration als Pruefmassstab — zwei vermutete Risiken sind damit gegenstandslos, statt sie vorsorglich zu behandeln | |
| ob die Unit ueberhaupt das tut, was sie sagt | NEIN: `server` steht zweimal in ExecStart, alles danach wird als Positionsargument gelesen. `k3s.io/node-args` fuehrt die Flags, `k3s.io/internal-ip` zeigt die oeffentliche Adresse, `traefik.yaml` ist ausgerollt | built: nichts — aber der Fund bestimmt das ganze Vorhaben: das Installationsskript schreibt die Unit neu, und dann werden drei seit vier Monaten stille Flags scharf | |
## Notes