iac/aero/README.md
emelinda 75d1fba14a Compose: убран прикладной стек — он давно переехал в k3s
В docker-compose.yaml описывались 25 сервисов, а на хосте существовали
контейнеры только для 9. Остальные 16 — django, celery, frontend, s3-proxy,
processing-api/frontend, engine, measurements, nginx и их зависимости
(postgres, redis, rabbitmq, minio с init-контейнерами) — развёрнуты в k3s и
описаны в apps/ и infrastructure/. Дубликат не просто лежал мёртвым грузом,
а расходился с кластером и вводил в заблуждение.

Что это был именно мёртвый груз, а не «выключенный на время» стек:

  * контейнеров не существовало вовсе — не Exited, а не создавались;
  * именованных томов sarex-postgres-data, sarex-redis-data,
    sarex-rabbitmq-data, sarex-minio-data в docker volume ls нет;
  * стек был взаимно несовместим: nginx публиковал 80/443, те же порты
    публикует k3s-server (внутри его контейнера на них сидит istio
    ingressgateway), и docker compose up nginx упал бы на bind;
  * подъём был выключен по умолчанию (sarex_compose_up: false).

Заодно убрано всё, что обслуживало только удалённые сервисы:

  * sarex_services, sarex_compose_up, задача up и poe-таски stack/up;
  * оба handler'а (пересоздание nginx/backend/celery) — файл handlers
    остался пустым и удалён;
  * генерация самоподписанного сертификата и cert_dir: TLS выписывает
    cert-manager внутри кластера, верхнего nginx больше нет;
  * каталоги nginx/, backend/, engine/ и задачи их копирования;
  * k3s/manifests/processing — DNS-мост на IP compose-postgres/minio,
    которых больше не существует (в кластере он и не был создан).

poe install теперь идёт через platform, а не через удалённый stack — это
заодно чинит старую дыру, из-за которой install не поднимал подложку.

Проверено: docker compose config отдаёт ровно 9 сервисов, совпадающих с
docker ps на aero-01; ansible-lint новых замечаний не даёт.
2026-08-10 12:55:04 +03:00

15 KiB
Raw Blame History

aero — Ansible-провижининг docker-хостов

Ansible-роль docker, устанавливающая на сервер:

  • Docker Engine (docker-ce, docker-ce-cli);
  • containerd;
  • docker compose и (где есть) buildx;
  • обновление пакетов системы при старте.

Ветка установки выбирается автоматически по пакетному менеджеру хоста:

Семейство Пакетный менеджер Пакеты
Ubuntu / Debian apt docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
RedOS / RHEL dnf docker-ce docker-ce-cli containerd docker-compose

Имена пакетов в RedOS отличаются (containerd, docker-compose), поэтому списки разнесены по семействам в roles/docker/defaults/main.yml.

Важно: где запускать

Ansible не работает как управляющий узел на нативном Windows (нужен POSIX). Запуск — из WSL (Ubuntu). Управление окружением — через uv.

Разовая настройка WSL

# 1. uv внутри WSL
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env

# 2. Если DNS в WSL не резолвит (curl: Could not resolve host):
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
printf "\n[network]\ngenerateResolvConf = false\n" | sudo tee -a /etc/wsl.conf

# 3. SSH-ключ в домашку WSL с правами 600 (на /mnt/c ssh отвергает ключ)
mkdir -p ~/.ssh && chmod 700 ~/.ssh
cp /mnt/c/Users/user/.ssh/local/id_ed25519 ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519

Запуск

Из WSL, в каталоге проекта (/mnt/c/.../iac/aero).

Установка с чистого листа — одной командой

uv run poe install

Последовательность: syncgalaxyprovision (базовые пакеты + docker и запуск службы) → platform (деплой конфигурации + подъём подложки: k3s, gitea, vault, bootstrap Flux) → superuser (показать учётные данные администратора платформы). Приложения после этого разворачивает Flux сам. Перед запуском впишите хосты в inventory.ini и убедитесь, что на control-node есть SSH-ключ и ~/.ssh/local/authorized_key.json для входа в реестр.

Пошагово / отдельные команды

