OC-managed cluster with OVN and HA nginx: full validated deployment

Deploy a 3-node OC-managed Incus cluster with OVN overlay networking
and an HA nginx workload behind an OVN load balancer. Key discoveries:

- OC needs_update blocker: OC tracks update delivery through its pipeline,
  not version comparison. Nodes deployed from the latest ISO are stuck at
  needs_update=true. Fix: generate ISO from older channel (--channel
  old-stable), let OC push the real update after registration.
- ISO upload skip bug: incusos-proxmox silently reused stale ISOs with the
  same filename. Fixed: --iso now replaces existing same-named ISOs.
- Empty app-seed-config ({}): avoids "Certificate already in trust store"
  Terraform error when cert is already injected via SEED_DATA.

Files changed:
- notes/operations-center-guide.md: full rewrite with real captured output
- CLAUDE.md: needs_update findings, ISO fix, OVN LB behavior
- incusos/examples/lab-oc-deploy.yaml: static IP for VLAN 69
- incusos/examples/lab-oc-nodes.yaml: static IPs + disk sizing
- incusos/incusos-proxmox: fix stale ISO reuse when --iso provided

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maarten 2026-02-23 11:08:28 +01:00
parent d8f2a067c4
commit b9f1894681
5 changed files with 894 additions and 787 deletions

View File

@ -458,34 +458,61 @@ incu-contrib/
- **No brownfield adoption**: nodes must boot from an OC-provisioned ISO. - **No brownfield adoption**: nodes must boot from an OC-provisioned ISO.
- **Token seeds**: named, reusable pre-seed configs attached to tokens. YAML - **Token seeds**: named, reusable pre-seed configs attached to tokens. YAML
must use structured format with section keys (`install:`, not flat). must use structured format with section keys (`install:`, not flat).
**No force_reboot** in token seeds for Proxmox (same fix as standard deploys).
```bash ```bash
operations-center provisioning token seed add <UUID> proxmox-preseed \ operations-center provisioning token seed add <UUID> proxmox-preseed \
/tmp/preseed.yaml --description "Force reboot for Proxmox" /tmp/preseed.yaml --description "No force_reboot for Proxmox"
operations-center provisioning token seed get-image <UUID> proxmox-preseed \ operations-center provisioning token seed get-image <UUID> proxmox-preseed \
/tmp/IncusOS-oc.iso --type iso --architecture x86_64 /tmp/IncusOS-oc.iso --type iso --architecture x86_64 --channel old-stable
``` ```
- **Hybrid deployment** (tested, recommended): - **Hybrid deployment** (tested, recommended):
`incusos-proxmox --iso /tmp/IncusOS-oc.iso --yes lab-oc-nodes.yaml` `incusos-proxmox --iso /tmp/IncusOS-oc.iso --yes lab-oc-nodes.yaml`
combines OC auto-registration (from boot ISO token) with `incusos-proxmox` combines OC auto-registration (from boot ISO token) with `incusos-proxmox`
VM creation, per-node SEED_DATA (hostname, force_reboot), install monitoring, VM creation, per-node SEED_DATA (hostname, static IP), install monitoring,
and media cleanup. Dual seeds (boot ISO + SEED_DATA on ide3) coexist. and media cleanup. Dual seeds (boot ISO + SEED_DATA on ide3) coexist.
- **Self-registration**: nodes auto-register with OC within ~30s of first - **Self-registration**: nodes auto-register with OC within ~30s of first
boot. Hostname from SEED_DATA is used as the server name. boot. Hostname from SEED_DATA is used as the server name.
- **Cluster formation** (tested): - **`needs_update` blocker** (critical discovery, 2026-02-23): OC requires
all nodes to have `needs_update: false` before `provisioning cluster add`
succeeds. Nodes deployed from an ISO matching the latest OC update version
are tracked as `needs_update: true` because the OS was never delivered
through OC's update pipeline. The `needs_update` flag is server-side
computed (not a simple version comparison) and cannot be overridden via
REST API PUT. **Solution**: generate the ISO from an older channel
(`--channel old-stable`) so nodes start with an older version. OC then
pushes the latest update through its pipeline, clearing the flag. Use
`provisioning update assign-channels` to control which versions are in
which channels.
- **Cluster formation** (tested, 2026-02-23):
```bash ```bash
# Use empty app config if cert already injected via SEED_DATA
echo '{}' > /tmp/oc-app-config.yaml
operations-center provisioning cluster add oc-cluster \ operations-center provisioning cluster add oc-cluster \
https://<NODE_01_IP>:8443 \ https://<NODE_01_IP>:8443 \
--server-names oc-node-01,oc-node-02,oc-node-03 \ --server-names oc-node-01,oc-node-02,oc-node-03 \
--server-type incus \ --server-type incus \
--application-seed-config /tmp/oc-app-config.yaml --application-seed-config /tmp/oc-app-config.yaml
``` ```
OC handles: `core.https_address` → cluster enable → joins → seed OC handles: `core.https_address` → cluster enable → joins → storage pool
application → Terraform config. Adds `meshbr0` network. + network creation → Terraform config. Adds `meshbr0` network. If the
client cert is already in the trust store (from SEED_DATA), use an empty
`{}` app config to avoid "Certificate already in trust store" Terraform
error. The cluster forms successfully either way.
- **apply_defaults: false is recommended** (tested): use `apply_defaults: false` - **apply_defaults: false is recommended** (tested): use `apply_defaults: false`
for OC-managed nodes. OC's Terraform handles storage pool, network, and cert for OC-managed nodes. OC's Terraform handles storage pool, network, and cert
creation cleanly. With `apply_defaults: true`, nodes already have these creation cleanly. With `apply_defaults: true`, nodes already have these
resources and OC's Terraform fails with "already exists" errors (cluster still resources and OC's Terraform fails with "already exists" errors (cluster still
forms, but Terraform artifacts are empty). forms, but Terraform artifacts are empty).
- **OC-managed cluster with OVN**: fully tested (2026-02-23). After cluster
formation via OC, OVN overlay networking works identically to manual clusters.
Deploy ovn-central container, enable OVN services, create UPLINK + OVN
network, add ovn-chassis roles. HA nginx workload with OVN load balancer
tested and working. See `notes/operations-center-guide.md` for full guide.
- **ISO upload skip bug** (fixed, 2026-02-23): `incusos-proxmox` previously
skipped uploading an ISO if one with the same filename existed on Proxmox.
This caused stale ISOs from previous deployments to be silently reused.
Fixed: when `--iso` is explicitly provided, the script now deletes the
existing ISO and re-uploads the new one.
#### Tested limitations #### Tested limitations
@ -505,6 +532,12 @@ incu-contrib/
- **Node failure recovery**: Proxmox hard-stop simulates crash. After restart, - **Node failure recovery**: Proxmox hard-stop simulates crash. After restart,
node auto-rejoins cluster in ~60s. Containers auto-start. Works cleanly node auto-rejoins cluster in ~60s. Containers auto-start. Works cleanly
if crontab bug doesn't hit (auto-healed by `fix_scrub_schedule()`). if crontab bug doesn't hit (auto-healed by `fix_scrub_schedule()`).
- **`needs_update` tracking is pipeline-based**: OC tracks whether an update
was delivered through its pipeline, not just whether versions match. Nodes
deployed from the latest ISO are tracked as needing updates even when
`version == available_version`. Must deploy from older ISO to work around.
- **OVN LB has no health checks**: connection-based hashing distributes
traffic to dead backends. Requests to stopped instances return empty.
- See `notes/operations-center-guide.md` for full tested OC reference. - See `notes/operations-center-guide.md` for full tested OC reference.

