Ansible playbooks to bootstrap and configure Kubuntu nodes, including Tailscale networking and a Kubernetes cluster with Calico.
Some files are intentionally excluded from git and must be created locally on the machine you run Ansible from. This keeps secrets off the remote server and out of version control.
| Path | In git? | Purpose |
|---|---|---|
group_vars/all.yml |
No | Group-wide variables (hosts, ports, etc.) |
group_vars/vault.yml |
No | Encrypted secrets (Ansible Vault) |
host_vars/<hostname>.yml |
No | Per-host variables |
inventory.yml |
No | List of hosts to configure |
The kubernetes role uses group membership to decide what to configure on
each host. Create an inventory file (e.g. inventory.yml) that reflects
your cluster topology.
kubernetes
├── cp ← runs control_plane.yml (kubeadm init, Calico, VIP)
└── worker ← runs worker.yml (kubeadm join)
Hosts in both cp and worker are initialised as a control plane and
then have the node-role.kubernetes.io/control-plane:NoSchedule taint
removed, so they can also schedule regular workloads.
Current setup — dedicated control plane and worker:
all:
children:
kubernetes:
children:
cp:
hosts:
bobse16:
worker:
hosts:
homelab:Single node — control plane that also runs workloads:
all:
children:
kubernetes:
children:
cp:
hosts:
bobse16:
worker:
hosts:
bobse16:HA control plane — three control planes, two dedicated workers:
all:
children:
kubernetes:
children:
cp:
hosts:
cp-1:
cp-2:
cp-3:
worker:
hosts:
homelab:
worker-2:Connection details (Ansible SSH user, port, etc.) go in
host_vars/<hostname>.yml, which is excluded from git.
Sensitive values — currently tailscale_api_key — live in
group_vars/vault.yml, which is an Ansible Vault-encrypted file.
The file is excluded from git via group_vars/.gitignore so it never
leaves your local machine.
Pick a strong password and store it in a file outside this repository:
mkdir -p ~/.ansible
python3 -c "import secrets; print(secrets.token_urlsafe(32))" > ~/.ansible/vault_pass
chmod 600 ~/.ansible/vault_passTo avoid typing --vault-password-file on every command, add this to
ansible.cfg in the project root (create the file if it does not exist):
[defaults]
vault_password_file = ~/.ansible/vault_pass- Sign in at https://login.tailscale.com/admin/settings/keys
- Under API keys, click Generate API key
- Give it a description (e.g.
ansible-kubernetes) and set an appropriate expiry - Copy the key — it starts with
tskey-api-
The API key is used by the
kubernetesrole to approve the Kubernetes VIP (10.254.0.1/32) as an enabled subnet route on each control plane node, enabling Tailscale HA failover between them.
ansible-vault create group_vars/vault.ymlThis opens your $EDITOR. Enter the following content, substituting
your actual key:
---
tailscale_api_key: "tskey-api-REPLACE_WITH_YOUR_KEY"Save and close the editor. The file is now AES256-encrypted on disk.
To verify it was created correctly:
ansible-vault view group_vars/vault.ymlTo edit it later:
ansible-vault edit group_vars/vault.ymlIf you need to change the vault password (e.g. after a key rotation):
ansible-vault rekey group_vars/vault.yml# Full setup (bootstrap → hibernation → sources → tailscale → kubernetes → printing)
ansible-playbook setup.yml
# Kubernetes only
ansible-playbook setup.yml --tags kubernetes
# Dry run
ansible-playbook setup.yml --check --diffIf you did not configure vault_password_file in ansible.cfg, add
--ask-vault-pass to any of the above commands:
ansible-playbook setup.yml --ask-vault-passWSL1 lacks systemd and netfilter kernel support, which blocks several playbook tasks:
- UFW firewall rules require netfilter/iptables kernel modules
- Service management (systemd_service) cannot start/restart services
- systemd packages cannot complete post-installation configuration
To run the playbook on WSL1, skip these operations:
ansible-playbook setup.yml --skip-tags requires_netfilter,requires_systemdThis will complete successfully with the following limitations:
- Firewall rules will not be configured
- Services will not be automatically started (tailscale, CUPS, NetworkManager, etc.)
- systemd-dependent packages will be partially installed but not configured
For full functionality, upgrade to WSL2 (which includes Hyper-V nested virtualization and systemd support). See the WSL2 installation guide.
Ansible itself comes from the system package, but the linter is pinned in
requirements.txt so it is reproducible. Set up (or refresh) a local tooling
venv with uv:
uv venv
uv pip install -r requirements.txtThen run the linter over the whole repo:
.venv/bin/ansible-lintNew code should lint clean against the production profile before committing.
After the kubernetes role runs, each control plane node will have:
10.254.0.1/32bound to adummy-k8svipinterface (persistent viasystemd-networkd)- The route advertised to Tailscale via
tailscale set --advertise-routes - The route approved in the Tailscale admin console via the API
When you add further control plane nodes (cp-1, cp-2, …), re-running
the playbook will advertise and approve the VIP route on those nodes too.
Tailscale will automatically fail over between advertisers if a node goes
down.
Note: Tailscale subnet route HA failover requires the route to be approved on each advertising node individually in the admin console. The role does this automatically when
tailscale_api_keyis set.