Files
Kris 1a600b60d3 feat: make the server config file mode configurable (#561)
* feat: make the server config file mode configurable

/etc/rancher/k3s/config.yaml holds the cluster token. The server and
upgrade roles write it at 0644, so every local account on a server node
can read the credential that joins a node to the cluster.

k3s_server_config_mode sets the mode of that file in both roles. It
defaults to 0644, so behaviour is unchanged unless it is set.

Signed-off-by: Kristofer <kristofer@atlascloud.is>

* docs: describe the server config file contents accurately

/etc/rancher/k3s/config.yaml does not always carry the cluster token. A
first server that lets K3s generate one writes the file without it, so the
file can contain the token rather than always holding it. Correct that
claim in the README, the inventory sample and the role default, and use
the US spelling of "honors".

Signed-off-by: Kristofer <kristofer@atlascloud.is>

* Name the floor the config file mode can be narrowed to

Saying any mode root can read works leaves the reader to work out what
that means in practice. The file is read only by k3s, running as root, so
0600 is the tightest mode that still works.

Signed-off-by: Kristofer <kristofer@atlascloud.is>

* Run the basic integration job with a non-default config file mode

The templated mode was only ever rendered with its default in CI, which
is the one value that cannot tell a working template from a broken one.
The basic inventory sets 0640 and the job reads the mode back off the
installed file.

Signed-off-by: Kristofer <kristofer@atlascloud.is>

---------

Signed-off-by: Kristofer <kristofer@atlascloud.is>
2026-09-16 11:55:02 -07:00

190 lines
8.6 KiB
YAML

---
# with_fileglob doesn't work with remote_src, it tries to find the file on the
# local control-plane instead of the remote host. Shell supports wildcards.
- name: Get k3s installed version
ansible.builtin.command: k3s --version
register: k3s_upgrade_version_output
changed_when: false
check_mode: false
- name: Set k3s installed version
ansible.builtin.set_fact:
k3s_upgrade_current_version: "{{ k3s_upgrade_version_output.stdout_lines[0].split(' ')[2] }}"
check_mode: false
# We update the node configuration in the following cases:
# - the installed version of K3s on the nodes is older than the requested version in ansible vars
# - the installed version equals the requested version (to apply config changes like extra_server_args/extra_agent_args)
- name: Update node only if needed
when: k3s_upgrade_current_version is version(k3s_version, '<=')
block:
- name: Stage airgap artifacts for upgrade
when: airgap_dir is defined
ansible.builtin.include_role:
name: airgap
tasks_from: main.yml
apply:
tags:
- distribute_artifacts
# We must stop the service because we want to modify the service file before starting it again.
# INSTALL_K3S_SKIP_START does work on upgrades, because the service is already installed and started.
- name: Stop K3s service
when: k3s_upgrade_current_version is version(k3s_version, '<')
ansible.builtin.service:
state: stopped
name: "{{ (server_group in group_names) | ternary('k3s', 'k3s-agent') }}"
# We only save the token if the user did not provide one, leading to an auto-generated token on first install.
# If you want the actual token value, you need to use the k3s_upgrade_old_token.stdout
- name: Save the existing K3s token if needed
when:
- token is not defined
- inventory_hostname == groups[server_group][0] or ansible_host == groups[server_group][0]
ansible.builtin.command: cut -d':' -f4 /var/lib/rancher/k3s/server/node-token
register: k3s_upgrade_old_token
changed_when: false
- name: Construct Server config
when: server_group in group_names
block:
# Start with an empty config
- name: Set empty server config
ansible.builtin.set_fact:
k3s_server_config: {}
# If token is provided, add it to the config
- name: Add token to server config
when: token is defined
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine({'token': token}) }}"
# If token is not defined, use the old token
- name: Add old token to server config
when: token is not defined
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine({'token': k3s_upgrade_old_token.stdout}) }}"
- name: Determine if tls-san is already in config or args
# noqa var-naming[no-role-prefix]
ansible.builtin.set_fact:
_api_endpoint_in_config: >-
{% if server_config_yaml is defined and api_endpoint is defined and server_config_yaml | regex_search('tls-san:.*' + api_endpoint | regex_escape(), ignorecase=True) %}
true
{% else %}
false
{% endif %}
_api_endpoint_in_args: >-
{% if api_endpoint is defined and extra_server_args | regex_search('--tls-san[=\s]+' + api_endpoint | regex_escape(), ignorecase=True) %}
true
{% else %}
false
{% endif %}
- name: Add TLS SAN to config if needed
when:
- api_endpoint is defined
- api_endpoint != ansible_facts['hostname']
- not (_api_endpoint_in_config | trim | bool)
- not (_api_endpoint_in_args | trim | bool)
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine({'tls-san': api_endpoint}) }}"
- name: Add cluster-init to server config for first server in HA-IC setup
when:
- (groups[server_group] | length) > 1
- inventory_hostname == groups[server_group][0] or ansible_host == groups[server_group][0]
- not use_external_database
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine({'cluster-init': true}) }}"
# If not the first server in an HA-IC setup, setup the server: URL for joining the cluster
# server: https://{{ api_endpoint }}:{{ api_port }}
- name: Add server URL to server config for joining servers in HA-IC setup
when: (groups[server_group] | length) > 1 and inventory_hostname != groups[server_group][0] and not use_external_database
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine({'server': 'https://' + (api_endpoint | ansible.utils.ipwrap) + ':' + api_port | string}) }}"
# If the user has provided additional server config, merge it with the generated config
- name: Merge user server config with generated server config
when: server_config_yaml is defined
ansible.builtin.set_fact:
k3s_server_config: "{{ k3s_server_config | combine(server_config_yaml | from_yaml) }}"
- name: Convert server config to YAML and write to file
when: not ansible_check_mode
ansible.builtin.copy:
content: "{{ k3s_server_config | to_nice_yaml }}"
dest: "/etc/rancher/k3s/config.yaml"
mode: "{{ k3s_server_config_mode }}"
register: k3s_server_config_result
- name: Install new K3s Version [server]
# For some reason, ansible-lint thinks using enviroment with command is an error
# even though its valid https://ansible.readthedocs.io/projects/lint/rules/inline-env-var/#correct-code
# Skip if only reconfiguring (no version change needed)
when:
- k3s_upgrade_current_version is version(k3s_version, '<')
- server_group in group_names
ansible.builtin.command: # noqa inline-env-var
cmd: /usr/local/bin/k3s-install.sh
environment: >-
{{ extra_install_envs
| combine({
"INSTALL_K3S_SKIP_START": "true",
"INSTALL_K3S_VERSION": k3s_version,
"INSTALL_K3S_EXEC": extra_server_args,
})
| combine(airgap_dir is defined and {"INSTALL_K3S_SKIP_DOWNLOAD": "true"} or {}) }}
changed_when: true
- name: Get the token from the first server
# noqa var-naming[no-role-prefix]
when:
- agent_group in group_names
- token is not defined
ansible.builtin.set_fact:
k3s_upgrade_old_server_token: "{{ hostvars[groups[server_group][0]].k3s_upgrade_old_token }}"
- name: Install new K3s Version [agent]
# For some reason, ansible-lint thinks using enviroment with command is an error
# even though its valid https://ansible.readthedocs.io/projects/lint/rules/inline-env-var/#correct-code
# Unlike server, we always run the install command, because we are using it to reconfigure the ENV and Args passed to k3s-agent.
# Instead we just skip the download/replace if airgapped or no version change is needed.
when:
- agent_group in group_names
# noqa var-naming[no-role-prefix]
ansible.builtin.command:
cmd: /usr/local/bin/k3s-install.sh
environment: "{{ _install_envs }}"
vars:
_base_envs:
INSTALL_K3S_SKIP_DOWNLOAD: "{{ (airgap_dir is defined or k3s_upgrade_current_version == k3s_version) | ternary('true', 'false') }}"
INSTALL_K3S_SKIP_START: "true"
INSTALL_K3S_SYSTEMD_DIR: "{{ systemd_dir }}"
INSTALL_K3S_VERSION: "{{ k3s_version }}"
INSTALL_K3S_EXEC: "agent --server https://{{ api_endpoint | ansible.utils.ipwrap }}:{{ api_port }} {{ extra_agent_args }}"
K3S_TOKEN: "{{ token if token is defined else k3s_upgrade_old_server_token.stdout }}"
# We overrides the extra_install_envs with required keys from _base_envs on purpose
_install_envs: "{{ extra_install_envs | default({}) | combine(_base_envs) }}"
changed_when: true
- name: Reload systemd daemon
when:
- not ansible_check_mode
- ansible_facts['service_mgr'] == 'systemd'
ansible.builtin.systemd:
daemon_reload: true
- name: Restart K3s service [server]
when: server_group in group_names
ansible.builtin.service:
state: restarted
name: k3s
- name: Restart K3s service [agent]
when: agent_group in group_names
ansible.builtin.service:
state: restarted
name: k3s-agent