Команда Действие
uv run poe ping проверка SSH + sudo
uv run poe provision базовые пакеты + docker/containerd + запуск службы
uv run poe deploy деплой конфигурации: файлы, .env, docker login
uv run poe platform деплой + поднять GitOps-подложку (k3s + gitea + vault + Flux)
uv run poe gen-env только сгенерировать пароли и .env
uv run poe login только docker login в реестр по ключу
uv run poe superuser показать логин+пароль администратора платформы
uv run poe dbsync досоздать базы, появившиеся после запуска СУБД
uv run poe reload перекатить нагрузки, конфигурация которых в ConfigMap
uv run poe flux-status состояние FluxCD в k3s
uv run poe vault-status состояние Vault (инициализирован / распечатан)
uv run poe check / deploy-check dry-run (--check --diff)

docker compose поднимает только подложку — k3s, gitea, vault и одноразовые установщики. Прикладные сервисы разворачивает Flux внутри k3s по манифестам из apps/ и infrastructure/, и отдельной команды для них нет: достаточно запушить изменения в репозиторий контура.

ANSIBLE_CONFIG и UV_LINK_MODE заданы в [tool.poe.env] — на диске C: (через /mnt/c) права 0777, и ansible игнорирует ansible.cfg в world-writable каталоге, поэтому путь к конфигу передаётся явно.

GitOps-подложка контура (k3s + gitea + vault + FluxCD)

Целевая схема контура — не «всё в compose», а compose как подложка, k3s как среда выполнения. Источником правды становится gitea внутри контура, а раскаткой занимается FluxCD.

docker compose                        k3s (3 ноды в контейнерах)
├── k3s-server        ──► нода  ┐
├── k3s-worker-1..2   ──► ноды  ├─► flux-system (controllers)
├── gitea (172.28.0.12) ◄────── ┘   и всё, что описано в clusters/aero:
│   └── infra/iac.git                istio, cert-manager, postgresql, minio,
├── vault (172.28.0.13) ◄─────────── rabbitmq, django, processing, workspaces,
│   └── vault-init (unseal-цикл)     measurements, bim
├── gitea-init        ──► админ, организация, пустой репозиторий
├── flux-k8s-init     ──► namespace flux-system + Service-мост до gitea
└── flux-bootstrap    ──► ставит Flux в k3s и привязывает к gitea

В compose — только это. Прикладные сервисы и их зависимости (СУБД, очереди, объектное хранилище) живут в k3s и описаны в apps/ и infrastructure/.

Мост до gitea. Контроллеры Flux работают внутри k3s, где резолвит CoreDNS, а имён compose-сети там нет. Поэтому flux-k8s-init заводит в namespace flux-system Service без селектора + Endpoints на статический IP gitea — тем же приёмом, каким заведён мост до Vault (k3s/manifests/vault). Источник в GitRepository указан именем gitea.flux-system.svc.cluster.local, а адрес задан ровно в одном месте — переменной GITEA_BRIDGE_IP (она же ipv4_address сервиса gitea). Контейнер flux-bootstrap резолвит то же имя через extra_hosts — иначе flux CLI не смог бы запушить манифесты.

Имена нод закреплены через hostname: в compose. Это не косметика: k3s берёт имя ноды из hostname, иначе им становится ID контейнера. Local-path привязывает PV к ноде через nodeAffinity по имени, и после пересоздания контейнера тома «повисли» бы.

Хранилище PVC

StorageClass local-path (встроенный в k3s, он же default). Дефолтный путь провижинера /var/lib/rancher/k3s/storage перекрыт bind-mount'ом на хост — поэтому настраивать ConfigMap local-path-config не нужно:

{{ deploy_dir }}/k3s-storage/
├── server/      ← PVC, севшие на k3s-server
├── worker-1/
└── worker-2/

Файлы лежат на хосте обычными каталогами: переживают пересоздание контейнера k3s и docker compose down -v, бэкапятся штатными средствами.

