docs: postmortem + changelog for 2026-06-06 template/DHCP session
Postmortem of the Linux template deploy/prepare 'no IP' investigation: separates the three distinct causes (apt index stale from --skip-update, deploy --force snapshot-chain, wedged host vmnet-dhcpd) and documents the recovery (restart vmware-networks). Changelog entry summarising the toolchain, xvfb, --force cleanup and cloud-init seal changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# Postmortem — Template Linux: deploy/prepare "no IP" (2026-06-06)
|
||||
|
||||
## Sintesi
|
||||
|
||||
Durante l'aggiunta di dipendenze GUI al toolchain del template Linux
|
||||
(`python3-gi gir1.2-gtk-4.0 xvfb`), una serie di fallimenti ha aperto una lunga
|
||||
sessione di debug. Sono emersi **due problemi distinti**, più una causa
|
||||
ambientale che ha confuso la diagnosi:
|
||||
|
||||
1. **Origine reale della sessione** — `prepare-linux` falliva allo Step 4a con
|
||||
`Unable to locate package gir1.2-gtk-4.0`. Causa: `--skip-update` saltava
|
||||
anche `apt-get update`, lasciando l'indice apt vecchio. **Non c'entrava la
|
||||
rete.** Fix banale, applicato.
|
||||
|
||||
2. **Bug `deploy-linux --force`** — il resize allo Step 4 falliva con
|
||||
*"disk is part of a snapshot chain"* perché `--force` riusava la directory
|
||||
senza rimuovere la snapshot chain precedente. Bug deterministico, colpito
|
||||
due volte. Fix applicato.
|
||||
|
||||
3. **Saga "no IP" (ambientale)** — il rilevamento IP via open-vm-tools andava in
|
||||
timeout in modo **intermittente**. Causa finale: il servizio DHCP/NAT di
|
||||
vmnet8 sull'host si era **inceppato** a causa della churn di debug (decine di
|
||||
`vmrun start/stop`, `tcpdump` promiscuo, DISCOVER ripetuti del guest).
|
||||
Risolto con un **restart di `vmware-networks`**. Non è un bug del codice né
|
||||
del CI normale.
|
||||
|
||||
Stato finale: template `BaseClean-Linux` ricostruito da powered-off, con gtk/xvfb
|
||||
e cloud-init sigillato. Tutti i test passano (coverage 94.4%).
|
||||
|
||||
---
|
||||
|
||||
## Cronologia
|
||||
|
||||
| Ora (host) | Evento |
|
||||
|-----------|--------|
|
||||
| ~10:50 | `prepare-linux --skip-update` → fallisce allo Step 4a (`gir1.2-gtk-4.0` non trovato) per indice apt stale |
|
||||
| ~11:00 | Dopo fix apt + revert snapshot: `prepare-linux` riesce, guest ottiene IP `.153`, snapshot ripreso |
|
||||
| ~11:03+ | I boot successivi dello snapshot non ottengono IP utile → inizio debug rete |
|
||||
| 11:20 | Cattura sul filo: DHCP OFFER/ACK presenti, ma il guest non applica l'IPv4 |
|
||||
| 12:06 | Deploy fresco: il guest **ottiene** `.143` (cloud-init), ma deploy Step 7 va in timeout sui tools |
|
||||
| 12:26 | Reboot: stesso guest **non** ottiene IP (intermittenza) |
|
||||
| ~12:30 | `vmware-networks --stop/--start` → l'IP torna stabile. Sblocco confermato |
|
||||
| dopo | Implementati e testati i fix; template rigenerato |
|
||||
|
||||
---
|
||||
|
||||
## Analisi cause
|
||||
|
||||
### 2.1 Step 4a apt (origine della sessione)
|
||||
|
||||
`Install-CIToolchain-Linux2404.sh` faceva `apt-get update` **solo** se non era
|
||||
passato `--skip-update`. Ma `--skip-update` dovrebbe saltare il solo *upgrade*
|
||||
(lento), non il refresh dell'indice. Con indice vecchio, i pacchetti nuovi
|
||||
(`gir1.2-gtk-4.0`, `xvfb`) non venivano trovati.
|
||||
|
||||
A riprova che a quel punto la rete funzionava: quel `prepare-linux` era entrato
|
||||
in SSH nel guest senza problemi.
|
||||
|
||||
### 2.2 deploy-linux --force e la snapshot chain
|
||||
|
||||
`deploy-linux --force` riusa la directory della VM ma non rimuoveva gli artefatti
|
||||
di un'eventuale snapshot precedente (`-NNNNNN.vmdk`, `.vmsn`, `.vmsd`). Con la
|
||||
chain presente, `vmware-vdiskmanager` rifiuta il resize dello Step 4:
|
||||
|
||||
```
|
||||
This disk is part of a snapshot chain in '.../LinuxBuild2404.vmx'.
|
||||
The selected operation can only be executed on a disk with no snapshots.
|
||||
```
|
||||
|
||||
### 2.3 La saga "no IP" — vmnet8 DHCP inceppato
|
||||
|
||||
Sintomo: `vmrun getGuestIPAddress` → *"VMware Tools are not running"* /
|
||||
*"Unable to get the IP address"*, **in modo intermittente**. Il guest in realtà:
|
||||
|
||||
- bootava correttamente (login prompt),
|
||||
- aveva open-vm-tools installato e attivo,
|
||||
- ma `systemd-networkd` a volte **non applicava** un lease DHCPv4 (link up,
|
||||
solo IPv6LL, `networkd-wait-online` in timeout).
|
||||
|
||||
Sul filo il DHCP a volte completava (`.143` applicato e raggiungibile), a volte
|
||||
no. Il server rispondeva (altre VM ottenevano lease). La causa finale: il demone
|
||||
`vmnet-dhcpd`/`vmnet-natd` di vmnet8 era andato in uno stato degradato dopo la
|
||||
churn di debug. Il **restart** `vmware-networks --stop/--start` lo ha sbloccato
|
||||
e l'IP è tornato stabile e immediato.
|
||||
|
||||
#### Ipotesi scartate (vicoli ciechi)
|
||||
- **Controller pvscsi vs lsilogic** — il boot funzionava con pvscsi; irrilevante.
|
||||
- **Naming NIC `ens192` vs `ens160`** — cloud-init si adattava; l'IP arrivava lo
|
||||
stesso quando la rete funzionava.
|
||||
- **Saturazione del pool DHCP** — il pool era di fatto libero (0 lease attivi);
|
||||
i MAC multipli per IP nel lease DB sono storia normale di ISC dhcpd.
|
||||
- **Seal cloud-init / machine-id** — difensivi ma non erano la causa del no-IP.
|
||||
|
||||
---
|
||||
|
||||
## Fix applicati (branch `fix/linux-template-toolchain-deploy`)
|
||||
|
||||
### Commit 1 — toolchain
|
||||
`template/Install-CIToolchain-Linux2404.sh`
|
||||
- **Step 4a**: installa `python3-gi gir1.2-gtk-4.0 xvfb` con assert su
|
||||
`import gi` e `Xvfb` (richiesta originale).
|
||||
- **Step 1**: `apt-get update` ora gira **sempre**; `--skip-update` salta solo
|
||||
l'upgrade. Risolve il fallimento Step 4a.
|
||||
|
||||
### Commit 2 — robustezza deploy/prepare
|
||||
`src/ci_orchestrator/commands/template.py` (+ test)
|
||||
- **`_clean_force_artifacts`**: con `--force` rimuove snapshot delta, `.vmsn`,
|
||||
`.vmsd` e `.lck` prima del resize → niente più errore snapshot-chain.
|
||||
Coperto da 3 unit test.
|
||||
- **prepare-linux Step 7d (seal cloud-init)**: rimuove la `50-cloud-init.yaml`
|
||||
stale, rigenera netplan (resta solo il `99-ci-dhcp-allnics` MAC-agnostic),
|
||||
disabilita cloud-init (`/etc/cloud/cloud-init.disabled`) così non rigira sui
|
||||
cloni.
|
||||
- **deploy user-data**: `package_update/upgrade: false` per non tenere il lock
|
||||
dpkg al primo boot e non ritardare open-vm-tools oltre il poll dello Step 7.
|
||||
- **Step 7 timeout**: `300s → 600s` come rete di sicurezza per primi boot lenti.
|
||||
|
||||
Verifica: `ruff` ✅, `mypy --strict` (23 file) ✅, suite completa ✅,
|
||||
coverage **94.4%** (gate 90%). Prod venv reinstallato (non-editable).
|
||||
|
||||
---
|
||||
|
||||
## Procedura di recovery: deploy/prepare non ottiene IP
|
||||
|
||||
Quando `deploy-linux` (Step 7) o `prepare-linux` non riescono a rilevare l'IP
|
||||
**ma il guest sembra sano** (booting, tools attivi), prima di sospettare
|
||||
immagine o codice, riavvia il networking VMware sull'host:
|
||||
|
||||
```bash
|
||||
sudo /usr/bin/vmware-networks --stop
|
||||
sudo /usr/bin/vmware-networks --start
|
||||
```
|
||||
|
||||
Flush opzionale dei lease (backup prima), **solo se nessuna VM è accesa**:
|
||||
|
||||
```bash
|
||||
sudo cp /etc/vmware/vmnet8/dhcpd/dhcpd.leases /tmp/dhcpd.leases.bak
|
||||
sudo sh -c ': > /etc/vmware/vmnet8/dhcpd/dhcpd.leases'
|
||||
```
|
||||
|
||||
Distinto da:
|
||||
- **Tools-not-running sul template** → revert a `BaseClean-Linux`
|
||||
(vedi `template-live-state-recovery`).
|
||||
- **vmmon/vmnet non compilati dopo bump kernel** → il guard systemd ricostruisce
|
||||
i moduli al boot (vedi `vmware-modules-kernel-upgrade`).
|
||||
|
||||
---
|
||||
|
||||
## Raccomandazioni (NON applicate di proposito)
|
||||
|
||||
### Cosa NON fare
|
||||
- **Restart/flush automatico ad ogni clone/deploy/prepare.** `vmware-networks`
|
||||
è globale (bridge + hostonly + NAT insieme): un restart sotto un job in corso
|
||||
rompe la rete di **tutte** le VM in esecuzione → job concorrenti falliti. Il
|
||||
flush dei lease con VM attive causa conflitti IP al rinnovo. Sproporzionato
|
||||
rispetto a un problema non dimostrato ricorrente.
|
||||
|
||||
### Cosa fare
|
||||
- **Monitorare.** L'inceppamento è nato dalla churn anomala di debug, non dal CI
|
||||
normale. Tenere la recovery manuale e agire solo se ricapita con dati reali.
|
||||
|
||||
### Se servisse hardening preventivo (a freddo, con cautela)
|
||||
- Abbassare `max-lease-time` (i cloni vivono minuti → IP liberati in fretta; i
|
||||
job lunghi rinnovano da soli via networkd).
|
||||
- Allargare il range DHCP per più margine.
|
||||
- **Caveat**: `/etc/vmware/vmnet8/dhcpd/dhcpd.conf` è **auto-generato da VMware**
|
||||
("DO NOT MODIFY", validato con `CFG_HASH` in `/etc/vmware/networking`). Edit a
|
||||
mano fragili: rischio rigenerazione al prossimo `--configure`/update. Da fare
|
||||
solo se il problema si dimostra ricorrente.
|
||||
|
||||
---
|
||||
|
||||
## Lezioni
|
||||
|
||||
- L'attività di debug intensa sulla rete host (start/stop ripetuti, sniffing
|
||||
promiscuo) può degradare `vmnet-dhcpd`: in futuro, minimizzare la churn e
|
||||
preferire osservazione mirata.
|
||||
- Tre cause distinte si sono sovrapposte; separarle prima ha richiesto tempo. Il
|
||||
guest era fondamentalmente sano per quasi tutta la sessione — l'errore di
|
||||
rilevamento IP non implicava un guest rotto.
|
||||
- Non sovrastimare: i fix committati coprono i bug **veri e provati**; il DHCP è
|
||||
stato lasciato invariato per evitare over-engineering.
|
||||
Reference in New Issue
Block a user