From ebf9fedf9b56139342ca5b2ba46662a2dc7c5402 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Mon, 17 Nov 2025 10:57:40 +0000 Subject: [PATCH] Remove etcd member by peerURLs (#12690) The way to obtain the IP of a particular member is convoluted and depend on multiple variables. The match is also textual and it's not clear against what we're matching It's also broken for etcd member which are not also Kubernetes nodes, because the "Lookup node IP in kubernetes" task will fail and abort the play. Instead, match against 'peerURLs', which does not need new variable, and use json output. - Add testcase for etcd removal on external etcd --- .../remove-etcd-node/tasks/main.yml | 21 ++----------------- tests/files/ubuntu24-ha-separate-etcd | 2 ++ tests/scripts/testcases_run.sh | 2 +- 3 files changed, 5 insertions(+), 20 deletions(-) create mode 100644 tests/files/ubuntu24-ha-separate-etcd diff --git a/roles/remove-node/remove-etcd-node/tasks/main.yml b/roles/remove-node/remove-etcd-node/tasks/main.yml index d4efed01c..2b8fc24cd 100644 --- a/roles/remove-node/remove-etcd-node/tasks/main.yml +++ b/roles/remove-node/remove-etcd-node/tasks/main.yml @@ -1,14 +1,4 @@ --- -- name: Lookup node IP in kubernetes - command: > - {{ kubectl }} get nodes {{ node }} - -o jsonpath-as-json='{.status.addresses[?(@.type=="InternalIP")].address}' - register: k8s_node_ips - changed_when: false - when: - - groups['kube_control_plane'] | length > 0 - delegate_to: "{{ groups['kube_control_plane'] | first }}" - - name: Remove etcd member from cluster environment: ETCDCTL_API: "3" @@ -19,25 +9,18 @@ delegate_to: "{{ groups['etcd'] | first }}" block: - name: Lookup members infos - command: "{{ bin_dir }}/etcdctl member list" + command: "{{ bin_dir }}/etcdctl member list -w json" register: etcd_members changed_when: false check_mode: false tags: - facts - name: Remove member from cluster - vars: - node_ip: >- - {%- if not ipv4_stack -%} - {{ ip6 if ip6 is defined else (access_ip6 if access_ip6 is defined else (k8s_node_ips.stdout | from_json)[0]) | ansible.utils.ipwrap }} - {%- else -%} - {{ ip if ip is defined else (access_ip if access_ip is defined else (k8s_node_ips.stdout | from_json)[0]) | ansible.utils.ipwrap }} - {%- endif -%} command: argv: - "{{ bin_dir }}/etcdctl" - member - remove - - "{{ ((etcd_members.stdout_lines | select('contains', '//' + node_ip + ':'))[0] | split(','))[0] }}" + - "{{ '%x' | format(((etcd_members.stdout | from_json).members | selectattr('peerURLs.0', '==', etcd_peer_url))[0].ID) }}" register: etcd_removal_output changed_when: "'Removed member' in etcd_removal_output.stdout" diff --git a/tests/files/ubuntu24-ha-separate-etcd b/tests/files/ubuntu24-ha-separate-etcd new file mode 100644 index 000000000..9a1bffe1a --- /dev/null +++ b/tests/files/ubuntu24-ha-separate-etcd @@ -0,0 +1,2 @@ +REMOVE_NODE_CHECK=true +REMOVE_NODE_NAME=etcd[2] diff --git a/tests/scripts/testcases_run.sh b/tests/scripts/testcases_run.sh index a427c550b..fbda81d7d 100755 --- a/tests/scripts/testcases_run.sh +++ b/tests/scripts/testcases_run.sh @@ -120,7 +120,7 @@ run_playbook tests/testcases/100_check-k8s-conformance.yml # Test node removal procedure if [ "${REMOVE_NODE_CHECK}" = "true" ]; then - run_playbook remove-node.yml -e skip_confirmation=yes -e node=${REMOVE_NODE_NAME} + run_playbook remove-node.yml -e skip_confirmation=yes -e node="${REMOVE_NODE_NAME}" fi # Clean up at the end, this is to allow stage1 tests to include cleanup test