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
This commit is contained in:
Sergey Antropoff
2026-04-25 06:34:48 +03:00
parent 8aa55a694c
commit a94039e0f1
30 changed files with 1301 additions and 169 deletions

113
group_vars/all/addons.yml Normal file
View File

@@ -0,0 +1,113 @@
---
# ═══════════════════════════════════════════════════════════════════════════════
# Аддоны кластера — выбери что устанавливать
# make install-full → core + все аддоны у которых true
# make addon-<name> → конкретный аддон напрямую (флаг игнорируется)
# ═══════════════════════════════════════════════════════════════════════════════
addon_nfs_server: false # NFS сервер
addon_csi_nfs: false # CSI NFS Driver + StorageClass
addon_ingress_nginx: true # ingress-nginx (Ingress controller)
addon_cert_manager: false # cert-manager (TLS через Let's Encrypt)
addon_metrics_server: true # metrics-server (kubectl top nodes/pods)
addon_prometheus_stack: false # Prometheus + Grafana + Alertmanager
addon_istio: false # Istio service mesh + Kiali UI
addon_argocd: false # ArgoCD (GitOps)
addon_longhorn: false # Longhorn (distributed block storage)
addon_kubernetes_dashboard: false # Kubernetes Dashboard
# ─── NFS Server ───────────────────────────────────────────────────────────────
nfs_exports:
- path: /srv/nfs/k8s
options: "*(rw,sync,no_subtree_check,no_root_squash)"
nfs_allowed_network: "192.168.1.0/24"
nfs_create_export_dirs: true
nfs_export_dir_mode: "0777"
nfs_export_dir_owner: "nobody"
nfs_export_dir_group: "nogroup"
# ─── CSI NFS Driver ───────────────────────────────────────────────────────────
csi_nfs_version: "v4.8.0"
csi_nfs_namespace: "kube-system"
# IP NFS сервера — по умолчанию берётся первый мастер
# Если NFS на отдельном хосте: csi_nfs_server: "192.168.1.20"
csi_nfs_server: "{{ hostvars[groups['k3s_master'][0]]['ansible_host'] }}"
csi_nfs_share: "/srv/nfs/k8s"
csi_nfs_storageclass_default: true
csi_nfs_reclaim_policy: "Delete" # Delete | Retain
csi_nfs_on_delete: "delete" # delete | retain | archive
csi_nfs_install_client: true
# ─── ingress-nginx ────────────────────────────────────────────────────────────
ingress_nginx_version: "4.10.1"
ingress_nginx_namespace: "ingress-nginx"
# LoadBalancer получит IP от kube-vip
ingress_nginx_service_type: "LoadBalancer"
# Конкретный IP из пула kube-vip (оставь "" для автоматического)
ingress_nginx_load_balancer_ip: ""
ingress_nginx_replica_count: 1
ingress_nginx_use_daemonset: false
ingress_nginx_metrics_enabled: false
ingress_nginx_class_name: "nginx"
ingress_nginx_set_default_class: true
# Кастомная страница ошибок
ingress_nginx_custom_errors_enabled: true
ingress_nginx_error_cluster_name: "K3S Cluster"
ingress_nginx_error_cluster_domain: "" # например: cluster.example.com
ingress_nginx_resources:
requests:
cpu: 100m
memory: 90Mi
limits:
cpu: 500m
memory: 256Mi
ingress_nginx_extra_args: {}
# ─── cert-manager ─────────────────────────────────────────────────────────────
cert_manager_version: "v1.15.3"
cert_manager_namespace: "cert-manager"
# Автоматически создать ClusterIssuer:
# none — не создавать
# selfsigned — самоподписанный CA
# letsencrypt — Let's Encrypt (требует публичный домен)
cert_manager_issuer: "letsencrypt"
# Email для Let's Encrypt (обязательно для letsencrypt)
cert_manager_acme_email: "admin@example.com"
# ─── kube-prometheus-stack ────────────────────────────────────────────────────
# prometheus_stack_version: "60.3.0"
# prometheus_retention_days: 7
# prometheus_storage_size: "10Gi"
# Grafana: логин и пароль — задай в vault.yml:
# vault_grafana_user: "admin"
# vault_grafana_password: "ваш-пароль"
# prometheus_grafana_ingress_enabled: false
# prometheus_grafana_ingress_host: "grafana.example.com"
# ─── Istio ────────────────────────────────────────────────────────────────────
# istio_version: "1.22.2"
# istio_mtls_mode: "STRICT" # STRICT | PERMISSIVE | DISABLE
# istio_install_gateway: true
# Kiali (UI для Istio) — опционально вместе с Istio
# kiali_enabled: false
# kiali_ingress_host: "kiali.example.com"
# ─── etcd backup ──────────────────────────────────────────────────────────────
etcd_backup_dir: "{{ k3s_data_dir }}/server/db/snapshots"
etcd_backup_retention: 5 # сколько снимков хранить
etcd_backup_copy_to_local: false # скопировать на Ansible-хост
etcd_backup_local_dir: "./etcd-backups"

View File

