--- # ───────────────────────────────────────────────────────────────────────────── # Безопасное удаление ноды из кластера # # Использование: # make remove-node NODE=worker04 # make remove-node NODE=master04 (ВНИМАНИЕ: теряется один etcd участник) # # Порядок: # 1. Cordon — запрещает планирование новых подов # 2. Drain — вытесняет все поды на другие ноды # 3. Delete — удаляет ноду из k8s API # 4. Uninstall — удаляет k3s с ноды # # После удаления мастера рекомендуется добавить новый: make add-node NODE=... # ───────────────────────────────────────────────────────────────────────────── # ── Удаляем из etcd кластера ПЕРЕД удалением из k3s ───────────────────────── - name: Remove node from external etcd cluster (if applicable) hosts: "{{ (groups['etcd_nodes'] | default([]) | reject('equalto', node_to_remove | default('')) | list)[0] | default('') }}" gather_facts: false become: true when: - k3s_etcd_type | default('embedded') == 'external' - groups['etcd_nodes'] is defined - node_to_remove is defined - node_to_remove in groups['etcd_nodes'] - (groups['etcd_nodes'] | reject('equalto', node_to_remove) | list) | length > 0 tags: [etcd] tasks: - name: Warn about etcd membership removal ansible.builtin.debug: msg: > Нода {{ node_to_remove }} является участником внешнего etcd кластера. Удаляю из etcd перед удалением из k3s. - name: Get member ID ansible.builtin.shell: | ETCDCTL_API=3 etcdctl \ --endpoints="https://{{ ansible_host }}:{{ etcd_client_port }}" \ --cacert="{{ etcd_pki_dir }}/ca.crt" \ --cert="{{ etcd_pki_dir }}/server.crt" \ --key="{{ etcd_pki_dir }}/server.key" \ member list -w json \ | python3 -c " import json, sys data = json.load(sys.stdin) for m in data['members']: if m.get('name') == '{{ node_to_remove }}': print(hex(m['ID'])) sys.exit(0) sys.exit(1) " register: _etcd_member_id changed_when: false failed_when: _etcd_member_id.rc != 0 - name: Remove member from etcd ansible.builtin.shell: | ETCDCTL_API=3 etcdctl \ --endpoints="https://{{ ansible_host }}:{{ etcd_client_port }}" \ --cacert="{{ etcd_pki_dir }}/ca.crt" \ --cert="{{ etcd_pki_dir }}/server.crt" \ --key="{{ etcd_pki_dir }}/server.key" \ member remove {{ _etcd_member_id.stdout }} changed_when: true - name: Validate and prepare hosts: "{{ groups['k3s_master'][0] }}" gather_facts: false become: true tags: [always] tasks: - name: Validate NODE is specified ansible.builtin.assert: that: node_to_remove is defined and node_to_remove | length > 0 fail_msg: "Укажи ноду: make remove-node NODE=" - name: Prevent removing the only/first master ansible.builtin.assert: that: - node_to_remove != groups['k3s_master'][0] fail_msg: > Нельзя удалить первый мастер ({{ groups['k3s_master'][0] }}) — он является точкой инициализации кластера. Перенести роль первого мастера: поменяй порядок нод в inventory и выполни restore. - name: Warn if removing etcd member ansible.builtin.debug: msg: > ВНИМАНИЕ: {{ node_to_remove }} является мастером (etcd участником). После удаления в кластере останется {{ groups['k3s_master'] | length - 1 }} мастер(а). Кворум: {{ (groups['k3s_master'] | length - 1) > (groups['k3s_master'] | length - 1) // 2 }} when: node_to_remove in groups['k3s_master'] - name: Check node exists in cluster ansible.builtin.command: k3s kubectl get node {{ node_to_remove }} register: node_exists changed_when: false failed_when: false - name: Cordon node ansible.builtin.command: k3s kubectl cordon {{ node_to_remove }} changed_when: true when: node_exists.rc == 0 - name: Drain node (evict pods) ansible.builtin.command: > k3s kubectl drain {{ node_to_remove }} --ignore-daemonsets --delete-emptydir-data --timeout=180s --force changed_when: true when: node_exists.rc == 0 register: drain_result failed_when: drain_result.rc != 0 and 'not found' not in drain_result.stderr - name: Delete node from Kubernetes ansible.builtin.command: k3s kubectl delete node {{ node_to_remove }} changed_when: true when: node_exists.rc == 0 - name: Uninstall K3S from removed node hosts: "{{ node_to_remove | default('') }}" gather_facts: false become: true tags: [k3s] tasks: - name: Run K3S server uninstall script ansible.builtin.command: /usr/local/bin/k3s-uninstall.sh failed_when: false changed_when: true when: node_to_remove in groups['k3s_master'] - name: Run K3S agent uninstall script ansible.builtin.command: /usr/local/bin/k3s-agent-uninstall.sh failed_when: false changed_when: true when: - groups['k3s_workers'] is defined - node_to_remove in groups['k3s_workers'] - name: Stop and disable etcd service (if running) ansible.builtin.systemd: name: etcd state: stopped enabled: false failed_when: false when: - k3s_etcd_type | default('embedded') == 'external' - groups['etcd_nodes'] is defined - node_to_remove in groups['etcd_nodes'] - name: Remove etcd data and config (if applicable) ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ etcd_data_dir }}" - "{{ etcd_config_dir }}" failed_when: false when: - k3s_etcd_type | default('embedded') == 'external' - groups['etcd_nodes'] is defined - node_to_remove in groups['etcd_nodes'] - name: Clean up k3s data directory ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ k3s_config_dir }}" - "{{ k3s_data_dir }}" failed_when: false - name: Post-removal verification hosts: "{{ groups['k3s_master'][0] }}" gather_facts: false become: true tags: [verify] tasks: - name: Show remaining nodes ansible.builtin.command: k3s kubectl get nodes -o wide register: remaining_nodes changed_when: false - name: Display cluster nodes ansible.builtin.debug: msg: "{{ remaining_nodes.stdout_lines }}" - name: Summary ansible.builtin.debug: msg: > Нода {{ node_to_remove }} успешно удалена из кластера. Удали её из inventory/hosts.ini. {% if node_to_remove in groups['k3s_master'] %} Рекомендуется добавить новую мастер-ноду: make add-node NODE=<новая-нода> {% endif %}