--- # showcase/k3s-control.yml — K3s server (control plane) setup # # Installs K3s in server mode. The join token is stored at # /var/lib/rancher/k3s/server/token for workers to read # via the Incus API. # # Note: The Aether blueprint profile should include: # security.nesting: "true" # for K3s to manage pods and containers properly. - name: Generate K3s server setup script ansible.builtin.set_fact: k3s_server_script: | #!/bin/bash set -e echo "=== K3s server setup starting ===" export DEBIAN_FRONTEND=noninteractive # Container workaround: create /dev/kmsg if missing [ -e /dev/kmsg ] || ln -s /dev/console /dev/kmsg # Install prerequisites apt-get install -y -qq curl 2>&1 | tail -1 # Install K3s server echo "Installing K3s server (this takes 1-2 minutes)..." curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="\ --write-kubeconfig-mode=644 \ --disable=traefik \ --snapshotter=native \ --kubelet-arg=feature-gates=KubeletInUserNamespace=true \ --node-ip={{ ffsdn_instance_ip | default('127.0.0.1') }} \ --tls-san={{ ffsdn_instance_ip | default('127.0.0.1') }}" sh - echo "[OK] K3s installed" # Wait for K3s to be ready echo "Waiting for K3s node to be ready..." export KUBECONFIG=/etc/rancher/k3s/k3s.yaml for i in $(seq 1 60); do if kubectl get nodes 2>/dev/null | grep -q ' Ready'; then echo "[OK] K3s node ready" break fi sleep 5 done # Show node status kubectl get nodes -o wide 2>/dev/null || true # Verify token file exists if [ -f /var/lib/rancher/k3s/server/node-token ]; then echo "[OK] Join token stored at /var/lib/rancher/k3s/server/node-token" else echo "[ERROR] Join token not found" exit 1 fi echo "=== K3s server setup complete ===" - name: Push K3s server setup script ansible.builtin.uri: url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/files?path=/tmp/k3s-server-setup.sh" method: POST client_cert: "{{ incus_cert }}" client_key: "{{ incus_key }}" validate_certs: false body: "{{ k3s_server_script }}" headers: Content-Type: "application/octet-stream" X-Incus-type: "file" X-Incus-mode: "0755" status_code: [200] - name: Execute K3s server setup script ansible.builtin.uri: url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/exec" method: POST client_cert: "{{ incus_cert }}" client_key: "{{ incus_key }}" validate_certs: false body_format: json body: command: ["/bin/bash", "/tmp/k3s-server-setup.sh"] record-output: true interactive: false wait-for-websocket: false environment: DEBIAN_FRONTEND: "noninteractive" status_code: [202] return_content: true register: k3s_server_exec - name: Wait for K3s server setup to complete (up to 10 minutes) ansible.builtin.uri: url: "{{ incus_api }}{{ k3s_server_exec.json.operation }}/wait?timeout=600" method: GET client_cert: "{{ incus_cert }}" client_key: "{{ incus_key }}" validate_certs: false return_content: true timeout: 620 register: k3s_server_wait failed_when: k3s_server_wait.json.metadata.status != "Success" - name: Verify K3s server setup exit code ansible.builtin.assert: that: - k3s_server_wait.json.metadata.metadata.return | int == 0 fail_msg: "K3s server setup exited with code {{ k3s_server_wait.json.metadata.metadata.return }}" success_msg: "K3s server setup completed — control plane ready" - name: Verify join token is accessible ansible.builtin.uri: url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/files?path=/var/lib/rancher/k3s/server/token" method: GET client_cert: "{{ incus_cert }}" client_key: "{{ incus_key }}" validate_certs: false return_content: true register: token_check - name: Display K3s server info ansible.builtin.debug: msg: "K3s control plane ready at {{ ffsdn_instance_ip | default('unknown') }}:6443 — token available for workers" - name: Clean up setup script ansible.builtin.uri: url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/exec" method: POST client_cert: "{{ incus_cert }}" client_key: "{{ incus_key }}" validate_certs: false body_format: json body: command: ["/bin/rm", "-f", "/tmp/k3s-server-setup.sh"] record-output: false interactive: false wait-for-websocket: false status_code: [202] ignore_errors: true