@@ -1,20 +1,12 @@
---
# ═══════════════════════════════════════════════════════════════════════════════
# Включение/отключение компонентов стека
# Поменяй на true/false чтобы установить или пропустить компонент
# ═══════════════════════════════════════════════════════════════════════════════
kube_vip_enabled: true # VIP для control plane + LoadBalancer
nfs_server_enabled: true # NFS сервер на nfs_server хосте
csi_nfs_enabled: true # CSI NFS Driver + StorageClass
ingress_nginx_enabled: true # ingress-nginx (HTTP/S Ingress controller)
cert_manager_enabled: false # cert-manager (TLS сертификаты)
istio_enabled: false # Istio service mesh
kiali_enabled: false # Kiali UI (требует istio_enabled: true)
prometheus_stack_enabled: false # Prometheus + Grafana + Alertmanager
# ─── K3S ──────────────────────────────────────────────────────────────────────
k3s_version: "v1.29.3+k3s1"
# Тип хранилища etcd:
# embedded — встроенный etcd в k3s (HA через Raft, по умолчанию)
# external — внешний etcd кластер (ноды задаются группой [etcd_nodes] в inventory)
k3s_etcd_type: "embedded"
# Токен из vault (создай group_vars/all/vault.yml)
k3s_token: "{{ vault_k3s_token }}"
@@ -96,108 +88,6 @@ kube_vip_version: "v0.8.3"
kube_vip_mode: "arp" # arp (L2) | bgp (L3)
kube_vip_services_enable: true # также обрабатывает LoadBalancer Services
# ─── NFS Server ───────────────────────────────────────────────────────────────
nfs_exports:
- path: /srv/nfs/k8s
options: "*(rw,sync,no_subtree_check,no_root_squash)"
nfs_allowed_network: "192.168.1.0/24"
nfs_create_export_dirs: true
nfs_export_dir_mode: "0777"
nfs_export_dir_owner: "nobody"
nfs_export_dir_group: "nogroup"
# ─── CSI NFS Driver ───────────────────────────────────────────────────────────
csi_nfs_version: "v4.8.0"
csi_nfs_namespace: "kube-system"
# IP NFS сервера — по умолчанию берётся master нода
# Если NFS на отдельном хосте: csi_nfs_server: "192.168.1.20"
csi_nfs_server: "{{ hostvars[groups['k3s_master'][0]]['ansible_host'] }}"
csi_nfs_share: "/srv/nfs/k8s"
# StorageClass именуется по hostname NFS сервера: nfs-master01, nfs-storage01, …
# Переопредели только если нужно другое имя.
csi_nfs_storageclass_default: true
csi_nfs_reclaim_policy: "Delete" # Delete | Retain
csi_nfs_on_delete: "delete" # delete | retain | archive
csi_nfs_install_client: true
# ─── ingress-nginx ────────────────────────────────────────────────────────────
ingress_nginx_version: "4.10.1"
ingress_nginx_namespace: "ingress-nginx"
# LoadBalancer получит IP от kube-vip
ingress_nginx_service_type: "LoadBalancer"
# Конкретный IP из пула kube-vip (оставь "" для автоматического)
ingress_nginx_load_balancer_ip: ""
ingress_nginx_replica_count: 1
ingress_nginx_use_daemonset: false
ingress_nginx_metrics_enabled: false
ingress_nginx_class_name: "nginx"
ingress_nginx_set_default_class: true
# Кастомная страница ошибок — название кластера для отображения на странице ошибки
ingress_nginx_custom_errors_enabled: true
ingress_nginx_error_cluster_name: "K3S Cluster"
ingress_nginx_error_cluster_domain: "" # например: cluster.example.com
ingress_nginx_resources:
requests:
cpu: 100m
memory: 90Mi
limits:
cpu: 500m
memory: 256Mi
ingress_nginx_extra_args: {}
# ─── cert-manager ─────────────────────────────────────────────────────────────
cert_manager_version: "v1.15.3"
cert_manager_namespace: "cert-manager"
# Автоматически создать ClusterIssuer после установки:
# none — не создавать (настрой вручную)
# selfsigned — самоподписанный CA (для внутреннего использования)
# letsencrypt — Let's Encrypt (требует публичный домен)
cert_manager_issuer: "letsencrypt" # none | selfsigned | letsencrypt
# Let's Encrypt (нужен только если cert_manager_issuer: letsencrypt)
# Создаются оба ClusterIssuer: letsencrypt-staging и letsencrypt-prod
# Сертификаты обновляются автоматически за ~30 дней до истечения — вручную ничего не нужно.
cert_manager_acme_email: "admin@example.com"
# ─── etcd backup ──────────────────────────────────────────────────────────────
etcd_backup_dir: "{{ k3s_data_dir }}/server/etcd/snapshots"
etcd_backup_retention: 5 # сколько снимков хранить локально
etcd_backup_copy_to_local: false # скопировать снимок на Ansible-хост
etcd_backup_local_dir: "./etcd-backups"
# ─── Istio (Service Mesh) ──────────────────────────────────────────────────────
# istio_version: "1.22.2"
# istio_mtls_mode: "STRICT" # STRICT | PERMISSIVE | DISABLE
# istio_install_gateway: true
# Kiali (UI для Istio) — требует istio_enabled: true
# Токен задаётся в vault.yml:
# vault_kiali_token: "" # заполни после первой установки (токен выведет Ansible)
# ─── kube-prometheus-stack (Prometheus + Grafana + Alertmanager) ───────────────
# prometheus_stack_version: "60.3.0"
# prometheus_retention_days: 7
# prometheus_storage_size: "10Gi"
# Grafana: логин и пароль — задай в vault.yml:
# vault_grafana_user: "admin"
# vault_grafana_password: "ваш-пароль"
# grafana_admin_user: "{{ vault_grafana_user | default('admin') }}"
# prometheus_grafana_admin_password: "{{ vault_grafana_password | default('admin') }}"
# prometheus_grafana_ingress_enabled: false
# prometheus_grafana_ingress_host: "grafana.local"
# ─── mdadm ────────────────────────────────────────────────────────────────────
# Поиск RAID массива и монтирование в /storage
# Отключить на конкретной ноде: задай mdadm_enabled: false в host_vars/<node>/main.yml