- SMTP Relay (bokysan/mail): Postfix relay через Yandex SMTP, порт 465 с TLS wrappermode, trusted networks only (pod/service CIDR), без аутентификации внутри кластера — поды отправляют на smtp-relay:25 - HashiCorp Vault (hashicorp/vault): standalone и HA (Raft) режимы, auto-unseal: k8s Secret (homelab), AWS KMS, GCP CKMS, Azure Key Vault, Transit; Vault Agent Injector по умолчанию; Job инициализации + Unsealer Deployment для k8s режима; README с полным гайдом по injection в YAML/Helm - External Secrets Operator (ESO): синхронизирует Vault секреты в k8s Secrets, ClusterSecretStore с AppRole auth, README с примерами ExternalSecret в YAML манифестах, Helm чартах и ArgoCD Обновлены: addons.yml (3 новых флага + секции), vault.yml.example (smtp_relay_password, aws_kms_*, eso_approle_secret_id), playbooks/addons.yml, Makefile
K3S Ansible Stack
Полный Kubernetes стек на базе K3S с HA (High Availability), управляемый через Ansible внутри Docker-контейнера. Ansible устанавливать не нужно — всё работает через
make.
Содержание
- Архитектура
- Требования
- Структура проекта
- Полная установка с нуля
- Рабочий процесс
- Настройка кластера
- Опциональные компоненты
- Управление нодами
- Резервное копирование etcd
- Все команды Make
- Тестирование через Molecule
- Компоненты стека
- Raspberry Pi
- Обновление 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:
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).
Установить отдельно:
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 без пароля на каждом сервере:
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 на свои):
# Создать пользователя с 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/enp3s0/end0/...)
# переопредели только если авто не работает
k3s_cni: "flannel" # flannel (умолч.) | calico | cilium
kube_vip_interface определяется автоматически через ansible_default_ipv4.interface — работает для Ubuntu, Debian, Raspberry Pi OS без дополнительных настроек.
Шаг 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 запускается внутри Docker — устанавливать ничего не нужно.
# Убедись что образ собран (если ещё не сделал на шаге 7):
# make build
# Запустить все тесты (~15-20 минут)
make molecule-all
Что поднимается: 3 контейнера (master01/worker01 на Ubuntu 22.04 + rpi01 на Debian 12), тест конфигурации K3S HA, шаблонов Prometheus и Istio.
Если всё зелёное — можно деплоить. Если есть ошибки — смотри раздел Тестирование через Molecule.
Шаг 9 — Проверить SSH и dry-run
# Проверить доступность всех нод
make ping
# Ожидаемый вывод: SUCCESS для каждой ноды
# Проверить плейбук без применения изменений
make check
Шаг 10 — Развернуть базовый стек
make install
Плейбук выполняет всё последовательно (serial: 1):
- master01: prereqs → K3S server (cluster-init) → ждёт готовности API
- worker01: prereqs → K3S server (join) → ждёт готовности ноды
- rpi01: prereqs + cgroups → K3S server (join) → применяет taint NoSchedule
- CNI плагин (если не Flannel — устанавливается Calico или Cilium)
- kube-vip: VIP + LoadBalancer (интерфейс определяется автоматически)
- NFS server на master01
- CSI NFS Driver на всех нодах → StorageClass
nfs-master01 - ingress-nginx через Helm
- cert-manager (только если
cert_manager_enabled: true) - Финальная проверка: 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-master01 (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 (только для 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:
k3s_cni: "calico"
calico_version: "v3.28.0"
calico_encapsulation: "VXLAN" # VXLAN | IPIP | None
Cilium:
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
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
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
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. По умолчанию все отключены — базовый стек работает без них.
cert-manager (TLS сертификаты)
Автоматически выдаёт и обновляет TLS сертификаты в кластере. Поддерживает самоподписанные сертификаты (для внутреннего использования) и Let's Encrypt (для публичных доменов).
Включить:
# group_vars/all/main.yml
cert_manager_enabled: true
Или разово:
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— базовый самоподписанный issuercluster-ca— Certificate (CA сертификат)cluster-ca-issuer— ClusterIssuer для выдачи сертификатов приложениям
Пример использования в манифестах:
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:
cert_manager_issuer: "letsencrypt"
cert_manager_acme_email: "admin@example.com"
cert_manager_acme_server: "prod" # или staging для тестирования
Добавить аннотацию на Ingress:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
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:
- Создаёт ServiceAccount
kiali-adminс правамиcluster-admin - Создаёт Secret типа
kubernetes.io/service-account-token - Читает сгенерированный 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 и настраивается на их использование без дополнительной конфигурации.
Управление нодами
Добавить ноду
Перед добавлением:
- Добавь ноду в
inventory/hosts.iniв нужную группу (k3s_masterилиk3s_workers) - Убедись что SSH доступ работает:
make ping
# Добавить мастер-ноду (станет etcd участником)
make add-node NODE=master04
# Добавить рабочую ноду (только agent, без etcd)
make add-node NODE=worker04
Новые ноды подключаются через VIP (kube_vip_address) — kube-vip должен быть запущен. После добавления нода автоматически появляется в кластере.
Пример 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
[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
Удалить ноду
make remove-node NODE=worker04
Порядок удаления:
cordon— запретить планирование новых подовdrain— вытеснить все поды на другие ноды (таймаут 180 сек)delete— удалить ноду из Kubernetes API- Запустить uninstall-скрипт K3S на удалённой ноде
- Очистить директории 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).
Создать снимок
make etcd-backup
Создаёт снимок с автоматическим именем k3s-etcd-<timestamp>.db, удаляет старые снимки сверх etcd_backup_retention (по умолчанию 5).
Опционально — скопировать снимок на локальную машину:
# group_vars/all/main.yml
etcd_backup_copy_to_local: true
etcd_backup_local_dir: "./etcd-backups"
Список снимков
make etcd-list-snapshots
Восстановить из снимка
# Посмотреть доступные снимки
make etcd-list-snapshots
# Восстановить (с подтверждением — введи 'yes')
make etcd-restore SNAPSHOT=k3s-etcd-20250101T120000.db
# Без интерактивного подтверждения
FORCE=true make etcd-restore SNAPSHOT=k3s-etcd-20250101T120000.db
Что происходит при восстановлении:
- K3S останавливается на всех мастерах и воркерах
- Запускается
k3s server --cluster-reset --cluster-reset-restore-path=<snapshot>на первом мастере (процесс завершается после сброса) - K3S запускается на первом мастере → ждёт готовности API
- K3S запускается на остальных мастерах
- 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
Настройка и сборка
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-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)
Управление нодами
make add-node NODE=worker04 # Добавить ноду в кластер
make remove-node NODE=worker04 # Безопасно удалить ноду
etcd — резервное копирование
make etcd-backup # Создать снимок etcd
make etcd-list-snapshots # Список доступных снимков
make etcd-restore SNAPSHOT=k3s-etcd-XXX.db # Восстановить из снимка
Тестирование (Molecule — всё в Docker, pip не нужен)
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 сек)
Обновление и диагностика
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-контейнерах, проходит набор автоматических проверок и удаляется. Реальные серверы при этом не нужны.
Требования
Только Docker — Molecule, Python и зависимости уже внутри образа k3s-ansible. Устанавливать ничего дополнительно не нужно.
# Убедись что образ собран:
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.yamlkiali-values.yaml.j2→/tmp/kiali-values.yamlpeer-authentication.yaml.j2→/tmp/peer-authentication.yamlkiali-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 сек)
make molecule-lint
Запускает yamllint . и ansible-lint на всём проекте внутри Docker-контейнера. Используй перед каждым коммитом.
Запуск линтинга...
✓ Линтинг прошёл
Тест одной роли
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
Все тесты
make molecule-all
Тестирую роль k3s (3 ноды: master01, worker01, rpi01)...
...
✓ k3s role: OK
Тестирую роль prometheus-stack...
...
✓ prometheus-stack role: OK
Тестирую роль istio...
...
✓ istio role: OK
✓ Все тесты прошли успешно
Отладка упавших тестов
Если тест упал — контейнеры удаляются автоматически. Чтобы оставить контейнеры живыми для ручной отладки, войди в интерактивный shell внутри Molecule-контейнера и запускай отдельные фазы:
# Открыть 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 контейнера:
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-контейнера)
# Войти в 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 — конфигурация драйвера и платформы:
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 сервером
Автоопределение интерфейса: 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:
[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-архитектуру и:
- Включает
memory cgroupв/boot/cmdline.txt(или/boot/firmware/cmdline.txtдля Bookworm) и перезагружает RPi - Применяет пониженные kubelet-резервы (
cpu=50m, memory=128Mi) - Настраивает пороги garbage collection образов (85%/80%)
- Применяет 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 и содержит всё необходимое — как для управления кластером, так и для 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
make upgrade VERSION=v1.30.0+k3s1
Происходит по очереди (serial: 1 в upgrade.yml):
- Drain ноды → вытеснить поды
- Обновить K3S бинарник
- Перезапустить сервис
- Дождаться
Ready - Uncordon → восстановить планирование
- Перейти к следующей ноде
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/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-хранилищем
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
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 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 |
Отладка с подробным выводом
ANSIBLE_VERBOSITY=2 make install # основной вывод
ANSIBLE_VERBOSITY=4 make install # максимальный вывод (SSH, модули)
ANSIBLE_VERBOSITY=2 ANSIBLE_TAGS=k3s make install # подробно только для k3s
Сброс и повторная установка
# Удалить весь стек (с подтверждением)
make uninstall
# Затем установить заново
make install