В 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 новых замечаний не даёт.
251 lines
15 KiB
Markdown
251 lines
15 KiB
Markdown
# 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 # метаданные и зависимости
|
||
```
|