Files
k3s-ansible/roles/k3s_server/tasks/main.yml
T
Techno Tim db85fa960c fix(metallb): guard cilium_bgp when undefined; clarify apiserver_endpoint docs (#683)
* fix(metallb): guard cilium_bgp variable before evaluating

- The 'Deploy metallb manifest' and 'Deploy metallb pool' conditionals evaluate
  'not cilium_bgp' directly, which raises an undefined-variable error when the
  k3s_server role runs without cilium_bgp in scope and no Cilium variables are set
- Guard with 'cilium_bgp is not defined' so the condition resolves cleanly when
  Cilium BGP is not configured
- Fixes #644

* docs(apiserver): clarify apiserver_endpoint must be a free routable IP

- Note that apiserver_endpoint must be an unassigned, routable IP on the
  network and that it is exposed by kube-vip / MetalLB
- Fixes #678

* fix(molecule): wait for MetalLB resources before asserting images

- The MetalLB image-tag assertion crashed with 'list object has no element 0'
  when the controller Deployment was not yet observable at verify time
- Retry the MetalLB controller/speaker lookup until the resources appear
- Fail with a clear message if MetalLB is genuinely absent
2026-08-03 04:52:27 +00:00

239 lines
7.9 KiB
YAML

---
- name: Stop k3s-init
ansible.builtin.systemd:
name: k3s-init
state: stopped
failed_when: false
# k3s-init won't work if the port is already in use
- name: Stop k3s
ansible.builtin.systemd:
name: k3s
state: stopped
failed_when: false
- name: Clean previous runs of k3s-init # noqa command-instead-of-module
# The systemd module does not support "reset-failed", so we need to resort to command.
ansible.builtin.command: systemctl reset-failed k3s-init
failed_when: false
changed_when: false
- name: Deploy K3s http_proxy conf
ansible.builtin.include_tasks: http_proxy.yml
when: proxy_env is defined
- name: Deploy vip manifest
ansible.builtin.include_tasks: vip.yml
- name: Deploy metallb manifest
ansible.builtin.include_tasks: metallb.yml
tags: metallb
when: kube_vip_lb_ip_range is not defined and (cilium_bgp is not defined or cilium_iface is not defined)
- name: Deploy kube-vip manifest
ansible.builtin.include_tasks: kube-vip.yml
tags: kubevip
when: kube_vip_lb_ip_range is defined
- name: Initialize and verify the K3s control plane
any_errors_fatal: true
when: not ansible_check_mode
block:
- name: Materialize per-host server initialization arguments
ansible.builtin.set_fact:
k3s_server_init_args: "{{ server_init_args }}"
no_log: true
when: groups[group_name_master | default('master')] | length > 1
- name: Orchestrate control-plane initialization from the first master
when: inventory_hostname == groups[group_name_master | default('master')][0]
block:
- name: Init the first master inside the transient k3s-init service
ansible.builtin.command:
cmd: >-
systemd-run -p RestartSec=2 -p Restart=on-failure
-p Delegate=yes -p TasksMax=infinity -p KillMode=process
-p LimitNOFILE=1048576 -p LimitNPROC=infinity -p LimitCORE=infinity
--unit=k3s-init k3s server {{ server_init_args }}
creates: "{{ systemd_dir }}/k3s-init.service"
no_log: true
- name: Wait for the first master Kubernetes API
ansible.builtin.command:
cmd: "{{ k3s_kubectl_binary | default('k3s kubectl') }} get --raw=/readyz"
register: first_master_api
until: first_master_api.rc == 0
retries: "{{ retry_count | default(20) }}"
delay: 2
changed_when: false
- name: Verify the first master API listener on its node address
ansible.builtin.wait_for:
host: >-
{{ hostvars[groups[group_name_master | default('master')][0]].k3s_node_ip
| split(',') | first }}
port: 6443
connect_timeout: 2
timeout: "{{ retry_count | default(20) * 2 }}"
- name: Join additional masters one at a time
ansible.builtin.include_tasks: join_master.yml
loop: "{{ groups[group_name_master | default('master')][1:] }}"
loop_control:
loop_var: joining_master
when: groups[group_name_master | default('master')] | length > 1
- name: Verify that all expected control-plane nodes registered
ansible.builtin.command:
cmd: >-
{{ k3s_kubectl_binary | default('k3s kubectl') }} get nodes
-o=jsonpath='{.items[*].metadata.name}'
register: nodes
until:
- nodes.rc == 0
- expected_control_plane_names | difference(nodes.stdout.split()) | length == 0
retries: "{{ retry_count | default(20) }}"
delay: 2
changed_when: false
vars:
expected_control_plane_names: >-
{{
groups[group_name_master | default('master')]
| map('extract', hostvars, 'ansible_hostname')
| list
}}
- name: Wait for K3s bootstrap CRDs to become established
ansible.builtin.command:
cmd: >-
{{ k3s_kubectl_binary | default('k3s kubectl') }} wait
--for=condition=Established --timeout=5s
crd/addons.k3s.cattle.io
crd/helmcharts.helm.cattle.io
crd/helmchartconfigs.helm.cattle.io
register: bootstrap_crds
until: bootstrap_crds.rc == 0
retries: "{{ retry_count | default(20) }}"
delay: 2
changed_when: false
always:
- name: Save logs of k3s-init.service
ansible.builtin.include_tasks: fetch_k3s_init_logs.yml
when: log_destination
vars:
log_destination: >-
{{ lookup('ansible.builtin.env', 'ANSIBLE_K3S_LOG_DIR', default=False) }}
- name: Kill the temporary service used for initialization
ansible.builtin.systemd:
name: k3s-init
state: stopped
failed_when: false
- name: Copy K3s service file
register: k3s_service
ansible.builtin.template:
src: k3s.service.j2
dest: "{{ systemd_dir }}/k3s.service"
owner: root
group: root
mode: "0644"
- name: Enable and check K3s service
ansible.builtin.systemd:
name: k3s
daemon_reload: true
state: restarted
enabled: true
- name: Wait for node-token
ansible.builtin.wait_for:
path: /var/lib/rancher/k3s/server/node-token
- name: Register node-token file access mode
ansible.builtin.stat:
path: /var/lib/rancher/k3s/server
register: p
- name: Change file access node-token
ansible.builtin.file:
path: /var/lib/rancher/k3s/server
mode: g+rx,o+rx
- name: Read node-token from master
ansible.builtin.slurp:
src: /var/lib/rancher/k3s/server/node-token
register: node_token
- name: Store Master node-token
ansible.builtin.set_fact:
token: "{{ node_token.content | b64decode | regex_replace('\n', '') }}"
- name: Restore node-token file access
ansible.builtin.file:
path: /var/lib/rancher/k3s/server
mode: "{{ p.stat.mode }}"
- name: Create directory .kube
ansible.builtin.file:
path: "{{ ansible_user_dir }}/.kube"
state: directory
owner: "{{ ansible_user_id }}"
mode: u=rwx,g=rx,o=
- name: Copy config file to user home directory
ansible.builtin.copy:
src: /etc/rancher/k3s/k3s.yaml
dest: "{{ ansible_user_dir }}/.kube/config"
remote_src: true
owner: "{{ ansible_user_id }}"
mode: u=rw,g=,o=
- name: Configure kubectl cluster to {{ endpoint_url }}
ansible.builtin.command: >-
{{ k3s_kubectl_binary | default('k3s kubectl') }} config set-cluster default
--server={{ endpoint_url }}
--kubeconfig {{ ansible_user_dir }}/.kube/config
changed_when: true
vars:
endpoint_url: >-
https://{{ apiserver_endpoint | ansible.utils.ipwrap }}:6443
# Deactivated linter rules:
# - jinja[invalid]: As of version 6.6.0, ansible-lint complains that the input to ipwrap
# would be undefined. This will not be the case during playbook execution.
# noqa jinja[invalid]
- name: Create kubectl symlink
ansible.builtin.file:
src: /usr/local/bin/k3s
dest: /usr/local/bin/kubectl
state: link
when: k3s_create_kubectl_symlink | default(true) | bool
- name: Create crictl symlink
ansible.builtin.file:
src: /usr/local/bin/k3s
dest: /usr/local/bin/crictl
state: link
when: k3s_create_crictl_symlink | default(true) | bool
- name: Get contents of manifests folder
ansible.builtin.find:
paths: /var/lib/rancher/k3s/server/manifests
file_type: file
register: k3s_server_manifests
- name: Get sub dirs of manifests folder
ansible.builtin.find:
paths: /var/lib/rancher/k3s/server/manifests
file_type: directory
register: k3s_server_manifests_directories
- name: Remove manifests and folders that are only needed for bootstrapping cluster so k3s doesn't auto apply on start
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
with_items:
- "{{ k3s_server_manifests.files }}"
- "{{ k3s_server_manifests_directories.files }}"
loop_control:
label: "{{ item.path }}"