Files
k3s-ansible/molecule/resources/verify_from_outside/tasks/test/verify-components.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

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"