# Kubernetes Infrastructure > **Корпоративный стандарт.** Ansible-развёртывание кластера Kubernetes на Rocky Linux 9. > Локальный запуск через CLI и автоматизированный запуск через GitLab CI/CD. --- ## Содержание - [Инфраструктура](#инфраструктура) - [Стек технологий](#стек-технологий) - [Структура репозитория](#структура-репозитория) - [Роли и компоненты](#роли-и-компоненты) - - **Стандарт работы с хранилищем** [`roles/longhorn/README.md`](roles/longhorn/README.md) - - **Стандарт публикации сервисов** [`roles/traefik/README.md`](roles/traefik/README.md) - - **Стандарт GitOps-деплоя приложений** [`roles/flux/README.md`](roles/flux/README.md) - - **Стандарт объектного хранилища S3** [`roles/minio/README.md`](roles/minio/README.md) - - **Стандарт централизованного сбора логов** [`roles/logging/README.md`](roles/logging/README.md) - - Sealed Secrets (kubeseal) - [CI/CD-пайплайн](#cicd-пайплайн) - [Порядок развёртывания](#порядок-развёртывания) - [Архитектурные решения](#архитектурные-решения) - [Локальная разработка](#локальная-разработка) - [Добавление нового компонента](#добавление-нового-компонента) --- ## Инфраструктура ``` ┌─────────────────────────────────────────────────────────────────┐ │ Operator workstation (manager_nodes) │ │ k8s-manager-01 10.203.0.92 │ │ kubectl · helm · k9s · kubectx/kubens │ │ Kubernetes Dashboard token → ~/.kube/dashboard-token │ └────────────────────────┬────────────────────────────────────────┘ │ kubectl / helm (kubeconfig) ┌────────────────────────▼────────────────────────────────────────┐ │ Kubernetes cluster (k8s_cluster) │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Control plane k8s-master-01 10.203.0.97 │ │ │ │ kube-apiserver · etcd · controller-manager · scheduler │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Worker k8s-worker-01 10.203.0.96 │ │ │ │ kubelet · kube-proxy · Longhorn disk agent │ │ │ │ Traefik DaemonSet │ │ │ │ :10001 → kubernetes-dashboard (ClusterIP :443) │ │ │ │ :10002 → longhorn-frontend (ClusterIP :80) [auth] │ │ │ │ :10003 → grafana (ClusterIP :80) │ │ │ │ :10005 → minio (ClusterIP :9000) │ │ │ │ :10006 → minio-console (ClusterIP :9001) │ │ │ │ :10103 → db/bgbilling-dev (TCP :3306) │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ CNI: Flannel v0.26.1 │ Storage: Longhorn 1.7.2 │ └─────────────────────────────────────────────────────────────────┘ ``` ### Узлы кластера | Hostname | IP | Группа инвентаря | Роль | |---|---|---|---| | k8s-manager-01 | 10.203.0.92 | `manager_nodes` | Рабочая станция оператора | | k8s-master-01 | 10.203.0.97 | `control_plane` | API server, etcd, scheduler | | k8s-worker-01 | 10.203.0.96 | `workers` | Вычислительный узел | | k8s-worker-02 | 10.203.0.212 | `workers` → `gpu_workers` | GPU-узел (NVIDIA), Ollama-стек | | k8s-worker-NN | TBD | `workers` | Дополнительные вычислительные узлы | > `manager_nodes` — **не** Kubernetes-узел. Это рабочая станция оператора, с которой запускаются `kubectl` и `helm`. ### Сервисы и точки доступа | Сервис | URL | Аутентификация | |---|---|---| | Kubernetes Dashboard | `http://10.203.0.96:10001` | токен из `~/.kube/dashboard-token` | | Longhorn UI | `http://10.203.0.96:10002` | BasicAuth (Secret `traefik-auth-longhorn` в ns `traefik`) | | Grafana | `http://10.203.0.96:10003` | логин Grafana (admin, пароль из Secret `grafana-admin-secret`) | | MinIO S3 API | `http://10.203.0.96:10005` | AWS Signature v4 (встроенная аутентификация MinIO) | | MinIO Console | `http://10.203.0.96:10006` | логин MinIO (rootUser из Secret `minio-root-credentials`) | | MariaDB BGBilling | `10.203.0.96:10103` | TCP-проброс (без HTTP); аутентификация средствами MariaDB | | Ollama Proxy (k8s_ai) | `http://10.203.0.96:10200` | своя аутентификация приложения | | Open WebUI (k8s_ai) | `http://10.203.0.96:10201` | своя аутентификация приложения | --- ## Стек технологий | Компонент | Версия | Управляется | |---|---|---| | ОС | Rocky Linux 9 | Ansible | | Kubernetes | 1.33 (pkgs.k8s.io) | Ansible → `k8s_control_plane`, `k8s_worker` | | Container runtime | containerd (Docker CE repo) | Ansible | | CNI | Flannel v0.26.1 / Calico v3.27.0 | Ansible → `k8s_control_plane` | | Постоянное хранилище | Longhorn 1.7.2 | Ansible → `longhorn_prereqs`, `longhorn` | | Ingress / proxy | Traefik 32.1.0 | Ansible → `traefik` | | Kubernetes Dashboard | v2.7.0 | Ansible → `k8s_manager` | | Объектное хранилище | MinIO 5.4.0 (chart) | Ansible → `minio` | | Централизованные логи | Loki 7.0.0 + Grafana Alloy 1.8.2 + Grafana 10.5.15 | Ansible → `logging` | | Шифрование Secret'ов | Sealed Secrets 2.16.1 | Ansible → `sealed_secrets` | | GitOps | Flux CD (latest stable) | Ansible → `flux` | | GitLab Agent | agentk (latest stable) | Ansible → `gitlab_agent` | | Автоматизация | Ansible (посм. requirements.yml) | — | | CI/CD | GitLab CI/CD | `.gitlab-ci.yml` | --- ## Структура репозитория ``` .gitlab-ci.yml — CI/CD-пайплайн (стадии validate → setup) ansible.cfg — кэширование фактов, pipelining, YAML-вывод requirements.yml — коллекции Ansible Galaxy inventory/prod/ hosts.yml — статический список хостов (3 группы) group_vars/ all.yml — SSH-учётные данные из переменных окружения k8s_cluster.yml — общие переменные для control_plane + workers control_plane.yml — k8s_version, CIDR, CNI workers.yml — конфигурация дисков Longhorn manager_nodes.yml — версии инструментов, часовой пояс traefik.yml — traefik_port_map: таблица портов и backend-сервисов gpu_workers.yml — node_labels, override дисков Longhorn для GPU-узлов playbooks/ — один плейбук = одна CI/CD-задача setup_manager.yml setup_control_plane.yml setup_worker_plane.yml setup_longhorn.yml setup_traefik.yml setup_flux.yml setup_gitlab_agent.yml setup_minio.yml setup_logging.yml setup_sealed_secrets.yml setup_gpu.yml roles/ k8s_manager/ k8s_control_plane/ k8s_worker/ longhorn_prereqs/ longhorn/ README.md ← стандарт работы с хранилищем traefik/ README.md ← стандарт публикации сервисов flux/ README.md ← стандарт GitOps-деплоя приложений gitlab_agent/ minio/ README.md ← стандарт объектного хранилища S3 logging/ README.md ← стандарт централизованного сбора логов sealed_secrets/ gpu_prereqs/ — nvidia-container-toolkit + containerd runtime на GPU-узле gpu_device_plugin/ — node label + NVIDIA k8s-device-plugin с manager-ноды gpu_model_storage/ README.md ← стандарт локального RAID1-хранилища под модели ``` --- ## Роли и компоненты ### `k8s_control_plane` · `k8s_worker` · `k8s_manager` Базовые роли развёртывания кластера. Не имеют отдельного role-README — полное описание в разделе [Архитектурные решения](#архитектурные-решения). | Роль | Что делает | Плейбук | |---|---|---| | `k8s_control_plane` | containerd → kubelet → kubeadm init → CNI → kubeconfig | `setup_control_plane.yml` | | `k8s_worker` | containerd → kubelet → kubeadm join | `setup_worker_plane.yml` | | `k8s_manager` | kubectl / helm / k9s, Kubernetes Dashboard | `setup_manager.yml` |
Переменные k8s_control_plane | Переменная | Значение по умолчанию | Описание | |---|---|---| | `k8s_version` | `1.33` | Версия Kubernetes | | `pod_network_cidr` | `10.244.0.0/16` | Сеть подов (Flannel) | | `service_cidr` | `10.96.0.0/12` | Сеть сервисов | | `cni_plugin` | `flannel` | `flannel` или `calico` | | `flannel_version` | `v0.26.1` | Версия манифеста Flannel | | `calico_version` | `v3.27.0` | Версия манифеста Calico | | `kubeconfig_fetch_to_managers` | `true` | Копировать kubeconfig на manager-узел |
Переменные k8s_worker | Переменная | Значение по умолчанию | Описание | |---|---|---| | `k8s_version` | `1.33` | Должна совпадать с control plane | | `worker_join_source_host` | первый хост `control_plane` | Откуда читать join-команду |
Переменные k8s_manager | Переменная | Значение по умолчанию | Описание | |---|---|---| | `dashboard_insecure` | `false` | HTTP-режим Dashboard (переопределяется в `true` в group_vars) | | `dashboard_manifest_url` | GitHub raw v2.7.0 | Без зависимости от CDN | | `k8s_manager_upgrade_packages` | `false` | Полное обновление ОС (opt-in) |
--- ### `gpu_prereqs` · `gpu_device_plugin` Подключение GPU-узла к кластеру. Драйвер NVIDIA ставится вручную заранее — роль его не устанавливает, только проверяет наличие (`nvidia-smi`). Запускается **после** `setup_worker_plane.yml`, отдельным плейбуком `setup_gpu.yml`, только для узлов группы `gpu_workers` (подгруппа `workers`). | Роль | Запускается на | Что делает | |---|---|---| | `gpu_prereqs` | `gpu_workers` | Проверка драйвера, установка `nvidia-container-toolkit`, `nvidia-ctk runtime configure` для containerd (default runtime) | | `gpu_device_plugin` | `manager_nodes` | `kubectl label node gpu=nvidia` для каждого узла `gpu_workers`; `kubectl apply` манифеста NVIDIA k8s-device-plugin |
Переменные gpu_prereqs / gpu_device_plugin | Переменная | Значение по умолчанию | Описание | |---|---|---| | `nvidia_container_toolkit_version` | `""` (последняя) | Версия пакета `nvidia-container-toolkit` | | `node_labels` | `{gpu: nvidia}` (в `gpu_workers.yml`) | Лейблы, навешиваемые на узел после join | | `gpu_device_plugin_version` | `v0.17.0` | Тег релиза NVIDIA k8s-device-plugin | | `gpu_device_plugin_manifest_url` | GitHub raw для указанной версии | Статический манифест DaemonSet |
> Порядок обязателен: `setup_worker_plane.yml` перезаписывает `/etc/containerd/config.toml` с нуля, поэтому `gpu_prereqs` (донастройка containerd под nvidia runtime) должна выполняться **после**, отдельным прогоном. --- ### `gpu_model_storage` Выделенный локальный RAID1-раздел под файлы моделей (Ollama) на GPU-узлах. `gpu_workers` исключены из Longhorn (`longhorn_disks: []`), а корневая ФС слишком маленькая (~30 ГБ) для хранения моделей — поэтому под них нарезается отдельный раздел из неразмеченного места на тех же зеркальных дисках, что и ОС. Запускается плейбуком `setup_gpu_model_storage.yml`, независимо от порядка `kubeadm join`.
Переменные gpu_model_storage | Переменная | Значение по умолчанию | Описание | |---|---|---| | `gpu_model_storage_devices` | `[]` | Диски-члены RAID1 (пусто = роль выключена); задаётся в `gpu_workers.yml` | | `gpu_model_storage_partition_size_gb` | `1000` | Размер новой партиции на каждом диске, ГБ | | `gpu_model_storage_raid_device` | `/dev/md1` | Имя нового mdadm-массива | | `gpu_model_storage_mountpoint` | `/mnt/ollama-models` | Точка монтирования | | `gpu_model_storage_fstype` | `xfs` | Файловая система |
> **Standard**: [roles/gpu_model_storage/README.md](roles/gpu_model_storage/README.md) --- ### `longhorn_prereqs` · `longhorn` Двухэтапное развёртывание распределённого блочного хранилища. | Роль | Запускается на | Что делает | |---|---|---| | `longhorn_prereqs` | `workers` | iSCSI / NFS пакеты, модули ядра, SELinux CIL-политика, форматирование и монтирование дисков XFS | | `longhorn` | `manager_nodes` | Аннотации узлов с disk-config JSON, `helm upgrade --install longhorn` | После установки доступен StorageClass `longhorn` (default). Приложения используют его через `storageClassName: longhorn` в PVC.
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `longhorn_chart_version` | `1.7.2` | Версия Helm-чарта | | `longhorn_namespace` | `longhorn-system` | Namespace | | `longhorn_default_replica_count` | `1` | Реплик на том; поднять до 2–3 при добавлении worker-нод | | `longhorn_minimal_available_storage_percentage` | `10` | Минимальный свободный % хранилища | | `longhorn_storage_over_provisioning_percentage` | `100` | Коэффициент over-provisioning | | `longhorn_disks` | `[{device: /dev/sdb, mountpoint: /mnt/longhorn-disk1}, ...]` | Диски для форматирования; переопределяется в hosts.yml |
> **Стандарт работы с хранилищем** (PVC, StatefulSet, реплики, бэкапы, диагностика): > [`roles/longhorn/README.md`](roles/longhorn/README.md) --- ### `traefik` HTTP/TCP port-proxy. DaemonSet на worker-нодах. Каждый сервис получает выделенный TCP-порт из диапазона 10001–10999. Вся топология определяется через `traefik_port_map` в `inventory/prod/group_vars/traefik.yml`. Поддерживаются два режима маршрутизации: HTTP (`IngressRoute`, поле `protocol` не задаётся) и raw TCP (`IngressRouteTCP` с `HostSNI("*")`, `protocol: tcp` в port_map). | Задача | Что делает | |---|---| | `firewall.yml` | Открывает 10000–10999/tcp; помещает `flannel.1`, `cni0` в зону `trusted` | | `helm.yml` | `helm upgrade --install traefik` как DaemonSet с EntryPoint-ами из port_map | | `middleware.yml` | Применяет `Middleware` CRD (BasicAuth) для записей с `basicauth.enabled: true` | | `routes.yml` | Применяет `IngressRoute` (HTTP) или `IngressRouteTCP` (TCP) в зависимости от `protocol` в port_map |
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `traefik_chart_version` | `32.1.0` | Версия Helm-чарта | | `traefik_namespace` | `traefik` | Namespace | | `traefik_port_map` | `[]` | Список сервисов; переопределяется в `group_vars/traefik.yml` |
> **Стандарт публикации сервисов** (добавление порта, BasicAuth, диагностика, таблица активных портов): > [`roles/traefik/README.md`](roles/traefik/README.md) --- ### `flux` Устанавливает Flux CD CLI и выполняет `flux bootstrap gitlab`, подключая кластер к fleet-репозиторию `k8s-fleet` на `gitlab.gigacoms.info`. | Задача | Что делает | |---|---| | `install.yml` | Flux CLI → `/usr/local/bin/flux`, bash-completion, `flux check --pre` | | `bootstrap.yml` | `flux bootstrap gitlab` — deploy key, Flux-контроллеры в `flux-system`, манифесты в `clusters/production` | Fleet-репозиторий: `gitlab.gigacoms.info/k8s/k8s-fleet` · путь: `clusters/production`
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `flux_version` | `""` (latest stable) | Версия Flux CLI | | `flux_gitlab_hostname` | `gitlab.gigacoms.info` | Хост GitLab | | `flux_gitlab_owner` | `k8s` | GitLab-группа fleet-репозитория | | `flux_gitlab_repository` | `k8s-fleet` | Имя fleet-репозитория | | `flux_gitlab_branch` | `main` | Ветка | | `flux_gitlab_path` | `clusters/production` | Путь внутри репозитория | | `flux_gitlab_token` | из `$GITLAB_FLUX_TOKEN` | GitLab PAT (scope: api) |
> **Стандарт GitOps-деплоя приложений** (структура fleet-repo, multi-branch, примеры с Longhorn и Traefik, CI/CD-шаблон): > [`roles/flux/README.md`](roles/flux/README.md) --- ### `minio` S3-совместимое объектное хранилище на базе MinIO. Запускается как standalone StatefulSet на worker-ноде, хранилище предоставляется отдельным StorageClass `longhorn-minio` (reclaimPolicy: Retain). Доступ — через Traefik на портах 10005 (S3 API) и 10006 (Console UI). | Задача | Что делает | |---|---| | `storageclass.yml` | Создаёт StorageClass `longhorn-minio` (Retain, numberOfReplicas=1) | | `namespace.yml` | Создаёт namespace `minio` | | `helm.yml` | `helm upgrade --install minio minio/minio` с values из шаблона | **Предварительное условие:** создать Secret `minio-root-credentials` вручную в namespace `minio`: ```bash kubectl create secret generic minio-root-credentials \ --from-literal=rootUser=minioadmin \ --from-literal=rootPassword='СИЛЬНЫЙ_ПАРОЛЬ' \ -n minio ```
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `minio_chart_version` | `5.4.0` | Версия Helm-чарта `minio/minio` | | `minio_image_tag` | `RELEASE.2025-04-22T22-12-26Z` | Тег образа MinIO | | `minio_namespace` | `minio` | Namespace | | `minio_storage_class` | `longhorn-minio` | StorageClass для PVC | | `minio_storage_size` | `1Ti` | Размер PVC | | `minio_console_url` | `http://10.203.0.96:10006` | URL Console (для MINIO_BROWSER_REDIRECT_URL) | | `minio_root_secret` | `minio-root-credentials` | Имя Secret с rootUser/rootPassword | | `minio_buckets` | `[backups, artifacts]` | Бакеты, создаваемые при старте |
> **Стандарт объектного хранилища S3** (StorageClass, пользователи, lifecycle, интеграция с Loki, масштабирование): > [`roles/minio/README.md`](roles/minio/README.md) --- ### `logging` PLG-стек централизованного сбора логов: **Loki** (S3 backend → MinIO), **Grafana Alloy** (DaemonSet, замена EOL Promtail), **Grafana** (UI + алерты). Все три компонента разворачиваются в namespace `monitoring` из manager-узла через Helm. | Задача | Что делает | |---|---| | `namespace.yml` | Создаёт namespace `monitoring` | | `minio-user.yml` | Создаёт bucket `loki-chunks`, пользователя `loki` с политикой только на этот bucket, Secret `loki-minio-secret` | | `loki.yml` | `helm upgrade --install loki` в режиме single-binary с S3 backend (MinIO) | | `alloy.yml` | `helm upgrade --install alloy` как DaemonSet — сбор логов подов + journald на всех нодах | | `grafana.yml` | `helm upgrade --install grafana` с provisioning datasource Loki | **Предварительное условие:** создать Secret `grafana-admin-secret` вручную в namespace `monitoring`: ```bash kubectl create namespace monitoring kubectl create secret generic grafana-admin-secret \ --from-literal=admin-user=admin \ --from-literal=admin-password='СИЛЬНЫЙ_ПАРОЛЬ' \ -n monitoring ```
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `loki_chart_version` | `7.0.0` | Версия Helm-чарта `grafana/loki` | | `alloy_chart_version` | `1.8.2` | Версия Helm-чарта `grafana/alloy` | | `grafana_chart_version` | `10.5.15` | Версия Helm-чарта `grafana/grafana` | | `loki_minio_endpoint` | `http://minio.minio.svc.cluster.local:9000` | S3 endpoint для Loki | | `loki_minio_bucket` | `loki-chunks` | Bucket для хранения чанков и индексов | | `loki_retention_days` | `31` | Срок хранения логов в днях | | `loki_wal_storage_size` | `10Gi` | PVC для WAL/temp Loki (не логи — они в MinIO) | | `grafana_storage_size` | `5Gi` | PVC для базы данных Grafana | | `grafana_root_url` | `http://10.203.0.96:10003` | Внешний URL Grafana (Traefik) | | `grafana_admin_secret` | `grafana-admin-secret` | Имя Secret с admin-user/admin-password |
> **Стандарт централизованного сбора логов** (архитектура PLG, масштабирование, переход на Graylog, диагностика): > [`roles/logging/README.md`](roles/logging/README.md) --- ### `sealed_secrets` Разворачивает [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) — контроллер для безопасного хранения зашифрованных Secret'ов в Git. Запускается из manager-узла: контроллер — в `kube-system` через Helm, CLI `kubeseal` — на manager-узле. | Задача | Что делает | |---|---| | `cli.yml` | Скачивает `kubeseal` с GitHub Releases (версия из переменной или latest), идемпотентно | | `helm.yml` | `helm upgrade --install sealed-secrets` из `bitnami-labs/sealed-secrets` в `kube-system` | **Использование после установки:** ```bash # Зашифровать Secret kubectl create secret generic my-secret --from-literal=password=mysecret --dry-run=client -o yaml \ | kubeseal --format yaml > my-sealedsecret.yaml # Применить (можно коммитить в Git — зашифрован публичным ключом контроллера) kubectl apply -f my-sealedsecret.yaml ```
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `sealed_secrets_chart_version` | `2.16.1` | Версия Helm-чарта | | `sealed_secrets_namespace` | `kube-system` | Namespace контроллера | | `sealed_secrets_cli_version` | `""` (latest) | Версия kubeseal CLI; пустая — берёт latest с GitHub |
--- ### `gitlab_agent` Устанавливает GitLab Agent for Kubernetes (agentk). Работает совместно с Flux: Flux деплоит, agentk даёт видимость в GitLab UI (Operate → Kubernetes clusters). Agentk инициирует исходящее WebSocket-соединение к KAS — входящих портов не требует. | Задача | Что делает | |---|---| | `config.yml` | Клонирует fleet-repo, создаёт `.gitlab/agents/production/config.yaml`, коммитит и пушит при изменении | | `helm.yml` | `helm upgrade --install gitlab-agent` с токеном и KAS-адресом | **Предварительное условие:** зарегистрировать агент в GitLab UI → Operate → Kubernetes clusters → Connect a cluster → имя `production` → токен в CI/CD переменную `GITLAB_AGENT_TOKEN`.
Переменные | Переменная | Значение по умолчанию | Описание | |---|---|---| | `gitlab_agent_hostname` | `gitlab.gigacoms.info` | Хост GitLab | | `gitlab_agent_name` | `production` | Имя агента (совпадает с именем в GitLab UI) | | `gitlab_agent_namespace` | `gitlab-agent` | Namespace | | `gitlab_agent_kas_address` | `wss://gitlab.gigacoms.info/-/kubernetes-agent/` | KAS WebSocket-адрес | | `gitlab_agent_token` | из `$GITLAB_AGENT_TOKEN` | Токен агента | | `gitlab_agent_ci_access_group` | `k8s` | GitLab-группа с доступом к кластеру через CI |
--- ## CI/CD-пайплайн Стадии: **validate** → **setup** | Задача | Триггер | Описание | |---|---|---| | `validate/lint` | каждый push / MR | `ansible-lint` всех плейбуков и ролей | | `validate/syntax-check` | каждый push / MR | Синтаксическая проверка всех плейбуков | | `setup/manager_nodes` | ручной, ветка `main` | `setup_manager.yml` | | `setup/control_plane` | ручной, ветка `main` | `setup_control_plane.yml` | | `setup/workers` | ручной, ветка `main` | `setup_worker_plane.yml` | | `setup/longhorn` | ручной, ветка `main` | `setup_longhorn.yml` | | `setup/traefik` | ручной, ветка `main` | `setup_traefik.yml` | | `setup/flux` | ручной, ветка `main` | `setup_flux.yml` | | `setup/gitlab_agent` | ручной, ветка `main` | `setup_gitlab_agent.yml` | | `setup/minio` | ручной, ветка `main` | `setup_minio.yml` | | `setup/logging` | ручной, ветка `main` | `setup_logging.yml` | | `setup/sealed_secrets` | ручной, ветка `main` | `setup_sealed_secrets.yml` | | `setup/gpu_workers` | ручной, ветка `main` | `setup_gpu.yml` | | `setup/gpu_model_storage` | ручной, ветка `main` | `setup_gpu_model_storage.yml` | `resource_group: production` — все setup-задачи сериализованы, одновременно выполняется только одна. ### Переменные GitLab CI/CD (Settings → CI/CD → Variables) | Переменная | Описание | |---|---| | `ANSIBLE_SSH_USER` | SSH-пользователь для целевых серверов | | `ANSIBLE_SSHKEY_ID_RSA` | Содержимое приватного SSH-ключа (тип: File) | | `ANSIBLE_BECOME_PASS` | Пароль sudo | | `GITLAB_FLUX_TOKEN` | GitLab PAT (scope: api) для Flux bootstrap и клонирования fleet-repo | | `GITLAB_AGENT_TOKEN` | Токен GitLab Agent (получить в GitLab → Operate → Kubernetes clusters) | | `MINIO_ROOT_PASSWORD` | Пароль root-пользователя MinIO (используется при ручном создании Secret) | | `LOKI_MINIO_PASSWORD` | Пароль пользователя `loki` в MinIO (создаётся Ansible при запуске setup_logging) | | `GRAFANA_ADMIN_PASSWORD` | Пароль администратора Grafana (задаётся в Secret `grafana-admin-secret` вручную) | --- ## Порядок развёртывания Для нового кластера — строго в следующем порядке: ``` 1. setup:manager_nodes — инструменты оператора 2. setup:control_plane — Kubernetes, CNI, Dashboard 3. setup:workers — подключение worker-нод 4. setup:longhorn — хранилище (prereqs на workers → Helm с manager) 5. setup:traefik — порт-прокси (firewall на workers → Helm с manager) 6. setup:minio — объектное хранилище S3 (StorageClass + Helm с manager) 7. setup:flux — GitOps bootstrap 8. setup:gitlab_agent — видимость кластера в GitLab UI 9. setup:logging — PLG-стек (Loki + Alloy + Grafana) 10. setup:sealed_secrets — контроллер Sealed Secrets + kubeseal CLI 11. setup:gpu_workers — только для узлов gpu_workers, после их setup:workers ``` > **Перед запуском setup:minio** создать Secret вручную: `kubectl create secret generic minio-root-credentials --from-literal=rootUser=minioadmin --from-literal=rootPassword='...' -n minio` > **Перед запуском setup:logging** создать Secret вручную: `kubectl create namespace monitoring && kubectl create secret generic grafana-admin-secret --from-literal=admin-user=admin --from-literal=admin-password='...' -n monitoring` --- ## Архитектурные решения ### kubeadm: version derive at runtime `kubernetesVersion` в конфиге kubeadm определяется из `kubeadm version -o short` в runtime. Исключает ошибки несоответствия версий при изменении пакета без изменения переменной. ### single-node: снятие control-plane taint Taint `node-role.kubernetes.io/control-plane:NoSchedule` снимается автоматически. Dashboard и прочие workload'ы размещаются на master-узле при single-node-конфигурации. ### kubeadm join: ежезапускная генерация токена Join-токены истекают через 24 часа. Роль `k8s_worker` генерирует свежий токен на control plane при каждом запуске; шаг join идемпотентен — пропускается, если узел уже в кластере. ### Longhorn: монтирование до аннотации Диски форматируются как XFS и монтируются по UUID (`nofail`) **до** установки Longhorn. Longhorn обнаруживает диски через аннотации узлов — не через автоопределение. Это предотвращает переформатирование при повторном запуске плейбука. ### Traefik: нет hostname, нет TLS Каждый сервис получает выделенный порт (10001–10999). Нет hostname-routing, нет TLS — клиент подключается по `http://:`. TLS терминируется на уровне Traefik при необходимости. Dashboard работает в HTTP-режиме (`dashboard_insecure: true`). ### Flux + agentk: разделение ответственности Ansible управляет **ОС и кластером** (установка, конфигурация нод). Flux управляет **приложениями внутри кластера** (деплой, обновление образов). agentk обеспечивает **видимость** состояния кластера в GitLab UI. ### GPU-узлы: отдельный плейбук, вручную ставится только драйвер `gpu_workers` — подгруппа `workers`. NVIDIA-драйвер ставится на сервер вручную и не управляется Ansible (роль `gpu_prereqs` лишь проверяет `nvidia-smi` и падает, если драйвера нет). `setup_gpu.yml` запускается отдельно и **после** `setup_worker_plane.yml`, так как роль `k8s_worker` перегенерирует `/etc/containerd/config.toml` с нуля и стёрла бы настройку nvidia runtime, если бы порядок был обратным. GPU-узлы исключены из дефолтной конфигурации Longhorn (`longhorn_disks: []` в `gpu_workers.yml`) — это compute-нода, не storage-нода. ### Firewalld и CNI-интерфейсы > **Критично.** `flannel.1` и `cni0` должны быть в зоне `trusted` на каждом узле кластера. Без этого pod-to-pod-трафик от workers, приходящий на master, обрабатывается зоной `internal` — она пропускает только явно перечисленные порты. Любой порт нового сервиса молча отбрасывается, даже если порт открыт на уровне хоста. При добавлении любой роли с `firewalld` — обязательно добавлять: ```yaml - name: Firewall | Trust CNI interfaces ansible.posix.firewalld: zone: trusted interface: "{{ item }}" permanent: true state: enabled loop: - flannel.1 - cni0 ``` --- ## Локальная разработка ```bash # Установка коллекций Ansible Galaxy ansible-galaxy collection install -r requirements.yml -p collections/ --force # Проверка синтаксиса ansible-playbook --syntax-check -i inventory/prod playbooks/setup_manager.yml # Линтинг ansible-lint playbooks/ roles/ # Dry-run (режим check + diff) ansible-playbook -i inventory/prod playbooks/setup_control_plane.yml --check --diff # Запуск только на одном хосте ansible-playbook -i inventory/prod playbooks/setup_worker_plane.yml --limit k8s-worker-01 ``` --- ## Добавление нового компонента 1. Добавить хост в `inventory/prod/hosts.yml` (если новая нода) 2. Создать `inventory/prod/group_vars/.yml` (если новая группа) 3. Создать роль `roles//` со стандартной структурой 4. Создать `roles//README.md` — подробный корп. стандарт использования компонента 5. Создать `playbooks/.yml` 6. Добавить CI/CD-задачу в `.gitlab-ci.yml` (`stage: setup`, `resource_group: production`) 7. Обновить **этот README**: краткое описание роли + ссылка на `roles//README.md` 8. Обновить `CLAUDE.md`: добавить роль в структуру репозитория и ключевые решения