## Переименование путей (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 под все новые команды
1705 lines
65 KiB
Markdown
1705 lines
65 KiB
Markdown
# K3S Ansible Stack
|
||
|
||
> Полный Kubernetes стек на базе K3S с HA (High Availability), управляемый через Ansible внутри Docker-контейнера.
|
||
> Ansible устанавливать **не нужно** — всё работает через `make`.
|
||
|
||
## Содержание
|
||
|
||
- [Архитектура](#архитектура)
|
||
- [Требования](#требования)
|
||
- [Структура проекта](#структура-проекта)
|
||
- [Полная установка с нуля](#полная-установка-с-нуля)
|
||
- [Рабочий процесс](#рабочий-процесс)
|
||
- [Настройка кластера](#настройка-кластера)
|
||
- [Опциональные компоненты](#опциональные-компоненты)
|
||
- [Управление нодами](#управление-нодами)
|
||
- [Резервное копирование etcd](#резервное-копирование-etcd)
|
||
- [Все команды Make](#все-команды-make)
|
||
- [Тестирование через Molecule](#тестирование-через-molecule)
|
||
- [Компоненты стека](#компоненты-стека)
|
||
- [Raspberry Pi](#raspberry-pi)
|
||
- [Обновление K3S](#обновление-k3s)
|
||
- [Диагностика](#диагностика)
|
||
- [Примеры манифестов](#примеры-манифестов)
|
||
- [Решение проблем](#решение-проблем)
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
Кластер работает в **HA-режиме** с embedded etcd: все три ноды являются полноценными мастерами. Raspberry Pi участвует в etcd-кворуме, но не принимает рабочие нагрузки.
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Локальная сеть 192.168.1.0/24 │
|
||
│ │
|
||
│ VIP: 192.168.1.100 (kube-vip) │
|
||
│ ├── :6443 K3S API Server (HA) │
|
||
│ └── :80/:443 ingress-nginx → приложения │
|
||
│ │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
|
||
│ │ master01 │ │ worker01 │ │ rpi01 │ │
|
||
│ │ 192.168.1.10 │ │ 192.168.1.11 │ │ .1.12 │ │
|
||
│ │ x86_64 │ │ x86_64 │ │ aarch64 │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ K3S server │ │ K3S server │ │ K3S server │ │
|
||
│ │ etcd #1 │ │ etcd #2 │ │ etcd #3 │ │
|
||
│ │ cluster-init │ │ kube-vip │ │ NoSchedule │ │
|
||
│ │ NFS server │ │ workloads ✓ │ │ workloads ✗│ │
|
||
│ │ workloads ✓ │ │ │ │ │ │
|
||
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
|
||
│ │
|
||
│ StorageClass: nfs-master01 (default) │
|
||
│ └── /srv/nfs/k8s на master01 │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Роли нод
|
||
|
||
| Нода | K3S роль | etcd | Workloads | Описание |
|
||
|---|---|---|---|---|
|
||
| master01 | server | #1 (leader) | ✓ | Первый сервер, инициализирует кластер (`cluster-init`) |
|
||
| worker01 | server | #2 | ✓ | Присоединяется к master01, запускает поды |
|
||
| rpi01 | server | #3 | ✗ | Мастер-нода для quorum, taint `NoSchedule` |
|
||
|
||
При отказе **любой одной** ноды кластер продолжает работать — etcd сохраняет кворум (2 из 3).
|
||
|
||
### Компоненты
|
||
|
||
| Компонент | Версия | Описание |
|
||
|---|---|---|
|
||
| K3S | v1.29.3+k3s1 | Лёгкий Kubernetes с embedded etcd (HA) |
|
||
| kube-vip | v0.8.3 | VIP для API + LoadBalancer сервисов |
|
||
| 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 (опционально) |
|
||
| Helm | 3.14.4 | Внутри Docker контейнера |
|
||
| kubectl | v1.29.3 | Внутри Docker контейнера |
|
||
|
||
### CNI плагин
|
||
|
||
По умолчанию используется встроенный в K3S **Flannel**. Переключение — одна переменная в `group_vars/all/main.yml`:
|
||
|
||
```yaml
|
||
k3s_cni: "flannel" # flannel (по умолч.) | calico | cilium
|
||
```
|
||
|
||
| CNI | Режим | Особенности |
|
||
|---|---|---|
|
||
| `flannel` | встроен в K3S | VXLAN overlay, минимальная настройка |
|
||
| `calico` | Tigera operator | Network Policy, IPAM, BGP, VXLAN/IPIP |
|
||
| `cilium` | Helm | eBPF dataplane, Hubble observability, L7 policy |
|
||
|
||
При `calico` или `cilium` — Flannel автоматически отключается в конфиге K3S (`flannel-backend: none`, `disable-network-policy: true`).
|
||
|
||
Установить отдельно:
|
||
```bash
|
||
make install-cni K3S_CNI=calico # или cilium
|
||
```
|
||
|
||
---
|
||
|
||
## Требования
|
||
|
||
### На твоей машине (откуда запускаешь)
|
||
|
||
| Инструмент | Версия | Установка |
|
||
|---|---|---|
|
||
| **Docker** | >= 24.0 | https://docs.docker.com/get-docker/ |
|
||
| **make** | любая | `apt install make` / `brew install make` |
|
||
| **SSH ключ** | — | `ssh-keygen -t ed25519` |
|
||
|
||
Ansible, Helm, kubectl, Molecule — устанавливать **не нужно**. Всё работает внутри Docker-контейнера.
|
||
|
||
### На серверах кластера
|
||
|
||
| Требование | Описание |
|
||
|---|---|
|
||
| ОС | Ubuntu 20.04/22.04/24.04, Debian 11/12, Raspberry Pi OS 64-bit |
|
||
| SSH | Доступ с твоего публичного ключа |
|
||
| sudo | Без пароля (`NOPASSWD`) — рекомендуется |
|
||
| Интернет | Для скачивания K3S, образов, Helm чартов |
|
||
| RAM | master01/worker01: 2+ ГБ; rpi01: 1+ ГБ |
|
||
|
||
Настроить sudo без пароля **на каждом сервере**:
|
||
```bash
|
||
echo "$USER ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/$USER
|
||
```
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
k3s-ansible/
|
||
├── Makefile ← Все команды (единая точка входа)
|
||
├── Dockerfile ← Образ: Ansible + Helm + kubectl
|
||
├── docker-compose.yml
|
||
├── .env.example ← Шаблон переменных окружения
|
||
├── .yamllint.yml ← Правила линтинга YAML
|
||
│
|
||
├── requirements.yml ← Ansible Galaxy коллекции
|
||
├── requirements-python.txt ← Python пакеты (ansible + molecule)
|
||
│
|
||
├── ansible.cfg
|
||
├── site.yml ← Главный плейбук (serial: 1)
|
||
├── upgrade.yml
|
||
├── uninstall.yml
|
||
├── healthcheck.yml
|
||
├── add-node.yml ← Добавить ноду в кластер
|
||
├── remove-node.yml ← Удалить ноду из кластера
|
||
├── etcd-backup.yml ← Создать снимок etcd
|
||
├── etcd-restore.yml ← Восстановить etcd из снимка
|
||
│
|
||
├── inventory/
|
||
│ └── hosts.ini ← IP и параметры серверов
|
||
│
|
||
├── group_vars/all/
|
||
│ ├── main.yml ← Все переменные кластера + опции
|
||
│ └── vault.yml ← Зашифрованные секреты (токены, пароли)
|
||
│
|
||
├── host_vars/
|
||
│ ├── master01/main.yml ← Labels, server args
|
||
│ ├── worker01/main.yml ← Labels, server args
|
||
│ └── rpi01/main.yml ← Labels, taint NoSchedule, ARM args
|
||
│
|
||
└── roles/
|
||
├── k3s/ ← K3S HA cluster (embedded etcd)
|
||
│ ├── tasks/
|
||
│ │ ├── main.yml ← Точка входа
|
||
│ │ ├── prereqs.yml ← Пакеты, sysctl, swap, модули ядра
|
||
│ │ ├── install_server.yml ← Установка K3S server
|
||
│ │ ├── node_config.yml ← Labels и taints
|
||
│ │ └── kubeconfig.yml ← Скачать kubeconfig локально
|
||
│ ├── templates/
|
||
│ │ └── k3s-server-config.yaml.j2
|
||
│ └── molecule/default/ ← Unit-тесты роли
|
||
│ ├── molecule.yml
|
||
│ ├── prepare.yml
|
||
│ ├── converge.yml
|
||
│ └── verify.yml
|
||
│
|
||
├── kube-vip/ ← Virtual IP + LoadBalancer
|
||
├── 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
|
||
│ ├── templates/
|
||
│ │ ├── istiod-values.yaml.j2
|
||
│ │ ├── kiali-values.yaml.j2
|
||
│ │ ├── kiali-token-secret.yaml.j2
|
||
│ │ └── peer-authentication.yaml.j2
|
||
│ └── molecule/default/
|
||
│
|
||
└── prometheus-stack/ ← Prometheus + Grafana + Alert (опционально)
|
||
├── tasks/main.yml
|
||
├── templates/
|
||
│ └── prometheus-stack-values.yaml.j2
|
||
└── molecule/default/
|
||
```
|
||
|
||
---
|
||
|
||
## Полная установка с нуля
|
||
|
||
Это полный сценарий от чистой машины до работающего кластера. Скопируй и выполни блоками.
|
||
|
||
### Шаг 1 — Подготовка серверов
|
||
|
||
Выполни **на каждом сервере** (замени `user` и IP на свои):
|
||
|
||
```bash
|
||
# Создать пользователя с sudo без пароля (если нужно)
|
||
sudo adduser ansible
|
||
echo "ansible ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ansible
|
||
|
||
# Или для текущего пользователя:
|
||
echo "$USER ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/$USER
|
||
```
|
||
|
||
### Шаг 2 — SSH ключи
|
||
|
||
```bash
|
||
# Создать ключ (если нет)
|
||
ssh-keygen -t ed25519 -C "k3s-ansible" -f ~/.ssh/id_ed25519
|
||
|
||
# Скопировать на каждый сервер
|
||
ssh-copy-id ubuntu@192.168.1.10 # master01
|
||
ssh-copy-id ubuntu@192.168.1.11 # worker01
|
||
ssh-copy-id pi@192.168.1.12 # rpi01
|
||
|
||
# Проверить
|
||
ssh ubuntu@192.168.1.10 "hostname"
|
||
```
|
||
|
||
### Шаг 3 — Клонировать и настроить проект
|
||
|
||
```bash
|
||
git clone <url> k3s-ansible && cd k3s-ansible
|
||
|
||
# Создать .env
|
||
make setup
|
||
```
|
||
|
||
Отредактируй `.env` — задай пароль от vault:
|
||
```bash
|
||
# .env
|
||
VAULT_PASSWORD=придумай-надёжный-пароль
|
||
```
|
||
|
||
### Шаг 4 — Инвентарь
|
||
|
||
Отредактируй `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 ansible_python_interpreter=/usr/bin/python3
|
||
|
||
[k3s_cluster:children]
|
||
k3s_master
|
||
|
||
[k3s_cluster:vars]
|
||
ansible_ssh_private_key_file=~/.ssh/id_ed25519
|
||
|
||
[nfs_server]
|
||
master01
|
||
```
|
||
|
||
### Шаг 5 — Переменные кластера
|
||
|
||
Отредактируй `group_vars/all/main.yml`. Обязательные поля:
|
||
```yaml
|
||
kube_vip_address: "192.168.1.100" # свободный IP, не в DHCP пуле
|
||
# kube_vip_interface: "" # пусто = автоопределение (eth0/enp3s0/end0/...)
|
||
# переопредели только если авто не работает
|
||
k3s_cni: "flannel" # flannel (умолч.) | calico | cilium
|
||
```
|
||
|
||
`kube_vip_interface` определяется автоматически через `ansible_default_ipv4.interface` — работает для Ubuntu, Debian, Raspberry Pi OS без дополнительных настроек.
|
||
|
||
### Шаг 6 — Vault с секретами
|
||
|
||
```bash
|
||
# Сгенерировать токен K3S
|
||
openssl rand -hex 32
|
||
# → a3f8c2d1e9b04756...
|
||
|
||
make vault-create
|
||
```
|
||
|
||
В открывшемся редакторе введи (замени значения на свои):
|
||
```yaml
|
||
vault_k3s_token: "a3f8c2d1e9b04756..." # токен из openssl выше
|
||
vault_grafana_user: "admin" # если планируешь Prometheus
|
||
vault_grafana_password: "мой-пароль"
|
||
vault_kiali_token: "" # заполнишь после установки Istio
|
||
```
|
||
|
||
Сохрани: `:wq` (в vim) или `Ctrl+X → Y` (в nano).
|
||
|
||
### Шаг 7 — Собрать Docker образ
|
||
|
||
```bash
|
||
make build
|
||
# Занимает ~3-5 минут при первом запуске
|
||
```
|
||
|
||
### Шаг 8 — Прогнать тесты (рекомендуется)
|
||
|
||
Перед деплоем убедись что роли корректны. Molecule запускается **внутри Docker** — устанавливать ничего не нужно.
|
||
|
||
```bash
|
||
# Убедись что образ собран (если ещё не сделал на шаге 7):
|
||
# make build
|
||
|
||
# Запустить все тесты (~15-20 минут)
|
||
make molecule-all
|
||
```
|
||
|
||
Что поднимается: 3 контейнера (master01/worker01 на Ubuntu 22.04 + rpi01 на Debian 12), тест конфигурации K3S HA, шаблонов Prometheus и Istio.
|
||
|
||
Если всё зелёное — можно деплоить. Если есть ошибки — смотри раздел [Тестирование через Molecule](#тестирование-через-molecule).
|
||
|
||
### Шаг 9 — Проверить SSH и dry-run
|
||
|
||
```bash
|
||
# Проверить доступность всех нод
|
||
make ping
|
||
# Ожидаемый вывод: SUCCESS для каждой ноды
|
||
|
||
# Проверить плейбук без применения изменений
|
||
make check
|
||
```
|
||
|
||
### Шаг 10 — Развернуть базовый стек
|
||
|
||
```bash
|
||
make install
|
||
```
|
||
|
||
Плейбук выполняет всё последовательно (`serial: 1`):
|
||
1. master01: prereqs → K3S server (cluster-init) → ждёт готовности API
|
||
2. worker01: prereqs → K3S server (join) → ждёт готовности ноды
|
||
3. rpi01: prereqs + cgroups → K3S server (join) → применяет taint NoSchedule
|
||
4. CNI плагин (если не Flannel — устанавливается Calico или Cilium)
|
||
5. kube-vip: VIP + LoadBalancer (интерфейс определяется автоматически)
|
||
6. NFS server на master01
|
||
7. CSI NFS Driver на всех нодах → StorageClass `nfs-master01`
|
||
8. ingress-nginx через Helm
|
||
9. cert-manager (только если `cert_manager_enabled: true`)
|
||
10. Финальная проверка: nodes / pods / svc / storageclass
|
||
|
||
Ожидаемое время: **15-25 минут** в зависимости от скорости интернета.
|
||
|
||
### Шаг 11 — Проверить результат
|
||
|
||
```bash
|
||
make verify
|
||
|
||
# Или с локальным kubectl:
|
||
export KUBECONFIG=$(pwd)/kubeconfig
|
||
|
||
kubectl get nodes -o wide
|
||
# NAME STATUS ROLES AGE VERSION
|
||
# master01 Ready control-plane,etcd,master 5m v1.29.3+k3s1
|
||
# worker01 Ready control-plane,etcd,master 3m v1.29.3+k3s1
|
||
# rpi01 Ready control-plane,etcd,master 1m v1.29.3+k3s1
|
||
|
||
kubectl get svc -n ingress-nginx
|
||
# NAME TYPE EXTERNAL-IP PORT(S)
|
||
# ingress-nginx-controller LoadBalancer 192.168.1.100 80:xxx/443:xxx
|
||
|
||
kubectl get storageclass
|
||
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
|
||
# nfs-master01 (default) nfs.csi.k8s.io Delete Immediate
|
||
```
|
||
|
||
### Шаг 12 (опционально) — Установить Istio + мониторинг
|
||
|
||
```bash
|
||
# Добавь в group_vars/all/main.yml:
|
||
# istio_enabled: true
|
||
# kiali_enabled: true
|
||
# prometheus_stack_enabled: true
|
||
|
||
make install-istio
|
||
make install-monitoring
|
||
|
||
# Или всё сразу с флагами:
|
||
# EXTRA_VARS="istio_enabled=true kiali_enabled=true prometheus_stack_enabled=true" make install
|
||
```
|
||
|
||
---
|
||
|
||
## Рабочий процесс
|
||
|
||
Рекомендуемый порядок при любых изменениях в ролях:
|
||
|
||
```
|
||
Изменить роль → Тест Molecule → Линтинг → Dry-run → Deploy
|
||
```
|
||
|
||
```bash
|
||
# 1. Внёс изменение в roles/k3s/ или roles/prometheus-stack/
|
||
# 2. Запустить тест только нужной роли
|
||
make molecule-k3s # или molecule-prometheus / molecule-istio
|
||
|
||
# 3. Линтинг всего проекта
|
||
make molecule-lint
|
||
|
||
# 4. Проверить что плейбук парсится корректно (не применяет изменения)
|
||
make check
|
||
|
||
# 5. Деплой на реальные серверы
|
||
make install
|
||
```
|
||
|
||
### Тегированный деплой (обновить только один компонент)
|
||
|
||
```bash
|
||
make install ANSIBLE_TAGS=k3s # только K3S
|
||
make install ANSIBLE_TAGS=kube_vip # только kube-vip
|
||
make install ANSIBLE_TAGS=nfs # NFS + CSI
|
||
make install ANSIBLE_TAGS=ingress # ingress-nginx
|
||
make install ANSIBLE_TAGS=istio # Istio + Kiali
|
||
make install ANSIBLE_TAGS=monitoring # Prometheus stack
|
||
```
|
||
|
||
---
|
||
|
||
## Настройка кластера
|
||
|
||
### K3S
|
||
|
||
```yaml
|
||
# group_vars/all/main.yml
|
||
k3s_version: "v1.29.3+k3s1"
|
||
k3s_cluster_cidr: "10.42.0.0/16"
|
||
k3s_service_cidr: "10.43.0.0/16"
|
||
k3s_flannel_backend: "vxlan" # vxlan | wireguard-native | host-gw (только для Flannel)
|
||
k3s_disable_traefik: true # ОБЯЗАТЕЛЬНО true при ingress-nginx
|
||
|
||
# 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
|
||
|
||
**Flannel** (по умолчанию) — ничего дополнительно настраивать не нужно.
|
||
|
||
**Calico:**
|
||
```yaml
|
||
k3s_cni: "calico"
|
||
calico_version: "v3.28.0"
|
||
calico_encapsulation: "VXLAN" # VXLAN | IPIP | None
|
||
```
|
||
|
||
**Cilium:**
|
||
```yaml
|
||
k3s_cni: "cilium"
|
||
cilium_version: "1.15.5"
|
||
cilium_hubble_enabled: true # Hubble observability
|
||
cilium_hubble_ui_enabled: false # Hubble UI (ресурсоёмко)
|
||
# cilium_k8s_service_host автоматически = kube_vip_address
|
||
```
|
||
|
||
При смене CNI необходима переустановка кластера — CNI нельзя поменять "горячей" заменой.
|
||
|
||
### kube-vip
|
||
|
||
```yaml
|
||
kube_vip_address: "192.168.1.100" # Свободный IP — обязательно задать!
|
||
kube_vip_interface: "" # пусто = автоопределение
|
||
# Ansible определит eth0/enp3s0/end0 автоматически
|
||
# Переопредели только если нужно: "eth0"
|
||
kube_vip_mode: "arp" # arp (L2) для домашних сетей | bgp (L3)
|
||
```
|
||
|
||
Автоопределение интерфейса работает через `ansible_default_ipv4.interface` — корректно определяет интерфейс на Ubuntu, Debian, Raspberry Pi OS. Если у разных нод разные имена интерфейсов — задай `kube_vip_interface` принудительно.
|
||
|
||
### NFS / CSI
|
||
|
||
```yaml
|
||
nfs_exports:
|
||
- path: /srv/nfs/k8s
|
||
options: "*(rw,sync,no_subtree_check,no_root_squash)"
|
||
nfs_allowed_network: "192.168.1.0/24"
|
||
|
||
csi_nfs_reclaim_policy: "Delete" # Delete | Retain
|
||
# StorageClass именуется автоматически: nfs-<hostname NFS сервера>
|
||
# Например: nfs-master01 (если NFS на master01)
|
||
# Переопредели только если нужно другое имя:
|
||
# csi_nfs_storageclass_name: "my-nfs"
|
||
```
|
||
|
||
### ingress-nginx
|
||
|
||
```yaml
|
||
ingress_nginx_service_type: "LoadBalancer"
|
||
ingress_nginx_load_balancer_ip: "" # "" = авто от kube-vip
|
||
ingress_nginx_set_default_class: true
|
||
```
|
||
|
||
### Индивидуальные настройки нод (`host_vars/`)
|
||
|
||
**master01** — дополнительные labels и server args:
|
||
```yaml
|
||
# host_vars/master01/main.yml
|
||
k3s_node_labels:
|
||
- "node-role=master"
|
||
- "disk-type=ssd"
|
||
k3s_extra_server_args: |
|
||
kube-controller-manager-arg: "node-monitor-grace-period=20s"
|
||
```
|
||
|
||
**rpi01** — taint запрещает планирование обычных подов:
|
||
```yaml
|
||
# host_vars/rpi01/main.yml
|
||
k3s_node_taints:
|
||
- "node-type=raspberry-pi:NoSchedule"
|
||
k3s_extra_server_args: |
|
||
kubelet-arg:
|
||
- "kube-reserved=cpu=50m,memory=128Mi"
|
||
- "system-reserved=cpu=50m,memory=128Mi"
|
||
```
|
||
|
||
Чтобы разрешить поды на RPi — очисти список:
|
||
```yaml
|
||
k3s_node_taints: []
|
||
```
|
||
|
||
### Ansible Vault
|
||
|
||
```bash
|
||
make vault-create # Создать
|
||
make vault-edit # Редактировать
|
||
make vault-view # Просмотреть
|
||
make vault-encrypt-string STR="токен" NAME="vault_k3s_token"
|
||
```
|
||
|
||
Полный шаблон `group_vars/all/vault.yml`:
|
||
```yaml
|
||
# Обязательно:
|
||
vault_k3s_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||
|
||
# Если используешь Prometheus stack:
|
||
vault_grafana_user: "admin"
|
||
vault_grafana_password: "мой-безопасный-пароль"
|
||
|
||
# Если используешь Kiali (заполни после первой установки):
|
||
vault_kiali_token: ""
|
||
```
|
||
|
||
---
|
||
|
||
## Опциональные компоненты
|
||
|
||
Включаются через переменные в `group_vars/all/main.yml`. По умолчанию все отключены — базовый стек работает без них.
|
||
|
||
---
|
||
|
||
### 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.
|
||
|
||
**Включить:**
|
||
```yaml
|
||
# group_vars/all/main.yml
|
||
istio_enabled: true
|
||
```
|
||
|
||
Или разово без изменения файлов:
|
||
```bash
|
||
make install-istio
|
||
```
|
||
|
||
**Параметры:**
|
||
|
||
| Переменная | Умолч. | Описание |
|
||
|---|---|---|
|
||
| `istio_enabled` | `false` | Включить установку |
|
||
| `istio_version` | `1.22.2` | Версия Helm chart |
|
||
| `istio_mtls_mode` | `STRICT` | Режим mTLS: `STRICT` / `PERMISSIVE` / `DISABLE` |
|
||
| `istio_install_gateway` | `true` | Устанавливать Ingress Gateway |
|
||
| `istio_telemetry_enabled` | `true` | Сбор метрик для Prometheus |
|
||
|
||
---
|
||
|
||
### Kiali (UI для Istio)
|
||
|
||
Веб-интерфейс для визуализации service mesh. Требует `istio_enabled: true`.
|
||
|
||
**Включить:**
|
||
```yaml
|
||
kiali_enabled: true
|
||
```
|
||
|
||
**Аутентификация по токену:**
|
||
|
||
Kiali настроен со стратегией `token` — для входа в UI нужен Kubernetes ServiceAccount токен.
|
||
|
||
При первой установке Ansible:
|
||
1. Создаёт ServiceAccount `kiali-admin` с правами `cluster-admin`
|
||
2. Создаёт Secret типа `kubernetes.io/service-account-token`
|
||
3. Читает сгенерированный k8s токен и **выводит его в конце плейбука**
|
||
|
||
```
|
||
══════════════════════════════════════════════════
|
||
Kiali UI: kubectl -n istio-system port-forward svc/kiali 20001:20001
|
||
Откройте: http://localhost:20001
|
||
Стратегия: token
|
||
Токен для входа:
|
||
eyJhbGciOiJSUzI1NiIsImtpZCI6...
|
||
Сохрани токен в vault.yml: vault_kiali_token: <токен>
|
||
══════════════════════════════════════════════════
|
||
```
|
||
|
||
Скопируй выведенный токен в vault:
|
||
```bash
|
||
make vault-edit
|
||
# добавь: vault_kiali_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6..."
|
||
```
|
||
|
||
При последующих запусках Ansible использует сохранённый токен и не создаёт новый.
|
||
|
||
**Параметры:**
|
||
|
||
| Переменная | Умолч. | Описание |
|
||
|---|---|---|
|
||
| `kiali_enabled` | `false` | Включить установку |
|
||
| `kiali_version` | `1.86.0` | Версия Helm chart |
|
||
| `kiali_token` | `vault_kiali_token` | Токен входа (из vault) |
|
||
| `kiali_ingress_enabled` | `false` | Создать Ingress |
|
||
| `kiali_ingress_host` | `kiali.local` | Hostname для Ingress |
|
||
|
||
Включить Ingress:
|
||
```yaml
|
||
kiali_ingress_enabled: true
|
||
kiali_ingress_host: "kiali.example.com"
|
||
kiali_ingress_class: "nginx"
|
||
```
|
||
|
||
---
|
||
|
||
### kube-prometheus-stack (Prometheus + Grafana + Alertmanager)
|
||
|
||
Полный monitoring-стек: Prometheus, Grafana, Alertmanager, node-exporter, kube-state-metrics. Данные хранятся на PVC через StorageClass `nfs-client`.
|
||
|
||
**Включить:**
|
||
```yaml
|
||
prometheus_stack_enabled: true
|
||
```
|
||
|
||
Или разово:
|
||
```bash
|
||
make install-monitoring
|
||
```
|
||
|
||
**Хранилище (PVC):**
|
||
|
||
| Компонент | Переменная | Умолч. | Назначение |
|
||
|---|---|---|---|
|
||
| Prometheus | `prometheus_storage_size` | `10Gi` | Метрики временных рядов |
|
||
| Grafana | `grafana_storage_size` | `5Gi` | Дашборды, плагины, настройки |
|
||
| Alertmanager | `prometheus_alertmanager_storage_size` | `2Gi` | Состояние алертов |
|
||
|
||
Изменить размер:
|
||
```yaml
|
||
# group_vars/all/main.yml
|
||
prometheus_storage_size: "20Gi"
|
||
grafana_storage_size: "10Gi"
|
||
prometheus_alertmanager_storage_size: "5Gi"
|
||
|
||
# Явно указать StorageClass (по умолчанию используется default = nfs-client):
|
||
# prometheus_storage_class: "nfs-client"
|
||
# grafana_storage_class: "nfs-client"
|
||
```
|
||
|
||
**Grafana — доступ:**
|
||
|
||
После установки Grafana доступна на **NodePort 32000** (на любой ноде):
|
||
```
|
||
http://192.168.1.10:32000
|
||
```
|
||
|
||
Логин и пароль из vault:
|
||
```yaml
|
||
vault_grafana_user: "admin"
|
||
vault_grafana_password: "мой-пароль"
|
||
```
|
||
|
||
Включить Ingress вместо NodePort:
|
||
```yaml
|
||
prometheus_grafana_ingress_enabled: true
|
||
prometheus_grafana_ingress_host: "grafana.example.com"
|
||
```
|
||
|
||
**Все параметры:**
|
||
|
||
| Переменная | Умолч. | Описание |
|
||
|---|---|---|
|
||
| `prometheus_stack_enabled` | `false` | Включить установку |
|
||
| `prometheus_stack_version` | `60.3.0` | Версия Helm chart |
|
||
| `prometheus_retention_days` | `7` | Срок хранения метрик (дней) |
|
||
| `prometheus_storage_size` | `10Gi` | PVC Prometheus |
|
||
| `grafana_storage_enabled` | `true` | Включить PVC для Grafana |
|
||
| `grafana_storage_size` | `5Gi` | PVC Grafana |
|
||
| `grafana_admin_user` | `vault_grafana_user` | Логин Grafana |
|
||
| `prometheus_grafana_admin_password` | `vault_grafana_password` | Пароль Grafana |
|
||
| `prometheus_alertmanager_enabled` | `true` | Включить Alertmanager |
|
||
| `prometheus_alertmanager_storage_size` | `2Gi` | PVC Alertmanager |
|
||
| `prometheus_node_exporter_enabled` | `true` | Метрики хостов (DaemonSet) |
|
||
| `prometheus_kube_state_metrics_enabled` | `true` | Метрики объектов k8s |
|
||
|
||
**Интеграция Kiali ↔ Prometheus/Grafana:**
|
||
|
||
При `istio_enabled: true` + `prometheus_stack_enabled: true` Kiali автоматически получает URL Prometheus и Grafana и настраивается на их использование без дополнительной конфигурации.
|
||
|
||
---
|
||
|
||
## Управление нодами
|
||
|
||
### Добавить ноду
|
||
|
||
Перед добавлением:
|
||
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
|
||
|
||
### Настройка и сборка
|
||
|
||
```bash
|
||
make setup # Создать .env из шаблона
|
||
make build # Собрать Docker образ (~5 мин)
|
||
make rebuild # Пересобрать без кэша
|
||
```
|
||
|
||
### Проверки
|
||
|
||
```bash
|
||
make ping # SSH до всех нод
|
||
make check # Dry-run без изменений
|
||
make lint # Проверить синтаксис плейбуков
|
||
```
|
||
|
||
### Установка
|
||
|
||
```bash
|
||
make install # Полный базовый стек
|
||
make install-k3s # Только K3S HA кластер
|
||
make install-cni # CNI плагин (make install-cni K3S_CNI=calico|cilium)
|
||
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
|
||
make molecule-k3s # Тест роли k3s — 3 контейнера (Ubuntu+Debian), ~8-12 мин
|
||
make molecule-prometheus # Тест роли prometheus-stack (~2-3 мин)
|
||
make molecule-istio # Тест роли istio (~2-3 мин)
|
||
make molecule-all # Все тесты последовательно (~15-20 мин)
|
||
make molecule-lint # Линтинг YAML+ansible-lint в контейнере (~30 сек)
|
||
```
|
||
|
||
### Обновление и диагностика
|
||
|
||
```bash
|
||
make upgrade VERSION=v1.30.0+k3s1 # Обновить K3S
|
||
make health # Полная диагностика
|
||
make verify # Сводка стека
|
||
```
|
||
|
||
### Vault
|
||
|
||
```bash
|
||
make vault-create
|
||
make vault-edit
|
||
make vault-view
|
||
make vault-encrypt-string STR=... NAME=...
|
||
```
|
||
|
||
### Прочее
|
||
|
||
```bash
|
||
make shell # bash внутри ansible-контейнера
|
||
make uninstall # Удалить весь стек (с подтверждением)
|
||
make clean # Удалить Docker образ
|
||
make clean-all # Образ + kubeconfig + кэш Docker
|
||
```
|
||
|
||
### Переменные командной строки
|
||
|
||
```bash
|
||
ANSIBLE_VERBOSITY=2 make install # Подробный вывод
|
||
ANSIBLE_TAGS=k3s,kube_vip make install # Только теги
|
||
EXTRA_VARS="k3s_version=v1.30.0+k3s1" make install-k3s # Доп. переменные
|
||
```
|
||
|
||
---
|
||
|
||
## Тестирование через Molecule
|
||
|
||
Molecule — стандартный инструмент для тестирования Ansible ролей. Каждая роль запускается в Docker-контейнерах, проходит набор автоматических проверок и удаляется. Реальные серверы при этом **не нужны**.
|
||
|
||
### Требования
|
||
|
||
Только **Docker** — Molecule, Python и зависимости уже внутри образа `k3s-ansible`. Устанавливать ничего дополнительно не нужно.
|
||
|
||
```bash
|
||
# Убедись что образ собран:
|
||
make build
|
||
|
||
# Запустить тесты (всё в Docker):
|
||
make molecule-k3s
|
||
```
|
||
|
||
Как это работает: `make molecule-*` запускает контейнер `k3s-ansible` с примонтированным Docker socket (`/var/run/docker.sock`). Внутри этого контейнера Molecule создаёт тестовые контейнеры для каждой роли — **Docker внутри Docker без демона** (только socket от хоста).
|
||
|
||
### Жизненный цикл теста
|
||
|
||
Команда `molecule test` выполняет следующие фазы по порядку:
|
||
|
||
```
|
||
dependency → скачать зависимые Ansible-роли (если есть)
|
||
lint → yamllint + ansible-lint (статический анализ)
|
||
syntax → ansible-playbook --syntax-check
|
||
create → запустить Docker-контейнер (платформа)
|
||
prepare → подготовить контейнер (установить Python, collections)
|
||
converge → выполнить тестируемые задачи внутри контейнера
|
||
idempotency → выполнить converge повторно (проверить что нет лишних изменений)
|
||
verify → запустить assertions (проверить результаты)
|
||
cleanup → очистить состояние (если задан cleanup.yml)
|
||
destroy → удалить Docker-контейнер
|
||
```
|
||
|
||
При ошибке на любой фазе — тест падает, контейнер удаляется автоматически.
|
||
|
||
### Что тестирует каждая роль
|
||
|
||
#### Роль `k3s` (3 контейнера, privileged Docker, ~8-12 мин)
|
||
|
||
Поднимает **3 контейнера**, точно как в inventory:
|
||
|
||
| Контейнер | Образ | Соответствует |
|
||
|---|---|---|
|
||
| `master01` | `geerlingguy/docker-ubuntu2204-ansible` | Ubuntu 22.04 x86_64 |
|
||
| `worker01` | `geerlingguy/docker-ubuntu2204-ansible` | Ubuntu 22.04 x86_64 |
|
||
| `rpi01` | `geerlingguy/docker-debian12-ansible` | Debian 12 (Raspberry Pi OS) |
|
||
|
||
Все контейнеры — privileged с systemd (нужно для `apt`, `sysctl`, модулей ядра).
|
||
|
||
`rpi01` получает `ansible_python_interpreter=/usr/bin/python3` и `NoSchedule` taint — как в реальном inventory.
|
||
|
||
**Тестируемые задачи:**
|
||
- `prereqs.yml` — пакеты, swap, модули ядра, sysctl
|
||
- Рендеринг `k3s-server-config.yaml.j2` → `/etc/kubernetes/k3s/config.yaml`
|
||
|
||
**Что проверяет `verify.yml`:**
|
||
|
||
| Проверка | Нода | Что именно |
|
||
|---|---|---|
|
||
| Директория `/etc/kubernetes/k3s` | все | Создана роль prereqs |
|
||
| Файл `config.yaml` | все | Существует, права `0600` |
|
||
| `cluster-init: true` | master01 | Первый мастер инициализирует кластер |
|
||
| `server: https://...:6443` | worker01, rpi01 | Присоединяются к master01 |
|
||
| `cluster-cidr` | все | Равен `10.42.0.0/16` |
|
||
| `disable: [traefik]` | все | Traefik выключен |
|
||
| `curl`, `iptables` | все | Системные пакеты |
|
||
| `net.ipv4.ip_forward = 1` | все | sysctl применён |
|
||
|
||
#### Роль `prometheus-stack` (lightweight Docker, ~2-3 мин)
|
||
|
||
Только рендеринг Jinja2-шаблона — не требует privileged режима.
|
||
|
||
**Тестируемые задачи:**
|
||
- Рендеринг `prometheus-stack-values.yaml.j2` → `/tmp/prometheus-stack-values.yaml`
|
||
|
||
**Что проверяет `verify.yml`:**
|
||
|
||
| Проверка | Что именно |
|
||
|---|---|
|
||
| `grafana.adminUser` | Значение переменной `grafana_admin_user` |
|
||
| `grafana.adminPassword` | Непустой пароль из переменной |
|
||
| `grafana.persistence.enabled` | `true` — PVC включён |
|
||
| `grafana.persistence.size` | `5Gi` — размер PVC Grafana |
|
||
| `prometheus.prometheusSpec.retention` | `7d` |
|
||
| Prometheus PVC storage | `10Gi` |
|
||
| Alertmanager enabled | `true` |
|
||
| Alertmanager PVC storage | `2Gi` |
|
||
| nodeExporter enabled | `true` |
|
||
|
||
#### Роль `istio` (lightweight Docker, ~2-3 мин)
|
||
|
||
Рендеринг всех четырёх шаблонов роли.
|
||
|
||
**Тестируемые задачи:**
|
||
- `istiod-values.yaml.j2` → `/tmp/istiod-values.yaml`
|
||
- `kiali-values.yaml.j2` → `/tmp/kiali-values.yaml`
|
||
- `peer-authentication.yaml.j2` → `/tmp/peer-authentication.yaml`
|
||
- `kiali-token-secret.yaml.j2` → `/tmp/kiali-token-secret.yaml`
|
||
|
||
**Что проверяет `verify.yml`:**
|
||
|
||
| Файл | Проверка |
|
||
|---|---|
|
||
| `istiod-values.yaml` | Ресурсы pilot (cpu/memory), `meshConfig` существует, `enablePrometheusMerge: true` |
|
||
| `kiali-values.yaml` | `auth.strategy: token`, Prometheus URL содержит `prom-kube-prometheus-stack-prometheus`, Grafana auth username |
|
||
| `peer-authentication.yaml` | `kind: PeerAuthentication`, `spec.mtls.mode: STRICT` |
|
||
| `kiali-token-secret.yaml` | `type: kubernetes.io/service-account-token`, аннотация `kiali-admin` |
|
||
|
||
---
|
||
|
||
### Запуск тестов: пошагово
|
||
|
||
#### Линтинг (в контейнере, ~30 сек)
|
||
|
||
```bash
|
||
make molecule-lint
|
||
```
|
||
|
||
Запускает `yamllint .` и `ansible-lint` на всём проекте внутри Docker-контейнера. Используй перед каждым коммитом.
|
||
|
||
```
|
||
Запуск линтинга...
|
||
✓ Линтинг прошёл
|
||
```
|
||
|
||
#### Тест одной роли
|
||
|
||
```bash
|
||
make molecule-k3s
|
||
```
|
||
|
||
Вывод (успешный, сокращён):
|
||
```
|
||
INFO Running default > create
|
||
INFO Sanity checks: 'docker'
|
||
|
||
TASK [Create instance(s)] ****
|
||
changed: [localhost] => (item=master01)
|
||
changed: [localhost] => (item=worker01)
|
||
changed: [localhost] => (item=rpi01)
|
||
|
||
INFO Running default > prepare
|
||
TASK [Wait for systemd to start] ***
|
||
ok: [master01]
|
||
ok: [worker01]
|
||
ok: [rpi01]
|
||
|
||
INFO Running default > converge
|
||
TASK [Mock k3s binary] ***
|
||
changed: [master01]
|
||
changed: [worker01]
|
||
changed: [rpi01]
|
||
|
||
TASK [Test server config template rendering] ***
|
||
changed: [master01]
|
||
changed: [worker01]
|
||
changed: [rpi01]
|
||
|
||
INFO Running default > idempotency
|
||
PLAY RECAP
|
||
master01 : ok=12 changed=0 unreachable=0 failed=0
|
||
worker01 : ok=12 changed=0 unreachable=0 failed=0
|
||
rpi01 : ok=12 changed=0 unreachable=0 failed=0
|
||
|
||
INFO Idempotency completed successfully.
|
||
|
||
INFO Running default > verify
|
||
TASK [Assert cluster-init is set (только master01)] ***
|
||
ok: [master01] => {"msg": "All assertions passed"}
|
||
skipping: [worker01]
|
||
skipping: [rpi01]
|
||
|
||
TASK [Assert server URL is set (worker01 и rpi01)] ***
|
||
skipping: [master01]
|
||
ok: [worker01] => {"msg": "All assertions passed"}
|
||
ok: [rpi01] => {"msg": "All assertions passed"}
|
||
|
||
TASK [Assert ip_forward is 1] ***
|
||
ok: [master01]
|
||
ok: [worker01]
|
||
ok: [rpi01]
|
||
|
||
INFO Running default > destroy
|
||
✓ k3s role: OK
|
||
```
|
||
|
||
#### Все тесты
|
||
|
||
```bash
|
||
make molecule-all
|
||
```
|
||
|
||
```
|
||
Тестирую роль k3s (3 ноды: master01, worker01, rpi01)...
|
||
...
|
||
✓ k3s role: OK
|
||
|
||
Тестирую роль prometheus-stack...
|
||
...
|
||
✓ prometheus-stack role: OK
|
||
|
||
Тестирую роль istio...
|
||
...
|
||
✓ istio role: OK
|
||
|
||
✓ Все тесты прошли успешно
|
||
```
|
||
|
||
---
|
||
|
||
### Отладка упавших тестов
|
||
|
||
Если тест упал — контейнеры удаляются автоматически. Чтобы оставить контейнеры живыми для ручной отладки, войди в интерактивный shell внутри Molecule-контейнера и запускай отдельные фазы:
|
||
|
||
```bash
|
||
# Открыть shell внутри ansible-runner контейнера с Docker socket
|
||
docker run --rm -it \
|
||
-v $(pwd):/ansible \
|
||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||
--entrypoint bash \
|
||
k3s-ansible
|
||
|
||
# Внутри контейнера — перейти к роли и запускать фазы вручную:
|
||
cd /ansible/roles/prometheus-stack
|
||
|
||
molecule converge # создать контейнер и запустить задачи (без удаления)
|
||
molecule login # войти в тестовый контейнер
|
||
# Внутри: cat /tmp/prometheus-stack-values.yaml
|
||
# Внутри: exit
|
||
|
||
molecule verify # только assertions
|
||
molecule converge -- -vvv # подробный вывод
|
||
molecule destroy # удалить тестовый контейнер
|
||
```
|
||
|
||
Для роли k3s, которая поднимает 3 контейнера:
|
||
```bash
|
||
cd /ansible/roles/k3s
|
||
|
||
molecule converge # поднимет master01, worker01, rpi01
|
||
molecule login --host master01 # войти в конкретный контейнер
|
||
molecule login --host rpi01
|
||
molecule verify
|
||
molecule destroy
|
||
```
|
||
|
||
#### Типичные ошибки и решения
|
||
|
||
| Ошибка | Причина | Решение |
|
||
|---|---|---|
|
||
| `Unable to pull image` | Нет интернета или Docker Hub | Проверь подключение, попробуй `docker pull geerlingguy/docker-ubuntu2204-ansible` |
|
||
| `FAILED: assert ... is defined` | Переменная не задана в `converge.yml` | Добавь переменную в секцию `vars:` в `converge.yml` |
|
||
| `Idempotency: CHANGED` | Таск меняет состояние при повторном запуске | Добавь `changed_when: false` или исправь идемпотентность задачи |
|
||
| `yamllint: wrong indentation` | Ошибка отступа в YAML | Исправь файл, запусти `make molecule-lint` |
|
||
| `ansible-lint: no-changed-when` | Таск shell/command без `changed_when` | Добавь `changed_when: <условие>` к задаче |
|
||
| `sysctl: Operation not permitted` | Контейнер не privileged | Убедись что в `molecule.yml` стоит `privileged: true` |
|
||
|
||
#### Фазы Molecule (внутри runner-контейнера)
|
||
|
||
```bash
|
||
# Войти в runner-контейнер:
|
||
docker run --rm -it -v $(pwd):/ansible -v /var/run/docker.sock:/var/run/docker.sock \
|
||
--entrypoint bash k3s-ansible
|
||
|
||
cd /ansible/roles/k3s # или prometheus-stack / istio
|
||
|
||
molecule lint # только линтинг
|
||
molecule syntax # только синтаксис
|
||
molecule create # только создать тестовые контейнеры
|
||
molecule prepare # только prepare.yml
|
||
molecule converge # только converge.yml (основной тест)
|
||
molecule idempotency # только проверка идемпотентности
|
||
molecule verify # только verify.yml (assertions)
|
||
molecule destroy # только удалить тестовые контейнеры
|
||
```
|
||
|
||
---
|
||
|
||
### Написание новых тестов
|
||
|
||
Структура каждого scenario:
|
||
|
||
**`molecule.yml`** — конфигурация драйвера и платформы:
|
||
```yaml
|
||
driver:
|
||
name: docker
|
||
platforms:
|
||
- name: my-test-instance
|
||
image: geerlingguy/docker-ubuntu2204-ansible:latest
|
||
pre_build_image: true
|
||
privileged: true # нужно для sysctl, модулей ядра
|
||
groups:
|
||
- k3s_master # добавить в Ansible-группу для шаблонов
|
||
provisioner:
|
||
name: ansible
|
||
verifier:
|
||
name: ansible
|
||
```
|
||
|
||
**`converge.yml`** — что запускать (не весь role, а конкретные задачи):
|
||
```yaml
|
||
- name: Converge
|
||
hosts: all
|
||
become: true
|
||
vars:
|
||
my_var: "test-value" # все нужные переменные задаются здесь
|
||
tasks:
|
||
- name: Render template
|
||
ansible.builtin.template:
|
||
src: "{{ playbook_dir }}/../../templates/my-template.j2"
|
||
dest: /tmp/result.yaml
|
||
```
|
||
|
||
**`verify.yml`** — что проверять:
|
||
```yaml
|
||
- name: Verify
|
||
hosts: all
|
||
tasks:
|
||
- name: Read result
|
||
ansible.builtin.slurp:
|
||
src: /tmp/result.yaml
|
||
register: raw
|
||
|
||
- name: Parse YAML
|
||
ansible.builtin.set_fact:
|
||
result: "{{ raw.content | b64decode | from_yaml }}"
|
||
|
||
- name: Assert key exists
|
||
ansible.builtin.assert:
|
||
that: result.myKey == 'expected-value'
|
||
fail_msg: "Ожидалось 'expected-value', получено: {{ result.myKey }}"
|
||
```
|
||
|
||
---
|
||
|
||
## Компоненты стека
|
||
|
||
### K3S HA (embedded etcd)
|
||
|
||
Три ноды в режиме `server` формируют etcd-кластер из 3 участников — кворум сохраняется при отказе **любой одной** ноды.
|
||
|
||
Порядок установки управляется `serial: 1` в `site.yml`:
|
||
|
||
```
|
||
master01 (cluster-init: true)
|
||
↓ готов, API отвечает на :6443
|
||
worker01 (server: https://192.168.1.10:6443)
|
||
↓ присоединился, etcd = 2/3
|
||
rpi01 (server: https://192.168.1.10:6443, NoSchedule taint)
|
||
↓ присоединился, etcd = 3/3, кворум достигнут
|
||
```
|
||
|
||
Конфигурационный файл K3S генерируется из шаблона `k3s-server-config.yaml.j2`. Шаблон автоматически определяет роль ноды:
|
||
|
||
```yaml
|
||
# master01 получает:
|
||
cluster-init: true
|
||
|
||
# worker01 и rpi01 получают:
|
||
server: "https://192.168.1.10:6443"
|
||
```
|
||
|
||
### kube-vip
|
||
|
||
Создаёт виртуальный IP через ARP (L2). VIP анонсируется всем устройствам в локальной сети. При отказе мастера VIP автоматически мигрирует на другую ноду в течение нескольких секунд.
|
||
|
||
Требования к VIP:
|
||
- В той же подсети что серверы
|
||
- Не занят другим устройством
|
||
- Не выдаётся DHCP сервером
|
||
|
||
**Автоопределение интерфейса:** Ansible определяет сетевой интерфейс каждой master-ноды через `ansible_default_ipv4.interface`. На Ubuntu это может быть `enp3s0`, на Raspberry Pi — `end0`, на VM — `eth0`. Принудительное задание: `kube_vip_interface: "eth0"` в `group_vars/all/main.yml`.
|
||
|
||
### NFS + CSI
|
||
|
||
NFS сервер разворачивается на master01. StorageClass автоматически именуется `nfs-<hostname NFS сервера>` — например, `nfs-master01`. Это позволяет сразу понять на какой сервер указывает StorageClass при `kubectl get sc`. Если NFS вынесен на отдельный хост с hostname `storage01`, StorageClass будет `nfs-storage01`.
|
||
|
||
Каждый PVC создаёт отдельную папку внутри `/srv/nfs/k8s`:
|
||
|
||
```
|
||
/srv/nfs/k8s/
|
||
├── default/ ← namespace
|
||
│ └── my-pvc/ ← имя PVC
|
||
│ └── pvc-xxx-yyy-zzz/ ← имя PV
|
||
│ └── данные...
|
||
```
|
||
|
||
NFS можно вынести на отдельный хост — задай в `inventory/hosts.ini`:
|
||
```ini
|
||
[nfs_server]
|
||
nfshost ansible_host=192.168.1.20 ansible_user=ubuntu
|
||
```
|
||
|
||
И в `group_vars/all/main.yml`:
|
||
```yaml
|
||
csi_nfs_server: "192.168.1.20"
|
||
```
|
||
|
||
### ingress-nginx
|
||
|
||
Устанавливается через Helm, отключает встроенный Traefik K3S, получает `LoadBalancer` IP от kube-vip. Настроен с JSON-логами, CORS, proxy timeouts и tolerations для всех типов нод (включая control-plane и RPi).
|
||
|
||
---
|
||
|
||
## Raspberry Pi
|
||
|
||
Роль автоматически определяет ARM-архитектуру и:
|
||
|
||
1. Включает `memory cgroup` в `/boot/cmdline.txt` (или `/boot/firmware/cmdline.txt` для Bookworm) и перезагружает RPi
|
||
2. Применяет пониженные kubelet-резервы (`cpu=50m, memory=128Mi`)
|
||
3. Настраивает пороги garbage collection образов (85%/80%)
|
||
4. Применяет taint `node-type=raspberry-pi:NoSchedule` — поды не планируются если нет явного toleration
|
||
|
||
**Рекомендуемая ОС:** Raspberry Pi OS Lite **64-bit (Bookworm)** для RPi 4/5.
|
||
|
||
Для деплоя **на** RPi добавь toleration в манифест:
|
||
```yaml
|
||
tolerations:
|
||
- key: "node-type"
|
||
operator: "Equal"
|
||
value: "raspberry-pi"
|
||
effect: "NoSchedule"
|
||
```
|
||
|
||
Чтобы снять ограничение и использовать RPi как обычную ноду — очисти `host_vars/rpi01/main.yml`:
|
||
```yaml
|
||
k3s_node_taints: []
|
||
```
|
||
|
||
---
|
||
|
||
## Docker образ
|
||
|
||
Образ собирается один раз через `make build` и содержит всё необходимое — как для управления кластером, так и для Molecule-тестирования:
|
||
|
||
```
|
||
FROM python:3.12-slim-bookworm
|
||
├── openssh-client, curl, jq, git
|
||
├── docker-ce-cli (Docker CLI для Molecule)
|
||
├── ansible-core 2.16, ansible 9.x
|
||
├── kubernetes, openshift Python пакеты
|
||
├── molecule >= 6.0 + molecule-plugins[docker]
|
||
├── yamllint, ansible-lint
|
||
├── Helm 3.14.4
|
||
├── kubectl v1.29.3
|
||
└── Ansible Collections:
|
||
├── community.general >= 8.0
|
||
├── ansible.posix >= 1.5
|
||
└── kubernetes.core >= 3.0
|
||
```
|
||
|
||
При обычном запуске (`make install`, `make ping`):
|
||
|
||
| Путь в контейнере | Источник | Назначение |
|
||
|---|---|---|
|
||
| `/ansible` | `$(PWD)` | Весь проект |
|
||
| `/root/.ssh` | `~/.ssh` (read-only) | SSH ключи |
|
||
|
||
При Molecule-запуске (`make molecule-*`) дополнительно:
|
||
|
||
| Путь в контейнере | Источник | Назначение |
|
||
|---|---|---|
|
||
| `/var/run/docker.sock` | хост | Docker socket для создания тестовых контейнеров |
|
||
|
||
---
|
||
|
||
## Обновление K3S
|
||
|
||
```bash
|
||
make upgrade VERSION=v1.30.0+k3s1
|
||
```
|
||
|
||
Происходит по очереди (`serial: 1` в `upgrade.yml`):
|
||
1. Drain ноды → вытеснить поды
|
||
2. Обновить K3S бинарник
|
||
3. Перезапустить сервис
|
||
4. Дождаться `Ready`
|
||
5. Uncordon → восстановить планирование
|
||
6. Перейти к следующей ноде
|
||
|
||
HA-режим гарантирует доступность кластера во время обновления: пока одна нода обновляется, остальные две обслуживают трафик.
|
||
|
||
---
|
||
|
||
## Диагностика
|
||
|
||
```bash
|
||
make health # Статус сервисов, поды, диск, память
|
||
make verify # Nodes, pods, services, storageclass, ingress
|
||
|
||
# Открыть shell в ansible-контейнере:
|
||
make shell
|
||
|
||
# Внутри контейнера:
|
||
ansible all -m shell -a "systemctl status k3s"
|
||
ansible k3s_master -m shell -a "k3s kubectl get nodes -o wide"
|
||
ansible k3s_master -m shell -a "k3s kubectl get pods -A | grep -v Running"
|
||
ansible k3s_master -m shell -a "k3s etcd-snapshot ls"
|
||
|
||
# Локально с kubectl (после make install):
|
||
export KUBECONFIG=$(pwd)/kubeconfig
|
||
kubectl top nodes
|
||
kubectl top pods -A
|
||
kubectl get events -A --sort-by='.lastTimestamp' | tail -20
|
||
```
|
||
|
||
### Проверка etcd
|
||
|
||
```bash
|
||
# Статус участников etcd
|
||
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/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
|
||
```
|
||
|
||
---
|
||
|
||
## Примеры манифестов
|
||
|
||
### Приложение с Ingress и NFS-хранилищем
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: app-data
|
||
spec:
|
||
accessModes: [ReadWriteMany]
|
||
storageClassName: nfs-master01 # или имя твоего NFS сервера
|
||
resources:
|
||
requests:
|
||
storage: 5Gi
|
||
---
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: my-app
|
||
spec:
|
||
replicas: 2
|
||
selector:
|
||
matchLabels: {app: my-app}
|
||
template:
|
||
metadata:
|
||
labels: {app: my-app}
|
||
spec:
|
||
containers:
|
||
- name: app
|
||
image: nginx:alpine
|
||
volumeMounts:
|
||
- mountPath: /data
|
||
name: storage
|
||
volumes:
|
||
- name: storage
|
||
persistentVolumeClaim:
|
||
claimName: app-data
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: my-app
|
||
spec:
|
||
selector: {app: my-app}
|
||
ports: [{port: 80}]
|
||
---
|
||
apiVersion: networking.k8s.io/v1
|
||
kind: Ingress
|
||
metadata:
|
||
name: my-app
|
||
spec:
|
||
ingressClassName: nginx
|
||
rules:
|
||
- host: myapp.local
|
||
http:
|
||
paths:
|
||
- path: /
|
||
pathType: Prefix
|
||
backend:
|
||
service:
|
||
name: my-app
|
||
port:
|
||
number: 80
|
||
```
|
||
|
||
### Приложение в Istio mesh
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Namespace
|
||
metadata:
|
||
name: my-app
|
||
labels:
|
||
istio-injection: enabled # автоматически внедрять sidecar
|
||
---
|
||
apiVersion: networking.istio.io/v1alpha3
|
||
kind: VirtualService
|
||
metadata:
|
||
name: my-app
|
||
namespace: my-app
|
||
spec:
|
||
hosts: ["myapp.local"]
|
||
gateways: ["istio-system/istio-ingressgateway"]
|
||
http:
|
||
- route:
|
||
- destination:
|
||
host: my-app
|
||
port:
|
||
number: 80
|
||
```
|
||
|
||
### Приложение только на x86 нодах (не на RPi)
|
||
|
||
```yaml
|
||
spec:
|
||
template:
|
||
spec:
|
||
nodeSelector:
|
||
node-type: x86_64
|
||
```
|
||
|
||
### Приложение с доступом к Grafana/Prometheus (ServiceMonitor)
|
||
|
||
```yaml
|
||
apiVersion: monitoring.coreos.com/v1
|
||
kind: ServiceMonitor
|
||
metadata:
|
||
name: my-app
|
||
namespace: my-app
|
||
labels:
|
||
release: prom # должен совпадать с prometheus_stack_release_name
|
||
spec:
|
||
selector:
|
||
matchLabels:
|
||
app: my-app
|
||
endpoints:
|
||
- port: metrics
|
||
interval: 30s
|
||
```
|
||
|
||
---
|
||
|
||
## Решение проблем
|
||
|
||
| Проблема | Решение |
|
||
|---|---|
|
||
| `Permission denied (publickey)` | `ssh-copy-id user@server` |
|
||
| `Vault decryption failed` | Проверь `VAULT_PASSWORD` в `.env` |
|
||
| kube-vip VIP не пингуется | Проверь `kube_vip_interface` — должен совпадать с `ip a` на master01 |
|
||
| RPi K3S не стартует | `ssh pi@rpi "cat /proc/cgroups \| grep memory"` — должна быть `1` |
|
||
| PVC в Pending | `kubectl get events -A` — проверь монтирование NFS на нодах |
|
||
| ingress EXTERNAL-IP `<pending>` | `kubectl -n kube-system get pods \| grep kube-vip` — поды kube-vip должны быть Running |
|
||
| etcd нет кворума | Нужно минимум 2 из 3 нод. `kubectl get nodes` — проверь статус |
|
||
| worker01/rpi01 не присоединились | Убедись что master01 полностью стартовал. `serial: 1` гарантирует порядок, но нода должна быть доступна |
|
||
| Kiali: "Could not get token" | Проверь: `kubectl -n istio-system get secret kiali-admin-token` |
|
||
| Grafana PVC Pending | `kubectl get sc` — `nfs-client` должен быть `(default)`. Проверь NFS сервер: `exportfs -v` |
|
||
| 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 |
|
||
|
||
### Отладка с подробным выводом
|
||
|
||
```bash
|
||
ANSIBLE_VERBOSITY=2 make install # основной вывод
|
||
ANSIBLE_VERBOSITY=4 make install # максимальный вывод (SSH, модули)
|
||
ANSIBLE_VERBOSITY=2 ANSIBLE_TAGS=k3s make install # подробно только для k3s
|
||
```
|
||
|
||
### Сброс и повторная установка
|
||
|
||
```bash
|
||
# Удалить весь стек (с подтверждением)
|
||
make uninstall
|
||
|
||
# Затем установить заново
|
||
make install
|
||
```
|