View File

@ -33,7 +33,12 @@ defaults:
disk: 50 disk: 50
start_vmid: 920 start_vmid: 920
proxmox:
gateway: 192.168.100.1
dns: 192.168.100.1
vms: vms:
- name: oc-server - name: oc-server
app: operations-center app: operations-center
apply_defaults: true apply_defaults: true
ip: 192.168.102.120/22

View File

@ -20,15 +20,23 @@ defaults:
disk: 50 disk: 50
start_vmid: 400 start_vmid: 400
proxmox:
gateway: 192.168.100.1
dns: 192.168.100.1
vms: vms:
- name: oc-node-01 - name: oc-node-01
app: incus app: incus
apply_defaults: false apply_defaults: false
disk: 64
ip: 192.168.102.140/22
- name: oc-node-02 - name: oc-node-02
app: incus app: incus
apply_defaults: false apply_defaults: false
ip: 192.168.102.141/22
- name: oc-node-03 - name: oc-node-03
app: incus app: incus
apply_defaults: false apply_defaults: false
ip: 192.168.102.142/22

View File

@ -3122,7 +3122,23 @@ phase_upload() {
local iso_path="${WORKDIR}/${ISO_FILENAME}" local iso_path="${WORKDIR}/${ISO_FILENAME}"
if [[ -f "$iso_path" ]]; then if [[ -f "$iso_path" ]]; then
if pve_iso_exists "$ISO_FILENAME"; then if pve_iso_exists "$ISO_FILENAME"; then
if [[ -n "$LOCAL_ISO" ]]; then
# --iso was provided: always replace the ISO on Proxmox.
# A stale ISO with the same filename (e.g., IncusOS-oc.iso from
# a previous OC deployment) would silently use the wrong version.
warn "ISO '${ISO_FILENAME}' already exists on Proxmox -- replacing with provided ISO"
local encoded_volid
encoded_volid=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PVE_ISO_STORAGE}:iso/${ISO_FILENAME}', safe=''))")
pve_api DELETE "/nodes/${PVE_NODE}/storage/${PVE_ISO_STORAGE}/content/${encoded_volid}" &>/dev/null || true
step "Uploading ISO: ${ISO_FILENAME}"
if ! pve_upload "$iso_path" "$ISO_FILENAME"; then
error "Failed to upload ISO to Proxmox"
exit 1
fi
success "ISO uploaded: ${ISO_FILENAME} (replaced)"
else
success "ISO already on Proxmox: ${ISO_FILENAME}" success "ISO already on Proxmox: ${ISO_FILENAME}"
fi
else else
step "Uploading ISO: ${ISO_FILENAME}" step "Uploading ISO: ${ISO_FILENAME}"
if ! pve_upload "$iso_path" "$ISO_FILENAME"; then if ! pve_upload "$iso_path" "$ISO_FILENAME"; then

File diff suppressed because it is too large Load Diff