Skip to content

Start nspawn workers after reboot on RHEL and recover stopped workers - #437

Open
hbc (bcho) with Copilot wants to merge 13 commits into
mainfrom
copilot/fix-machines-target-issue
Open

hbc (bcho) with Copilot wants to merge 13 commits into
mainfrom
copilot/fix-machines-target-issue

Conversation

Copilot AI commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

RHEL presets leave machines.target disabled, preventing workers from starting after reboot. The daemon still reported success because it checked persisted settings, not worker liveness.

  • Boot startup: Add Wants=machines.target to host and container daemon units.
  • Preflight: Warn on disabled targets; fail on missing or masked targets.
  • Worker health: Verify the active worker before reporting success, restart it without repaving if stopped, and return reconcile errors when inspection or recovery fails.

Validation

Full test suite, affected-package race tests, vet, documentation checks, build, and CLI smoke test passed. CodeQL and secret scans were clean.

golangci-lint was unavailable; live RHEL reboot testing remains outstanding.

Changelog

  • I added a user/operator-facing entry under Unreleased in CHANGELOG.md, or this change does not require a changelog entry.

Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix machines.target disabled issue on RHEL hosts Start nspawn workers after reboot on RHEL and recover stopped workers Nov 10, 2026
Copilot AI requested a review from hbc (bcho) November 10, 2026 18:20
Comment thread pkg/daemon/repave_reconciler.go Resolved
Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Comment thread pkg/daemon/preflight.go Resolved
Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Copilot AI deployed to e2e-testing November 10, 2026 19:31 Active
@plombardi89
Philip Lombardi (plombardi89) marked this pull request as ready for review November 10, 2026 20:18
Copilot AI balanced review requested due to automatic review settings November 10, 2026 20:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Worker failures can go undetected and unrecovered, while the new preflight check blocks online bootstrap before required packages can be installed.

2 open issues
What changed in this PR

This PR addresses the reboot-startup portion of #436 by making the Flex Node daemon request machines.target, allowing enabled nspawn workers to start even when RHEL presets disable the target.

Changes:

  • Add machines.target dependencies to host and container daemon units.
  • Register a preflight check for target availability and enablement.
  • Add checker tests, update service snapshots, and document preflight behavior.
File Description
pkg/​daemon/​testdata/​aks-flex-node-agent.service.golden Update the expected host-unit dependency.
pkg/​daemon/​testdata/​aks-flex-node-agent-arc.service.golden Update the expected Arc-enabled unit dependency.
pkg/​daemon/​preflight.go Add target-state validation.
pkg/​daemon/​preflight_test.go Test target states and daemon-unit dependencies.
pkg/​daemon/​assets/​aks-flex-node-agent.service Request worker startup through machines.target.
pkg/​daemon/​assets/​aks-flex-node-agent-container.service Add the same dependency for container deployments.
pkg/​cmd/​preflight/​preflight.go Register the new preflight check.
docs/​usage/​operations.md Explain target-check errors and warnings.

Review effort: Balanced

Comment thread pkg/daemon/preflight.go Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The boot fix itself works. Requesting changes for the preflight check and some housekeeping.

Blocking

  1. The machines-target check fails preflight on a fresh host where systemd-container isn't installed yet, although bootstrap installs it and unbounded's own checks only warn about it. Inline on pkg/daemon/preflight.go:37, with a reproducer on RHEL 9.8.
  2. Housekeeping before merge:
    • The description is out of date. It still lists the "Worker health" changes that 695b06b removed.
    • Fixes #436 will close the issue, but only half of it is fixed. The issue's second request (the agent noticing a stopped worker instead of reporting success for three days) was dropped. Please use Refs #436 and open a follow-up issue, or note on #436 that this part won't be done.
    • No CHANGELOG.md entry. There's an [Unreleased] section, and this change is user-facing: a fix (workers now start after reboot on RHEL) and a new preflight check.

