From 5af06acee120bf9409e4d9885b540c8be6240708 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Tue, 15 Sep 2026 08:55:22 +0000 Subject: [PATCH] fix: ansible 2.19: calico resources final evaluation (#13503) The bgp changes are a bit verbose, because I chose to reproduce the exact semantics of the current code, so that the manifest handed to calicoctl does not contain unset fields (instead of just using defaults value). It would be more readable to just accept default value in the manifests, but might be a problem if there is other writer of the same Calico resources. --- .../kubespray_defaults/defaults/main/main.yml | 3 - roles/network_plugin/calico/tasks/install.yml | 218 +++++++----------- .../calico_defaults/defaults/main.yml | 7 + 3 files changed, 95 insertions(+), 133 deletions(-) diff --git a/roles/kubespray_defaults/defaults/main/main.yml b/roles/kubespray_defaults/defaults/main/main.yml index 0bd09c279..eb9171119 100644 --- a/roles/kubespray_defaults/defaults/main/main.yml +++ b/roles/kubespray_defaults/defaults/main/main.yml @@ -241,9 +241,6 @@ cilium_identity_allocation_mode: crd # https://docs.cilium.io/en/stable/concepts/networking/ipam/ cilium_ipam_mode: cluster-pool -# Determines if calico_rr group exists -peer_with_calico_rr: "{{ 'calico_rr' in groups and groups['calico_rr'] | length > 0 }}" - # Choose data store type for calico: "etcd" or "kdd" (kubernetes datastore) calico_datastore: "kdd" diff --git a/roles/network_plugin/calico/tasks/install.yml b/roles/network_plugin/calico/tasks/install.yml index 8946aba2b..d2992f35e 100644 --- a/roles/network_plugin/calico/tasks/install.yml +++ b/roles/network_plugin/calico/tasks/install.yml @@ -191,44 +191,36 @@ ignore_errors: true changed_when: false - - name: Calico | Set kubespray calico network pool - set_fact: - _calico_pool: > - { - "kind": "IPPool", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "{{ calico_pool_name }}", - }, - "spec": { - "blockSize": {{ calico_pool_blocksize }}, - "cidr": "{{ calico_pool_cidr | default(kube_pods_subnet) }}", - "ipipMode": "{{ calico_ipip_mode }}", - "vxlanMode": "{{ calico_vxlan_mode }}", - "natOutgoing": {{ nat_outgoing | default(false) }} - } - } - - - name: Calico | Process calico network pool - when: - - _calico_pool_cmd is success - block: - - name: Calico | Get current calico network pool blocksize - set_fact: - _calico_blocksize: > - { - "spec": { - "blockSize": {{ (_calico_pool_cmd.stdout | from_json).spec.blockSize }} - } - } - - name: Calico | Merge calico network pool - set_fact: - _calico_pool: "{{ _calico_pool_cmd.stdout | from_json | combine(_calico_pool, _calico_blocksize, recursive=True) }}" - - name: Calico | Configure calico network pool + vars: + _calico_blocksize: "{{ {'spec': {'blockSize': (_calico_pool_cmd.stdout | from_json).spec.blockSize } } }}" + # We need to specify the dict inside jinja because otherwhise the blockSize can become a string + # instead of a int and no amount of | int makes it back into and integer + # TODO: use a normal yaml dict after upgrading to ansible-core 2.19, where the issue does not exist + _calico_pool: + kind: "IPPool" + apiVersion: "projectcalico.org/v3" + metadata: + name: "{{ calico_pool_name }}" + spec: + blockSize: "{{ calico_pool_blocksize }}" + cidr: "{{ calico_pool_cidr | default(kube_pods_subnet) }}" + ipipMode: "{{ calico_ipip_mode }}" + vxlanMode: "{{ calico_vxlan_mode }}" + natOutgoing: "{{ nat_outgoing | default(false) }}" + _calico_pool_merged: "{{ ( + (_calico_pool_cmd.stdout | from_json) + if _calico_pool_cmd is success else {} + ) + | combine( + _calico_pool, + _calico_blocksize if _calico_pool_cmd is success else {}, + recursive=True) }}" + # override order is current state < config < blocksize + # because blocksize is immutable, so we always reuse it command: cmd: "{{ bin_dir }}/calicoctl.sh apply -f -" - stdin: "{{ _calico_pool is string | ternary(_calico_pool, _calico_pool | to_json) }}" + stdin: "{{ _calico_pool_merged | to_json }}" changed_when: false - name: Calico | Configure Calico IPv6 Pool @@ -242,66 +234,38 @@ ignore_errors: true changed_when: false - - name: Calico | Set kubespray calico network pool - set_fact: - _calico_pool_ipv6: > - { - "kind": "IPPool", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "{{ calico_pool_name }}-ipv6", - }, - "spec": { - "blockSize": {{ calico_pool_blocksize_ipv6 }}, - "cidr": "{{ calico_pool_cidr_ipv6 | default(kube_pods_subnet_ipv6) }}", - "ipipMode": "{{ calico_ipip_mode_ipv6 }}", - "vxlanMode": "{{ calico_vxlan_mode_ipv6 }}", - "natOutgoing": {{ nat_outgoing_ipv6 | default(false) }} - } - } - - - name: Calico | Process calico ipv6 network pool - when: - - _calico_pool_ipv6_cmd is success - block: - - name: Calico | Get current calico ipv6 network pool blocksize - set_fact: - _calico_blocksize_ipv6: > - { - "spec": { - "blockSize": {{ (_calico_pool_ipv6_cmd.stdout | from_json).spec.blockSize }} - } - } - - name: Calico | Merge calico ipv6 network pool - set_fact: - _calico_pool_ipv6: "{{ _calico_pool_ipv6_cmd.stdout | from_json | combine(_calico_pool_ipv6, _calico_blocksize_ipv6, recursive=True) }}" - - - name: Calico | Configure calico ipv6 network pool + - name: Calico | Configure calico IPv6 network pool + vars: + _calico_blocksize_ipv6: "{{ {'spec': {'blockSize': (_calico_pool_ipv6_cmd.stdout | from_json).spec.blockSize } } }}" + # We need to specify the dict inside jinja because otherwhise the blockSize can become a string + # instead of a int and no amount of | int makes it back into and integer + # TODO: use a normal yaml dict after upgrading to ansible-core 2.19, where the issue does not exist + _calico_pool_ipv6: + kind: "IPPool" + apiVersion: "projectcalico.org/v3" + metadata: + name: "{{ calico_pool_name }}-ipv6" + spec: + blockSize: "{{ calico_pool_blocksize_ipv6 }}" + cidr: "{{ calico_pool_cidr_ipv6 | default(kube_pods_subnet_ipv6) }}" + ipipMode: "{{ calico_ipip_mode_ipv6 }}" + vxlanMode: "{{ calico_vxlan_mode_ipv6 }}" + natOutgoing: "{{ nat_outgoing_ipv6 | default(false) }}" + _calico_pool_merged_ipv6: "{{ ( + (_calico_pool_ipv6_cmd.stdout | from_json) + if _calico_pool_ipv6_cmd is success else {} + ) + | combine( + _calico_pool_ipv6, + _calico_blocksize_ipv6 if _calico_pool_ipv6_cmd is success else {}, + recursive=True) }}" + # override order is current state < config < blocksize + # because blocksize is immutable, so we always reuse it command: cmd: "{{ bin_dir }}/calicoctl.sh apply -f -" - stdin: "{{ _calico_pool_ipv6 is string | ternary(_calico_pool_ipv6, _calico_pool_ipv6 | to_json) }}" + stdin: "{{ _calico_pool_merged_ipv6 | to_json }}" changed_when: false -- name: Populate Service External IPs - set_fact: - _service_external_ips: "{{ _service_external_ips | default([]) + [{'cidr': item}] }}" - with_items: "{{ calico_advertise_service_external_ips }}" - run_once: true - -- name: Populate Service LoadBalancer IPs - set_fact: - _service_loadbalancer_ips: "{{ _service_loadbalancer_ips | default([]) + [{'cidr': item}] }}" - with_items: "{{ calico_advertise_service_loadbalancer_ips }}" - run_once: true - -- name: "Determine nodeToNodeMesh needed state" - set_fact: - nodeToNodeMeshEnabled: "false" - when: - - peer_with_router | default(false) or peer_with_calico_rr | default(false) - - ('k8s_cluster' in group_names) - run_once: true - - name: Calico | Configure Calico BGP when: - inventory_hostname == groups['kube_control_plane'][0] @@ -312,46 +276,40 @@ ignore_errors: true changed_when: false - - name: Calico | Set kubespray BGP Configuration - set_fact: - # noqa: jinja[spacing] - _bgp_config: > - { - "kind": "BGPConfiguration", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default", - }, - "spec": { - "listenPort": {{ calico_bgp_listen_port }}, - "logSeverityScreen": "Info", - {% if not calico_no_global_as_num | default(false) %}"asNumber": {{ global_as_num }},{% endif %} - "nodeToNodeMeshEnabled": {{ nodeToNodeMeshEnabled | default('true') }} , - {% if calico_advertise_cluster_ips | default(false) %} - "serviceClusterIPs": - {%- if ipv4_stack and ipv6_stack-%} - [{"cidr": "{{ kube_service_addresses }}", "cidr": "{{ kube_service_addresses_ipv6 }}"}], - {%- elif ipv6_stack-%} - [{"cidr": "{{ kube_service_addresses_ipv6 }}"}], - {%- else -%} - [{"cidr": "{{ kube_service_addresses }}"}], - {%- endif -%} - {% endif %} - {% if calico_advertise_service_loadbalancer_ips | length > 0 %}"serviceLoadBalancerIPs": {{ _service_loadbalancer_ips }},{% endif %} - "serviceExternalIPs": {{ _service_external_ips | default([]) }} - } - } - - - name: Calico | Process BGP Configuration - set_fact: - _bgp_config: "{{ _bgp_config_cmd.stdout | from_json | combine(_bgp_config, recursive=True) }}" - when: - - _bgp_config_cmd is success - - name: Calico | Set up BGP Configuration + vars: + _service_ips: + - "{{ kube_service_addresses if ipv4_stack else '' }}" + - "{{ kube_service_addresses_ipv6 if ipv6_stack else '' }}" + _cluster_ips: + spec: + serviceClusterIPs: "{{ _service_ips | select | map('community.general.dict_kv', 'cidr') }}" + _lb_ips: + spec: + serviceLoadBalancerIPs: "{{ calico_advertise_service_loadbalancer_ips | map('community.general.dict_kv', 'cidr') }}" + _as_num: + spec: + asNumber: "{{ global_as_num }}" + _bgp_config_base: + kind: "BGPConfiguration" + apiVersion: "projectcalico.org/v3" + metadata: + name: "default" + spec: + listenPort: "{{ calico_bgp_listen_port }}" + logSeverityScreen: "Info" + nodeToNodeMeshEnabled: "{{ not (peer_with_router or peer_with_calico_rr) }}" + serviceExternalIPs: "{{ calico_advertise_service_external_ips | map('community.general.dict_kv', 'cidr') }}" + _bgp_config_merged: "{{ ((_bgp_config_cmd.stdout | from_json) if _bgp_config_cmd is success else {}) + | combine(_bgp_config_base, + _cluster_ips if calico_advertise_cluster_ips else {}, + _lb_ips if calico_advertise_service_loadbalancer_ips != [] else {}, + _as_num if not calico_no_global_as_num else {}, + recursive=True) + }}" command: cmd: "{{ bin_dir }}/calicoctl.sh apply -f -" - stdin: "{{ _bgp_config is string | ternary(_bgp_config, _bgp_config | to_json) }}" + stdin: "{{ _bgp_config_merged | to_json }}" changed_when: false - name: Calico | Create calico manifests @@ -477,9 +435,9 @@ - name: Calico | Peer with Calico Route Reflector include_tasks: peer_with_calico_rr.yml when: - - peer_with_calico_rr | default(false) + - peer_with_calico_rr - name: Calico | Peer with the router include_tasks: peer_with_router.yml when: - - peer_with_router | default(false) + - peer_with_router diff --git a/roles/network_plugin/calico_defaults/defaults/main.yml b/roles/network_plugin/calico_defaults/defaults/main.yml index bade3978a..3ab5cf933 100644 --- a/roles/network_plugin/calico_defaults/defaults/main.yml +++ b/roles/network_plugin/calico_defaults/defaults/main.yml @@ -32,11 +32,18 @@ calico_cert_dir: /etc/calico/certs # Global as_num (/calico/bgp/v1/global/as_num) global_as_num: "64512" +calico_no_global_as_num: false +# TODO : deprecate and use undef() or another invalid value in global_as_num for the same meaning +peer_with_router: false +peer_with_calico_rr: "{{ groups.get('calico_rr', []) != [] }}" + # You can set MTU value here. If left undefined or empty, it will # not be specified in calico CNI config, so Calico will use built-in # defaults. The value should be a number, not a string. # calico_mtu: 1500 +calico_advertise_cluster_ips: false + # Advertise Service External IPs calico_advertise_service_external_ips: []