incus-contrib/notes/incusos-break-fix.md

16 KiB

IncusOS Break-Fix Lab -- Immutability Exploration & Resilience Testing

IncusOS is an immutable, purpose-built operating system for running Incus clusters. This guide documents what we discovered about its internals through the /os/1.0 API, and defines a catalog of break-fix exercises for testing cluster resilience in a safe lab environment.

All observations come from a 3-node Proxmox-hosted cluster (VMID 900-902) running IncusOS with virtual TPM, Secure Boot, and OVN networking.

IncusOS Architecture

IncusOS is an immutable OS designed for a single purpose: running Incus. Key architectural properties discovered via the /os/1.0 API:

  • Immutable root filesystem with A/B partition scheme. The running system boots from one partition; updates install to the other. Rollback is automatic if the new partition fails validation.
  • TPM-based full disk encryption -- root and swap volumes are encrypted and unlocked automatically via TPM measured boot. No passphrase required during normal operation.
  • Secure Boot enforced with 4 certificates in the UEFI firmware:
    • PK (Platform Key) -- root of trust
    • KEK (Key Exchange Key) -- authorizes db updates
    • db (2025 signing cert) -- validates current signed binaries
    • db (2026 signing cert) -- validates next-year signed binaries
  • Version format: YYYYMMDDHHMI (build timestamp, not semver). Example: 202602240349 = 2026-02-24 at 03:49 UTC.
  • ZFS storage: pool "local" on a dedicated partition (raid0 on single disk, ~30 GiB usable), encrypted with its own pool recovery key.
  • Update system: stable channel, 6-hour check frequency, auto_reboot: false (updates download but do not reboot automatically).

Lab cluster state

Node VMID IP IncusOS Version
node-01 900 192.168.102.140 202602240349
node-02 901 192.168.102.141 202602230420
node-03 902 192.168.102.142 202602230420

node-01 received a newer build than nodes 02/03, proving that nodes update independently (rolling updates, not cluster-wide atomic upgrades).

Partition & Disk Layout

Each node has a single 64 GiB SCSI disk (QEMU HARDDISK) with this layout:

+------------------------------------------------------+
| SCSI Disk  (64 GiB, QEMU HARDDISK)                  |
|------------------------------------------------------|
| Partition 1   EFI System Partition                   |
| Partition A   Root filesystem (active or standby)    |
| Partition B   Root filesystem (standby or active)    |
| Partition X   Swap (encrypted, TPM-unlocked)         |
| Partition 11  ZFS data pool ("local")                |
+------------------------------------------------------+

Key observations from the storage API:

  • Partition 11 is the ZFS data partition, hosting pool "local".
  • Pool "local": raid0, single vdev, ~30 GiB total, contains volume "incus" (~10 GiB used for Incus database, images, instances).
  • Root + swap: both encrypted, both unlocked by TPM at boot. No manual key entry needed unless TPM state is corrupted.

Security Chain

The boot security chain, as validated through the API:

UEFI firmware
  --> Secure Boot certificate validation (PK -> KEK -> db certs)
    --> Signed kernel + initrd loaded
      --> TPM measured boot (PCR measurements recorded)
        --> TPM validates measurements match expected policy
          --> Root partition decrypted and mounted
          --> Swap partition decrypted and activated
            --> System boots into trusted state

API-reported security state:

Property Value
TPM status ok
Secure Boot enabled, enforced
System state trusted
Encryption recovery keys 1 key (retrievable via API)
Pool recovery keys 1 key for pool "local"

Recovery keys are a safety net. If TPM state is corrupted (e.g., by a hard-stop during first boot), the encryption recovery key allows manual unlock. The pool recovery key allows ZFS pool import on a different system.

Services Configuration