Verified: Wants=machines.target. I tested it on systemd 252 (Rocky Linux 9.8, the same systemd as the RHEL 9.8 hosts in #436):

  • The worker was the real systemd-nspawn@.service, with only its process replaced by sleep.
  • The agent unit was the actual golden unit from main or from this PR, again with only ExecStart replaced.
  • machines.target was disabled and systemd-nspawn@kube1.service was enabled, as on the reporter's hosts.
  • Each scenario was a reboot.
Scenario machines.target Worker Agent
Agent unit from main inactive never started (reproduces #436) active
Agent unit from this PR active active active, without waiting for the worker
PR's container-variant unit, containerd start fails active active not started (dependency failed)
Worker disabled and stopped on purpose, as unbounded does when it stops a machine active stays stopped active

The upgrade path also heals hosts that are already stuck. Writing the new unit and running daemon-reload alone starts nothing, but the agent restart that follows an upgrade starts machines.target and the stopped worker. Stopping the agent doesn't stop either of them.

After=machines.target: no change needed. Unbounded's own daemon unit has After= as well as Wants=. Adding it here would hold the agent until the worker's start finishes; I tested that, and a worker whose start fails doesn't block the agent. A start that hangs would hold it for up to the worker's start timeout, 90 seconds by default for the Type=notify nspawn unit. I found nothing in pkg/daemon that assumes the worker is up at startup, and the worker's pre-start/post-start hooks don't depend on the agent unit. So Wants= alone is the better choice here.

The remaining gap is a real reboot of a real RHEL host with a real nspawn worker, which the PR description already lists as outstanding.

Comment thread pkg/daemon/preflight.go Outdated
case "disabled", "enabled-runtime":
return preflight.ResultsWarning(c.Name(), target, "machines.target is not enabled persistently; bootstrap installs a daemon Wants=machines.target dependency to start workers at boot")
default:
return preflight.ResultsError(c.Name(), target, "machines.target is missing, masked, or unavailable; install systemd-container and unmask machines.target before bootstrap")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: preflight fails on a fresh host that bootstrap would set up fine

machines.target ships in systemd-container, on RHEL 9 and on Ubuntu 24.04 alike. On a host where that package isn't installed yet, systemctl show returns an empty UnitFileState, and this default branch reports an error. But systemd-container is one of the packages bootstrap installs. Unbounded's own host checks report it as missing with a warning online ("may be installed by bootstrap"), and as an error only when offlineArtifacts is configured (pkg/agent/phases/host/preflight_host.go:156-172 in unbounded v0.21.0). So on the same host, host-packages says bootstrap will install the package, while machines-target fails preflight for the package being missing. docs/usage/operations.md tells users to "Run preflight before mutating the host", which is exactly when the package may not be there yet.

Reproducer. I built aks-flex-node from this PR (69f1cc6) and ran it in a Rocky Linux 9.8 container with systemd as PID 1. RHEL's own UBI 9.8 image gives the same machines-target error, but UBI's repos don't carry systemd-container, so I used Rocky for the before/after.

  1. Start the host and confirm the package is missing:
    docker run -d --name rhel9 --privileged --cgroupns=private --tmpfs /run --tmpfs /tmp \
      -v "$PWD/aks-flex-node:/usr/local/bin/aks-flex-node:ro" -v "$PWD/config.json:/repro/config.json:ro" \
      docker.io/rockylinux/rockylinux:9-ubi-init
    docker exec rhel9 rpm -q systemd-container        # package systemd-container is not installed
    docker exec rhel9 systemctl show --property=UnitFileState --value machines.target   # empty, exit 0
    config.json is the bootstrap-token example from docs/usage/configuration.md, with placeholder values.
  2. Run preflight. The two ignored checks fail only because of the test environment: a placeholder API server, and no /etc/sysctl.d in the image.
    docker exec rhel9 aks-flex-node preflight --config /repro/config.json --output json \
      --ignore-preflight-errors=api-server-reachable,host-os-configuration
    Result: exit 1, "status": "failed", and machines-target is the only error:
    error    machines-target  machines.target is missing, masked, or unavailable; install systemd-container and unmask machines.target before bootstrap
    warning  host-packages    required host packages are missing and may be installed by bootstrap: systemd-container, curl, nftables
    
  3. Install the package, as bootstrap would, and run step 2 again:
    docker exec rhel9 dnf -y install systemd-container
    docker exec rhel9 systemctl is-enabled machines.target    # disabled (the RHEL preset from #436)
    Result: exit 0, "status": "ok", and machines-target is now a warning ("not enabled persistently; ... Wants=machines.target").

With machines-target added to --ignore-preflight-errors in step 2, preflight exits 0 (checked on the UBI 9.8 image), so this check alone fails preflight.

Suggested fix:

  • Handle a missing target (empty UnitFileState, i.e. LoadState=not-found) the way unbounded handles a missing package:
    • a warning when bootstrap can install systemd-container
    • an error when agentCfg.OfflineArtifactsConfigured() is set; pkg/cmd/preflight/preflight.go already has agentCfg to pass in
  • Keep masked and masked-runtime as errors, since bootstrap doesn't unmask the target.
  • Include the actual state in the default message. States like linked, indirect or generated currently get "missing, masked, or unavailable", which doesn't say which one.
  • Update the docs sentence in operations.md ("fails if machines.target is missing or masked") to match.
  • Add a test for the offline/online split. Matching unbounded's behavior also makes the planned move to unbounded straightforward.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 85ea2de: empty/not-found UnitFileState warns during native online bootstrap with guidance that bootstrap may install systemd-container, but fails offline. Masked/masked-runtime remain fatal; unexpected-state diagnostics include the actual state. That commit added online/offline tests and updated operations guidance. dc6cf2e additionally tests missing/not-found and both masked states across all four bootstrap modes. Ready for reviewer resolution.

@bcho

Copy link
Copy Markdown
Member

Copilot address pr comments

Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
@bcho
hbc (bcho) requested a balanced review from Copilot November 11, 2026 19:12
Comment thread CHANGELOG.md Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Preflight incorrectly rejects valid linked systemd targets, and runtime-only links also need a persistence warning.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread pkg/daemon/preflight.go Resolved
Comment thread CHANGELOG.md Resolved
Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 21:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed to weigh the operational impact of limited preflight failure diagnostics.

1 open issue
1 resolved since last review

Review effort: Balanced

Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 21:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed to weigh an unpublished low-severity concern about diagnosing systemd inspection failures.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@bcho

Copy link
Copy Markdown
Member

Copilot resolve conflicts

…target-issue

# Conflicts:
#	CHANGELOG.md

Co-authored-by: bcho <1975118+bcho@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 21:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The preflight result omits the underlying error, leaving operators without the cause when systemctl cannot be started.

1 open issue

Review effort: Balanced

Comment thread pkg/daemon/preflight.go Resolved

Copilot AI commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Copilot resolve conflicts

Resolved the changelog conflict by merging main in f05131a. Targeted Go tests and documentation checks passed.

This branch is being deployed

1 in progress deployment
e2e-testing — f05131ae Deployed Nov 11, 2026 by Copilot via E2E Tests #1324
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] machines.target disabled on RHEL hosts

4 participants