feat: крупное обновление стека — пути, bootstrap, etcd, cert-manager, custom errors, ноды

## Переименование путей (rancher → kubernetes)

- Все пути /var/lib/rancher/k3s → /var/lib/kubernetes/k3s
- Все пути /etc/rancher/k3s   → /etc/kubernetes/k3s
- Добавлены переменные k3s_config_dir, k3s_data_dir, k3s_kubeconfig_path
- K3S install получил --data-dir и K3S_CONFIG_FILE флаги
- k3s-server-config.yaml.j2: добавлены write-kubeconfig и data-dir ключи
- Все роли (csi-nfs, ingress-nginx, cert-manager, prometheus, istio, cni)
  переведены на {{ k3s_kubeconfig_path }} вместо хардкода

## Bootstrap (новое)

- bootstrap.yml — playbook для первоначальной настройки нод
- roles/bootstrap/ — создаёт пользователя ansible, настраивает sudoers,
  деплоит SSH публичный ключ по паролю из vault
- host_vars/*/vault.yml.example — шаблоны с bootstrap_user/bootstrap_password
- make bootstrap, make vault-bootstrap-create NODE=..., make vault-bootstrap-edit NODE=...

## Добавление/удаление нод (новое)

- add-node.yml — добавляет мастер или воркер в существующий кластер через VIP
- remove-node.yml — cordon → drain → delete → uninstall → cleanup
- inventory/hosts.ini: добавлена группа [k3s_workers], обновлён [k3s_cluster:children]
- roles/k3s/tasks/main.yml: install_agent.yml для воркеров

## etcd backup/restore (новое)

- etcd-backup.yml / etcd-restore.yml — top-level playbooks
- roles/etcd/tasks/backup.yml — k3s etcd-snapshot save + retention cleanup
- roles/etcd/tasks/restore.yml — cluster-reset + перезапуск всех нод
- make etcd-backup, make etcd-restore SNAPSHOT=..., make etcd-list-snapshots

## cert-manager addon (новое)

- roles/cert-manager/ — установка через Helm + опциональный ClusterIssuer
- Поддержка: none | selfsigned | letsencrypt
- Шаблоны ClusterIssuer для selfsigned CA и ACME HTTP-01
- Управляется флагом cert_manager_enabled: false

## Custom error backend для ingress-nginx (новое)

- custom-error-page.html.j2 — тёмная Kubernetes-styled страница ошибок
- custom-error-backend.yaml.j2 — ConfigMap + Deployment (nginx) + Service
- nginx использует sub_filter для динамической подстановки X-Code/X-Message
- ingress-nginx Helm values: custom-http-errors, default-backend-service
- Управляется флагом ingress_nginx_custom_errors_enabled: true

## Hostname и пакеты (новое)

- prereqs.yml: установка hostname из inventory_hostname (пропускается в Molecule)
- prereqs.yml: установка k3s_common_packages (nfs-common, mc, htop, vim, wget, и др.)
- molecule_test: true в converge.yml исключает hostname из тестов

## Molecule improvements

- 3 платформы: master01 (Ubuntu 22.04) + worker01 (Ubuntu 22.04) + rpi01 (Debian 12)
- Molecule запускается из Docker контейнера через /var/run/docker.sock (DinD)
- Все пути в converge.yml и verify.yml обновлены под /etc/kubernetes/k3s

## Флаги включения компонентов

- kube_vip_enabled, nfs_server_enabled, csi_nfs_enabled, ingress_nginx_enabled
- cert_manager_enabled, istio_enabled, kiali_enabled, prometheus_stack_enabled
- Каждая роль пропускает установку через meta: end_play при disabled

## Документация

- README полностью переработан: все новые возможности с примерами
- Новые разделы: Управление нодами, etcd backup/restore, cert-manager, bootstrap
- Обновлены Makefile, docker/entrypoint.sh под все новые команды
This commit is contained in:
Sergey Antropoff
2026-04-23 06:32:14 +03:00
parent d9a35478a6
commit 24846d2e52
46 changed files with 1860 additions and 67 deletions

240
README.md
View File

@@ -12,6 +12,8 @@
- [Рабочий процесс](#рабочий-процесс)
- [Настройка кластера](#настройка-кластера)
- [Опциональные компоненты](#опциональные-компоненты)
- [Управление нодами](#управление-нодами)
- [Резервное копирование etcd](#резервное-копирование-etcd)
- [Все команды Make](#все-команды-make)
- [Тестирование через Molecule](#тестирование-через-molecule)
- [Компоненты стека](#компоненты-стека)
@@ -71,6 +73,7 @@
| NFS Server | — | Персистентное хранилище на master01 |
| CSI NFS Driver | v4.8.0 | Динамические PVC через NFS |
| ingress-nginx | chart 4.10.1 | HTTP/S Ingress controller |
| cert-manager | v1.15.3 | TLS сертификаты (опционально) |
| Istio | 1.22.2 | Service mesh (опционально) |
| Kiali | 1.86.0 | UI для Istio, вход по токену (опционально) |
| kube-prometheus-stack | 60.3.0 | Prometheus + Grafana + Alertmanager (опционально) |
@@ -147,6 +150,10 @@ k3s-ansible/
├── upgrade.yml
├── uninstall.yml
├── healthcheck.yml
├── add-node.yml ← Добавить ноду в кластер
├── remove-node.yml ← Удалить ноду из кластера
├── etcd-backup.yml ← Создать снимок etcd
├── etcd-restore.yml ← Восстановить etcd из снимка
├── inventory/
│ └── hosts.ini ← IP и параметры серверов
@@ -180,6 +187,17 @@ k3s-ansible/
├── nfs-server/ ← NFS сервер
├── csi-nfs/ ← CSI Driver + StorageClass
├── ingress-nginx/ ← Ingress controller
├── cert-manager/ ← TLS сертификаты (опционально)
│ ├── tasks/main.yml
│ ├── defaults/main.yml
│ └── templates/
│ ├── clusterissuer-selfsigned.yaml.j2
│ └── clusterissuer-letsencrypt.yaml.j2
├── etcd/ ← Backup/restore задачи etcd
│ ├── tasks/
│ │ ├── backup.yml
│ │ └── restore.yml
│ └── defaults/main.yml
├── istio/ ← Service mesh + Kiali (опционально)
│ ├── tasks/main.yml
@@ -346,7 +364,8 @@ make install
6. NFS server на master01
7. CSI NFS Driver на всех нодах → StorageClass `nfs-master01`
8. ingress-nginx через Helm
9. Финальная проверка: nodes / pods / svc / storageclass
9. cert-manager (только если `cert_manager_enabled: true`)
10. Финальная проверка: nodes / pods / svc / storageclass
Ожидаемое время: **15-25 минут** в зависимости от скорости интернета.
@@ -440,6 +459,11 @@ k3s_disable_traefik: true # ОБЯЗАТЕЛЬНО true при ingress-ngi
# CNI выбирается здесь:
k3s_cni: "flannel" # flannel | calico | cilium
# Пути K3S (изменены с /var/lib/rancher на /var/lib/kubernetes)
k3s_config_dir: /etc/kubernetes/k3s # конфиги и kubeconfig
k3s_data_dir: /var/lib/kubernetes/k3s # данные etcd, токены, манифесты
k3s_kubeconfig_path: "{{ k3s_config_dir }}/k3s.yaml"
```
### CNI
@@ -557,6 +581,77 @@ vault_kiali_token: ""
---
### cert-manager (TLS сертификаты)
Автоматически выдаёт и обновляет TLS сертификаты в кластере. Поддерживает самоподписанные сертификаты (для внутреннего использования) и Let's Encrypt (для публичных доменов).
**Включить:**
```yaml
# group_vars/all/main.yml
cert_manager_enabled: true
```
Или разово:
```bash
make install-cert-manager
```
**Типы ClusterIssuer:**
| `cert_manager_issuer` | Описание |
|---|---|
| `none` | Только установка cert-manager, без ClusterIssuer |
| `selfsigned` | Самоподписанный CA (умолч.) — для внутреннего использования |
| `letsencrypt` | Let's Encrypt ACME — требует публичный домен и ingress-nginx |
**Параметры:**
| Переменная | Умолч. | Описание |
|---|---|---|
| `cert_manager_enabled` | `false` | Включить установку |
| `cert_manager_version` | `v1.15.3` | Версия Helm chart |
| `cert_manager_namespace` | `cert-manager` | Namespace |
| `cert_manager_issuer` | `selfsigned` | Тип ClusterIssuer |
| `cert_manager_acme_email` | `admin@example.com` | Email для Let's Encrypt |
| `cert_manager_acme_server` | `prod` | `prod` или `staging` |
**Использование selfsigned (внутренний CA):**
После установки будут созданы:
- `selfsigned-issuer` — базовый самоподписанный issuer
- `cluster-ca` — Certificate (CA сертификат)
- `cluster-ca-issuer` — ClusterIssuer для выдачи сертификатов приложениям
Пример использования в манифестах:
```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-app-tls
spec:
secretName: my-app-tls-secret
issuerRef:
name: cluster-ca-issuer
kind: ClusterIssuer
dnsNames:
- myapp.local
```
**Использование Let's Encrypt:**
```yaml
cert_manager_issuer: "letsencrypt"
cert_manager_acme_email: "admin@example.com"
cert_manager_acme_server: "prod" # или staging для тестирования
```
Добавить аннотацию на Ingress:
```yaml
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
```
---
### Istio (Service Mesh)
Устанавливает `istio/base` (CRDs) → `istiod` (control plane) → `istio/gateway` (LoadBalancer) → глобальную политику mTLS.
@@ -716,6 +811,119 @@ prometheus_grafana_ingress_host: "grafana.example.com"
---
## Управление нодами
### Добавить ноду
Перед добавлением:
1. Добавь ноду в `inventory/hosts.ini` в нужную группу (`k3s_master` или `k3s_workers`)
2. Убедись что SSH доступ работает: `make ping`
```bash
# Добавить мастер-ноду (станет etcd участником)
make add-node NODE=master04
# Добавить рабочую ноду (только agent, без etcd)
make add-node NODE=worker04
```
Новые ноды подключаются через **VIP** (`kube_vip_address`) — kube-vip должен быть запущен. После добавления нода автоматически появляется в кластере.
Пример `inventory/hosts.ini` с рабочими нодами:
```ini
[k3s_master]
master01 ansible_host=192.168.1.10 ansible_user=ubuntu
worker01 ansible_host=192.168.1.11 ansible_user=ubuntu
rpi01 ansible_host=192.168.1.12 ansible_user=pi
[k3s_workers]
worker04 ansible_host=192.168.1.14 ansible_user=ubuntu
worker05 ansible_host=192.168.1.15 ansible_user=ubuntu
[k3s_cluster:children]
k3s_master
k3s_workers
```
### Удалить ноду
```bash
make remove-node NODE=worker04
```
Порядок удаления:
1. `cordon` — запретить планирование новых подов
2. `drain` — вытеснить все поды на другие ноды (таймаут 180 сек)
3. `delete` — удалить ноду из Kubernetes API
4. Запустить uninstall-скрипт K3S на удалённой ноде
5. Очистить директории K3S
После удаления убери ноду из `inventory/hosts.ini`.
**Ограничения:**
- Нельзя удалить первый мастер (`groups['k3s_master'][0]`) — он является точкой инициализации кластера
- При удалении мастера etcd-кворум уменьшается: 3 → 2 нод. Рекомендуется добавить новый мастер после удаления
---
## Резервное копирование etcd
etcd хранит **всё состояние кластера** — поды, сервисы, конфигмапы, секреты, PVC. Регулярные снимки позволяют восстановить кластер после сбоя.
Снимки хранятся на сервере по пути `{{ k3s_data_dir }}/server/db/snapshots` (по умолчанию `/var/lib/kubernetes/k3s/server/db/snapshots`).
### Создать снимок
```bash
make etcd-backup
```
Создаёт снимок с автоматическим именем `k3s-etcd-<timestamp>.db`, удаляет старые снимки сверх `etcd_backup_retention` (по умолчанию 5).
Опционально — скопировать снимок на локальную машину:
```yaml
# group_vars/all/main.yml
etcd_backup_copy_to_local: true
etcd_backup_local_dir: "./etcd-backups"
```
### Список снимков
```bash
make etcd-list-snapshots
```
### Восстановить из снимка
```bash
# Посмотреть доступные снимки
make etcd-list-snapshots
# Восстановить (с подтверждением — введи 'yes')
make etcd-restore SNAPSHOT=k3s-etcd-20250101T120000.db
# Без интерактивного подтверждения
FORCE=true make etcd-restore SNAPSHOT=k3s-etcd-20250101T120000.db
```
**Что происходит при восстановлении:**
1. K3S останавливается на **всех** мастерах и воркерах
2. Запускается `k3s server --cluster-reset --cluster-reset-restore-path=<snapshot>` на первом мастере (процесс завершается после сброса)
3. K3S запускается на первом мастере → ждёт готовности API
4. K3S запускается на остальных мастерах
5. K3S-agent запускается на воркерах
**Параметры резервного копирования:**
| Переменная | Умолч. | Описание |
|---|---|---|
| `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` | Локальная директория для снимков |
---
## Все команды Make
### Настройка и сборка
@@ -743,10 +951,26 @@ make install-cni # CNI плагин (make install-cni K3S_CNI=calico|ci
make install-kubevip # Только kube-vip
make install-nfs # NFS + CSI
make install-ingress # ingress-nginx
make install-cert-manager # cert-manager + ClusterIssuer
make install-istio # Istio + Kiali (нужен istio_enabled: true в vars)
make install-monitoring # Prometheus + Grafana (нужен prometheus_stack_enabled: true)
```
### Управление нодами
```bash
make add-node NODE=worker04 # Добавить ноду в кластер
make remove-node NODE=worker04 # Безопасно удалить ноду
```
### etcd — резервное копирование
```bash
make etcd-backup # Создать снимок etcd
make etcd-list-snapshots # Список доступных снимков
make etcd-restore SNAPSHOT=k3s-etcd-XXX.db # Восстановить из снимка
```
### Тестирование (Molecule — всё в Docker, pip не нужен)
```bash
@@ -848,13 +1072,13 @@ destroy → удалить Docker-контейнер
**Тестируемые задачи:**
- `prereqs.yml` — пакеты, swap, модули ядра, sysctl
- Рендеринг `k3s-server-config.yaml.j2``/etc/rancher/k3s/config.yaml`
- Рендеринг `k3s-server-config.yaml.j2``/etc/kubernetes/k3s/config.yaml`
**Что проверяет `verify.yml`:**
| Проверка | Нода | Что именно |
|---|---|---|
| Директория `/etc/rancher/k3s` | все | Создана роль prereqs |
| Директория `/etc/kubernetes/k3s` | все | Создана роль prereqs |
| Файл `config.yaml` | все | Существует, права `0600` |
| `cluster-init: true` | master01 | Первый мастер инициализирует кластер |
| `server: https://...:6443` | worker01, rpi01 | Присоединяются к master01 |
@@ -1308,9 +1532,9 @@ kubectl get events -A --sort-by='.lastTimestamp' | tail -20
kubectl -n kube-system exec -it \
$(kubectl -n kube-system get pods -l component=etcd -o name | head -1) \
-- etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \
--cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \
--key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \
--cacert=/var/lib/kubernetes/k3s/server/tls/etcd/server-ca.crt \
--cert=/var/lib/kubernetes/k3s/server/tls/etcd/server-client.crt \
--key=/var/lib/kubernetes/k3s/server/tls/etcd/server-client.key \
member list
```
@@ -1456,6 +1680,10 @@ spec:
| Molecule: image pull failed | `docker pull geerlingguy/docker-ubuntu2204-ansible:latest` вручную |
| Molecule: idempotency failed | Таск выдаёт `changed` при повторном запуске — добавь `changed_when: false` |
| `ansible-lint` ошибки | Запусти `make molecule-lint` и исправь указанные файлы |
| etcd restore: снимок не найден | Запусти `make etcd-list-snapshots` — снимок должен быть на первом мастере |
| add-node: нода не присоединяется | Убедись что kube-vip работает: `kubectl -n kube-system get pods \| grep kube-vip` |
| remove-node: drain завис | Поды с PodDisruptionBudget могут блокировать drain. Проверь PDB: `kubectl get pdb -A` |
| cert-manager: сертификат в Pending | `kubectl describe certificate <name>` — проверь условия. При letsencrypt нужен публичный домен и ingress |
### Отладка с подробным выводом