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

251 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
# 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`).
### Установка с чистого листа — одной командой
```bash
uv run poe install
```
Последовательность: `sync``galaxy`**`provision`** (базовые пакеты + docker
и запуск службы) → **`platform`** (деплой конфигурации + подъём подложки:
k3s, gitea, vault, bootstrap Flux) → **`superuser`** (показать учётные данные
администратора платформы). Приложения после этого разворачивает Flux сам.
Перед запуском впишите хосты в [inventory.ini](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/*`.
```bash
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/`.
```bash
uv run ansible-playbook deploy.yml --check --diff # dry-run
uv run ansible-playbook deploy.yml # подготовка файлов
```
- Пароли генерируются один раз и персистятся на control-node в `aero/.secrets/`
(gitignored) — повторный прогон не меняет `.env` (идемпотентно).
- Только пароли и `.env`, без остального деплоя — отдельной командой:
```bash
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`. Ставятся при
необходимости:
```bash
uv run poe galaxy
```
## Настройка
Хосты — в [`inventory.ini`](inventory.ini) (группа `docker_hosts`). Root — через
`sudo` (`become`). Если `sudo` с паролем — добавьте `--ask-become-pass`:
```bash
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 # метаданные и зависимости
```