mirror of
https://github.com/techno-tim/k3s-ansible.git
synced 2026-08-08 23:13:19 +02:00
c82f2e0415
* 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
355 lines
15 KiB
YAML
355 lines
15 KiB
YAML
---
|
|
# Scenario-aware verification of cluster components and their live image tags.
|
|
# 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
|
|
kubernetes.core.k8s_info:
|
|
kind: node
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: verify_nodes
|
|
|
|
- name: Assert each node reports the expected Kubernetes version
|
|
ansible.builtin.assert:
|
|
that: item.status.nodeInfo.kubeletVersion == k3s_version
|
|
success_msg: "{{ item.metadata.name }} reports {{ k3s_version }}"
|
|
fail_msg: >-
|
|
{{ item.metadata.name }} reports
|
|
{{ item.status.nodeInfo.kubeletVersion }},
|
|
expected {{ k3s_version }}
|
|
loop: "{{ verify_nodes.resources }}"
|
|
loop_control:
|
|
label: "{{ item.metadata.name }}"
|
|
|
|
- name: Verify Flannel is the active CNI
|
|
when: verify_cni == 'flannel'
|
|
block:
|
|
- name: Assert every node reports Ready
|
|
ansible.builtin.assert:
|
|
that: item.status.conditions
|
|
| selectattr('type', 'equalto', 'Ready')
|
|
| map(attribute='status') | first | default('') == 'True'
|
|
success_msg: "{{ item.metadata.name }} is Ready"
|
|
fail_msg: "{{ item.metadata.name }} is not Ready"
|
|
loop: "{{ verify_nodes.resources }}"
|
|
loop_control:
|
|
label: "{{ item.metadata.name }} ready"
|
|
|
|
- name: Assert every node registered a node IP from its interface
|
|
# Each k3s node is launched with --node-ip derived from flannel_iface.
|
|
# Confirm every node carries a real InternalIP (not a loopback), which
|
|
# proves k3s bound to the cluster interface rather than defaulting to 127.0.0.1.
|
|
ansible.builtin.assert:
|
|
that: >-
|
|
(node_internal_ips | length) >= 1 and
|
|
(node_internal_ips | reject('eq', '127.0.0.1') | list | length) == node_internal_ips | length
|
|
success_msg: "{{ item.metadata.name }} is bound to {{ node_internal_ips | join(', ') }}"
|
|
fail_msg: >-
|
|
{{ item.metadata.name }} has no non-loopback InternalIP
|
|
(got: {{ node_internal_ips | join(', ') }})
|
|
vars:
|
|
node_internal_ips: >-
|
|
{{
|
|
(item.status.addresses | default([]))
|
|
| selectattr('type', 'equalto', 'InternalIP')
|
|
| map(attribute='address')
|
|
| list
|
|
}}
|
|
loop: "{{ verify_nodes.resources }}"
|
|
loop_control:
|
|
label: "{{ item.metadata.name }} InternalIP"
|
|
|
|
- name: Get any Calico namespaces with Flannel enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: Namespace
|
|
name: calico-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: flannel_calico_absent
|
|
|
|
- name: Assert there is no Calico system namespace
|
|
ansible.builtin.assert:
|
|
that: flannel_calico_absent.resources | length == 0
|
|
success_msg: "No Calico present with Flannel"
|
|
fail_msg: "A Calico namespace exists alongside Flannel"
|
|
|
|
- name: Get the Cilium namespace with Flannel enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: Namespace
|
|
name: cilium
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: flannel_cilium
|
|
|
|
- name: Assert the Cilium namespace is absent
|
|
ansible.builtin.assert:
|
|
that: flannel_cilium.resources | length == 0
|
|
success_msg: "No Cilium present with Flannel"
|
|
fail_msg: "A Cilium namespace exists alongside Flannel"
|
|
|
|
- name: Verify Calico is the active CNI
|
|
when: verify_cni == 'calico'
|
|
block:
|
|
- name: Get the Calico node DaemonSet image
|
|
kubernetes.core.k8s_info:
|
|
kind: DaemonSet
|
|
name: calico-node
|
|
namespace: calico-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: calico_node_ds
|
|
|
|
- name: Assert the Calico node image uses the expected tag
|
|
ansible.builtin.assert:
|
|
that:
|
|
- calico_node_ds.resources | length == 1
|
|
- calico_node_image | regex_search(':' ~ calico_tag) is not none
|
|
success_msg: "Calico node image uses tag {{ calico_tag }}"
|
|
fail_msg: >-
|
|
Calico node image {{ calico_node_image }},
|
|
expected {{ calico_tag }}
|
|
vars:
|
|
calico_node_image: "{{ calico_node_ds.resources[0].spec.template.spec.containers[0].image }}"
|
|
|
|
- name: Get Calico TigeraStatus for calico and apiserver
|
|
kubernetes.core.k8s_info:
|
|
api_version: operator.tigera.io/v1
|
|
kind: TigeraStatus
|
|
name: "{{ item }}"
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: calico_tigerastatus
|
|
loop:
|
|
- calico
|
|
- apiserver
|
|
loop_control:
|
|
label: "Tigerastatus/{{ item }}"
|
|
|
|
- name: Assert Calico TigeraStatus reports Available
|
|
ansible.builtin.assert:
|
|
that: >-
|
|
item.resources | length == 1 and
|
|
(item.resources[0].status.conditions
|
|
| selectattr('type', 'equalto', 'Available')
|
|
| map(attribute='status') | first | default('')) == 'True'
|
|
success_msg: "Tigerastatus {{ item.resources[0].metadata.name }} is Available"
|
|
fail_msg: "Tigerastatus is not Available"
|
|
loop: "{{ calico_tigerastatus.results }}"
|
|
loop_control:
|
|
label: "Tigerastatus Available"
|
|
|
|
- name: Get any Flannel DaemonSets with Calico enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: DaemonSet
|
|
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
|
|
success_msg: "No Flannel DaemonSet present with Calico"
|
|
fail_msg: "A Flannel DaemonSet exists alongside Calico"
|
|
|
|
- name: Verify Cilium is the active CNI
|
|
when: verify_cni == 'cilium'
|
|
block:
|
|
- name: Get the Cilium agent and operator images
|
|
kubernetes.core.k8s_info:
|
|
kind: "{{ item.kind }}"
|
|
name: "{{ item.name }}"
|
|
namespace: kube-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: cilium_info
|
|
loop:
|
|
- { kind: DaemonSet, name: cilium }
|
|
- { kind: Deployment, name: cilium-operator }
|
|
loop_control:
|
|
label: "{{ item.kind }}/{{ item.name }}"
|
|
|
|
- name: Assert Cilium agent and operator use the expected image tag
|
|
ansible.builtin.assert:
|
|
that:
|
|
- cilium_agent_image | regex_search(':' ~ cilium_tag) is not none
|
|
- cilium_operator_image | regex_search(':' ~ cilium_tag) is not none
|
|
success_msg: "Cilium agent and operator use {{ cilium_tag }}"
|
|
fail_msg: >-
|
|
Cilium agent {{ cilium_agent_image }},
|
|
operator {{ cilium_operator_image }},
|
|
expected {{ cilium_tag }}
|
|
vars:
|
|
cilium_agent_image: >-
|
|
{{ (cilium_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'DaemonSet')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
cilium_operator_image: >-
|
|
{{ (cilium_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'Deployment')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
|
|
- name: Get Hubble relay and UI deployments when enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: Deployment
|
|
name: "{{ item }}"
|
|
namespace: kube-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: hubble_info
|
|
loop:
|
|
- hubble-relay
|
|
- hubble-ui
|
|
loop_control:
|
|
label: "Deployment/{{ item }}"
|
|
when: cilium_hubble | bool
|
|
|
|
- name: Assert Hubble components are Ready when enabled
|
|
ansible.builtin.assert:
|
|
that:
|
|
- item.resources | length == 1
|
|
- item.resources[0].status.readyReplicas | default(0) >= 1
|
|
success_msg: "Hubble deployment {{ item.resources[0].metadata.name }} is Ready"
|
|
fail_msg: "Hubble deployment is not Ready"
|
|
loop: "{{ hubble_info.results }}"
|
|
loop_control:
|
|
label: "Hubble deployment"
|
|
when: cilium_hubble | bool
|
|
|
|
- name: Get any Flannel DaemonSets with Cilium enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: DaemonSet
|
|
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
|
|
success_msg: "No Flannel DaemonSet present with Cilium"
|
|
fail_msg: "A Flannel DaemonSet exists alongside Cilium"
|
|
|
|
- name: Verify MetalLB is the active load balancer
|
|
when: verify_lb == 'metallb'
|
|
block:
|
|
- name: Get the MetalLB controller and speaker images
|
|
kubernetes.core.k8s_info:
|
|
kind: "{{ item.kind }}"
|
|
name: "{{ item.name }}"
|
|
namespace: metallb-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: metallb_info
|
|
until: metallb_info.resources | length > 0
|
|
retries: 15
|
|
delay: 10
|
|
loop:
|
|
- { kind: Deployment, name: controller }
|
|
- { kind: DaemonSet, name: speaker }
|
|
loop_control:
|
|
label: "{{ item.kind }}/{{ item.name }}"
|
|
|
|
- name: Fail with a clear message if MetalLB resources are missing
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Did not find {{ item.kind | lower }} {{ item.name }} in
|
|
metallb-system. Expected MetalLB to be deployed in this
|
|
scenario (verify_lb: {{ verify_lb }}).
|
|
when: item.resources | length == 0
|
|
loop: "{{ metallb_info.results }}"
|
|
loop_control:
|
|
label: "{{ item.item.kind }}/{{ item.item.name }}"
|
|
|
|
- name: Assert MetalLB controller and speaker use the expected image tags
|
|
ansible.builtin.assert:
|
|
# regex_search returns a string or none; check for a match with `is not
|
|
# none` so the assertion is a real boolean (ansible-core 2.19 rejects
|
|
# string conditionals and `| bool` deprecates string coercion).
|
|
that:
|
|
- controller_image | regex_search(metal_lb_controller_tag_version) is not none
|
|
- speaker_image | regex_search(metal_lb_speaker_tag_version) is not none
|
|
success_msg: >-
|
|
MetalLB controller {{ metal_lb_controller_tag_version }},
|
|
speaker {{ metal_lb_speaker_tag_version }}
|
|
fail_msg: >-
|
|
MetalLB controller {{ controller_image }},
|
|
speaker {{ speaker_image }}
|
|
vars:
|
|
controller_image: >-
|
|
{{ (metallb_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'Deployment')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
speaker_image: >-
|
|
{{ (metallb_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'DaemonSet')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
|
|
- name: Verify kube-vip is the active load balancer
|
|
when: verify_lb == 'kube-vip'
|
|
block:
|
|
- name: Get the kube-vip and cloud provider images
|
|
kubernetes.core.k8s_info:
|
|
kind: "{{ item.kind }}"
|
|
name: "{{ item.name }}"
|
|
namespace: kube-system
|
|
kubeconfig: "{{ kubecfg_path }}"
|
|
register: kubevip_info
|
|
loop:
|
|
- { kind: DaemonSet, name: kube-vip-ds }
|
|
- { kind: Deployment, name: kube-vip-cloud-provider }
|
|
loop_control:
|
|
label: "{{ item.kind }}/{{ item.name }}"
|
|
|
|
- name: Assert the kube-vip and cloud provider image tags
|
|
ansible.builtin.assert:
|
|
that:
|
|
- kubevip_image | regex_search(':' ~ kube_vip_tag_version) is not none
|
|
- cloud_provider_image | regex_search(verify_kube_vip_cloud_provider_tag) is not none
|
|
success_msg: >-
|
|
kube-vip {{ kube_vip_tag_version }},
|
|
cloud provider {{ verify_kube_vip_cloud_provider_tag }}
|
|
fail_msg: >-
|
|
kube-vip {{ kubevip_image }},
|
|
cloud provider {{ cloud_provider_image }}
|
|
vars:
|
|
kubevip_image: >-
|
|
{{ (kubevip_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'DaemonSet')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
cloud_provider_image: >-
|
|
{{ (kubevip_info.results
|
|
| selectattr('resources', 'defined')
|
|
| map(attribute='resources')
|
|
| list
|
|
| map(attribute='0')
|
|
| selectattr('kind', 'equalto', 'Deployment')
|
|
| list)[0].spec.template.spec.containers[0].image }}
|
|
|
|
- name: Get the MetalLB namespace with kube-vip enabled
|
|
kubernetes.core.k8s_info:
|
|
kind: Namespace
|
|
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
|
|
success_msg: "MetalLB is not installed with kube-vip"
|
|
fail_msg: "MetalLB namespace exists alongside kube-vip"
|