Первый прикладной сервис переехал из compose в кластер. Развёрнуты backend, frontend, redis, srx-admin и s3-proxy; celery выключен до следующего шага. Платформа доступна по https://sarex.local.lonsdaleites.ru — корень отдаёт frontend, /api/ и /admin/ уходят в backend, /media/ в s3-proxy. Kafka и Zitadel в контуре не развёрнуты, и приложению они не нужны: SERVER_KAFKA_ENABLED в base уже False, а zitadel_enabled в sarex-backend читается ровно в одном месте — update_ams_user(), которая при выключенном флаге сразу выходит. Но аннотации инжекции этих секретов пришлось убрать: vault-agent-init не рендерит шаблон для несуществующего секрета и роняет под в Init:Error ещё до старта приложения. nginx-configmap заменён целиком. В base зашиты upstream'ы на namespace pm, workspaces и processing; nginx резолвит их при старте и падал с "host not found in upstream". pm убран как ненужный, для остальных двух адрес вынесен в переменную с resolver — их резолвинг откладывается до запроса, поэтому появление сервисов позже не потребует правки конфига. Заменять пришлось именно патчем: объект с тем же kind+name уже приходит из base, и добавление его в resources валит сборку слоя целиком. Секреты django в Vault: postgres, rabbitmq, minio и общие RSA-ключи JWT. RabbitMQ и MinIO подключены под административными кредами — отдельных пользователей приложения чарты создавать не умеют, это осознанный долг. Задача создания суперпользователя переписана с docker compose exec на kubectl exec. ВНИМАНИЕ: она пока НЕ РАБОТАЕТ — команда падает, вывод скрыт no_log. Механизм проверен отдельно и исправен, значит ошибка на стороне Django. Чтобы увидеть причину, нужно временно снять no_log.
271 lines
12 KiB
YAML
271 lines
12 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"
|
||
|
||
# nginx монтирует ./nginx/templates и ./nginx/certs — без шаблонов nginx не
|
||
# стартует, поэтому копируем конфигурацию рядом с compose.
|
||
# Изменение шаблона → handler пересоздаёт nginx (envsubst рендерит на старте).
|
||
- name: Скопировать шаблоны nginx
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/nginx/templates/"
|
||
dest: "{{ deploy_dir }}/nginx/templates/"
|
||
mode: "0644"
|
||
notify: Recreate nginx
|
||
|
||
# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) и
|
||
# ./backend/production.py (классическая JWT-аутентификация).
|
||
# Изменение конфигов → handler пересоздаёт backend и celery.
|
||
- name: Скопировать конфиги backend (uwsgi.ini, production.py)
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/backend/"
|
||
dest: "{{ deploy_dir }}/backend/"
|
||
mode: "0644"
|
||
notify: Recreate backend and celery
|
||
|
||
# k3s-манифесты DNS-моста (namespace processing + Service/Endpoints postgres/minio).
|
||
# processing-k8s-init монтирует ./k3s/manifests/processing и применяет их kubectl'ом.
|
||
- name: Скопировать k3s-манифесты (DNS-мост processing)
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/k3s/manifests/"
|
||
dest: "{{ deploy_dir }}/k3s/manifests/"
|
||
mode: "0644"
|
||
|
||
# engine монтирует ./engine/yc-s3-service-account.json (доступ Job-подов к S3).
|
||
- name: Скопировать конфиги engine (S3 service-account)
|
||
ansible.builtin.copy:
|
||
src: "{{ deploy_src_root }}/engine/"
|
||
dest: "{{ deploy_dir }}/engine/"
|
||
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]
|
||
|
||
# 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]
|
||
|
||
# --- Самоподписанный TLS-сертификат для трёх доменов ----------------------
|
||
- name: Создать каталог для сертификатов
|
||
ansible.builtin.file:
|
||
path: "{{ cert_dir }}"
|
||
state: directory
|
||
mode: "0750"
|
||
|
||
- name: Сгенерировать самоподписанный сертификат (SAN на все домены)
|
||
ansible.builtin.shell:
|
||
# umask 077 → приватный ключ создаётся сразу с правами 0600
|
||
cmd: >-
|
||
umask 077 && openssl req -x509 -nodes -newkey rsa:2048 -days {{ cert_days }}
|
||
-keyout {{ cert_dir }}/selfsigned.key
|
||
-out {{ cert_dir }}/selfsigned.crt
|
||
-subj "/CN={{ platform_domain }}"
|
||
-addext "subjectAltName=DNS:{{ platform_domain }},DNS:{{ admin_domain }},DNS:{{ minio_domain }}"
|
||
creates: "{{ cert_dir }}/selfsigned.crt"
|
||
|
||
# --- Доступ к приватному реестру (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]
|
||
|
||
# --- Поднять сервисы приложения ------------------------------------------
|
||
- name: Поднять сервисы приложения (docker compose up -d)
|
||
ansible.builtin.command:
|
||
cmd: "{{ compose_cmd }} up -d {{ sarex_services | join(' ') }}"
|
||
chdir: "{{ deploy_dir }}"
|
||
when: sarex_compose_up | bool
|
||
changed_when: true
|
||
tags: [up]
|
||
|
||
# --- Django-суперпользователь: генерация логина+пароля, создание -----------
|
||
# Только по явной команде (uv run poe superuser передаёт sarex_create_superuser=true),
|
||
# чтобы обычный deploy не дёргал backend. Логин/пароль персистятся в secrets_store
|
||
# (переиспользуются при повторе — идемпотентно).
|
||
- name: Сгенерировать логин суперпользователя
|
||
ansible.builtin.set_fact:
|
||
superuser_name: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_NAME chars=ascii_lowercase,digits length=10') }}"
|
||
when: sarex_create_superuser | default(false) | bool
|
||
no_log: true
|
||
tags: [superuser]
|
||
|
||
- name: Сгенерировать пароль суперпользователя
|
||
ansible.builtin.set_fact:
|
||
superuser_password: "{{ lookup('password', secrets_store ~ '/' ~ inventory_hostname ~ '/SAREX_SUPERUSER_PASSWORD chars=ascii_letters,digits length=' ~ secret_length) }}"
|
||
when: sarex_create_superuser | default(false) | bool
|
||
no_log: true
|
||
tags: [superuser]
|
||
|
||
# Backend теперь живёт в k3s, а не в compose, поэтому идём через kubectl exec
|
||
# внутри контейнера k3s-server, а не через docker compose exec.
|
||
#
|
||
# Пароль передаётся переменной окружения (env в kubectl exec), а не аргументом
|
||
# командной строки: аргументы видны в ps любому, кто имеет доступ к ноде.
|
||
# Метка именно app=backend: universal-chart не проставляет
|
||
# app.kubernetes.io/instance, и селектор по нему возвращает пустой список.
|
||
- name: Найти под backend в k3s
|
||
when: sarex_create_superuser | default(false) | bool
|
||
ansible.builtin.command:
|
||
cmd: >-
|
||
{{ compose_cmd }} exec -T k3s-server
|
||
kubectl -n {{ django_namespace }} get pods -l app=backend
|
||
--field-selector=status.phase=Running
|
||
-o jsonpath={.items[0].metadata.name}
|
||
chdir: "{{ deploy_dir }}"
|
||
register: backend_pod
|
||
changed_when: false
|
||
tags: [superuser]
|
||
|
||
# argv, а не shell: аргументы передаются процессу напрямую, без разбора
|
||
# шеллом. Иначе python-однострочник попадает внутрь двойных кавычек внутри
|
||
# sh -ec '...' и разваливается на вложенном экранировании.
|
||
#
|
||
# Пароль идёт переменной окружения, а не аргументом: аргументы видны в ps
|
||
# любому, у кого есть доступ к ноде.
|
||
- name: Создать/обновить Django-суперпользователя в backend (k3s)
|
||
when: sarex_create_superuser | default(false) | bool
|
||
ansible.builtin.command:
|
||
argv:
|
||
- "{{ compose_cmd.split()[0] }}"
|
||
- compose
|
||
- exec
|
||
- -T
|
||
- k3s-server
|
||
- kubectl
|
||
- -n
|
||
- "{{ django_namespace }}"
|
||
- exec
|
||
- "{{ backend_pod.stdout | trim }}"
|
||
- -c
|
||
- backend
|
||
- --
|
||
- env
|
||
- "SU_NAME={{ superuser_name }}"
|
||
- "SU_PASS={{ superuser_password }}"
|
||
- "SU_EMAIL={{ superuser_name }}@{{ platform_domain }}"
|
||
- python
|
||
- manage.py
|
||
- shell
|
||
- -c
|
||
- >-
|
||
import os;
|
||
from django.contrib.auth import get_user_model;
|
||
U = get_user_model();
|
||
u, c = U.objects.get_or_create(username=os.environ['SU_NAME'], defaults={'email': os.environ['SU_EMAIL']});
|
||
u.is_staff = True; u.is_superuser = True; u.set_password(os.environ['SU_PASS']); u.save();
|
||
print('created' if c else 'updated')
|
||
chdir: "{{ deploy_dir }}"
|
||
register: superuser_result
|
||
changed_when: true
|
||
no_log: true
|
||
tags: [superuser]
|
||
|
||
- name: Показать учётные данные суперпользователя
|
||
ansible.builtin.debug:
|
||
msg: "Django superuser → login: {{ superuser_name }} password: {{ superuser_password }}"
|
||
when: sarex_create_superuser | default(false) | bool
|
||
tags: [superuser]
|