Files
K3S/playbooks/remove-node.yml
Sergey Antropoff a94039e0f1 feat: аддоны через addons.yml, внешний etcd, управление etcd нодами
## Аддоны (group_vars/all/addons.yml)

- Создан group_vars/all/addons.yml — единое место для включения/отключения
  аддонов (addon_ingress_nginx: true/false и т.д.) и их основных настроек
- Из group_vars/all/main.yml убраны все секции аддонов (NFS, CSI, ingress,
  cert-manager, etcd backup, Istio, Prometheus) — остался только core кластер
- Создан playbooks/addons.yml — комбинированный плейбук с 10 плеями,
  каждый с `when: addon_X | default(false) | bool`; запускает только включённые
- make install-full: core (site.yml) + аддоны по addons.yml
- make install-addons: только аддоны без переустановки core
- Убраны все *_enabled флаги из аддонов (cert_manager_enabled, istio_enabled,
  prometheus_stack_enabled и др.) — аддон ставится явным вызовом
- kube-vip: убран skip guard и kube_vip_enabled флаг (core, всегда ставится)
- TLS defaults в argocd/longhorn/kubernetes-dashboard: убрана зависимость
  от cert_manager_enabled, теперь просто false (задаётся явно)
- Kiali: убрана зависимость от prometheus_stack_enabled, добавлены переменные
  kiali_prometheus_enabled/url и kiali_grafana_enabled/url

## Внешний etcd кластер

- Новая переменная k3s_etcd_type: embedded|external в main.yml
- inventory/hosts.ini: добавлена группа [etcd_nodes] — любые серверы,
  не обязательно мастера
- roles/etcd/: полная роль для установки внешнего etcd кластера:
  - install.yml — скачивает бинарник, создаёт пользователя и директории
  - pki.yml — генерирует CA + server/peer/client сертификаты через openssl
    на Ansible-контроллере; раскладывает на etcd ноды и k3s мастера
  - service.yml — разворачивает etcd.env и systemd сервис, проверяет здоровье
  - etcd.env.j2 и etcd.service.j2 — шаблоны конфигурации
  - etcd_pki_local_dir: persistent путь (<project>/etcd-pki/) вместо /tmp,
    etcd-pki/ добавлен в .gitignore
- roles/k3s/templates/k3s-server-config.yaml.j2: при external режиме
  подставляет datastore-endpoint со всеми etcd нодами + пути к клиентским
  сертификатам; при embedded — прежняя логика cluster-init
- playbooks/site.yml: условный плей для etcd перед k3s (тег etcd)
- make install-etcd: отдельная команда для развёртывания etcd кластера

## Управление etcd нодами

- playbooks/add-etcd-node.yml: добавить ноду в работающий etcd кластер
  (PKI генерация → install → etcdctl member add → start с state=existing → verify)
- playbooks/remove-etcd-node.yml: безопасно удалить ноду из etcd кластера
  (проверка кворума → member remove → stop → clean up PKI)
- playbooks/add-node.yml: при k3s_etcd_type=external и наличии ноды в
  [etcd_nodes] автоматически добавляет её в etcd кластер после k3s
- playbooks/remove-node.yml: при k3s_etcd_type=external сначала удаляет
  ноду из etcd (member remove + stop), затем из k3s
- make add-etcd-node NODE=etcd04 / make remove-etcd-node NODE=etcd04
- Команды add-etcd-node / remove-etcd-node в docker/entrypoint.sh
2026-04-25 06:34:48 +03:00

200 lines
7.7 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
# ─────────────────────────────────────────────────────────────────────────────
# Безопасное удаление ноды из кластера
#
# Использование:
# 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=<nodename>"
- 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 %}