Следствие для stateful-сервисов. Хранилище node-local: PVC привязан к той ноде, где под запустился первым. postgresql, minio и rabbitmq уже работают в k3s, поэтому их данные лежат в каталоге той ноды, куда сел под, — потеря ноды делает том недоступным, а kubectl delete pod с последующим переездом на другую ноду выглядит как «база опустела». Бэкап должен покрывать все каталоги k3s-storage/*.

uv run poe platform          # поднять подложку
uv run poe flux-status       # убедиться, что Flux реконсилирует

Что происходит по шагам:

  1. Волна 1 — стартуют k3s-server, три воркера и gitea. Роль ждёт, пока k3s запишет ./k3s/kubeconfig.yaml (и что файл непустой) — иначе flux-bootstrap смонтировал бы каталог вместо файла.
  2. gitea-init — одноразовый: заводит администратора (gitea admin user create), организацию и пустой репозиторий через API. Идемпотентен.
  3. flux-k8s-init — одноразовый: namespace flux-system и Service-мост до gitea (см. выше). Обязан отработать до bootstrap.
  4. vault + vault-init — Vault на file-хранилище. vault-init живёт постоянно: инициализирует Vault одним ключом, распечатывает его и включает kv-v2 на пути secrets/ (тот же путь, что в k8s-контурах). После ребута хоста Vault поднимается запечатанным — цикл распечатывает его сам.
  5. flux-bootstrap — одноразовый: flux bootstrap git ставит контроллеры в k3s и пушит их манифесты в gitea в clusters/aero/flux-system.

FluxCD не работает в docker-compose — это контроллеры Kubernetes. В compose живёт только установщик; после его успеха Flux работает внутри k3s.

Секреты подложки (GITEA_ADMIN_PASSWORD, K3S_TOKEN) генерируются так же, как остальные — в aero/.secrets/<host>/. Unseal-ключ и root-токен Vault лежат на отдельном docker-томе sarex-vault-init с правами 600 и в git не попадают.

Развёртывание (роль sarex_stack)

Копирует docker-compose.yaml, конфигурацию Vault и k3s-манифесты DNS-мостов в /root/sarex, рендерит .env из .env.example со сгенерированными паролями и доменами *.sarex.local.lonsdaleites.ru, логинится в реестр, кладёт секреты в Vault и поднимает подложку тремя волнами (uv run poe platform). Дальше за дело берётся Flux: базы, бакеты, очереди и сами приложения появляются в k3s из apps/ и infrastructure/.

uv run ansible-playbook deploy.yml --check --diff   # dry-run
uv run ansible-playbook deploy.yml                  # подготовка файлов
  • Пароли генерируются один раз и персистятся на control-node в aero/.secrets/ (gitignored) — повторный прогон не меняет .env (идемпотентно).

  • Только пароли и .env, без остального деплоя — отдельной командой:

    uv run poe gen-env                          # сгенерировать/обновить .env
    rm -rf .secrets/<host> && uv run poe gen-env  # перегенерировать пароли заново
    
  • Образы приложений приватные (cr.yandex), поэтому нужен docker loginего делает uv run poe login, а в кластер доступ приезжает секретами regcred/yc-cr-auth (registry_secrets в роли).

  • Домены нужно завести в DNS/hosts, чтобы они резолвились на хост.

  • TLS-сертификат выписывает cert-manager внутри кластера (infrastructure/istio-config/aero); самоподписанного сертификата на хосте больше нет — он был нужен верхнему nginx, а тот заменён istio ingressgateway.

Galaxy-коллекции (опционально)

Коллекции из requirements.yml (community.docker и др.) не требуются для работы роли — она использует только модули ansible.builtin. Ставятся при необходимости:

uv run poe galaxy

Настройка

Хосты — в inventory.ini (группа docker_hosts). Root — через sudo (become). Если sudo с паролем — добавьте --ask-become-pass:

uv run poe play -- --ask-become-pass

Переменные роли (roles/docker/defaults/main.yml):

Переменная По умолчанию Назначение
docker_update_packages true Обновлять пакеты при старте
docker_upgrade_dist false Полный dist-upgrade (только Debian)
docker_service_enabled true Автозапуск службы docker
docker_service_state started Состояние службы после прогона
docker_users [] Пользователи в группу docker (без sudo)
docker_redhat_add_ce_repo false Внешний репозиторий Docker CE (RHEL; RedOS не нужен)

Структура

aero/
├── pyproject.toml            # окружение uv + poe-задачи
├── ansible.cfg               # настройки ansible (inventory, become)
├── requirements.yml          # galaxy-коллекции (опционально)
├── inventory.ini             # хосты
├── site.yml                  # плейбук
└── roles/docker/
    ├── defaults/main.yml     # переменные и списки пакетов по семействам
    ├── tasks/
    │   ├── main.yml          # диспетчер по пакетному менеджеру + общие шаги
    │   ├── debian.yml        # ветка apt (Ubuntu/Debian)
    │   └── redhat.yml        # ветка dnf (RedOS/RHEL)
    ├── handlers/main.yml     # restart docker
    └── meta/main.yml         # метаданные и зависимости