--- # Bootstraps this node as the (single) k3s control plane. playbooks/k3s.yml # runs the k3s_control_plane play before k3s_workers, so by the time # agent.yml runs anywhere, K3S_URL below already points at something live. # # The token comes from Vault as a fixed, pre-shared value rather than letting # k3s generate one on first install — see docs/vault-secrets.md # (homelab/k3s-homelab-utils). That's what makes a full rebuild (wipe both SD # cards, reinstall) reproduce the same cluster identity instead of needing the # new token hunted down and re-distributed by hand. # # --node-name pins the k8s node object to the Ansible inventory_hostname # rather than whatever the OS hostname happens to be — roles/k3s_maintenance # addresses nodes by inventory_hostname when draining/uncordoning, and that # only works if the two names are guaranteed to match. - name: Bootstrap the k3s server no_log: true # K3S_TOKEN passes through this block block: - name: Look up the k3s cluster secrets ansible.builtin.set_fact: k3s_secrets: >- {{ lookup('community.hashi_vault.vault_kv2_get', k3s_vault_path, engine_mount_point=vault_kv_mount, url=vault_addr, auth_method=vault_auth_method, role_id=vault_role_id | default(omit), secret_id=vault_secret_id | default(omit)).secret }} - name: Compute the desired k3s server exec line ansible.builtin.set_fact: k3s_server_exec: >- server --node-name {{ inventory_hostname }} --tls-san {{ k3s_api_tls_san }} --write-kubeconfig-mode 644 {{ (k3s_extra_args + k3s_server_extra_args) | join(' ') }} # Compared against what k3s was last installed with (see the copy task # below) so a change to k3s_extra_args/k3s_server_extra_args — or # k3s_api_tls_san — gets applied on the next run instead of silently # sitting unused: the version check below has no way to notice an # exec-line-only change. Missing file (first install) counts as changed. - name: Read the exec line k3s was last installed with ansible.builtin.slurp: src: /etc/rancher/k3s/.ansible_install_exec register: k3s_installed_exec_raw failed_when: false check_mode: false - name: Determine whether the exec line has changed ansible.builtin.set_fact: k3s_exec_changed: >- {{ k3s_installed_exec_raw.content is not defined or (k3s_installed_exec_raw.content | b64decode) != k3s_server_exec }} - name: Check the installed k3s version ansible.builtin.command: k3s --version register: k3s_installed_version changed_when: false failed_when: false check_mode: false # The installer is safe to re-run — it's a no-op if the requested version # and exec line are already active — but skipping it when neither changed # avoids restarting the service (and briefly dropping the API) on every # playbook run. Restarting the k3s process itself (as opposed to # rebooting the node, which roles/k3s_maintenance handles separately) is # a brief control-plane/kubelet blip, not a pod outage — containerd keeps # every already-running pod up underneath it. - name: Install/upgrade k3s server ansible.builtin.shell: curl -sfL https://get.k3s.io | sh - environment: INSTALL_K3S_VERSION: "{{ k3s_version }}" INSTALL_K3S_EXEC: "{{ k3s_server_exec }}" K3S_TOKEN: "{{ k3s_secrets.K3S_TOKEN }}" when: >- k3s_installed_version.rc != 0 or k3s_version not in k3s_installed_version.stdout or k3s_exec_changed - name: Ensure the k3s service is enabled and running ansible.builtin.systemd_service: name: k3s enabled: true state: started # k3s creates /etc/rancher/k3s itself once it has something to put there # (the generated kubeconfig, on the server) — defensive rather than relied # on, so this marker doesn't assume the directory already exists. - name: Ensure /etc/rancher/k3s exists ansible.builtin.file: path: /etc/rancher/k3s state: directory owner: root group: root mode: "0755" - name: Record the exec line k3s was installed with ansible.builtin.copy: dest: /etc/rancher/k3s/.ansible_install_exec content: "{{ k3s_server_exec }}" owner: root group: root mode: "0600"