## Аддоны (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
207 lines
8.3 KiB
YAML
207 lines
8.3 KiB
YAML
---
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Безопасно удалить ноду из внешнего etcd кластера
|
||
#
|
||
# Использование:
|
||
# make remove-etcd-node NODE=etcd04 — удалить ноду etcd04
|
||
# make remove-etcd-node NODE=master04 — удалить мастер-ноду из etcd участников
|
||
#
|
||
# ВНИМАНИЕ:
|
||
# Нельзя удалить ноду если в кластере меньше 3 участников без потери кворума.
|
||
# После удаления обнови inventory/hosts.ini — убери ноду из [etcd_nodes].
|
||
#
|
||
# Порядок:
|
||
# 1. Валидация (кворум, не последняя нода)
|
||
# 2. etcdctl member remove <id>
|
||
# 3. Остановить etcd на ноде
|
||
# 4. Очистить данные и сертификаты
|
||
# 5. Верификация оставшегося кластера
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
# ── Валидация ─────────────────────────────────────────────────────────────────
|
||
- name: Validate and check quorum
|
||
hosts: "{{ (groups['etcd_nodes'] | reject('equalto', node_to_remove | default('')) | list)[0] | default('localhost') }}"
|
||
gather_facts: false
|
||
become: true
|
||
tags: [always]
|
||
tasks:
|
||
- name: Check node_to_remove is specified
|
||
ansible.builtin.assert:
|
||
that: node_to_remove is defined and node_to_remove | length > 0
|
||
fail_msg: "Укажи ноду: make remove-etcd-node NODE=<nodename>"
|
||
delegate_to: localhost
|
||
become: false
|
||
|
||
- name: Check node is in etcd_nodes group
|
||
ansible.builtin.assert:
|
||
that: node_to_remove in groups['etcd_nodes']
|
||
fail_msg: >
|
||
Нода '{{ node_to_remove }}' не найдена в группе [etcd_nodes].
|
||
Убедись что имя совпадает с inventory.
|
||
delegate_to: localhost
|
||
become: false
|
||
|
||
- name: Check minimum cluster size
|
||
ansible.builtin.assert:
|
||
that: groups['etcd_nodes'] | length > 1
|
||
fail_msg: >
|
||
В кластере только одна нода etcd — удаление невозможно.
|
||
Нельзя оставить кластер без узлов.
|
||
delegate_to: localhost
|
||
become: false
|
||
|
||
- name: Warn about quorum loss risk
|
||
ansible.builtin.debug:
|
||
msg: >
|
||
ВНИМАНИЕ: после удаления в кластере останется {{ groups['etcd_nodes'] | length - 1 }} нод(ы).
|
||
Кворум (большинство): требуется минимум {{ ((groups['etcd_nodes'] | length - 1) // 2 + 1) }} нод.
|
||
{% if (groups['etcd_nodes'] | length - 1) < 2 %}
|
||
ПРЕДУПРЕЖДЕНИЕ: менее 2 нод — кластер потеряет HA!
|
||
{% endif %}
|
||
|
||
- name: Get member ID of node to remove
|
||
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: _member_id
|
||
changed_when: false
|
||
failed_when: _member_id.rc != 0
|
||
|
||
- name: Show member ID
|
||
ansible.builtin.debug:
|
||
msg: "Member ID для {{ node_to_remove }}: {{ _member_id.stdout }}"
|
||
|
||
- name: Remove member from etcd cluster
|
||
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 {{ _member_id.stdout }}
|
||
changed_when: true
|
||
|
||
# ── Останавливаем etcd на удаляемой ноде ─────────────────────────────────────
|
||
- name: Stop etcd on removed node
|
||
hosts: "{{ node_to_remove }}"
|
||
gather_facts: false
|
||
become: true
|
||
tags: [stop]
|
||
tasks:
|
||
- name: Stop and disable etcd service
|
||
ansible.builtin.systemd:
|
||
name: etcd
|
||
state: stopped
|
||
enabled: false
|
||
failed_when: false
|
||
|
||
- name: Remove etcd systemd service file
|
||
ansible.builtin.file:
|
||
path: /etc/systemd/system/etcd.service
|
||
state: absent
|
||
notify: Reload systemd
|
||
|
||
- name: Remove etcd data directory
|
||
ansible.builtin.file:
|
||
path: "{{ etcd_data_dir }}"
|
||
state: absent
|
||
|
||
- name: Remove etcd config and PKI
|
||
ansible.builtin.file:
|
||
path: "{{ etcd_config_dir }}"
|
||
state: absent
|
||
|
||
- name: Remove etcd binaries
|
||
ansible.builtin.file:
|
||
path: "{{ etcd_install_dir }}/{{ item }}"
|
||
state: absent
|
||
loop:
|
||
- etcd
|
||
- etcdctl
|
||
failed_when: false
|
||
|
||
handlers:
|
||
- name: Reload systemd
|
||
ansible.builtin.systemd:
|
||
daemon_reload: true
|
||
|
||
# ── Удаляем локальные сертификаты удалённой ноды ─────────────────────────────
|
||
- name: Clean up local PKI artifacts
|
||
hosts: localhost
|
||
gather_facts: false
|
||
become: false
|
||
tags: [pki]
|
||
tasks:
|
||
- name: Remove node certs from local PKI dir
|
||
ansible.builtin.file:
|
||
path: "{{ etcd_pki_local_dir }}/{{ item }}"
|
||
state: absent
|
||
loop:
|
||
- "server-{{ node_to_remove }}.crt"
|
||
- "server-{{ node_to_remove }}.key"
|
||
- "server-{{ node_to_remove }}.csr"
|
||
- "peer-{{ node_to_remove }}.crt"
|
||
- "peer-{{ node_to_remove }}.key"
|
||
- "peer-{{ node_to_remove }}.csr"
|
||
failed_when: false
|
||
|
||
# ── Верификация ───────────────────────────────────────────────────────────────
|
||
- name: Verify remaining cluster
|
||
hosts: "{{ (groups['etcd_nodes'] | reject('equalto', node_to_remove) | list)[0] }}"
|
||
gather_facts: false
|
||
become: true
|
||
tags: [verify]
|
||
tasks:
|
||
- name: Check cluster health
|
||
ansible.builtin.shell: |
|
||
ETCDCTL_API=3 etcdctl \
|
||
--endpoints="{{ remaining_endpoints }}" \
|
||
--cacert="{{ etcd_pki_dir }}/ca.crt" \
|
||
--cert="{{ etcd_pki_dir }}/server.crt" \
|
||
--key="{{ etcd_pki_dir }}/server.key" \
|
||
endpoint health
|
||
vars:
|
||
remaining_endpoints: >-
|
||
{%- set eps = [] -%}
|
||
{%- for h in groups['etcd_nodes'] | reject('equalto', node_to_remove) | list -%}
|
||
{%- set _ = eps.append('https://' ~ hostvars[h]['ansible_host'] ~ ':' ~ etcd_client_port) -%}
|
||
{%- endfor -%}
|
||
{{ eps | join(',') }}
|
||
register: _health
|
||
changed_when: false
|
||
|
||
- name: Show remaining members
|
||
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 table
|
||
register: _members
|
||
changed_when: false
|
||
|
||
- name: Display remaining members
|
||
ansible.builtin.debug:
|
||
msg: "{{ _members.stdout_lines }}"
|
||
|
||
- name: Summary
|
||
ansible.builtin.debug:
|
||
msg: >
|
||
Нода {{ node_to_remove }} успешно удалена из etcd кластера.
|
||
Осталось нод: {{ groups['etcd_nodes'] | length - 1 }}.
|
||
Удали ноду из [etcd_nodes] в inventory/hosts.ini.
|