Discovered via /os/1.0/services/*:

Service Status Notes
OVN enabled Connects to cluster DB at tcp:192.168.102.142:6642, Geneve tunnels
iSCSI disabled
LVM disabled
Multipath disabled
NVMe disabled
Tailscale disabled
USB/IP disabled

OVN is the only enabled service beyond Incus itself, providing the overlay network for cross-node container connectivity and network policies.

Network Configuration

Each node has a single management interface:

Property Value
Interface name mgmt
Role management + cluster traffic
Addressing Static
Subnet /22 (192.168.100.0/22)
DNS 192.168.100.1
Gateway 192.168.100.1

Node IP assignments:

  • node-01: 192.168.102.140/22
  • node-02: 192.168.102.141/22
  • node-03: 192.168.102.142/22

The management interface carries both management traffic (API, cluster heartbeats) and OVN Geneve tunnel traffic. In production, these should be separated.

Update Mechanics (Observed)

The IncusOS update system uses A/B partitions for safe, rollback-capable updates. Observations from querying the update API:

  1. No pending update: os_version and os_version_next are identical. This means no update has been downloaded or is waiting for reboot.

  2. Pending update: os_version_next would differ from os_version, indicating a new build has been downloaded to the standby partition.

  3. Auto-reboot disabled: auto_reboot: false means updates download to the standby partition but the node continues running the current version until explicitly rebooted (or until an admin triggers reboot via API / UI).

  4. No reboot needed: needs_reboot: false confirms no downloaded update is waiting for a reboot to activate.

  5. Independent node updates: node-01 is on 202602240349 while nodes 02/03 are on 202602230420. This proves each node checks for and applies updates independently. There is no cluster-wide coordinated update mechanism at the OS level.

  6. Check frequency: 6 hours. The stable channel is checked automatically on this interval.

Update lifecycle (theoretical)

Check timer fires (every 6h)
  --> Query stable channel for new version
    --> Download new rootfs to standby partition (A or B)
      --> os_version_next updated, needs_reboot = true
        --> (if auto_reboot) Reboot automatically
        --> (if !auto_reboot) Wait for manual reboot
          --> On reboot: boot from new partition
            --> TPM re-measures, validates new boot chain
              --> If valid: new partition becomes active
              --> If invalid: rollback to previous partition

Break-Fix Exercise Catalog

These exercises are designed to test IncusOS cluster resilience in the Proxmox lab. All exercises follow strict safety rules (see Safety Rules section below).

Current status: All exercises are defined but not yet executed.


Exercise 1: Normal Update Observation

Goal: Observe the full update lifecycle -- download, reboot, A/B partition switch, version verification.

Prerequisites:

  • Proxmox snapshot of all cluster nodes (VMID 900-902)
  • Grafana monitoring active (observe cluster metrics during update)
  • Verify cluster health with incusos-health --all

Steps:

  1. Record current versions: incusos-health --update
  2. Trigger update via Operations Center UI, or wait for 6h check interval
  3. Monitor via Grafana for download activity and node state changes
  4. When needs_reboot: true, reboot one node at a time
  5. After reboot, verify new version: incusos-health --status
  6. Confirm A/B partition switch: incusos-health --partitions

What to observe:

  • Download phase duration
  • Reboot duration (typically 30-60s for IncusOS)
  • Cluster behavior while one node reboots (2/3 quorum maintained)
  • New version number in os_version
  • Previous version still available on standby partition

Status: Defined, not yet executed.


Exercise 2: Simulated Failed Update (Hard-Stop Mid-Update)

Goal: Verify that IncusOS rolls back to the previous partition after a failed update (simulated by hard-stopping the VM during update).

Prerequisites:

  • Proxmox snapshot of node-03 (VMID 902) -- non-leader, fewest workloads
  • Verify node-03 has no critical workloads
  • Confirm 3/3 nodes healthy before starting

Target node: node-03 (VMID 902) ONLY. Never the cluster leader.

Steps:

  1. Take Proxmox snapshot of node-03: incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/snapshot -d '{"snapname":"pre-break-fix-2"}'
  2. Trigger an update on node-03 (if one is available)
  3. During the update download or early reboot phase, hard-stop node-03 via Proxmox API: incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/stop
  4. Wait 10 seconds, then start node-03: incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/start
  5. Monitor boot via console screenshots: incusos/helpers/proxmox-screenshot 902
  6. After boot, check version: incusos-health --status on node-03

Expected behavior:

  • Node boots from the previous (known-good) partition
  • Failed partition is marked as bad / not bootable
  • os_version returns to the pre-update version
  • Node rejoins the cluster automatically

Safety:

  • ONLY node-03 -- maintains 2/3 quorum (node-01 + node-02 continue)
  • Do NOT hard-stop during first boot (corrupts TPM permanently)
  • If recovery fails, restore from Proxmox snapshot

Status: Defined, not yet executed.


Exercise 3: Network Isolation

Goal: Observe how the cluster handles a node losing network connectivity -- OVN tunnel loss, cluster membership changes, and automatic recovery on reconnection.

Prerequisites:

  • Evacuate all workloads from node-03 before disconnecting
  • Grafana monitoring active (watch OVN tunnel metrics, cluster events)
  • Verify cluster health: incusos-health --all

Steps:

  1. Evacuate workloads from node-03: incus cluster evacuate oc-node-03 --target oc-node-01
  2. Disconnect node-03 NIC via Proxmox API: incusos/helpers/proxmox-api PUT /nodes/pve/qemu/902/config -d '{"net0":"virtio=...,link_down=1"}'
  3. Observe in Grafana: OVN Geneve tunnel loss, cluster detecting missing node
  4. Wait 2-5 minutes for cluster to mark node-03 as offline
  5. Reconnect NIC: set link_down=0 via Proxmox API
  6. Monitor recovery: node-03 should rejoin cluster, OVN tunnels re-establish
  7. Measure total recovery time from reconnection to healthy state

Expected behavior:

  • Cluster detects node-03 offline within heartbeat timeout
  • OVN tunnels from/to node-03 fail (Geneve encap packets lost)
  • Cluster continues operating with 2/3 quorum
  • On reconnection: node-03 rejoins, tunnels re-establish, workloads can be restored

Recovery:

  • If node-03 does not rejoin: check OVN service status, restart if needed
  • If cluster state is inconsistent: restore from Proxmox snapshot

Status: Defined, not yet executed.


Exercise 4: Full Node Failure

Goal: Verify that the cluster survives a complete node loss and maintains operations with 2/3 quorum. Measure cluster rejoin time after node recovery.

Prerequisites:

  • Proxmox snapshot of node-03 (VMID 902)
  • Evacuate ALL workloads from node-03
  • Verify cluster health: incusos-health --all

Steps:

  1. Evacuate workloads: incus cluster evacuate oc-node-03
  2. Take Proxmox snapshot of node-03
  3. Hard-stop node-03 via Proxmox: incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/stop
  4. Verify cluster continues operating:
    • incus cluster list should show 2 online, 1 offline
    • Existing workloads on node-01/02 remain accessible
    • OVN gateway should failover if node-03 was a gateway
  5. Wait 5 minutes, observing Grafana metrics
  6. Restart node-03: incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/start
  7. Monitor boot: incusos/helpers/proxmox-screenshot 902
  8. Measure time from start to cluster rejoin (node shows online)
  9. Restore workloads: incus cluster restore oc-node-03

Expected behavior:

  • 2/3 quorum maintained -- all cluster operations continue
  • OVN gateway failover occurs if node-03 was elected gateway
  • After restart: node-03 boots, TPM unlocks, Incus starts, rejoins cluster
  • Typical rejoin time: 1-3 minutes after boot

Safety:

  • This is NOT a first-boot scenario -- hard-stop is safe for already- provisioned nodes (TPM state is already sealed)
  • One node at a time ONLY
  • If node-03 fails to rejoin after restart, restore snapshot

Status: Defined, not yet executed.

Safety Rules

These rules are non-negotiable for all break-fix exercises:

  1. Never hard-stop during first boot. First boot seals TPM measurements and writes encryption keys. Interrupting this corrupts TPM state permanently, requiring full reinstallation.

  2. One node at a time. A 3-node cluster requires 2/3 quorum. Never take down more than one node simultaneously, or the cluster loses quorum and all operations halt.

  3. Proxmox snapshots before every destructive test. Snapshot the target node's VM (VMID 900-939) before any exercise that involves stopping, disconnecting, or modifying the node.

  4. Verify cluster health before and after. Run incusos-health --all (or equivalent API checks) before starting an exercise and after completing it. Do not proceed if the cluster is already degraded.

  5. Monitor via Grafana during all exercises. Visual monitoring catches issues that API polling might miss (e.g., OVN tunnel flapping, storage I/O spikes).

  6. Target node-03 for destructive tests. node-03 (VMID 902) is the preferred target because it is not the cluster leader and typically has the fewest workloads. Evacuate before testing.

  7. Keep recovery keys accessible. The encryption recovery key and ZFS pool recovery key should be retrieved and stored securely before any exercise that might corrupt TPM state.

Helper Script: incusos-health

The incusos/helpers/incusos-health script queries the IncusOS API on cluster nodes to report system state. It is the primary tool for verifying cluster health before and after break-fix exercises.

Usage

incusos/helpers/incusos-health [ACTION] [OPTIONS]

Actions

Action Description
--status Basic system info: version, hostname, TPM status, Secure Boot
--partitions Disk and partition layout, A/B partition state
--tpm TPM details, Secure Boot certificates, encryption keys
--services Enabled/disabled services (OVN, iSCSI, LVM, etc.)
--network Network interface configuration, DNS, gateway
--update Update channel, check frequency, pending update status
--all Run all of the above in sequence

Example output workflow

# Before exercise: verify all nodes healthy
incusos/helpers/incusos-health --all

# After exercise: verify recovery
incusos/helpers/incusos-health --status   # Quick version check
incusos/helpers/incusos-health --all      # Full health report

API Reference

All IncusOS system information is available via the REST API on each node.

Endpoint Returns
/os/1.0 Version, hostname, basic system info
/os/1.0/system/security TPM status, Secure Boot, encryption keys
/os/1.0/system/storage Disks, partitions, ZFS pools
/os/1.0/system/resources CPU, memory, hardware info
/os/1.0/system/network Interfaces, DNS, routes
/os/1.0/system/update Update channel, version, pending updates
/os/1.0/services/ovn OVN configuration and status
/os/1.0/services/iscsi iSCSI configuration
/os/1.0/services/lvm LVM configuration
/os/1.0/services/multipath Multipath configuration
/os/1.0/services/nvme NVMe-oF configuration
/os/1.0/services/tailscale Tailscale VPN configuration
/os/1.0/services/usbip USB/IP configuration

The API listens on the management interface, HTTPS, port 8443 (same as Incus). Authentication uses the Incus client certificate.