# 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 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- # Например: 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-.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=` на первом мастере (процесс завершается после сброса) 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-` — например, `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 `` | `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 ` — проверь условия. При 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 ```