| .. | ||
| roles | ||
| .gitattributes | ||
| .gitignore | ||
| ansible.cfg | ||
| deploy.yml | ||
| inventory.ini | ||
| pyproject.toml | ||
| README.md | ||
| requirements.yml | ||
| site.yml | ||
| uv.lock | ||
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
Последовательность: sync → galaxy → provision (базовые пакеты + docker
и запуск службы) → stack (деплой конфигурации + docker compose up -d
сервисов приложения) → superuser (генерация и создание Django-админа).
Перед запуском впишите хосты в 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 stack |
деплой + поднять стек (docker compose up -d) |
uv run poe gen-env |
только сгенерировать пароли и .env |
uv run poe login |
только docker login в реестр по ключу |
uv run poe superuser |
сгенерировать логин+пароль и создать Django-суперпользователя |
uv run poe flux-status |
состояние FluxCD в k3s |
uv run poe vault-status |
состояние Vault (инициализирован / распечатан) |
uv run poe check / deploy-check |
dry-run (--check --diff) |
Поднимаемые сервисы задаются в sarex_services (роль sarex_stack) —
postgres(+init), redis, rabbitmq, minio(+init), measurements,
backend, celery, frontend, nginx (k3s/gitea/processing не поднимаются).
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 (4 ноды в контейнерах)
├── k3s-server ──► нода ┐
├── k3s-worker-1..3 ──► ноды ├─► flux-system (controllers)
├── gitea (172.28.0.12) ◄────── ┘ и всё, что опишем в clusters/aero
│ └── infra/iac.git
├── vault (+init)
├── flux-k8s-init ──► namespace flux-system + Service-мост до gitea
└── flux-bootstrap ──► ставит Flux в k3s и привязывает к gitea
Мост до gitea. Контроллеры Flux работают внутри k3s, где резолвит CoreDNS,
а имён compose-сети там нет. Поэтому flux-k8s-init заводит в namespace
flux-system Service без селектора + Endpoints на статический IP gitea — тем же
приёмом, что bridge-postgres/bridge-minio для processing. Источник в
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/
└── worker-3/
Файлы лежат на хосте обычными каталогами: переживают пересоздание контейнера
k3s и docker compose down -v, бэкапятся штатными средствами.
Следствие для stateful-сервисов. Хранилище node-local: PVC привязан к той ноде, где под запустился первым. При переезде postgres/redis/rabbitmq/minio в k3s их нужно явно прибивать к конкретной ноде (
nodeSelector), иначе данные размажутся по четырём каталогам, а потеря ноды сделает том недоступным. Бэкап должен покрывать все каталогиk3s-storage/*.
uv run poe platform # поднять подложку
uv run poe flux-status # убедиться, что Flux реконсилирует
Что происходит по шагам:
- Волна 1 — стартуют
k3s-server, три воркера иgitea. Роль ждёт, пока k3s запишет./k3s/kubeconfig.yaml(и что файл непустой) — иначеflux-bootstrapсмонтировал бы каталог вместо файла. gitea-init— одноразовый: заводит администратора (gitea admin user create), организацию и пустой репозиторий через API. Идемпотентен.flux-k8s-init— одноразовый: namespaceflux-systemи Service-мост до gitea (см. выше). Обязан отработать до bootstrap.vault+vault-init— Vault на file-хранилище.vault-initживёт постоянно: инициализирует Vault одним ключом, распечатывает его и включаетkv-v2на путиsecrets/(тот же путь, что в k8s-контурах). После ребута хоста Vault поднимается запечатанным — цикл распечатывает его сам.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 не попадают.
Границы текущего этапа
Сделана только подложка. Осознанно не сделано:
- Репозиторий в gitea содержит лишь
clusters/aero/flux-system— то, что запушил bootstrap. Наполнение (apps/,infrastructure/) и перенос прикладных сервисов из compose в k3s — следующий этап. - Vault поднят и распечатан, но kubernetes auth не настроен: для него нужен Vault Agent Injector внутри k3s, а он приедет уже через Flux. Пока в Vault можно только класть секреты, поды их ещё не читают.
- Прикладной стек (
sarex_services) не тронут и продолжает работать в compose.
Развёртывание sarex-стека (роль sarex_stack)
Копирует docker-compose.yaml, nginx/templates и backend/uwsgi.ini в
/root/sarex, рендерит .env из .env.example со сгенерированными паролями
и доменами *.sarex.local.lonsdaleites.ru, генерирует самоподписанный
TLS-сертификат на все три домена (nginx/certs/selfsigned.{crt,key}), логинится
в реестр и (при sarex_compose_up=true) поднимает сервисы приложения.
Роли/базы и MinIO-бакеты создаются одноразовыми postgres-init/minio-init.
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 # перегенерировать пароли заново -
Стек по умолчанию не поднимается (
sarex_compose_up: false). Образы — приватные (cr.yandex), поэтому сперваdocker login cr.yandexна хосте, затемcd /root/sarex && docker compose up -d(или прогон с-e sarex_compose_up=true). -
Домены нужно завести в DNS/hosts, чтобы они резолвились на хост.
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 # метаданные и зависимости