From 6d49ad6977140ecd4d5cff4aa5837809a0b6dcaf Mon Sep 17 00:00:00 2001 From: Timothy Stewart Date: Sat, 1 Aug 2026 22:36:14 -0500 Subject: [PATCH] fix(dependencies): address PR review findings - Read the MetalLB speaker tag check from the managed host with slurp instead of a controller-side file lookup, and match the full image reference - Restore the tigera-operator namespace on the Calico operator Deployment wait while keeping the managed CRD waits cluster-scoped - Make Molecule verify inputs durable and scenario-specific via a per-scenario verify-vars.yml, driven by explicit verify_cni/verify_lb values instead of non-persisted converge facts - Rename the kube-vip multi-peer BGP env var from bgppeers to bgp_peers and vip_cidr to vip_subnet so v1.2.2 actually reads them - Map the legacy Cilium routed mode to tunnel and stop passing the alias directly to the chart - Use return-code based failed_when on apply and preflight commands so non-error failures are no longer treated as success - Clarify the sequential K3s upgrade path and backups in the README - Add kube-vip and MetalLB regression tests and a Cilium mode mapping unit --- .ansible-lint | 4 + .github/scripts/test-kube-vip-manifest.py | 130 ++++++++++++++++++ .github/scripts/test-metallb-remote-read.sh | 29 ++++ .pre-commit-config.yaml | 14 ++ README.md | 9 +- molecule/calico/verify-vars.yml | 6 + molecule/cilium/verify-vars.yml | 6 + molecule/default/verify-vars.yml | 9 ++ molecule/ipv6/verify-vars.yml | 7 + molecule/kube-vip/verify-vars.yml | 9 ++ molecule/resources/verify.yml | 3 + .../tasks/test/deploy-example.yml | 22 +-- .../tasks/test/verify-components.yml | 39 ++++-- molecule/single_node/verify-vars.yml | 6 + roles/k3s_server/tasks/metallb.yml | 15 +- roles/k3s_server/templates/vip.yaml.j2 | 4 +- roles/k3s_server_post/meta/main.yml | 6 +- roles/k3s_server_post/tasks/calico.yml | 9 +- roles/k3s_server_post/tasks/cilium.yml | 20 ++- 19 files changed, 309 insertions(+), 38 deletions(-) create mode 100644 .github/scripts/test-kube-vip-manifest.py create mode 100755 .github/scripts/test-metallb-remote-read.sh create mode 100644 molecule/calico/verify-vars.yml create mode 100644 molecule/cilium/verify-vars.yml create mode 100644 molecule/default/verify-vars.yml create mode 100644 molecule/ipv6/verify-vars.yml create mode 100644 molecule/kube-vip/verify-vars.yml create mode 100644 molecule/single_node/verify-vars.yml diff --git a/.ansible-lint b/.ansible-lint index c535afc..51884c6 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -15,6 +15,10 @@ exclude_paths: - molecule/**/prepare.yml - molecule/**/reset.yml + # Scenario verify inputs are plain variable files, not playbooks. They are + # loaded as vars, not executed, so ansible-lint must not treat them as plays. + - molecule/**/verify-vars.yml + # The file was generated by galaxy ansible - don't mess with it. - galaxy.yml diff --git a/.github/scripts/test-kube-vip-manifest.py b/.github/scripts/test-kube-vip-manifest.py new file mode 100644 index 0000000..c74cf00 --- /dev/null +++ b/.github/scripts/test-kube-vip-manifest.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Render the kube-vip DaemonSet template and assert env key correctness. + +kube-vip v1.2.2 reads `bgp_peers` and `vip_subnet`; it ignores the older +`bgppeers` and `vip_cidr` names. This test proves the rendered manifest uses +the keys the target image actually parses. +""" + +from __future__ import print_function + +import os +import subprocess + +from jinja2 import Environment, FileSystemLoader, StrictUndefined + + +def repo_root(): + return subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True + ).strip() + + +def fail(message): + raise SystemExit("kube-vip manifest test failed: " + message) + + +def fake_ipsubnet(value): + # ansible.utils.ipsubnet -> network of the address as x.y.z.0/24 + parts = value.split(".") + return ".".join(parts[:3]) + ".0/24" + + +def fake_ipaddr(_value, expr=None): + # ansible.utils.ipaddr('prefix') -> prefix length + return "24" + + +def fake_bool(value): + # Minimal stand-in for Ansible's truthiness filter used by the template. + if isinstance(value, bool): + return value + return str(value).lower() in ("1", "true", "yes", "on") + + +def fake_map(seq, *args, **kwargs): + # Minimal stand-in for Ansible's map() filter in the two forms used by the + # template: map(attribute='x') on a list of dicts, and map('join', sep) on + # a list of sequences. + if "attribute" in kwargs: + return [item[kwargs["attribute"]] for item in seq] + if kwargs: + # e.g. map(default='x') not used here; ignore unknown kwargs. + return list(seq) + if args: + filter_name = args[0] + sep = args[1] if len(args) > 1 else "" + if filter_name == "join": + return [sep.join(str(x) for x in item) for item in seq] + return list(seq) + + +def fake_zip(*seqs): + return list(zip(*seqs)) + + +def render(env, extra_vars): + base_vars = { + "apiserver_endpoint": "192.168.30.222", + "kube_vip_iface": "", + "kube_vip_arp": True, + "kube_vip_bgp": True, + "kube_vip_bgp_routerid": "127.0.0.1", + "_kube_vip_bgp_peers": [ + {"peer_address": "192.168.30.1", "peer_asn": "64512"}, + {"peer_address": "192.168.30.2", "peer_asn": "64513"}, + ], + "kube_vip_tag_version": "v1.2.2", + } + base_vars.update(extra_vars) + template = env.get_template("vip.yaml.j2") + return template.render(**base_vars) + + +def main(): + root = repo_root() + template_dir = os.path.join(root, "roles", "k3s_server", "templates") + env = Environment( + loader=FileSystemLoader(template_dir), undefined=StrictUndefined + ) + env.filters["ansible.utils.ipsubnet"] = fake_ipsubnet + env.filters["ansible.utils.ipaddr"] = fake_ipaddr + env.filters["bool"] = fake_bool + env.filters["map"] = fake_map + env.filters["zip"] = fake_zip + + # Multi-peer BGP armed: must emit bgp_peers, never bgppeers. + output = render(env, {}) + if "name: bgp_peers" not in output: + fail("rendered manifest is missing bgp_peers") + if "name: bgppeers" in output: + fail("rendered manifest still uses the ignored bgppeers key") + if "name: vip_subnet" not in output: + fail("rendered manifest is missing vip_subnet") + if "name: vip_cidr" in output: + fail("rendered manifest still uses the ignored vip_cidr key") + if "192.168.30.1:64512,192.168.30.2:64513" not in output: + fail("bgp_peers value is not comma-separated address:ASN entries") + if "ghcr.io/kube-vip/kube-vip:v1.2.2" not in output: + fail("kube-vip image tag is not v1.2.2") + + # BGP enabled with no merged peers: single-peer fallback vars, no bgp_peers. + output = render( + env, + { + "_kube_vip_bgp_peers": [], + "kube_vip_bgp_as": "64513", + "kube_vip_bgp_peeraddress": "192.168.30.1", + "kube_vip_bgp_peeras": "64512", + }, + ) + if "name: bgp_as" not in output: + fail("single-peer bgp_as was not rendered") + if "name: bgp_peers" in output: + fail("bgp_peers present even though the peer list is empty") + + print("kube-vip manifest regression test passed") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/test-metallb-remote-read.sh b/.github/scripts/test-metallb-remote-read.sh new file mode 100755 index 0000000..5a1b9b0 --- /dev/null +++ b/.github/scripts/test-metallb-remote-read.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +metallb_task="$repo_root/roles/k3s_server/tasks/metallb.yml" + +# The speaker tag verification must read the rendered manifest on the managed +# host with slurp. A controller-side lookup('ansible.builtin.file', ...) would +# read from the Ansible control node, which does not have the file, and would +# fail on every MetalLB scenario. +grep -Fq -- 'ansible.builtin.slurp' "$metallb_task" || { + printf 'MetalLB speaker tag check does not use slurp on the managed host\n' >&2 + exit 1 +} + +grep -Eq -- 'lookup\(.?ansible\.builtin\.file' "$metallb_task" && { + printf 'MetalLB speaker tag check uses a controller-side file lookup\n' >&2 + exit 1 +} + +# The check must reference the full image reference, not just a bare version +# string that could appear anywhere in the manifest. +grep -Fq -- 'quay.io/metallb/speaker:' "$metallb_task" || { + printf 'MetalLB speaker tag check does not match the full image reference\n' >&2 + exit 1 +} + +printf 'MetalLB remote manifest read regression test passed\n' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e94cb72..0c28902 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -70,3 +70,17 @@ repos: - Jinja2>=3.1 pass_filenames: false files: ^roles/k3s_server_post/templates/cilium\.crs\.j2$|^\.github/scripts/test-cilium-bgp-manifest\.py$ + - id: kube-vip-manifest-test + name: kube-vip manifest test + entry: python3 .github/scripts/test-kube-vip-manifest.py + language: python + additional_dependencies: + - Jinja2>=3.1 + pass_filenames: false + files: ^roles/k3s_server/templates/vip\.yaml\.j2$|^\.github/scripts/test-kube-vip-manifest\.py$ + - id: metallb-remote-read-test + name: MetalLB remote read test + entry: .github/scripts/test-metallb-remote-read.sh + language: system + pass_filenames: false + files: ^roles/k3s_server/tasks/metallb\.yml$|^\.github/scripts/test-metallb-remote-read\.sh$ diff --git a/README.md b/README.md index 129206b..9211472 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,12 @@ They are not a supported direct in-place upgrade path for an existing cluster. K3s, Calico, and Cilium each require staged upgrades for long-lived clusters. - **K3s**: do not jump an embedded-etcd cluster straight to Kubernetes 1.36. - First run a K3s patch that contains etcd 3.5.26 (for example `v1.33.7+k3s3` - or newer in the 1.33 line), then advance one Kubernetes minor version at a - time. Upgrade servers one at a time before agents. See + Upgrade one Kubernetes minor version at a time. From the sample default + (`v1.30.2+k3s2`) the sequence is: the latest supported 1.30 patch, then 1.31, + 1.32, a 1.33 patch that contains etcd 3.5.26 (for example `v1.33.7+k3s3`), + then 1.34, 1.35, and finally 1.36. Upgrade servers one at a time before + agents. Take backups and confirm cluster health at each step; this playbook + does not automate the upgrade, so those remain manual operational steps. See [K3s manual upgrades](https://docs.k3s.io/upgrades/manual) and the [v1.34 release notes](https://docs.k3s.io/release-notes/v1.34.X). - **Cilium**: upstream supports only consecutive minor upgrades. Update to the diff --git a/molecule/calico/verify-vars.yml b/molecule/calico/verify-vars.yml new file mode 100644 index 0000000..7b801d8 --- /dev/null +++ b/molecule/calico/verify-vars.yml @@ -0,0 +1,6 @@ +--- +# Durable verify inputs for the calico (Calico CNI + MetalLB) scenario. +verify_cni: calico +verify_lb: metallb +verify_lb_ip_range: + - 192.168.30.100-192.168.30.109 diff --git a/molecule/cilium/verify-vars.yml b/molecule/cilium/verify-vars.yml new file mode 100644 index 0000000..4ea5980 --- /dev/null +++ b/molecule/cilium/verify-vars.yml @@ -0,0 +1,6 @@ +--- +# Durable verify inputs for the cilium (Cilium CNI + MetalLB) scenario. +verify_cni: cilium +verify_lb: metallb +verify_lb_ip_range: + - 192.168.30.110-192.168.30.119 diff --git a/molecule/default/verify-vars.yml b/molecule/default/verify-vars.yml new file mode 100644 index 0000000..d0988c2 --- /dev/null +++ b/molecule/default/verify-vars.yml @@ -0,0 +1,9 @@ +--- +# Durable verify inputs for the default (flannel + MetalLB) scenario. +# These are plain inventory vars linked into the shared Molecule inventory so +# the verify play can see them even though the converge play's set_fact values +# are not persisted between the two Ansible processes. +verify_cni: flannel +verify_lb: metallb +verify_lb_ip_range: + - 192.168.30.80-192.168.30.90 diff --git a/molecule/ipv6/verify-vars.yml b/molecule/ipv6/verify-vars.yml new file mode 100644 index 0000000..868654b --- /dev/null +++ b/molecule/ipv6/verify-vars.yml @@ -0,0 +1,7 @@ +--- +# Durable verify inputs for the ipv6 (flannel CNI + MetalLB) scenario. +verify_cni: flannel +verify_lb: metallb +verify_lb_ip_range: + - fdad:bad:ba55::1b:0/112 + - 192.168.123.80-192.168.123.90 diff --git a/molecule/kube-vip/verify-vars.yml b/molecule/kube-vip/verify-vars.yml new file mode 100644 index 0000000..b693ece --- /dev/null +++ b/molecule/kube-vip/verify-vars.yml @@ -0,0 +1,9 @@ +--- +# Durable verify inputs for the kube-vip (flannel CNI + kube-vip LB) scenario. +verify_cni: flannel +verify_lb: kube-vip +# The kube-vip cloud provider tag is not defined in the linked sample group +# vars (its sample entry is commented out), so it is supplied here. +verify_kube_vip_cloud_provider_tag: v0.0.12 +verify_lb_ip_range: + - 192.168.30.110-192.168.30.119 diff --git a/molecule/resources/verify.yml b/molecule/resources/verify.yml index ef7ea52..d56be84 100644 --- a/molecule/resources/verify.yml +++ b/molecule/resources/verify.yml @@ -1,5 +1,8 @@ --- - name: Verify hosts: all + vars_files: + - >- + {{ lookup("ansible.builtin.env", "MOLECULE_SCENARIO_DIRECTORY") }}/verify-vars.yml roles: - verify_from_outside diff --git a/molecule/resources/verify_from_outside/tasks/test/deploy-example.yml b/molecule/resources/verify_from_outside/tasks/test/deploy-example.yml index 13104ca..8a890d5 100644 --- a/molecule/resources/verify_from_outside/tasks/test/deploy-example.yml +++ b/molecule/resources/verify_from_outside/tasks/test/deploy-example.yml @@ -50,20 +50,24 @@ success_msg: "LoadBalancer address {{ ip_value }} is in the configured range" fail_msg: >- LoadBalancer address {{ ip_value }} is not in the configured - range {{ lb_range }} + range {{ verify_lb_ip_range }} vars: ip_value: >- {{ nginx_services.resources[0].status.loadBalancer.ingress[0].ip }} - lb_range: >- - {{ kube_vip_lb_ip_range | default(metal_lb_ip_range, true) }} - lb_int: "{{ ip_value | ansible.utils.ipaddr('int') }}" - start_int: "{{ lb_range.split('-')[0] | ansible.utils.ipaddr('int') }}" - end_int: "{{ lb_range.split('-')[1] | ansible.utils.ipaddr('int') }}" + lb_first: "{{ verify_lb_ip_range[0] | default('') }}" + lb_is_range: "{{ '-' in lb_first and '/' not in lb_first }}" + lb_start: "{{ lb_first.split('-')[0] | default(ip_value) }}" + lb_end: "{{ lb_first.split('-')[1] | default(ip_value) }}" + # Strict membership is checked only when the first range entry is a + # start-end pair. CIDR entries (used by the ipv6 scenario) fall back to + # the reachability probe above. lb_addr_in_range: >- {{ - (lb_range.split('-') | length == 2) and - (lb_int >= start_int) and - (lb_int <= end_int) + (verify_lb_ip_range | length > 0) and + ((not lb_is_range) or ( + (ip_value | ansible.utils.ipaddr('int') >= lb_start | ansible.utils.ipaddr('int')) and + (ip_value | ansible.utils.ipaddr('int') <= lb_end | ansible.utils.ipaddr('int')) + )) }} # Deactivated linter rules: # - jinja[invalid]: As of version 6.6.0, ansible-lint complains that the input to ipwrap diff --git a/molecule/resources/verify_from_outside/tasks/test/verify-components.yml b/molecule/resources/verify_from_outside/tasks/test/verify-components.yml index 3572b80..d5279b5 100644 --- a/molecule/resources/verify_from_outside/tasks/test/verify-components.yml +++ b/molecule/resources/verify_from_outside/tasks/test/verify-components.yml @@ -1,7 +1,9 @@ --- # Scenario-aware verification of cluster components and their live image tags. -# Branches on the active CNI (flannel / calico / cilium) and load balancer -# (MetalLB / kube-vip) using the same variable names the install roles use. +# Scenario identity (verify_cni / verify_lb) and expected address range come +# from each scenario's verify-vars.yml, which is plain inventory data available +# to the verify play. Converge-time set_fact values are not persisted between +# the two Ansible processes, so they are never used here. - name: Verify cluster components report expected versions block: - name: Get all nodes with their kubelet versions @@ -23,7 +25,7 @@ label: "{{ item.metadata.name }}" - name: Verify Flannel is the active CNI - when: cilium_iface is not defined and calico_iface is not defined + when: verify_cni == 'flannel' block: - name: Get the Flannel DaemonSet kubernetes.core.k8s_info: @@ -42,8 +44,24 @@ success_msg: "Flannel DaemonSet is Ready" fail_msg: "Flannel DaemonSet is not fully Ready" + - name: Verify there is no incompatible CNI with Flannel + when: verify_cni == 'flannel' + block: + - name: Get any Calico namespaces with Flannel enabled + kubernetes.core.k8s_info: + kind: Namespace + name: calico-system + kubeconfig: "{{ kubecfg_path }}" + register: calico_absent + + - name: Assert there is no Calico system namespace + ansible.builtin.assert: + that: calico_absent.resources | length == 0 + success_msg: "No Calico present with Flannel" + fail_msg: "A Calico namespace exists alongside Flannel" + - name: Verify Calico is the active CNI - when: calico_iface is defined + when: verify_cni == 'calico' block: - name: Get the Calico node DaemonSet image kubernetes.core.k8s_info: @@ -97,6 +115,7 @@ namespace: kube-flannel kubeconfig: "{{ kubecfg_path }}" register: no_flannel_ds + - name: Assert there are no Flannel DaemonSets ansible.builtin.assert: that: no_flannel_ds.resources | length == 0 @@ -104,7 +123,7 @@ fail_msg: "A Flannel DaemonSet exists alongside Calico" - name: Verify Cilium is the active CNI - when: cilium_iface is defined + when: verify_cni == 'cilium' block: - name: Get the Cilium agent and operator images kubernetes.core.k8s_info: @@ -179,6 +198,7 @@ namespace: kube-flannel kubeconfig: "{{ kubecfg_path }}" register: no_flannel_ds_cilium + - name: Assert there are no Flannel DaemonSets ansible.builtin.assert: that: no_flannel_ds_cilium.resources | length == 0 @@ -186,7 +206,7 @@ fail_msg: "A Flannel DaemonSet exists alongside Cilium" - name: Verify MetalLB is the active load balancer - when: kube_vip_lb_ip_range is not defined + when: verify_lb == 'metallb' block: - name: Get the MetalLB controller and speaker images kubernetes.core.k8s_info: @@ -231,7 +251,7 @@ | list)[0].spec.template.spec.containers[0].image }} - name: Verify kube-vip is the active load balancer - when: kube_vip_lb_ip_range is defined + when: verify_lb == 'kube-vip' block: - name: Get the kube-vip and cloud provider images kubernetes.core.k8s_info: @@ -250,10 +270,10 @@ ansible.builtin.assert: that: - kubevip_image | regex_search(':' ~ kube_vip_tag_version) - - cloud_provider_image | regex_search(kube_vip_cloud_provider_tag_version) + - cloud_provider_image | regex_search(verify_kube_vip_cloud_provider_tag) success_msg: >- kube-vip {{ kube_vip_tag_version }}, - cloud provider {{ kube_vip_cloud_provider_tag_version }} + cloud provider {{ verify_kube_vip_cloud_provider_tag }} fail_msg: >- kube-vip {{ kubevip_image }}, cloud provider {{ cloud_provider_image }} @@ -281,6 +301,7 @@ name: metallb-system kubeconfig: "{{ kubecfg_path }}" register: metallb_absent + - name: Assert the MetalLB namespace does not exist ansible.builtin.assert: that: metallb_absent.resources | length == 0 diff --git a/molecule/single_node/verify-vars.yml b/molecule/single_node/verify-vars.yml new file mode 100644 index 0000000..55b794b --- /dev/null +++ b/molecule/single_node/verify-vars.yml @@ -0,0 +1,6 @@ +--- +# Durable verify inputs for the single_node (flannel + MetalLB) scenario. +verify_cni: flannel +verify_lb: metallb +verify_lb_ip_range: + - 192.168.30.91-192.168.30.99 diff --git a/roles/k3s_server/tasks/metallb.yml b/roles/k3s_server/tasks/metallb.yml index ec4de88..4dfb72f 100644 --- a/roles/k3s_server/tasks/metallb.yml +++ b/roles/k3s_server/tasks/metallb.yml @@ -29,19 +29,26 @@ label: "{{ item.change }} => {{ item.to }}" when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] -- name: Read back MetalLB manifest to verify the speaker image tag +- name: Read back MetalLB manifest from first master + ansible.builtin.slurp: + src: /var/lib/rancher/k3s/server/manifests/metallb-crds.yaml + register: metallb_manifest + when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] + +- name: Check the MetalLB speaker image reference is present ansible.builtin.set_fact: metallb_manifest_has_speaker_tag: >- {{ - metal_lb_speaker_tag_version in - lookup('ansible.builtin.file', '/var/lib/rancher/k3s/server/manifests/metallb-crds.yaml') + ('quay.io/metallb/speaker:' ~ metal_lb_speaker_tag_version) in + (metallb_manifest.content | default('') | b64decode) }} when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] - name: Fail if MetalLB speaker tag was not applied to the manifest ansible.builtin.fail: msg: >- - MetalLB speaker image tag {{ metal_lb_speaker_tag_version }} + MetalLB speaker image reference + quay.io/metallb/speaker:{{ metal_lb_speaker_tag_version }} was not found in the downloaded manifest. The upstream image reference may have changed. when: diff --git a/roles/k3s_server/templates/vip.yaml.j2 b/roles/k3s_server/templates/vip.yaml.j2 index a3a2fe3..39e16de 100644 --- a/roles/k3s_server/templates/vip.yaml.j2 +++ b/roles/k3s_server/templates/vip.yaml.j2 @@ -36,7 +36,7 @@ spec: - name: vip_interface value: {{ kube_vip_iface }} {% endif %} - - name: vip_cidr + - name: vip_subnet value: "{{ apiserver_endpoint | ansible.utils.ipsubnet | ansible.utils.ipaddr('prefix') }}" - name: cp_enable value: "true" @@ -62,7 +62,7 @@ spec: value: "{{ kube_vip_bgp_routerid }}" {% endif %} {% if _kube_vip_bgp_peers | length > 0 %} - - name: bgppeers + - name: bgp_peers value: "{{ _kube_vip_bgp_peers | map(attribute='peer_address') | zip(_kube_vip_bgp_peers | map(attribute='peer_asn')) | map('join', ':') | join(',') }}" # yamllint disable-line rule:line-length {% else %} {% if kube_vip_bgp_as is defined %} diff --git a/roles/k3s_server_post/meta/main.yml b/roles/k3s_server_post/meta/main.yml index 6497c44..93c2cfb 100644 --- a/roles/k3s_server_post/meta/main.yml +++ b/roles/k3s_server_post/meta/main.yml @@ -93,10 +93,14 @@ argument_specs: default: ~ cilium_mode: - description: Inner-node communication mode + description: + - Inner-node communication mode. + - Cilium accepts `native` or `tunnel`. `routed` is accepted as a + legacy alias and is mapped to `tunnel` at install time. default: native choices: - native + - tunnel - routed cilium_tag: diff --git a/roles/k3s_server_post/tasks/calico.yml b/roles/k3s_server_post/tasks/calico.yml index 1dac1dc..5cc932e 100644 --- a/roles/k3s_server_post/tasks/calico.yml +++ b/roles/k3s_server_post/tasks/calico.yml @@ -37,7 +37,7 @@ 'created' in apply_crds.stdout or 'configured' in apply_crds.stdout or 'applied' in apply_crds.stdout - failed_when: "'Error' in apply_crds.stderr" + failed_when: apply_crds.rc != 0 - name: Apply Tigera Operator manifest idempotently with server-side apply ansible.builtin.command: >- @@ -49,11 +49,12 @@ 'created' in apply_operator.stdout or 'configured' in apply_operator.stdout or 'applied' in apply_operator.stdout - failed_when: "'Error' in apply_operator.stderr" + failed_when: apply_operator.rc != 0 - name: Wait for Tigera Operator and managed CRDs to become available ansible.builtin.command: >- {{ k3s_kubectl_binary | default('k3s kubectl') }} wait {{ item.type }}/{{ item.name }} + {% if item.namespace is defined %}--namespace='{{ item.namespace }}'{% endif %} --for=condition={{ item.condition }} --timeout=30s register: tigera_result @@ -62,7 +63,7 @@ retries: 7 delay: 7 with_items: - - { name: tigera-operator, type: deployment, condition: Available=True } + - { name: tigera-operator, type: deployment, namespace: tigera-operator, condition: Available=True } - { name: installations.operator.tigera.io, type: crd, condition: Established } - { name: apiservers.operator.tigera.io, type: crd, condition: Established } loop_control: @@ -85,7 +86,7 @@ 'configured' in apply_cr.stdout or 'created' in apply_cr.stdout or 'unchanged' in apply_cr.stdout - failed_when: "'Error' in apply_cr.stderr" + failed_when: apply_cr.rc != 0 - name: Wait for Calico system resources to be available ansible.builtin.command: >- diff --git a/roles/k3s_server_post/tasks/cilium.yml b/roles/k3s_server_post/tasks/cilium.yml index 735273a..9b7f1ea 100644 --- a/roles/k3s_server_post/tasks/cilium.yml +++ b/roles/k3s_server_post/tasks/cilium.yml @@ -143,6 +143,13 @@ Target Cilium version: {{ cilium_tag }}, Update needed: {{ cilium_needs_update }} + - name: Map the legacy routed mode to Cilium tunnel mode + ansible.builtin.set_fact: + # Cilium 1.20 accepts `native` or `tunnel`. `routed` was the name used + # by older releases and is kept only as a backward-compatible alias. + cilium_routing_mode: >- + {{ 'tunnel' if cilium_mode == 'routed' else cilium_mode }} + - name: Install Cilium ansible.builtin.command: >- {% if cilium_installed.rc != 0 %} @@ -154,13 +161,13 @@ --helm-set operator.replicas="1" {{ '--helm-set devices=' + cilium_iface if cilium_iface != 'auto' else '' }} --helm-set ipam.operator.clusterPoolIPv4PodCIDRList={{ cluster_cidr }} - {% if cilium_mode == "native" or (cilium_bgp and cilium_exportPodCIDR != 'false') %} + {% if cilium_routing_mode == "native" or (cilium_bgp and cilium_exportPodCIDR != 'false') %} --helm-set ipv4NativeRoutingCIDR={{ cluster_cidr }} {% endif %} --helm-set k8sServiceHost="127.0.0.1" --helm-set k8sServicePort="6444" - --helm-set routingMode={{ cilium_mode }} - --helm-set autoDirectNodeRoutes={{ "true" if cilium_mode == "native" else "false" }} + --helm-set routingMode={{ cilium_routing_mode }} + --helm-set autoDirectNodeRoutes={{ "true" if cilium_routing_mode == "native" else "false" }} --helm-set kubeProxyReplacement={{ kube_proxy_replacement }} --helm-set bpf.masquerade={{ enable_bpf_masquerade }} --helm-set bgpControlPlane.enabled={{ cilium_bgp | default("false") }} @@ -236,7 +243,7 @@ apply --dry-run=server -f /tmp/k3s/cilium-bgp.yaml register: preflight_cr changed_when: false - failed_when: "'error' in preflight_cr.stderr | lower" + failed_when: preflight_cr.rc != 0 - name: Apply BGP manifests ansible.builtin.command: >- @@ -244,7 +251,7 @@ apply -f /tmp/k3s/cilium-bgp.yaml register: apply_cr changed_when: "'configured' in apply_cr.stdout or 'created' in apply_cr.stdout" - failed_when: "'is invalid' in apply_cr.stderr" + failed_when: apply_cr.rc != 0 - name: Remove deprecated CiliumBGPPeeringPolicy after v2 resources are accepted ansible.builtin.command: >- @@ -252,8 +259,9 @@ delete CiliumBGPPeeringPolicy.cilium.io 01-bgp-peering-policy register: delete_old_policy changed_when: "'deleted' in delete_old_policy.stdout" + # The policy (and possibly its CRD) may already be absent; this is + # intentionally tolerated, not a command whose failure must be hidden. failed_when: false - ignore_errors: true - name: Test for BGP config resources ansible.builtin.command: "{{ item }}"