From 16e3670dd4e5675cbff819ed06d0ac38c6ec5b3f Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Mon, 17 Nov 2025 10:53:40 +0000 Subject: [PATCH] Remove etcd member by peerURLs (#12691) 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 | 18 ++---------------- tests/files/ubuntu24-ha-separate-etcd | 2 ++ tests/scripts/testcases_run.sh | 2 +- 3 files changed, 5 insertions(+), 17 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 4b845ff6c..2b8fc24cd 100644 --- a/roles/remove-node/remove-etcd-node/tasks/main.yml +++ b/roles/remove-node/remove-etcd-node/tasks/main.yml @@ -1,16 +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 - - ip is not defined - - access_ip is not defined - delegate_to: "{{ groups['kube_control_plane'] | first }}" - - name: Remove etcd member from cluster environment: ETCDCTL_API: "3" @@ -21,20 +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: "{{ ip if ip is defined else (access_ip if access_ip is defined else (k8s_node_ips.stdout | from_json)[0]) }}" 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 7e34d98f5..7ae8f9778 100755 --- a/tests/scripts/testcases_run.sh +++ b/tests/scripts/testcases_run.sh @@ -130,7 +130,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