Skip to content

Support Azure Container Linux hosts with a configurable install prefix - #323

Draft
Philip Lombardi (plombardi89) wants to merge 6 commits into
mainfrom
phlombar/acl-support
Draft

Philip Lombardi (plombardi89) wants to merge 6 commits into
mainfrom
phlombar/acl-support

Conversation

@plombardi89

Copy link
Copy Markdown

Status: WIP draft. Not ready to merge.

Long-lived branch collecting the changes AKS Flex Node needs to run on Azure
Container Linux (ACL). Further commits will land here as the work proceeds
rather than as a series of separate PRs.

The dependency, first

go.mod pins an unreleased commit of github.com/Azure/unbounded, not a
tag. The change that makes the agent's host paths configurable is still under
review upstream, and nothing here compiles without it.

That pin is temporary and must become a release version before this merges.
It is the one thing blocking everything else in this PR.

Why ACL does not work today

/usr is mounted read-only and there is no package manager. Two separate
places install into it unconditionally:

  • defaultAgentUpgradePaths hardcodes /usr/local/bin and
    /usr/local/lib/aks-flex-node, and is called with no override, so the
    location cannot be changed.

  • recoveryScriptPath hardcodes a second location under /usr/local/lib.
    This one fails after the node has already joined the cluster:

    install-service: write /usr/local/lib/aks-flex-node/aks-flex-node-recovery.sh:
    mkdir /usr/local/lib/aks-flex-node: read-only file system
    

The host itself is in better shape than expected. Every other prerequisite is
present in stock ACL, and /opt, /var/lib and /etc are writable, so a
writable prefix exists.

What is here

Commit
Raise the agent library the dependency described above
Add a host installation prefix to the node config agent.hostPrefix, validated by delegating to the agent library
Resolve the agent upgrade layout from the install prefix F1
Install the recovery script under the install prefix F1b

Hosts that set no prefix resolve exactly the paths they do today.

Two decisions worth reviewing

The prefix is declared, never inferred. Keying off distribution identity
would misclassify a hardened host whose /usr happens to be read-only, and
would silently relocate files on any host whose os-release changed. A wrong
guess is expensive because generated systemd units reference these paths
absolutely.

The prefix is read from this project's own config, not the agent library's
applied config.
The library exposes a lookup for exactly this purpose and
using it here is wrong: bootstrap in this project never writes that file, so on
a real host it is absent, the lookup silently returns the default, and the
failure surfaces later as initialize agent binary layout: no executable agent binary found, naming neither the prefix nor the path it looked in.

Still to come on this branch

  • scripts/install.sh reports ACL as supported Azure Linux 3 and then fails
    copying into a read-only /usr/local/bin. ACL is distinguishable:
    VARIANT_ID=azurecontainerlinux, and ID_LIKE="flatcar" as a second signal
    that no Azure Linux 3 image carries. Neither field is parsed today.
  • install.sh and uninstall.sh hardcode INSTALL_DIR, while bootstrap.sh
    already supports AKS_FLEX_NODE_INSTALL_DIR and --install-dir.
  • An Ignition bootstrap path. ACL ships no cloud-init at all, so a cloud-init
    payload passed as customData is never acted on and nothing reports an
    error.

What has and has not been verified

An ACL host provisioned entirely by Ignition joined an AKS cluster and ran
workloads with these changes applied: no SSH, no reboot, binaries under
/opt/aks-flex-node/{bin,lib} with /usr/local/bin unused, pod-to-pod traffic
working in both directions.

That run predates this branch being rebased onto current main and repointed
at the extracted upstream API, so treat it as establishing that the design
works rather than that this exact tree does.

Not covered: ARM Machine registration was deliberately deferred, so it fails
with a 403 and is skipped; and a blue/green agent upgrade and rollback has not
been exercised on ACL.

The agent library places its own binaries and helper scripts under a fixed
/usr/local prefix. Azure Container Linux mounts /usr read-only, so nothing
can be installed there and a Flex Node cannot run on it at all.

The version that makes the prefix configurable is not released yet, so this
pins the commit under review upstream rather than a tag. That is temporary
and has to become a release version before this merges.

The same build also reports host package capability truthfully instead of
assuming a package manager exists, which matters here because the image has
none.
Hosts that mount /usr read-only cannot take the agent's own binaries and
helper scripts in the default location. A config field lets them name a
writable one.

The prefix is declared, never inferred. Keying off distribution identity
would misclassify a hardened host whose /usr happens to be read-only, and
would silently relocate files on any host whose os-release changed. A wrong
guess is expensive to recover from, because generated systemd units reference
these paths absolutely.

Validation is delegated to the agent library rather than duplicated. The
prefix is interpolated into generated units and into a shell script, neither
of which quotes it, so the accepted syntax is deliberately narrow; keeping
that rule in one place stops the two projects from disagreeing about what is
accepted.
defaultAgentUpgradePaths hardcoded /usr/local/bin and
/usr/local/lib/aks-flex-node, both under a read-only /usr on Azure Container
Linux, and it is called with no override, so the location could not be
changed.

The prefix is read from this project's own config rather than the agent
library's applied config. That distinction was found the hard way: the
library exposes a lookup for exactly this, but bootstrap here never writes
that file, so on a real host it is absent, the lookup silently returns the
default, and the failure surfaces later as

    initialize agent binary layout: no executable agent binary found

which names neither the prefix nor the path it looked in.

An unreadable or absent config yields the empty prefix, which selects the
default. That is what a host installed before this existed actually has on
disk, so nothing changes for it.

Callers that already hold a config pass it directly; the lookup exists for
entry points started by systemd, which cannot inherit the prefix from the
environment that ran bootstrap.
recoveryScriptPath was a second hardcoded location under /usr/local/lib. On
Azure Container Linux this fails bootstrap after the node has already joined:

    install-service: write /usr/local/lib/aks-flex-node/aks-flex-node-recovery.sh:
    mkdir /usr/local/lib/aks-flex-node: read-only file system

which is a worse failure than refusing up front, because the cluster has a
node in it by then.

The constant stays, because it is also the substitution placeholder inside
the embedded recovery unit. Only the install location becomes prefix
relative; the literal in the unit template has to keep matching the asset.

systemdSystemDir is deliberately not prefix relative. Units have to live where
systemd looks for them, and /etc is writable even when /usr is not.
The prefix work it depends on was consolidated upstream and rebased onto
main after the bootstrap ownership change merged, so the previously pinned
commit no longer exists on any branch.

Still an unreleased commit rather than a tag, and still has to become a
release version before this merges.
The e2e offline artifacts scenario builds its bundle with
agent-artifacts-builder, installed from the pinned agent library version, so
raising that pin changed the tool.

Its --legal-files-dir used to default to empty, which skipped materializing
LICENSE and NOTICE. The default is now the working directory, and the
materialization is unconditional whenever the flag is set, so the build fails
on this repository, which has no NOTICE:

    materialize "NOTICE": link failed: link NOTICE
    .../offline-bootstrap-artifacts/v1.35.0/NOTICE: no such file or directory

The bundle never reaches a user. It is published to a loopback registry on a
throwaway VM for the duration of one test, so there is nothing for it to
carry, and passing the flag empty restores the previous behaviour.

Worth flagging upstream separately: a flag default that changed from skip to
require breaks any consumer that never passed it.

This branch was successfully deployed

1 active deployment
e2e-testing ce0a0ad5 Deployed Sep 21, 2026 by plombardi89 via E2E Tests #1041
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.

1 participant