k8s-management — Ansible configuration

Ansible project that configures the k8s-management Proxmox LXC container (ID 105) over SSH:

RoleWhat it does
ssh_serverInstalls/enables openssh-server, creates the jan login (member of sudo, passwordless sudo), installs the control-node public key for jan and root, and hardens sshd (key-only logins, root restricted to keys) through /etc/ssh/sshd_config.d/99-k8s-management.conf.
toolsInstalls the base CLI packages plus talosctl and kubectl (latest stable by default) and enables kubectl bash completion.
talos_clientWrites the managed talosconfig (/root/.talos/config) for the control plane: the client certs come from the OpenTofu talosconfig output, and the Talos control-plane endpoint/node are pinned with talosctl config endpoint / config node.
kubeconfigGenerates the admin kubeconfig (/root/.kube/config) with talosctl kubeconfig and points its server at the reachable control plane, so kubectl works without arguments.

Layout

a├├├├├└n──────s──────ibahrsgr├││││├││├││└lnoeiro────essqtol────/itueuebsi.pss├├├└t├└t├└k├└l.ry_/s────o──a──u──eyemh────o──l──b──.mmla_loeclersdhttsdtsdtcdtfnseeaae/ea_eaoeagt/rfnsmfscfsnfssavadkpaklakfak.leulslusiusiusylrle/al/el/gl/m./trmttmntm/tmlyssaesatsasam//is/i//i/ilmmnmnmnmnaa.sa.a.a.iiysiyiyiynnmhnmnmnm..ld.l.l.lyy_yyymmcmmmllolll#####nfciapuionnlsgnvsae.teiyrdrnbbs/otlo,9loeo9-r.kk-nyp:eko:oy8dssssetis,-hxhmce_saocssnncoehafolrdginlvegtee+macre(ittnini+otneoo.vrntlce'oonsosntlefohst.rotjym+i2,en-tgknaseelyto,wso_brceklcioaemdnedt)re+sskubeconfig

Prerequisites

Usage

cd ansible
ansible-galaxy collection install -r requirements.yml   # once
ansible-playbook site.yml

Logging in from another server

The remote login name is always jan (the account that exists on the container). The client machine does not need a local jan account — it only needs a private key whose public key is authorized on the container:

Notes:

Inventory and the container address

hosts.yml points at the container’s DHCP address on the home network (vmbr0). This is deliberately not 10.10.50.10: that address lives on the isolated vmbr1 bridge and is only reachable from inside the Talos subnet.

If the DHCP lease changes, override it for a run:

ansible-playbook site.yml \
  -e "ansible_host=$(cd .. && tofu output -json management | jq -r .wan_ips.eth0)"

Variables

All defaults live in group_vars/all.yml:

VariableDefaultPurpose
admin_userjanLogin created on the container.
admin_groups[sudo]Supplementary groups for the login.
ssh_public_key_path~/.ssh/id_ed25519.pubKey installed for jan and root.
ssh_extra_authorized_keys[]Extra public keys authorized for jan (other servers/operators).
ssh_authorized_keys_exclusivefalseWhen true, remove any keys not listed above.
sshd_password_authenticationfalseInteractive password logins.
sshd_permit_root_loginprohibit-passwordRoot may log in with a key only.
sshd_allow_usersroot janAccounts sshd accepts.
talosctl_version"" (latest)Pin a talosctl release.
kubectl_version"" (stable)Pin a kubectl release.
tools_packagessee fileBase CLI packages.
talos_endpoint_ip10.10.50.22Talos control-plane endpoint written to the talosconfig (also used as the default node).
talos_node_ip{{ talos_endpoint_ip }}Node(s) bare talosctl commands target.
talos_config_file/root/.talos/configPath of the managed talosconfig.
talos_tofu_dir{{ playbook_dir }}/..OpenTofu project providing the talosconfig output (client certs).
kube_config_dir/root/.kubeDirectory of the managed admin kubeconfig.
kubeconfig_file{{ kube_config_dir }}/configPath of the managed kubeconfig.
kube_api_endpoint{{ talos_endpoint_ip }}Control plane host written into the kubeconfig (:6443).

Verification

The playbook finishes by asserting the effective sshd configuration is key-only, asserting the talosconfig targets the Talos control plane, asserting the kubeconfig points at the control plane, probing both APIs (non-fatal) and reporting the installed versions. You can also check by hand:

ssh jan@<container> 'whoami; id; sudo -n whoami'
ssh jan@<container> 'talosctl version --client --short; kubectl version --client'
ssh root@<container> 'talosctl config info'        # endpoint/node of the control plane
ssh root@<container> 'kubectl config view --minify -o jsonpath="{.clusters[0].cluster.server}"'

Set the control-plane endpoint with the same command the role uses:

ssh root@<container> '/usr/local/bin/talosctl config endpoint 10.10.50.22'

Note: the client certificate/key in the managed talosconfig come from the OpenTofu talosconfig output. If you generate a talosconfig by hand (talosctl gen config) it belongs to a different PKI and cannot talk to the cluster — re-run this playbook to restore the matching one.

Note: the OpenTofu node_ips[0] / cluster endpoint is 10.10.50.10, but that address is also the management container’s own eth1 (management_lan_ip), so it is not reachable as a Talos API from the container. The default talos_endpoint_ip therefore points at the reachable control plane 10.10.50.22 (talos-node-3); override it with -e talos_endpoint_ip=... once the IP conflict is resolved.

Relationship to the OpenTofu bootstrap

management.tf only bootstraps the container: it brings the interfaces up and seeds root’s authorized key so Ansible can connect. Everything else — the SSH server, the jan login and the tooling — is owned by this project, so the container’s boot script no longer installs anything.

The container entrypoint is written at creation time, so a change to the boot script only takes effect after a recreate:

tofu apply -refresh=false -auto-approve \
  -replace=proxmox_virtual_environment_container.management \
  -target=proxmox_virtual_environment_container.management

Security notes