В 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 новых замечаний не даёт.
182 lines
9.4 KiB
YAML
182 lines
9.4 KiB
YAML
---
|
||
# --- Предпосылки на хосте -------------------------------------------------
|
||
# Загружаем ДО подъёма k3s: istio-init внутри подов правит iptables через ядро
|
||
# хоста, и без этих модулей поды в namespace с istio-injection не стартуют.
|
||
# persistent: present закрепляет загрузку в /etc/modules-load.d, иначе после
|
||
# перезагрузки хоста контур снова развалился бы.
|
||
- name: Загрузить модули ядра, нужные istio-init
|
||
community.general.modprobe:
|
||
name: "{{ item }}"
|
||
state: present
|
||
persistent: present
|
||
loop: "{{ aero_kernel_modules }}"
|
||
tags: [kernel]
|
||
|
||
# --- Каталоги и файлы ----------------------------------------------------
|
||
- name: Создать каталог развёртывания
|
||
ansible.builtin.file:
|
||
path: "{{ deploy_dir }}"
|
||
state: directory
|
||
owner: root
|
||
group: root
|
||
mode: "0750"
|
||
tags: [env]
|
||
|
||
- name: Скопировать docker-compose.yaml
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/docker-compose.yaml"
|
||
dest: "{{ deploy_dir }}/docker-compose.yaml"
|
||
mode: "0640"
|
||
|
||
# k3s-манифесты DNS-мостов: flux-system → gitea (монтирует flux-k8s-init) и
|
||
# vault → Vault в compose (применяет vault-k8s.yml). Оба нужны потому, что у
|
||
# кластера своя DNS и compose-имена в ней не резолвятся.
|
||
- name: Скопировать k3s-манифесты (DNS-мосты gitea и vault)
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/k3s/manifests/"
|
||
dest: "{{ deploy_dir }}/k3s/manifests/"
|
||
mode: "0644"
|
||
|
||
# vault монтирует ./vault/config как :ro — без конфига сервер не стартует.
|
||
- name: Скопировать конфигурацию Vault
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/vault/config/"
|
||
dest: "{{ deploy_dir }}/vault/config/"
|
||
mode: "0644"
|
||
|
||
# Каталоги PVC-данных k3s — по одному на ноду. Создаём заранее: иначе docker
|
||
# создаст их от root с umask демона, а local-path пишет туда от имени подов.
|
||
- name: Создать каталоги PVC-хранилища k3s (по ноде)
|
||
ansible.builtin.file:
|
||
path: "{{ deploy_dir }}/k3s-storage/{{ item }}"
|
||
state: directory
|
||
mode: "0755"
|
||
loop: "{{ aero_k3s_nodes }}"
|
||
|
||
# --- Секреты: генерация с персистом на control-node (идемпотентно) --------
|
||
- name: Сгенерировать/загрузить пароли (persist в secrets_store, gitignored)
|
||
ansible.builtin.set_fact:
|
||
sarex_secrets: >-
|
||
{{ sarex_secrets | default({}) | combine({
|
||
item: lookup('password',
|
||
secrets_store ~ '/' ~ inventory_hostname ~ '/' ~ item
|
||
~ ' length=' ~ secret_length ~ ' chars=ascii_letters,digits')
|
||
}) }}
|
||
loop: "{{ sarex_secret_vars }}"
|
||
no_log: true
|
||
tags: [env]
|
||
|
||
# Учётка администратора платформы. Живёт отдельно от общего цикла выше по двум
|
||
# причинам: у логина свой алфавит (Django не примет username со спецсимволами)
|
||
# и оба значения не должны попасть в .env — compose-стек их не использует,
|
||
# потребитель один, и это Vault.
|
||
#
|
||
# Генерируется всегда, а не по флагу sarex_create_superuser: значения уезжают
|
||
# в secrets/apps/django/superuser (см. vault_app_secrets), откуда их забирает
|
||
# стартовый скрипт пода backend. Без них vault-agent не отрендерит шаблон и
|
||
# под встанет в Init:Error. lookup('password') идемпотентен — на повторных
|
||
# прогонах возвращает уже сохранённое значение, а не генерирует новое.
|
||
- name: Сгенерировать/загрузить логин администратора платформы
|
||
ansible.builtin.set_fact:
|
||
superuser_name: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_NAME chars=ascii_lowercase,digits length=10') }}"
|
||
no_log: true
|
||
tags: [env, superuser]
|
||
|
||
- name: Сгенерировать/загрузить пароль администратора платформы
|
||
ansible.builtin.set_fact:
|
||
superuser_password: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_PASSWORD chars=ascii_letters,digits length=' ~ secret_length) }}"
|
||
no_log: true
|
||
tags: [env, superuser]
|
||
|
||
# RSA-ключи JWT (RS512): генерируем на control-node, persist в secrets_store.
|
||
- name: Сгенерировать приватный RSA-ключ JWT
|
||
ansible.builtin.command: "openssl genrsa -out {{ jwt_private_key_path }} 4096"
|
||
args:
|
||
creates: "{{ jwt_private_key_path }}"
|
||
delegate_to: localhost
|
||
become: false
|
||
tags: [env]
|
||
|
||
- name: Извлечь публичный RSA-ключ JWT
|
||
ansible.builtin.command: "openssl rsa -in {{ jwt_private_key_path }} -pubout -out {{ jwt_public_key_path }}"
|
||
args:
|
||
creates: "{{ jwt_public_key_path }}"
|
||
delegate_to: localhost
|
||
become: false
|
||
tags: [env]
|
||
|
||
- name: Собрать словарь переопределений для .env (пароли + домены + JWT)
|
||
ansible.builtin.set_fact:
|
||
env_overrides: >-
|
||
{{ sarex_secrets | combine({
|
||
'PLATFORM_DOMAIN': platform_domain,
|
||
'ADMIN_DOMAIN': admin_domain,
|
||
'MINIO_DOMAIN': minio_domain,
|
||
'SAREX_MINIO_CONSOLE_URL': 'https://' ~ minio_domain,
|
||
'GITEA_ADMIN_USER': gitea_admin_user,
|
||
'GITEA_ADMIN_EMAIL': gitea_admin_email,
|
||
'GITEA_ORG': gitea_org,
|
||
'GITEA_REPO': gitea_repo,
|
||
'AERO_GIT_BRANCH': aero_git_branch,
|
||
'AERO_FLUX_PATH': aero_flux_path,
|
||
'SAREX_JWT_PRIVATE_KEY': "'" ~ (lookup('file', jwt_private_key_path) | replace('\n', '\\n')) ~ "'",
|
||
'SAREX_JWT_PUBLIC_KEY': "'" ~ (lookup('file', jwt_public_key_path) | replace('\n', '\\n')) ~ "'",
|
||
}) }}
|
||
no_log: true
|
||
tags: [env]
|
||
|
||
- name: Отрендерить .env из .env.example с переопределениями
|
||
ansible.builtin.template:
|
||
src: env.j2
|
||
dest: "{{ deploy_dir }}/.env"
|
||
owner: root
|
||
group: root
|
||
mode: "0600"
|
||
no_log: true
|
||
tags: [env]
|
||
|
||
# --- Доступ к приватному реестру (docker на хосте + registries.yaml нод) ---
|
||
# Обязательно ДО подъёма k3s: registries.yaml монтируется в ноды, и k3s читает
|
||
# его только при старте.
|
||
- name: Настроить доступ к приватному реестру
|
||
ansible.builtin.include_tasks: registry.yml
|
||
tags: [login, registry]
|
||
|
||
# --- GitOps-подложка: k3s + gitea + vault + Flux --------------------------
|
||
# Отдельный тег и отдельный флаг: подложка поднимается публичными образами и
|
||
# не зависит от docker login, поэтому её можно накатывать независимо от стека.
|
||
- name: Поднять GitOps-подложку контура
|
||
ansible.builtin.include_tasks: platform.yml
|
||
when: aero_platform_up | bool
|
||
tags: [platform]
|
||
|
||
# --- Досоздание баз в работающей СУБД -------------------------------------
|
||
# Только по тегу dbsync. Обоснование — в шапке databases.yml.
|
||
- name: Досоздать недостающие базы
|
||
ansible.builtin.include_tasks: databases.yml
|
||
when: sarex_dbsync | bool
|
||
tags: [dbsync]
|
||
|
||
# --- Перекат нагрузок с изменённой конфигурацией --------------------------
|
||
# Только по тегу reload: правка ConfigMap сама под не перезапускает, а делать
|
||
# это на каждом прогоне нельзя. Обоснование — в шапке reload.yml.
|
||
- name: Перекатить нагрузки с изменённой конфигурацией
|
||
ansible.builtin.include_tasks: reload.yml
|
||
when: sarex_reload | bool
|
||
tags: [reload]
|
||
|
||
# --- Django-суперпользователь ----------------------------------------------
|
||
# Самого создания здесь больше нет — его делает под backend при каждом старте
|
||
# (apps/django/aero/kustomization.yaml, args стартового скрипта). Так учётка
|
||
# появляется вместе с приложением, а не отдельной командой оператора, и
|
||
# переживает пересоздание пода без ручного вмешательства.
|
||
#
|
||
# Ansible остаётся владельцем самих значений: генерирует их выше (тег env) и
|
||
# кладёт в Vault. Здесь — только показать логин по явному запросу
|
||
# (uv run poe superuser передаёт sarex_create_superuser=true).
|
||
- name: Показать учётные данные администратора платформы
|
||
ansible.builtin.debug:
|
||
msg: "Django superuser → login: {{ superuser_name }} password: {{ superuser_password }}"
|
||
when: sarex_create_superuser | default(false) | bool
|
||
tags: [superuser]
|