Environment
- Ansible version: ansible-core 2.20.5
- Collection version: v1.3.2 (commit edbd8ad)
- Kubernetes distribution (k3s/kubeadm/RKE2): k3s
- OS: Ubuntu 24.04 LTS (kernel 6.8)
Expected behavior
examples/ubuntu/ is structured as four playbooks (prepare-sudo.yml, prepare-ubuntu.yml, k3s.orchestration.site, cozystack.installer.site). Any tooling layer that wants to checkpoint between OS prep and k3s install can naturally invoke them separately:
ansible-playbook --inventory inventory.yml prepare-ubuntu.yml
ansible-playbook --inventory inventory.yml k3s.orchestration.site
I expected this to produce the same k3s installation as ansible-playbook site.yml.
Actual behavior
The split invocation produces an upstream-default k3s install — traefik, servicelb, local-storage, metrics-server, flannel, and kube-proxy are all enabled. The cozystack-required disables and overrides are absent from /etc/systemd/system/k3s.service and /etc/rancher/k3s/config.yaml:
- missing flags:
--disable=traefik, --disable=servicelb, --disable=local-storage, --disable=metrics-server, --disable-network-policy, --disable-kube-proxy, --flannel-backend=none, --cluster-domain=cozy.local, --kubelet-arg=max-pods=220
- missing config:
cluster-cidr, service-cidr
Before that, the second invocation also fails outright with:
TASK [k3s.orchestration.k3s_server : Add TLS SAN to config if needed]
[ERROR]: Error while evaluating conditional: 'ansible_hostname' is undefined
because the dynamically-created k3s_cluster group is empty in the new process. After adding the group explicitly to inventory.yml:
k3s_cluster:
children:
server:
agent:
the second invocation succeeds — but produces the misconfigured k3s described above.
Root cause
prepare-ubuntu.yml uses two ansible mechanisms that only live inside one ansible-playbook process:
add_host populates the k3s_cluster group (task "Create k3s_cluster group for k3s.orchestration").
set_fact populates extra_server_args (from cozystack_k3s_server_args + cozystack_k3s_extra_args) and server_config_yaml (the CIDRs). The k3s.orchestration.k3s_server role reads these to assemble the systemd unit and /etc/rancher/k3s/config.yaml.
Across separate ansible-playbook invocations, both pieces of in-memory state are gone. The k3s role then takes its own defaults, and the cozystack-tuned configuration silently disappears. Ansible does not warn.
The current site.yml works because import_playbook chains everything in a single executor — but that contract is not documented anywhere a reader of examples/ubuntu/ would find before splitting the invocations.
Steps to reproduce
-
Clone the repo, cd examples/ubuntu.
-
Edit inventory.yml for a single-node Ubuntu 24.04 host.
-
Run:
ansible-playbook --inventory inventory.yml prepare-ubuntu.yml
ansible-playbook --inventory inventory.yml k3s.orchestration.site
-
Observe the second run failing with 'ansible_hostname' is undefined.
-
Add k3s_cluster: {children: {server: , agent: }} to inventory.
-
Re-run step 3 — both playbooks succeed.
-
sudo systemctl cat k3s | grep -- --disable returns nothing; kubectl -n kube-system get pods shows traefik / servicelb / metrics-server / local-path-provisioner running; node has flannel-managed pod network.
Suggested fixes (any subset would help)
-
Document the contract. Add to examples/ubuntu/README.md: Always run via ansible-playbook site.yml. Splitting prepare-ubuntu.yml and k3s.orchestration.site into separate invocations drops cozystack-tuned k3s flags (they cross the boundary via set_fact, which does not survive across ansible-playbook processes).
-
Declare k3s_cluster in the inventory template. Remove the runtime add_host dependency. The prepare-ubuntu.yml task can keep the add_host as a fallback for old inventories.
-
Move the cozystack k3s defaults out of set_fact into group_vars/all/cozystack.yml (or similar). Then cozystack_k3s_server_args and cozystack_k3s_server_config_yaml are available to any playbook in examples/ubuntu/ regardless of execution order, and the second set_fact step in prepare-ubuntu.yml becomes a no-op fallback.
-
Fast-fail in k3s.orchestration.site if the expected vars are not set. An assert at the top of the play with that: extra_server_args is defined and a fail_msg: pointing at the README is much friendlier than the silent default-application path.
Items 2 + 3 together make the playbooks runnable independently — which matches the structure of examples/ubuntu/ and what a user reading it would expect.
Relevant logs
TASK [k3s.orchestration.k3s_server : Add TLS SAN to config if needed]
[ERROR]: Task failed: Error while evaluating conditional: 'ansible_hostname' is undefined
Origin: k3s/orchestration/roles/k3s_server/tasks/main.yml:48:7
fatal: [<HOST>]: FAILED! => {"changed": false, "msg": "Task failed: Error while evaluating conditional: 'ansible_hostname' is undefined"}
Environment
Expected behavior
examples/ubuntu/is structured as four playbooks (prepare-sudo.yml,prepare-ubuntu.yml,k3s.orchestration.site,cozystack.installer.site). Any tooling layer that wants to checkpoint between OS prep and k3s install can naturally invoke them separately:I expected this to produce the same k3s installation as
ansible-playbook site.yml.Actual behavior
The split invocation produces an upstream-default k3s install — traefik, servicelb, local-storage, metrics-server, flannel, and kube-proxy are all enabled. The cozystack-required disables and overrides are absent from
/etc/systemd/system/k3s.serviceand/etc/rancher/k3s/config.yaml:--disable=traefik,--disable=servicelb,--disable=local-storage,--disable=metrics-server,--disable-network-policy,--disable-kube-proxy,--flannel-backend=none,--cluster-domain=cozy.local,--kubelet-arg=max-pods=220cluster-cidr,service-cidrBefore that, the second invocation also fails outright with:
because the dynamically-created
k3s_clustergroup is empty in the new process. After adding the group explicitly toinventory.yml:the second invocation succeeds — but produces the misconfigured k3s described above.
Root cause
prepare-ubuntu.ymluses two ansible mechanisms that only live inside oneansible-playbookprocess:add_hostpopulates thek3s_clustergroup (task "Create k3s_cluster group for k3s.orchestration").set_factpopulatesextra_server_args(fromcozystack_k3s_server_args+cozystack_k3s_extra_args) andserver_config_yaml(the CIDRs). Thek3s.orchestration.k3s_serverrole reads these to assemble the systemd unit and/etc/rancher/k3s/config.yaml.Across separate
ansible-playbookinvocations, both pieces of in-memory state are gone. The k3s role then takes its own defaults, and the cozystack-tuned configuration silently disappears. Ansible does not warn.The current
site.ymlworks becauseimport_playbookchains everything in a single executor — but that contract is not documented anywhere a reader ofexamples/ubuntu/would find before splitting the invocations.Steps to reproduce
Clone the repo,
cd examples/ubuntu.Edit
inventory.ymlfor a single-node Ubuntu 24.04 host.Run:
Observe the second run failing with
'ansible_hostname' is undefined.Add
k3s_cluster: {children: {server: , agent: }}to inventory.Re-run step 3 — both playbooks succeed.
sudo systemctl cat k3s | grep -- --disablereturns nothing;kubectl -n kube-system get podsshows traefik / servicelb / metrics-server / local-path-provisioner running; node has flannel-managed pod network.Suggested fixes (any subset would help)
Document the contract. Add to
examples/ubuntu/README.md: Always run viaansible-playbook site.yml. Splittingprepare-ubuntu.ymlandk3s.orchestration.siteinto separate invocations drops cozystack-tuned k3s flags (they cross the boundary viaset_fact, which does not survive acrossansible-playbookprocesses).Declare
k3s_clusterin the inventory template. Remove the runtimeadd_hostdependency. Theprepare-ubuntu.ymltask can keep theadd_hostas a fallback for old inventories.Move the cozystack k3s defaults out of
set_factintogroup_vars/all/cozystack.yml(or similar). Thencozystack_k3s_server_argsandcozystack_k3s_server_config_yamlare available to any playbook inexamples/ubuntu/regardless of execution order, and the secondset_factstep inprepare-ubuntu.ymlbecomes a no-op fallback.Fast-fail in
k3s.orchestration.siteif the expected vars are not set. Anassertat the top of the play withthat: extra_server_args is definedand afail_msg:pointing at the README is much friendlier than the silent default-application path.Items 2 + 3 together make the playbooks runnable independently — which matches the structure of
examples/ubuntu/and what a user reading it would expect.Relevant logs