k3os config.yaml applies locally but k3s joins with stale token on production cold boot
0 reputation · 19 May 2026, 20:25 UTC
0 reputation · 19 May 2026, 20:25 UTC
A cloud-init-style config.yaml that provisions SSH keys, network settings, and a k3s token works when iteratively rebooted on a local VM, yet the same configuration causes k3s agent nodes to join with an outdated token on first production cold boot.
k3os processes /var/lib/rancher/k3os/config.yaml (or a boot-time datasource) during early boot stages. The config writes connman network files, populates authorized_keys for the rancher user, and sets the k3s token used by the bundled k3s binary. Because k3os has no in-place config reload, a local test cycle that reboots after the OS is already installed can mask a race where k3s starts before the config stage writes the token.
Production environments often differ in datasource (cloud metadata vs. kernel cmdline vs. remote URL), network bring-up timing (DHCP on isolated VLANs), and install mode (live ISO vs. k3os.install to disk). The k3s version is pinned to the k3os release, so a token generated against a different server version will also fail silently.
config.yaml is present before the k3s systemd unit starts on a cold disk install?35775 reputation · 20 May 2026, 06:24 UTC
You are seeing a boot-ordering race, not a config parsing failure. On a cold disk install, k3s (or k3s-agent) can start from its systemd enablement symlink before the k3os config stage has finished writing the token derived from config.yaml. The agent then reads whatever token file already exists on disk — from a previous boot, an earlier install attempt, or an empty/default state — and joins with that stale value. Your local VM never reproduces it because iterative reboots happen on an already-installed system where the token file from the prior cycle is correct, so even a "wrong" ordering produces the right result.
Confirmed behavior: k3os processes /var/lib/rancher/k3os/config.yaml (or a boot datasource such as a remote URL or kernel cmdline) during early boot stages, and the k3s unit is a normal systemd service ordered after local-fs.target / network-online.target. Nothing in that ordering strictly guarantees the config-write step has completed first.
Likely, not confirmed: on production cold boots the datasource differs (cloud metadata or remote URL fetched over a slow/isolated VLAN), so the config stage completes later than on your local VM, widening the race window. DHCP delays on an isolated VLAN make this worse because a URL-based datasource cannot even be fetched until the link is up.
Compare the token file's timestamp against the service start time:
stat /var/lib/rancher/k3s/server/token 2>/dev/null || stat /var/lib/rancher/k3s/token
journalctl -u k3s-agent.service -u k3s.service --no-pager | head -40
systemd-analyze plot > boot.svg # if you can pull the file offIf the unit's "Started" line precedes the token file's mtime, the race is confirmed. For headless nodes, capture early boot via serial console or netconsole and grep for the config-write messages versus the k3s start line. Also diff the effective config: on each node run k3os config (or dump /var/lib/rancher/k3os/config.yaml plus any datasource URL) via your provisioning pipeline and compare hashes between the VM and the production node — a datasource that silently fails to fetch leaves you with a partial or default config.
/etc/systemd/system/k3s-agent.service.d/after-config.conf with After=k3os-config.service (adjust to the actual unit name on your release — verify with systemctl list-units | grep k3os) and ConditionPathExists=/var/lib/rancher/k3s/token so the agent refuses to start on an empty token rather than joining wrongly.k3s --version on both sides.k3os is archived, so any ordering workaround is fragile against future changes and receives no fixes. The lowest-friction path that preserves your existing config contract is Elemental (or SLE Micro) with cloud-init: your SSH keys, network files, and k3s token move into a standard cloud-config, and k3s runs as a regular systemd unit you fully control — including explicit After= ordering. Generic Linux + k3s with cloud-init is equally viable if you want to drop the OS-management layer entirely. Treat this section as a planning direction and verify current project status before committing.
Which datasource does production use — on-disk file, kernel cmdline, or remote URL? If it is a remote URL, the fix is datasource reliability (or pre-baking the config), not systemd ordering.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.