Files
k3s-ansible/roles/k3s_server/tasks/main.yml
Techno Tim c82f2e0415 chore(deps): apply dependency updates in one combined change (#694)
* chore(deps): apply dependency updates in one combined change

- Bump ansible-core to 2.19.11 and jmespath to 1.1.0 in requirements.in
- Regenerate the Python 3.11 pip-compile lock in requirements.txt
- Bump molecule-plugins to 23.6.0 while keeping molecule on the stable 6.x
  series (avoids the molecule-plugins 26 major jump that broke vagrant module
  resolution in CI)
- Bump ruamel-yaml-clib to 0.2.15
- Bump the zgosalvez/github-actions-ensure-sha-pinned-actions action to 5.0.6
  (SHA-pinned) in lint.yml

* fix(server): make log_destination conditional boolean for ansible-core 2.19

- The always block's 'Save logs of k3s-init.service' task used when: log_destination
  where log_destination is a path string derived from an env var
- ansible-core 2.19 rejects string-derived conditionals; evaluate the path as a
  real boolean (non-empty) check so the conditional is a true boolean
- Required to keep the k3s_server role working with ansible-core 2.19.11 (the
  dependency bump in this change)

* fix(verify): coerce regex_search assertions to bool for ansible-core 2.19

- ansible-core 2.19 requires assert conditionals to be boolean; regex_search
  returns a string, which is now rejected
- Wrap all regex_search results used in assert.that with | bool so the calico,
  cilium, metallb, and kube-vip image-tag checks produce boolean results

* fix(verify): use boolean is regex_search test instead of | bool

- | bool on a regex_search result coerces a tag string like v0.16.0 to False
  in ansible-core 2.19, failing the image-tag assertions
- Use the is regex_search test which returns a real boolean without string
  coercion for the calico, cilium, metallb, and kube-vip image assertions

* fix(verify): use is not none for regex_search assertions

- ansible-core 2.19 has no "is regex_search" test and rejects bool string
  coercion, so use the regex_search filter with an "is not none" comparison,
  which yields a real boolean for the image-tag assertions
- Applies to calico, cilium, metallb, and kube-vip image checks

* fix(metallb): retry transient apiserver resets in config tests

- The Layer 2 and BGP final configuration checks ran a kubectl get per
  resource with no retry, so a transient connection refused from the kube
  API could abort converge
- Mirror the download_retries/download_delay retry pattern used by the
  'Wait for MetalLB resources' task so these checks survive api server
  resets on slow runners
2026-08-04 17:54:58 -05:00

246 lines
8.4 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
# Deploy MetalLB unless kube-vip owns the load balancer IP range, or Cilium
# BGP is enabled (Cilium then provides its own load balancing). The cilium_bgp
# default keeps this safe when Cilium variables are not in scope at all (#644)
# while still deploying MetalLB when a non-BGP Cilium CNI is in use.
when: kube_vip_lb_ip_range is not defined and not (cilium_bgp | default(false) | bool)
- 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
# ANSIBLE_K3S_LOG_DIR is a path string when set; evaluate it as a boolean
# so the conditional is a real boolean (ansible-core 2.19 rejects string
# conditionals derived from env vars).
when: log_destination | default('') != ''
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 }}"