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:
parent
d8f2a067c4
commit
b9f1894681
45
CLAUDE.md
45
CLAUDE.md
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue