Sergey Antropoff 095b276cb3 first commit
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00
2026-04-17 08:37:27 +03:00

K3S Ansible Stack

Полный Kubernetes стек на базе K3S с HA (High Availability), управляемый через Ansible внутри Docker-контейнера. Ansible устанавливать не нужно — всё работает через make.

Содержание


Архитектура

Кластер работает в 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-client (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
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 контейнера

Требования

На твоей машине (откуда запускаешь)

Инструмент Версия Установка
Docker >= 24.0 https://docs.docker.com/get-docker/
make любая apt install make / brew install make
SSH ключ ssh-keygen -t ed25519

Ansible, Helm, kubectl — устанавливать не нужно, они внутри Docker.

Для Molecule-тестирования дополнительно нужен Python 3.9+:

pip install -r requirements-python.txt

На серверах кластера

Требование Описание
ОС 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 без пароля на каждом сервере:

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
│
├── 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
    │
    ├── 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 на свои):

# Создать пользователя с 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 ключи

# Создать ключ (если нет)
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 — Клонировать и настроить проект

git clone <url> k3s-ansible && cd k3s-ansible

# Создать .env
make setup

Отредактируй .env — задай пароль от vault:

# .env
VAULT_PASSWORD=придумай-надёжный-пароль

Шаг 4 — Инвентарь

Отредактируй inventory/hosts.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. Обязательные поля:

kube_vip_address: "192.168.1.100"   # свободный IP, не в DHCP пуле
kube_vip_interface: "eth0"          # узнать: ssh ubuntu@192.168.1.10 "ip -br a"

Шаг 6 — Vault с секретами

# Сгенерировать токен K3S
openssl rand -hex 32
# → a3f8c2d1e9b04756...

make vault-create

В открывшемся редакторе введи (замени значения на свои):

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 образ

make build
# Занимает ~3-5 минут при первом запуске

Шаг 8 — Прогнать тесты (рекомендуется)

Перед деплоем убедись что роли корректны:

# Установить зависимости для Molecule (один раз)
pip install -r requirements-python.txt

# Запустить все тесты (~10-15 минут)
make molecule-all

Если всё зелёное — можно деплоить. Если есть ошибки — смотри раздел Тестирование через Molecule.

Шаг 9 — Проверить SSH и dry-run

# Проверить доступность всех нод
make ping
# Ожидаемый вывод: SUCCESS для каждой ноды

# Проверить плейбук без применения изменений
make check

Шаг 10 — Развернуть базовый стек

make install

Плейбук выполняет всё последовательно (serial: 1):

  1. master01: prereqs → K3S server (cluster-init) → ждёт готовности API → kube-vip
  2. worker01: prereqs → K3S server (join) → ждёт готовности ноды
  3. rpi01: prereqs + cgroups → K3S server (join) → применяет taint NoSchedule
  4. NFS server на master01
  5. CSI NFS Driver на всех нодах
  6. ingress-nginx через Helm
  7. Финальная проверка: nodes / pods / svc / storageclass

Ожидаемое время: 15-25 минут в зависимости от скорости интернета.

Шаг 11 — Проверить результат

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-client (default)   nfs.csi.k8s.io   Delete          Immediate

Шаг 12 (опционально) — Установить Istio + мониторинг

# Добавь в 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
# 1. Внёс изменение в roles/k3s/ или roles/prometheus-stack/
# 2. Запустить тест только нужной роли
make molecule-k3s          # или molecule-prometheus / molecule-istio

# 3. Линтинг всего проекта
make molecule-lint

# 4. Проверить что плейбук парсится корректно (не применяет изменения)
make check

# 5. Деплой на реальные серверы
make install

Тегированный деплой (обновить только один компонент)

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

# 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
k3s_disable_traefik: true       # ОБЯЗАТЕЛЬНО true при ingress-nginx

kube-vip

kube_vip_address: "192.168.1.100"   # Свободный IP — обязательно задать!
kube_vip_interface: "eth0"          # Интерфейс master01 (ip a)
kube_vip_mode: "arp"                # arp (L2) для домашних сетей | bgp (L3)

NFS / CSI

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

ingress-nginx

ingress_nginx_service_type: "LoadBalancer"
ingress_nginx_load_balancer_ip: ""  # "" = авто от kube-vip
ingress_nginx_set_default_class: true

Индивидуальные настройки нод (host_vars/)

master01 — дополнительные labels и server args:

# 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 запрещает планирование обычных подов:

# 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 — очисти список:

k3s_node_taints: []

Ansible Vault

make vault-create                                        # Создать
make vault-edit                                          # Редактировать
make vault-view                                          # Просмотреть
make vault-encrypt-string STR="токен" NAME="vault_k3s_token"

Полный шаблон group_vars/all/vault.yml:

# Обязательно:
vault_k3s_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Если используешь Prometheus stack:
vault_grafana_user: "admin"
vault_grafana_password: "мой-безопасный-пароль"

# Если используешь Kiali (заполни после первой установки):
vault_kiali_token: ""

Опциональные компоненты

Включаются через переменные в group_vars/all/main.yml. По умолчанию все отключены — базовый стек работает без них.


Istio (Service Mesh)

Устанавливает istio/base (CRDs) → istiod (control plane) → istio/gateway (LoadBalancer) → глобальную политику mTLS.

Включить:

# group_vars/all/main.yml
istio_enabled: true

Или разово без изменения файлов:

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.

Включить:

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:

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:

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.

Включить:

prometheus_stack_enabled: true

Или разово:

make install-monitoring

Хранилище (PVC):

Компонент Переменная Умолч. Назначение
Prometheus prometheus_storage_size 10Gi Метрики временных рядов
Grafana grafana_storage_size 5Gi Дашборды, плагины, настройки
Alertmanager prometheus_alertmanager_storage_size 2Gi Состояние алертов

Изменить размер:

# 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:

vault_grafana_user: "admin"
vault_grafana_password: "мой-пароль"

Включить Ingress вместо NodePort:

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 и настраивается на их использование без дополнительной конфигурации.


Все команды Make

Настройка и сборка

make setup          # Создать .env из шаблона
make build          # Собрать Docker образ (~5 мин)
make rebuild        # Пересобрать без кэша

Проверки

make ping           # SSH до всех нод
make check          # Dry-run без изменений
make lint           # Проверить синтаксис плейбуков

Установка

make install              # Полный базовый стек
make install-k3s          # Только K3S HA кластер
make install-kubevip      # Только kube-vip
make install-nfs          # NFS + CSI
make install-ingress      # ingress-nginx
make install-istio        # Istio + Kiali (нужен istio_enabled: true в vars)
make install-monitoring   # Prometheus + Grafana (нужен prometheus_stack_enabled: true)

Тестирование (Molecule)

make molecule-k3s           # Тест роли k3s (~5-8 мин)
make molecule-prometheus    # Тест роли prometheus-stack (~2-3 мин)
make molecule-istio         # Тест роли istio (~2-3 мин)
make molecule-all           # Все тесты последовательно (~15 мин)
make molecule-lint          # Только линтинг YAML+ansible-lint (без Docker, ~30 сек)

Обновление и диагностика

make upgrade VERSION=v1.30.0+k3s1   # Обновить K3S
make health                          # Полная диагностика
make verify                          # Сводка стека

Vault

make vault-create
make vault-edit
make vault-view
make vault-encrypt-string STR=... NAME=...

Прочее

make shell          # bash внутри ansible-контейнера
make uninstall      # Удалить весь стек (с подтверждением)
make clean          # Удалить Docker образ
make clean-all      # Образ + kubeconfig + кэш Docker

Переменные командной строки

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-контейнере, проходит набор автоматических проверок и удаляется. Реальные серверы при этом не нужны.

Установка

# Установить зависимости (один раз)
pip install -r requirements-python.txt

# Проверить
molecule --version
# molecule 6.x.x ...

docker --version
# Docker version 24.x.x ...

Жизненный цикл теста

Команда 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 (privileged Docker, ~5-8 мин)

Использует Docker-контейнер с systemd в привилегированном режиме — это необходимо для тестирования apt, sysctl и модулей ядра.

Тестируемые задачи:

  • prereqs.yml — установка пакетов (curl, ca-certificates, iptables), отключение swap, загрузка модулей ядра (overlay, br_netfilter), настройка sysctl

Рендеринг шаблона:

  • k3s-server-config.yaml.j2/etc/rancher/k3s/config.yaml

Что проверяет verify.yml:

Проверка Что именно
Директория /etc/rancher/k3s Создана роль prereqs
Файл config.yaml Существует и имеет права 0600
YAML синтаксис Файл парсится как корректный YAML
token Установлен из переменной
cluster-cidr Равен 10.42.0.0/16
service-cidr Равен 10.43.0.0/16
cluster-init: true Первый мастер инициализирует кластер
disable: [traefik] Traefik выключен (используется ingress-nginx)
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

Запуск тестов: пошагово

Быстрая проверка (lint, без Docker)

make molecule-lint

Запускает yamllint . и ansible-lint на всём проекте. Выполняется за ~30 секунд, Docker не нужен. Используй перед каждым коммитом.

Запуск линтинга...
yamllint .
ansible-lint
✓ Линтинг прошёл

Тест одной роли

make molecule-k3s

Вывод (успешный):

INFO     default scenario test matrix: dependency, lint, cleanup, destroy, syntax, create, prepare, converge, idempotency, verify, cleanup, destroy
INFO     Performing prerun with role_name_check=1...

INFO     Running default > create
INFO     Sanity checks: 'docker'

INFO     Running default > prepare
PLAY [Prepare k3s test environment] ****

TASK [Wait for systemd to start] ***
ok: [k3s-node]

TASK [Install Python3] ***
changed: [k3s-node]

INFO     Running default > converge
PLAY [Converge — k3s role unit tests] ****

TASK [Mock k3s binary] ***
changed: [k3s-node]

TASK [Test prereqs — install packages] ***
changed: [k3s-node] => (item=curl)
changed: [k3s-node] => (item=iptables)

TASK [Test server config template rendering] ***
changed: [k3s-node]

INFO     Running default > idempotency
PLAY [Converge — k3s role unit tests] ****
...
PLAY RECAP
k3s-node  : ok=12  changed=0  unreachable=0  failed=0

INFO     Idempotency completed successfully.

INFO     Running default > verify
PLAY [Verify — k3s role] ****

TASK [Assert config directory] ***
ok: [k3s-node] => {"changed": false, "msg": "All assertions passed"}

TASK [Assert config file exists] ***
ok: [k3s-node] => {"changed": false, "msg": "All assertions passed"}

TASK [Assert cluster-init is set] ***
ok: [k3s-node] => {"changed": false, "msg": "All assertions passed"}

TASK [Assert traefik is disabled] ***
ok: [k3s-node] => {"changed": false, "msg": "All assertions passed"}

TASK [Assert ip_forward is 1] ***
ok: [k3s-node] => {"changed": false, "msg": "All assertions passed"}

TASK [Summary] ***
ok: [k3s-node] => {
    "msg": "Все проверки прошли успешно для ноды k3s-node"
}

INFO     Running default > destroy
INFO     Pruning extra files from scenario ephemeral directory

✓ k3s role: OK

Все тесты

make molecule-all
Тестирую роль k3s...
...
✓ k3s role: OK

Тестирую роль prometheus-stack...
...
✓ prometheus-stack role: OK

Тестирую роль istio...
...
✓ istio role: OK

✓ Все тесты прошли успешно

Отладка упавших тестов

Если тест упал — контейнер удаляется автоматически. Чтобы оставить контейнер живым для ручной отладки, используй отдельные фазы:

# Перейти в директорию роли
cd roles/prometheus-stack

# Только создать контейнер и запустить задачи (без удаления)
molecule converge

# Проверить вручную внутри контейнера
molecule login
# Теперь ты внутри Docker-контейнера:
cat /tmp/prometheus-stack-values.yaml
python3 -c "import yaml; yaml.safe_load(open('/tmp/prometheus-stack-values.yaml'))"
exit

# Запустить только verify (assertions)
molecule verify

# Посмотреть полный вывод с -vvv
molecule converge -- -vvv

# Удалить контейнер когда закончил
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

Запуск конкретной фазы вручную

cd roles/k3s
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 — конфигурация драйвера и платформы:

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, а конкретные задачи):

- 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 — что проверять:

- 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. Шаблон автоматически определяет роль ноды:

# master01 получает:
cluster-init: true

# worker01 и rpi01 получают:
server: "https://192.168.1.10:6443"

kube-vip

Создаёт виртуальный IP через ARP (L2). VIP анонсируется всем устройствам в локальной сети. При отказе мастера VIP автоматически мигрирует на другую ноду в течение нескольких секунд.

Требования к VIP:

  • В той же подсети что серверы
  • Не занят другим устройством
  • Не выдаётся DHCP сервером

NFS + CSI

NFS сервер разворачивается на master01. Каждый PVC создаёт отдельную папку внутри /srv/nfs/k8s:

/srv/nfs/k8s/
├── default/                    ← namespace
│   └── my-pvc/                 ← имя PVC
│       └── pvc-xxx-yyy-zzz/   ← имя PV
│           └── данные...

NFS можно вынести на отдельный хост — задай в inventory/hosts.ini:

[nfs_server]
nfshost ansible_host=192.168.1.20 ansible_user=ubuntu

И в group_vars/all/main.yml:

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 в манифест:

tolerations:
  - key: "node-type"
    operator: "Equal"
    value: "raspberry-pi"
    effect: "NoSchedule"

Чтобы снять ограничение и использовать RPi как обычную ноду — очисти host_vars/rpi01/main.yml:

k3s_node_taints: []

Docker образ

Образ собирается один раз через make build и содержит всё необходимое для управления кластером:

FROM python:3.12-slim-bookworm
├── openssh-client, curl, jq, git
├── ansible-core 2.16, ansible 9.x
├── kubernetes, openshift Python пакеты
├── Helm 3.14.4
├── kubectl v1.29.3
└── Ansible Collections:
    ├── community.general >= 8.0
    ├── ansible.posix >= 1.5
    └── kubernetes.core >= 3.0

При запуске монтируются:

Путь в контейнере Источник Назначение
/ansible $(PWD) Весь проект
/root/.ssh ~/.ssh (read-only) SSH ключи

Обновление K3S

make upgrade VERSION=v1.30.0+k3s1

Происходит по очереди (serial: 1 в upgrade.yml):

  1. Drain ноды → вытеснить поды
  2. Обновить K3S бинарник
  3. Перезапустить сервис
  4. Дождаться Ready
  5. Uncordon → восстановить планирование
  6. Перейти к следующей ноде

HA-режим гарантирует доступность кластера во время обновления: пока одна нода обновляется, остальные две обслуживают трафик.


Диагностика

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

# Статус участников 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/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 \
     member list

Примеры манифестов

Приложение с Ingress и NFS-хранилищем

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes: [ReadWriteMany]
  storageClassName: nfs-client
  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

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)

spec:
  template:
    spec:
      nodeSelector:
        node-type: x86_64

Приложение с доступом к Grafana/Prometheus (ServiceMonitor)

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 scnfs-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 и исправь указанные файлы

Отладка с подробным выводом

ANSIBLE_VERBOSITY=2 make install                # основной вывод
ANSIBLE_VERBOSITY=4 make install                # максимальный вывод (SSH, модули)
ANSIBLE_VERBOSITY=2 ANSIBLE_TAGS=k3s make install  # подробно только для k3s

Сброс и повторная установка

# Удалить весь стек (с подтверждением)
make uninstall

# Затем установить заново
make install
Description
No description provided
Readme 1.9 MiB
Languages
Jinja 56.1%
Makefile 20%
Python 10.7%
Shell 7.7%
Smarty 3.8%
Other 1.7%