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 новых замечаний не даёт.
This commit is contained in:
emelinda 2026-08-10 12:55:04 +03:00
parent da2a0b33a7
commit 75d1fba14a
15 changed files with 82 additions and 1105 deletions

View File

@ -50,8 +50,9 @@ uv run poe install
```
Последовательность: `sync``galaxy`**`provision`** (базовые пакеты + docker
и запуск службы) → **`stack`** (деплой конфигурации + `docker compose up -d`
сервисов приложения) → **`superuser`** (генерация и создание Django-админа).
и запуск службы) → **`platform`** (деплой конфигурации + подъём подложки:
k3s, gitea, vault, bootstrap Flux) → **`superuser`** (показать учётные данные
администратора платформы). Приложения после этого разворачивает Flux сам.
Перед запуском впишите хосты в [inventory.ini](inventory.ini) и убедитесь, что
на control-node есть SSH-ключ и `~/.ssh/local/authorized_key.json` для входа в
реестр.
@ -62,19 +63,21 @@ uv run poe install
| --- | --- |
| `uv run poe ping` | проверка SSH + sudo |
| `uv run poe provision` | базовые пакеты + docker/containerd + запуск службы |
| `uv run poe deploy` | деплой конфигурации: файлы, `.env`, сертификаты, `docker login` |
| `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 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`) |
Поднимаемые сервисы задаются в `sarex_services` (роль `sarex_stack`) —
`postgres(+init)`, `redis`, `rabbitmq`, `minio(+init)`, `measurements`,
`backend`, `celery`, `frontend`, `nginx` (k3s/gitea/processing не поднимаются).
`docker compose` поднимает **только подложку** — k3s, gitea, vault и одноразовые
установщики. Прикладные сервисы разворачивает Flux внутри k3s по манифестам из
`apps/` и `infrastructure/`, и отдельной команды для них нет: достаточно
запушить изменения в репозиторий контура.
> `ANSIBLE_CONFIG` и `UV_LINK_MODE` заданы в `[tool.poe.env]` — на диске `C:`
> (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в
@ -87,20 +90,25 @@ uv run poe install
раскаткой занимается FluxCD.
```
docker compose k3s (4 ноды в контейнерах)
docker compose k3s (3 ноды в контейнерах)
├── k3s-server ──► нода ┐
├── k3s-worker-1..3 ──► ноды ├─► flux-system (controllers)
├── gitea (172.28.0.12) ◄────── ┘ и всё, что опишем в clusters/aero
│ └── infra/iac.git
├── vault (+init)
├── 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 — тем же
приёмом, что `bridge-postgres`/`bridge-minio` для processing. Источник в
приёмом, каким заведён мост до Vault (`k3s/manifests/vault`). Источник в
`GitRepository` указан именем `gitea.flux-system.svc.cluster.local`, а адрес
задан ровно в одном месте — переменной `GITEA_BRIDGE_IP` (она же `ipv4_address`
сервиса `gitea`). Контейнер `flux-bootstrap` резолвит то же имя через
@ -121,18 +129,18 @@ docker compose k3s (4 ноды в контейнерах)
{{ deploy_dir }}/k3s-storage/
├── server/ ← PVC, севшие на k3s-server
├── worker-1/
├── worker-2/
└── worker-3/
└── worker-2/
```
Файлы лежат на хосте обычными каталогами: переживают пересоздание контейнера
k3s и `docker compose down -v`, бэкапятся штатными средствами.
> **Следствие для stateful-сервисов.** Хранилище **node-local**: PVC привязан к
> той ноде, где под запустился первым. При переезде postgres/redis/rabbitmq/minio
> в k3s их нужно явно прибивать к конкретной ноде (`nodeSelector`), иначе данные
> размажутся по четырём каталогам, а потеря ноды сделает том недоступным.
> Бэкап должен покрывать все каталоги `k3s-storage/*`.
> той ноде, где под запустился первым. postgresql, minio и rabbitmq уже работают
> в k3s, поэтому их данные лежат в каталоге той ноды, куда сел под, — потеря
> ноды делает том недоступным, а `kubectl delete pod` с последующим переездом
> на другую ноду выглядит как «база опустела». Бэкап должен покрывать все
> каталоги `k3s-storage/*`.
```bash
uv run poe platform # поднять подложку
@ -162,26 +170,14 @@ uv run poe flux-status # убедиться, что Flux реконсил
остальные — в `aero/.secrets/<host>/`. Unseal-ключ и root-токен Vault лежат на
отдельном docker-томе `sarex-vault-init` с правами `600` и **в git не попадают**.
#### Границы текущего этапа
### Развёртывание (роль `sarex_stack`)
Сделана только подложка. Осознанно **не** сделано:
- Репозиторий в 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` в
Копирует `docker-compose.yaml`, конфигурацию Vault и k3s-манифесты DNS-мостов в
`/root/sarex`, рендерит `.env` из `.env.example` со **сгенерированными паролями**
и доменами `*.sarex.local.lonsdaleites.ru`, генерирует самоподписанный
TLS-сертификат на все три домена (`nginx/certs/selfsigned.{crt,key}`), логинится
в реестр и (при `sarex_compose_up=true`) поднимает сервисы приложения.
Роли/базы и MinIO-бакеты создаются одноразовыми `postgres-init`/`minio-init`.
и доменами `*.sarex.local.lonsdaleites.ru`, логинится в реестр, кладёт секреты в
Vault и поднимает подложку тремя волнами (`uv run poe platform`). Дальше за дело
берётся Flux: базы, бакеты, очереди и сами приложения появляются в k3s из
`apps/` и `infrastructure/`.
```bash
uv run ansible-playbook deploy.yml --check --diff # dry-run
@ -196,11 +192,13 @@ uv run ansible-playbook deploy.yml # подготовка фа
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`).
- Образы приложений приватные (`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-коллекции (опционально)

View File

@ -34,8 +34,6 @@ check = "ansible-playbook site.yml --check --diff"
# Деплой конфигурации: файлы, .env с паролями, сертификаты, docker login
deploy = "ansible-playbook deploy.yml"
deploy-check = "ansible-playbook deploy.yml --check --diff"
# Деплой + поднять стек приложения (docker compose up -d)
stack = "ansible-playbook deploy.yml -e sarex_compose_up=true"
# Деплой + поднять GitOps-подложку: k3s + gitea + vault + bootstrap FluxCD.
# Без --tags намеренно: подложке нужны .env, docker-compose.yaml и vault/config,
# которые кладут нетегированные задачи роли (при --tags они были бы пропущены).
@ -60,9 +58,6 @@ dbsync = "ansible-playbook deploy.yml -e sarex_dbsync=true --tags dbsync"
lint = "ansible-lint"
# --- Операционные задачи на хосте (без полного деплоя) ----------------------
# Поднять/пересоздать сервисы приложения (docker compose up -d sarex_services).
# Только тег up: не трогает файлы/.env/сертификаты/login, лишь применяет стек.
up = "ansible-playbook deploy.yml -e sarex_compose_up=true --tags up"
[tool.poe.tasks.ps]
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose ps chdir=/root/sarex'"
@ -108,6 +103,7 @@ required = true
[tool.poe.tasks.setup]
sequence = ["sync", "galaxy"]
# Установка с чистого листа: окружение → docker → деплой+стек → суперпользователь
# Установка с чистого листа: окружение → docker → подложка (k3s+gitea+vault+Flux,
# дальше приложения разворачивает Flux сам) → показать учётку администратора.
[tool.poe.tasks.install]
sequence = ["sync", "galaxy", "provision", "stack", "superuser"]
sequence = ["sync", "galaxy", "provision", "platform", "superuser"]

View File

@ -24,15 +24,15 @@ aero_kernel_modules:
# Источник файлов — корень репозитория iac (playbook лежит в aero/)
deploy_src_root: "{{ playbook_dir }}/.."
# Домены верхнего nginx (попадают в .env и в SAN сертификата)
# Домены контура. Сертификаты им выписывает cert-manager внутри кластера
# (infrastructure/istio-config/aero, Certificate contour-tls), самоподписанного
# сертификата на хосте больше нет — он был нужен верхнему nginx, а тот уехал в
# istio ingressgateway. Эти же имена продублированы в dnsNames сертификата;
# добавляешь домен — правь оба места.
platform_domain: sarex.local.lonsdaleites.ru
admin_domain: admin.sarex.local.lonsdaleites.ru
minio_domain: minio.sarex.local.lonsdaleites.ru
# Самоподписанный TLS-сертификат
cert_days: 365
cert_dir: "{{ deploy_dir }}/nginx/certs"
# Переменные-пароли: им генерируются стойкие значения взамен слабых дефолтов.
sarex_secret_vars:
- K3S_TOKEN
@ -97,9 +97,6 @@ registry_secrets:
# Содержимое то же, что у regcred, — доступ в тот же cr.yandex.
- {name: dockerhub, namespace: processing}
# Поднимать стек (docker compose up -d). Требует запущенной службы docker и
# docker login в cr.yandex — по умолчанию выключено.
sarex_compose_up: false
compose_cmd: "docker compose"
# --- Перекат нагрузок после правки конфигурации -----------------------------
@ -553,28 +550,6 @@ sarex_postgres_bim_user: bim
# завести отдельно, иначе загрузка файлов будет падать.
sarex_django_s3_bucket: sarex-media-storage
# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет
# зависимости и порядок). k3s/gitea намеренно не поднимаем.
sarex_services:
# k3s + провижининг DNS-моста (нужны engine'у до старта)
- k3s-server
- processing-k8s-init
- postgres
- postgres-init
- redis
- rabbitmq
- minio
- minio-init
- measurements
- s3-proxy
- backend
- celery
- frontend
- processing-api
- processing-frontend
- engine
- nginx
# Namespace прикладного слоя django в k3s. Используется задачей создания
# суперпользователя (tasks/main.yml) — backend переехал из compose в кластер.
django_namespace: django

View File

@ -1,20 +0,0 @@
---
# Пересоздание контейнеров при изменении их bind-mount конфигов.
# docker compose up -d НЕ пересоздаёт контейнер, если поменялся только
# смонтированный файл (шаблон nginx, production.py) — контент тома он не
# отслеживает. Поэтому на изменение конфига форсим --force-recreate конкретного
# сервиса. Срабатывает только когда стек реально поднимается (sarex_compose_up),
# иначе на обычном deploy без up мы бы стартовали контейнеры.
- name: Recreate nginx
ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d --force-recreate --no-deps nginx"
chdir: "{{ deploy_dir }}"
when: sarex_compose_up | bool
- name: Recreate backend and celery
# Оба монтируют ./backend/production.py (celery — ещё и через тот же файл),
# поэтому пересоздаём вместе.
ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d --force-recreate --no-deps backend celery"
chdir: "{{ deploy_dir }}"
when: sarex_compose_up | bool

View File

@ -28,41 +28,15 @@
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)
# 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"
# 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:
@ -161,24 +135,6 @@
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 читает
# его только при старте.
@ -209,15 +165,6 @@
when: sarex_reload | bool
tags: [reload]
# --- Поднять сервисы приложения ------------------------------------------
- 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-суперпользователь ----------------------------------------------
# Самого создания здесь больше нет — его делает под backend при каждом старте
# (apps/django/aero/kustomization.yaml, args стартового скрипта). Так учётка

View File

@ -1,7 +1,8 @@
---
# Подъём GitOps-подложки контура: k3s + gitea + vault + bootstrap FluxCD.
# Отделено от прикладного стека (sarex_services) намеренно: здесь только
# публичные образы, docker login в приватный реестр не требуется.
# Это единственное, что поднимается через docker compose. Прикладной стек
# разворачивает Flux внутри k3s по манифестам из apps/ и infrastructure/ —
# compose его больше не описывает.
#
# Порядок важен и поэтому разбит на две волны: flux-bootstrap монтирует
# ./k3s/kubeconfig.yaml, который k3s-server создаёт только при первом старте.

View File

@ -1,141 +0,0 @@
from .base import *
from logging.handlers import SysLogHandler
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
FILE_UPLOAD_PERMISSIONS = 0o644
DEBUG = False
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = False
# STATIC_ROOT = env('STATIC_ROOT')
# MEDIA_ROOT = env('MEDIA_ROOT')
# --- Media / объектное хранилище S3 -----------------------------------------
# Файлы (ортофото, облака точек, вложения) складываются в S3/MinIO кастомным
# storage-бэкендом; иначе Django пишет их на локальный диск контейнера.
DEFAULT_FILE_STORAGE = 'sarex.core.storages.CustomS3Boto3Storage'
# CustomS3Boto3Storage.url() отдаёт абсолютный URL = HOST + MEDIA_URL + key.
# В base.py HOST захардкожен на https://lk.sarex.io — переопределяем на наш
# домен платформы (из SERVER_HOST env), иначе media-ссылки уйдут на чужой хост.
# Верхний nginx проксирует /media/ в s3-proxy (тот отдаёт объекты из бакета).
HOST = SERVER_HOST
# SECRET_KEY = env('SECRET_KEY')
SECRET_KEY = 't2=9+($2f%7ptsdy4!rby$)mcfl1l%o2e@vs^d(g&(wwi&%k1v'
# DATABASE CONFIGURATION START
# ------------------------------------------------------------------------------
POSTGRES_DATABASE = env('DJANGO_POSTGRES_DATABASE')
POSTGRES_USER = env('DJANGO_POSTGRES_USER')
POSTGRES_PASSWORD = env('DJANGO_POSTGRES_PASSWORD')
POSTGRES_HOST = env('DJANGO_POSTGRES_HOST')
POSTGRES_PORTS = env('DJANGO_POSTGRES_PORTS', default="5432")
DATABASES = {
'default': {
'ENGINE': 'django_prometheus.db.backends.postgresql',
'NAME': POSTGRES_DATABASE,
'USER': POSTGRES_USER,
'PASSWORD': POSTGRES_PASSWORD,
'HOST': POSTGRES_HOST,
'PORT': POSTGRES_PORTS,
}
}
# DATABASE CONFIGURATION END
# ------------------------------------------------------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'formatters': {
'verbose': {
'format': '[contactor] %(levelname)s %(asctime)s %(message)s'
},
},
'handlers': {
# Send all messages to console
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
# critical errors are logged to sentry
'sentry': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'logging.StreamHandler',
},
},
'loggers': {
# This is the "catch all" logger
'': {
'handlers': ['console', 'sentry'],
'level': 'DEBUG',
'propagate': False,
},
}
}
COMPARATOR_SECTION = env("COMPARATOR_SECTION", default="sarex-production-storage")
SIMPLE_JWT = {
# 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'RS512',
'SIGNING_KEY': env.str("JWT_PRIVATE_KEY").replace("\\n", "\n"),
'VERIFYING_KEY': env.str("JWT_PUBLIC_KEY").replace("\\n", "\n"),
'AUDIENCE': None,
'ISSUER': SIMPLE_JWT_ISSUER,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
# 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_LIFETIME': timedelta(hours=1),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
COMPARISON_API_URL = f"{WORKFLOWSSETTINGS.host}/comparisons"
DOCUMENTATION_API_URL = f"{WORKFLOWSSETTINGS.host}/documentations"
PDM_FILES_API_URL = f"{WORKFLOWSSETTINGS.host}/files"
# --- Override для self-hosted (auth_type=original) --------------------------
# В base.py классическая SimpleJWT-аутентификация закомментирована (образ собран
# под Zitadel), поэтому Bearer-токен от /api/token/ отвергается защищёнными
# эндпоинтами. Возвращаем её первым классом; Session — для админки.
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"] = [
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
]
# --- Оставляем в сайдбаре только модуль «Съёмки» ----------------------------
# Фронт показывает модуль, только если его uri есть в modules_list (= SAREX_MODULES).
SAREX_MODULES = [
{"name": "Съёмки", "uri": "/targets"},
]

View File

@ -1,24 +0,0 @@
[uwsgi]
log-format = {"time": "%(time)", "method": "%(method)", "uri": "%(uri)", "status": "%(status)", "size": "%(size)", "addr": "%(addr)", "user": "%(user)", "proto": "%(proto)", "user_agent": "%(uagent)", "referer": "%(referer)", "trace_id": "%(trace_id)", "span_id": "%(span_id)"}
module = config.wsgi:application
DJANGO_SETTINGS_MODULE = config.settings.production
# HTTP-протокол вместо uwsgi (nginx проксирует через proxy_pass, доступен напрямую)
http-socket = 0.0.0.0:8000
processes = 4
master = true
vacuum = true
die-on-term = true
enable-threads = true
buffer-size = 65535
stats = :3031
stats-http = true
memory-report = true
lazy-apps = true
listen = 1024
disable-write-exception= 0
harakiri = 300
socket-timeout = 300
chunked-input-timeout = 300
http-timeout = 300
worker-reload-mercy = 240
mule-reload-mercy = 240

View File

@ -1,4 +1,23 @@
# Общая конфигурация воркер-нод k3s — см. сервисы k3s-worker-1..3.
# GitOps-ПОДЛОЖКА КОНТУРА, И ТОЛЬКО ОНА.
#
# Здесь описано ровно то, что не может жить внутри кластера, потому что кластер
# на этом и стоит: сами ноды k3s, git-репозиторий-источник (gitea), хранилище
# секретов (vault) и одноразовые установщики, доводящие всё это до состояния,
# в котором за дело берётся Flux.
#
# ПРИКЛАДНОГО СТЕКА ЗДЕСЬ НЕТ И БЫТЬ НЕ ДОЛЖНО. Django, celery, frontend,
# processing, measurements, bim, а также postgres, redis, rabbitmq и minio
# развёрнуты в k3s и описаны декларативно в apps/ и infrastructure/; источник
# правды для них — репозиторий в gitea, а не этот файл. Раньше они дублировались
# и здесь; дубликат удалён, потому что расходился с кластером и вводил в
# заблуждение. Возвращать их сюда не надо: nginx конфликтовал бы с k3s-server за
# порты 80/443 (их занимает istio ingressgateway внутри контейнера ноды), а
# вторая копия СУБД тихо разъехалась бы с той, где лежат данные.
#
# Поднимает всё это ansible-роль sarex_stack тремя волнами — см. aero/roles/
# sarex_stack/tasks/platform.yml, порядок там значим.
# Общая конфигурация воркер-нод k3s — см. сервисы k3s-worker-1..2.
# У каждой ноды СВОЙ том состояния и СВОЙ каталог PVC-хранилища: провижинер
# local-path кладёт данные на ту ноду, где под запустился первым, и через
# nodeAffinity навсегда привязывает к ней PV.
@ -59,7 +78,8 @@ services:
- "80:80" # ingress http
- "443:443" # ingress https
# kube-apiserver наружу НЕ публикуем: доступ к нему — только изнутри
# compose-сети (engine ходит по KUBE_ADDR=https://k3s-server:6443).
# compose-сети, по имени k3s-server:6443 (так к нему ходят flux-bootstrap
# и одноразовые init-контейнеры).
# Если понадобится внешний kubectl — раскомментируй и ОБЯЗАТЕЛЬНО
# ограничь фаерволом до доверенных адресов.
# - "6443:6443"
@ -112,26 +132,6 @@ services:
- ./k3s-storage/worker-2:/var/lib/rancher/k3s/storage:z
- ./k3s/registries.yaml:/etc/rancher/k3s/registries.yaml:ro,z
# Одноразовый провижининг k3s: namespace processing + DNS-мост postgres/minio.
# Образ k3s содержит kubectl; server/tls перекрываем на k3s-server:6443
# (kubeconfig из /output даёт клиентские креды, адрес/TLS переопределяем флагами).
processing-k8s-init:
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
container_name: processing-k8s-init
restart: "no"
depends_on:
k3s-server:
condition: service_healthy
entrypoint: ["/bin/sh", "-ec"]
command:
- |
kubectl --kubeconfig /kube/config \
--server https://k3s-server:6443 --insecure-skip-tls-verify \
apply -f /manifests/
volumes:
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
- ./k3s/manifests/processing:/manifests:ro,z
# ---------------------------------------------------------------------------
# GitOps-подложка контура: gitea (источник) + vault (секреты) + flux (в k3s).
# Порядок: gitea → gitea-init (админ/орг/репо) → flux-bootstrap (ставит Flux
@ -396,494 +396,6 @@ services:
--allow-insecure-http \
--interval=2m
# ---------------------------------------------------------------------------
# Инфраструктурные зависимости apps/django (sarex-backend).
# Django деплоится в k3s и ходит сюда — про связность k3s→compose см. README.
# Креды/версии взяты из sarex-backend/docker-compose.yaml и apps/django/.env.example.
# ---------------------------------------------------------------------------
postgres:
image: ${POSTGRES_IMAGE:-postgres:12-alpine}
container_name: postgres
restart: unless-stopped
environment:
# суперпользователь (администрирование)
POSTGRES_DB: ${SAREX_POSTGRES_DB:-sarex_db}
POSTGRES_USER: ${SAREX_POSTGRES_USER:-sarex}
POSTGRES_PASSWORD: ${SAREX_POSTGRES_PASSWORD:-sarex}
# роли приложений с паролями — читаются init-скриптом 00-init.sh
DJANGO_DB: ${SAREX_POSTGRES_DB:-sarex_db}
DJANGO_DB_USER: ${SAREX_DJANGO_DB_USER:-django}
DJANGO_DB_PASSWORD: ${SAREX_DJANGO_DB_PASSWORD:-django-secret}
PROC_DB: ${SAREX_PROCESSING_DB:-workflow_db}
PROC_DB_USER: ${SAREX_PROCESSING_DB_USER:-processing}
PROC_DB_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
volumes:
- sarex-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${SAREX_POSTGRES_USER:-sarex} -d ${SAREX_POSTGRES_DB:-sarex_db}"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
# Статический IP: на него ссылается k8s-Endpoints `postgres` (DNS-мост в k3s).
networks:
default:
ipv4_address: 172.28.0.10
# Одноразовый init: на каждое приложение заводит роль (LOGIN+пароль) и базу
# данных с этим владельцем. Идемпотентно (по образцу minio-init).
postgres-init:
image: ${POSTGRES_IMAGE:-postgres:12-alpine}
depends_on:
postgres:
condition: service_healthy
entrypoint:
- /bin/sh
- -c
- |
set -e
until psql -h postgres -U "$$SUPER_USER" -d "$$SUPER_DB" -c 'SELECT 1' >/dev/null 2>&1; do
echo 'waiting for postgres...'; sleep 2;
done
q() { psql -v ON_ERROR_STOP=1 -h postgres -U "$$SUPER_USER" -d "$$SUPER_DB" "$$@"; }
# provision <db> <user> <password>
provision() {
q -tc "SELECT 1 FROM pg_roles WHERE rolname='$$2'" | grep -q 1 \
|| q -c "CREATE ROLE \"$$2\" LOGIN PASSWORD '$$3'"
q -c "ALTER ROLE \"$$2\" WITH LOGIN PASSWORD '$$3'"
q -tc "SELECT 1 FROM pg_database WHERE datname='$$1'" | grep -q 1 \
|| q -c "CREATE DATABASE \"$$1\" OWNER \"$$2\""
q -c "ALTER DATABASE \"$$1\" OWNER TO \"$$2\""
q -c "GRANT ALL PRIVILEGES ON DATABASE \"$$1\" TO \"$$2\""
echo "db $$1 + user $$2 ready"
}
provision "$$DJANGO_DB" "$$DJANGO_USER" "$$DJANGO_PASSWORD"
provision "$$PROC_DB" "$$PROC_USER" "$$PROC_PASSWORD"
provision "$$BIM_DB" "$$BIM_USER" "$$BIM_PASSWORD"
provision "$$WORKSPACE_DB" "$$WORKSPACE_USER" "$$WORKSPACE_PASSWORD"
# Расширения PostgreSQL в базе django: CREATE EXTENSION может только
# суперпользователь. Django-миграции (CreateExtension) идут с IF NOT
# EXISTS → после этого станут no-op и не упадут на правах.
for ext in $$DJANGO_EXTENSIONS; do
q -c "CREATE EXTENSION IF NOT EXISTS \"$$ext\"" && echo "extension $$ext ready in $$DJANGO_DB"
done
# Расширения для processing (workflow_db) — ОТДЕЛЬНАЯ база, поэтому явно
# -d PROC_DB. Роль processing не суперпользователь → CREATE EXTENSION в
# её миграциях (uuid-ossp/ltree/hstore) падает; заводим суперпользователем.
for ext in $$PROC_EXTENSIONS; do
psql -v ON_ERROR_STOP=1 -h postgres -U "$$SUPER_USER" -d "$$PROC_DB" \
-c "CREATE EXTENSION IF NOT EXISTS \"$$ext\"" && echo "extension $$ext ready in $$PROC_DB"
done
environment:
PGPASSWORD: ${SAREX_POSTGRES_PASSWORD:-sarex}
SUPER_USER: ${SAREX_POSTGRES_USER:-sarex}
SUPER_DB: ${SAREX_POSTGRES_DB:-sarex_db}
# django (использует основную базу SAREX_POSTGRES_DB)
DJANGO_DB: ${SAREX_POSTGRES_DB:-sarex_db}
DJANGO_USER: ${SAREX_DJANGO_DB_USER:-django}
DJANGO_PASSWORD: ${SAREX_DJANGO_DB_PASSWORD:-django-secret}
DJANGO_EXTENSIONS: ${SAREX_DJANGO_DB_EXTENSIONS:-ltree}
# processing
PROC_DB: ${SAREX_PROCESSING_DB:-workflow_db}
PROC_USER: ${SAREX_PROCESSING_DB_USER:-processing}
PROC_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
PROC_EXTENSIONS: ${SAREX_PROCESSING_DB_EXTENSIONS:-uuid-ossp ltree hstore}
# bim
BIM_DB: ${SAREX_BIM_DB:-bim_db}
BIM_USER: ${SAREX_BIM_DB_USER:-bim}
BIM_PASSWORD: ${SAREX_BIM_DB_PASSWORD:-bim-secret}
# workspace
WORKSPACE_DB: ${SAREX_WORKSPACE_DB:-workspace_db}
WORKSPACE_USER: ${SAREX_WORKSPACE_DB_USER:-workspace}
WORKSPACE_PASSWORD: ${SAREX_WORKSPACE_DB_PASSWORD:-workspace-secret}
restart: "no"
redis:
image: ${SAREX_REDIS_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10}
container_name: redis
restart: unless-stopped
# protected-mode no: redis только во внутренней docker-сети (порт наружу не
# публикуется), иначе бэкенд/celery из других контейнеров отбиваются "DENIED"
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning", "--protected-mode", "no", "--stop-writes-on-bgsave-error", "no"]
volumes:
- sarex-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 12
start_period: 5s
rabbitmq:
image: ${SAREX_RABBITMQ_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/rabbimtq:3.11.23}
container_name: rabbitmq
restart: unless-stopped
environment:
RABBITMQ_VHOST: ${SAREX_RABBITMQ_VHOST:-api}
RABBITMQ_USERNAME: ${SAREX_RABBITMQ_USER:-rabbit}
RABBITMQ_PASSWORD: ${SAREX_RABBITMQ_PASSWORD:-rabbit}
RABBITMQ_DISK_FREE_ABSOLUTE_LIMIT: 200MB
volumes:
- sarex-rabbitmq-data:/bitnami/rabbitmq/mnesia
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 15s
timeout: 10s
retries: 12
start_period: 30s
minio:
image: ${SAREX_MINIO_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/minio:250724}
container_name: minio
restart: unless-stopped
command: ["server", "/data", "--console-address", ":9001"]
environment:
MINIO_ROOT_USER: ${SAREX_MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${SAREX_MINIO_ROOT_PASSWORD:-minioadmin}
# Консоль отдаётся через верхний nginx с корня minio-домена
MINIO_BROWSER_REDIRECT_URL: ${SAREX_MINIO_CONSOLE_URL:-https://minio.sarex.local}
volumes:
- sarex-minio-data:/data
# Статический IP: на него ссылается k8s-Endpoints `minio` (DNS-мост в k3s).
networks:
default:
ipv4_address: 172.28.0.11
# Одноразовый init: ждёт MinIO и на каждое приложение заводит бакет +
# отдельного пользователя со scoped-политикой (доступ только к своему бакету).
minio-init:
image: ${SAREX_MC_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/mc:250724}
depends_on:
- minio
entrypoint:
- /bin/sh
- -c
- |
until mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD"; do
echo 'waiting for minio...'; sleep 2;
done
# provision <bucket> <user> <password> <policy>
provision() {
mc mb -p "local/$$1"
mc admin user add local "$$2" "$$3"
printf '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:*"],"Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}' "$$1" "$$1" > /tmp/policy.json
mc admin policy create local "$$4" /tmp/policy.json || true
mc admin policy attach local "$$4" --user "$$2" || true
echo "bucket $$1 + user $$2 ready"
}
# Единый бакет на все приложения (processing/measurements тоже сюда)
provision "$$MINIO_BUCKET" "$$MINIO_APP_USER" "$$MINIO_APP_PASSWORD" "$$MINIO_APP_POLICY"
environment:
MINIO_ROOT_USER: ${SAREX_MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${SAREX_MINIO_ROOT_PASSWORD:-minioadmin}
# единый бакет/пользователь приложения
MINIO_BUCKET: ${SAREX_MINIO_BUCKET:-sarex-media-storage}
MINIO_APP_USER: ${SAREX_MINIO_APP_USER:-sarex-app}
MINIO_APP_PASSWORD: ${SAREX_MINIO_APP_PASSWORD:-sarex-app-secret}
MINIO_APP_POLICY: ${SAREX_MINIO_APP_POLICY:-sarex-app-rw}
restart: "no"
# ---------------------------------------------------------------------------
# Приложение sarex (backend + celery + frontend).
# Образы приватные (cr.yandex) — нужен `docker login cr.yandex`.
# Для локальной сборки backend раскомментируй build (репозиторий — соседний).
# ---------------------------------------------------------------------------
backend:
image: ${SAREX_BACKEND_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/backend:production_80d1d847}
# build:
# context: ../../generic/sarex-backend
# dockerfile: Dockerfile
container_name: sarex-backend
restart: unless-stopped
environment: &sarex-backend-env
ALLOWED_HOSTS: "*"
DJANGO_SETTINGS_MODULE: config.settings.production
JWT_PRIVATE_KEY: ${SAREX_JWT_PRIVATE_KEY:-JWT}
JWT_PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK}
DJANGO_POSTGRES_HOST: postgres
DJANGO_POSTGRES_DATABASE: ${SAREX_POSTGRES_DB:-sarex_db}
DJANGO_POSTGRES_USER: ${SAREX_DJANGO_DB_USER:-django}
DJANGO_POSTGRES_PASSWORD: ${SAREX_DJANGO_DB_PASSWORD:-django-secret}
# celery: брокер rabbitmq + result backend redis (pydantic env_prefix CELERY_*)
CELERY_RABBITMQ_HOST: rabbitmq
CELERY_RABBITMQ_USER: ${SAREX_RABBITMQ_USER:-rabbit}
CELERY_RABBITMQ_PASSWORD: ${SAREX_RABBITMQ_PASSWORD:-rabbit}
CELERY_RABBITMQ_VHOST: ${SAREX_RABBITMQ_VHOST:-api}
CELERY_REDIS_HOST: redis
# S3 / объектное хранилище → MinIO (сервис minio)
S3_HOST: http://minio:9000
AWS_S3_ENDPOINT_URL: http://minio:9000
S3_LOGIN: ${SAREX_MINIO_APP_USER:-sarex-app}
S3_PASSWORD: ${SAREX_MINIO_APP_PASSWORD:-sarex-app-secret}
S3_BUCKET: ${SAREX_MINIO_BUCKET:-sarex-media-storage}
S3_REGION: ${SAREX_MINIO_REGION:-us-east-1}
# Сервис measurements (MeasurementSettings, env_prefix MEASUREMENTS_)
MEASUREMENTS_HOST: ${SAREX_MEASUREMENTS_HOST:-http://measurements:8000/}
# Домен платформы. HOST/SERVER_HOST → абсолютные URL, включая media
# (CustomS3Boto3Storage.url = HOST + /media/ + key). Без этого base.py
# берёт дефолт lk.sarex.io и media-ссылки уходят на чужой хост.
SERVER_HOST: https://${PLATFORM_DOMAIN:-sarex.local}
SERVER_API_HOST: https://${PLATFORM_DOMAIN:-sarex.local}
# Media: django-storage (S3). Backend отдаёт /media/<key>, верхний nginx
# проксирует их в s3-proxy (CORS+Range). Из apps/django/base/backend-deployment.yaml.
SERVER_USE_DJANGO_STORAGE: "1"
SERVER_DJANGO_URLS: "1"
# processing (workflows-api): backend ходит в processing-api за workflow'ами.
# WORKFLOWSSETTINGS.host строит COMPARISON/DOCUMENTATION/PDM_FILES_API_URL.
# По умолчанию use=False и host=api.sarex.io — переопределяем на наш стек.
WORKFLOWS_USE: "1"
WORKFLOWS_HOST: http://processing-api:8080
WORKFLOWS_BASE_HOST: http://backend:8000
WORKFLOWS_PREFIX: /internal/v1
tty: true
ports:
- "8000:8000" # HTTP API (uWSGI http-socket), migrate выполняется в entrypoint
volumes:
# свой uwsgi.ini поверх образного: http-socket + 4 воркера (:z — SELinux/RedOS)
- ./backend/uwsgi.ini:/opt/sarex/uwsgi.ini:ro,z
# production.py с включённой классической JWT-аутентификацией
- ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z
depends_on:
postgres:
condition: service_healthy
postgres-init:
condition: service_completed_successfully
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
celery:
image: ${SAREX_BACKEND_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/backend:production_80d1d847}
container_name: sarex-celery
restart: unless-stopped
entrypoint: celery -A config worker -l info -E -Q default -n default_worker.%h --concurrency=1 -B
environment:
<<: *sarex-backend-env
volumes:
- ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z
depends_on:
postgres:
condition: service_healthy
postgres-init:
condition: service_completed_successfully
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
minio-init:
condition: service_completed_successfully
frontend:
image: ${SAREX_FRONTEND_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/frontend:contour_42138486}
container_name: sarex-frontend
restart: unless-stopped
ports:
- "8080:80" # web UI
depends_on:
- backend
# ---------------------------------------------------------------------------
# s3-proxy — отдаёт объекты MinIO/S3 браузеру с CORS и поддержкой Range (media).
# В k8s креды приходят из Vault в /vault/secrets/s3; здесь Vault нет, поэтому
# entrypoint из манифеста gracefully пропускает отсутствующий файл, а
# AWS_*-переменные подставляем напрямую (MinIO app-пользователь + бакет).
# Источник: apps/django/base/s3-proxy.yaml.
# ---------------------------------------------------------------------------
s3-proxy:
image: ${SAREX_S3_PROXY_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/s3-proxy:vault_6e586217}
container_name: s3-proxy
restart: unless-stopped
entrypoint: ["/bin/sh", "-ec"]
command:
- |
set -a
[ -f /vault/secrets/s3 ] && . /vault/secrets/s3
set +a
exec /s3-proxy
environment:
ACCESS_LOG: "true"
CORS_ALLOW_HEADERS: "Content-Type, Accept-Ranges, Content-Range, Content-Encoding"
# Замена Vault-секрета /vault/secrets/s3 прямыми значениями:
AWS_API_ENDPOINT: http://minio:9000
AWS_S3_BUCKET: ${SAREX_MINIO_BUCKET:-sarex-media-storage}
AWS_ACCESS_KEY_ID: ${SAREX_MINIO_APP_USER:-sarex-app}
AWS_SECRET_ACCESS_KEY: ${SAREX_MINIO_APP_PASSWORD:-sarex-app-secret}
# Порт наружу не публикуем — доступ только через верхний nginx (/media/),
# как ClusterIP в k8s. Внутренняя docker-сеть резолвит s3-proxy:80.
depends_on:
minio-init:
condition: service_completed_successfully
# ---------------------------------------------------------------------------
# Приложение processing (workflows-api + workflows-frontend).
# Образы приватные (cr.yandex). БД — отдельная workflow_db в общем postgres.
# Версии/env взяты из apps/processing/base/*.yaml.
# ---------------------------------------------------------------------------
processing-api:
image: ${SAREX_PROCESSING_API_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-api_prod:075fc0}
# Локальная сборка (Go, cmd/httpserver). Исходник — на dev-машине:
# build:
# context: /mnt/c/Users/user/PycharmProjects/platform/workflows-api
# dockerfile: Dockerfile
container_name: processing-api
restart: unless-stopped
entrypoint: ["/httpserver"]
command: ["migrate"] # в манифесте: /httpserver migrate (миграции + сервер на :8080)
environment:
HTTP_HOST: 0.0.0.0:8080
POSTGRES_ADDRESS: postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: ${SAREX_PROCESSING_DB:-workflow_db}
POSTGRES_USER: ${SAREX_PROCESSING_DB_USER:-processing}
POSTGRES_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
POSTGRES_POOL_SIZE: "3"
POSTGRES_SSL_USE: "0"
ENABLE_SQL_QUERY: "0"
# JWT public key (общий с backend) — единственная интеграция, ей
# workflows-api проверяет Bearer-токен на /api. Больше код ничего не читает
# (ни S3, ни DJANGO_HOST — они в манифесте были мёртвыми).
PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK}
# Порт наружу не публикуем — доступ через верхний nginx (/workflows/api/).
depends_on:
postgres:
condition: service_healthy
postgres-init:
condition: service_completed_successfully
processing-frontend:
image: ${SAREX_PROCESSING_FRONTEND_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-frontend:ugok2_85f6ce2c}
# Локальная сборка (node, npm run build-module). Исходник — на dev-машине.
# Нужны build-args BUILD_ENV=contour и NPM_NEXUS_TOKEN (приватный npm).
# build:
# context: /mnt/c/Users/user/WebstormProjects/platform/workflows-frontend
# dockerfile: Dockerfile
# args:
# BUILD_ENV: contour
# NPM_NEXUS_TOKEN: ${NPM_NEXUS_TOKEN:-}
container_name: processing-frontend
restart: unless-stopped
# Порт наружу не публикуем — доступ через верхний nginx (/workflows/).
depends_on:
- processing-api
# ---------------------------------------------------------------------------
# workflows-engine — оркестратор processing: читает workflow_db, создаёт Job'ы
# в k3s (K8s-исполнитель; AMQP выкл). В кластер ходит out-of-cluster через
# kubeconfig (KUBE_CONFIG+KUBE_ADDR). Job-поды резолвят postgres/minio через
# DNS-мост namespace processing. Источник: apps/processing/base/engine*.yaml.
# ---------------------------------------------------------------------------
engine:
image: ${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}
container_name: engine
restart: unless-stopped
entrypoint: ["/engine"]
environment:
APP_NAME: workflows-engine
LOG_LEVEL: info
ENVIRONMENT: contour
# исполнители: только Kubernetes
ENABLE_KUBERNETES_EXECUTOR: "1"
ENABLE_AMQP_EXECUTOR: "0"
COUNT_RUNNING_WORKERS: "1"
COUNT_CANCELING_WORKERS: "1"
COUNT_HANDLE_JOB_WORKERS: "1"
WORKFLOW_PRIORITY: low
MAX_WORKFLOWS_LIMIT: "5"
# доступ в k3s (out-of-cluster): kubeconfig + перекрытый адрес apiserver
KUBE_CONFIG: /kube/config
KUBE_CONTEXT: default
KUBE_ADDR: https://k3s-server:6443
JOBS_NAMESPACE: processing
# свой доступ к workflow_db по compose-DNS
POSTGRES_ADDRESS: postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: ${SAREX_PROCESSING_DB:-workflow_db}
POSTGRES_USER: ${SAREX_PROCESSING_DB_USER:-processing}
POSTGRES_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
POSTGRES_POOL_SIZE: "20"
POSTGRES_SSL_USE: "0"
# хранилища: только S3, напрямую в minio (yc-s3 SA JSON смонтирован)
ENABLE_S3_STORAGE: "1"
S3_SERVICE_ACCOUNT: /etc/sarex/yc-s3/yc-s3-service-account.json
# дефолты планирования Job-подов
DEFAULT_IMAGE_PULL_POLICY: IfNotPresent
DEFAULT_CPU_REQUESTS: 100m
DEFAULT_MEMORY_REQUESTS: 64Mi
volumes:
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
- ./engine/yc-s3-service-account.json:/etc/sarex/yc-s3/yc-s3-service-account.json:ro,z
depends_on:
postgres:
condition: service_healthy
postgres-init:
condition: service_completed_successfully
k3s-server:
condition: service_healthy
processing-k8s-init:
condition: service_completed_successfully
# ---------------------------------------------------------------------------
# measurements — FastAPI, считает измерения по растрам из S3/MinIO (GDAL vsis3).
# БД/воркера нет. Обязателен S3_JSON_SETTINGS. Источник: apps/measurements/base.
# ---------------------------------------------------------------------------
measurements:
image: ${SAREX_MEASUREMENTS_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/measurements:production_a5bb3e84}
container_name: measurements
restart: unless-stopped
entrypoint: ["/opt/entrypoint.sh"]
environment:
# host http:// → GDAL переключается на AWS_HTTPS=NO (см. CONFIGURATION.md)
S3_JSON_SETTINGS: '{"host":"http://minio:9000","login":"${SAREX_MINIO_APP_USER:-sarex-app}","password":"${SAREX_MINIO_APP_PASSWORD:-sarex-app-secret}","verify":false,"buckets":["${SAREX_MINIO_BUCKET:-sarex-media-storage}"]}'
LOG_LEVEL: INFO
TRACING_USE: "false"
AUTH: "0"
DJANGO_HOST: http://backend:8000
# порт наружу не публикуем — доступ только из backend по http://measurements:8000/
depends_on:
minio-init:
condition: service_completed_successfully
# Верхний reverse-proxy: 3 домена → платформа / rabbitmq (admin) / minio
nginx:
image: ${NGINX_IMAGE:-nginx:1.27-alpine}
container_name: sarex-nginx
restart: unless-stopped
ports:
- "80:80" # http
- "443:443" # https (самоподписанный сертификат)
environment:
# подставляются envsubst'ом в шаблон при старте контейнера
PLATFORM_DOMAIN: ${PLATFORM_DOMAIN:-sarex.local}
ADMIN_DOMAIN: ${ADMIN_DOMAIN:-admin.sarex.local}
MINIO_DOMAIN: ${MINIO_DOMAIN:-minio.sarex.local}
volumes:
- ./nginx/templates:/etc/nginx/templates:ro,z # :z — SELinux label для RedOS
- ./nginx/certs:/etc/nginx/certs:ro,z # TLS-сертификаты
depends_on:
# - backend # TODO: закомментированы в шаблоне nginx, пока в работе
# - frontend
- minio
- rabbitmq
- s3-proxy
- processing-api
- processing-frontend
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
volumes:
k3s-server-data:
k3s-worker-1-data:
@ -891,13 +403,12 @@ volumes:
gitea-data:
sarex-vault-data: # file-хранилище Vault
sarex-vault-init: # unseal-ключ и root-токен (только для vault-init)
sarex-postgres-data:
sarex-redis-data:
sarex-rabbitmq-data:
sarex-minio-data:
# Дефолтной сети задаём фиксированный subnet, чтобы закрепить IP за postgres/minio.
# На эти адреса ссылаются k8s-Endpoints DNS-моста (namespace processing в k3s).
# Дефолтной сети задаём фиксированный subnet, чтобы закрепить IP за gitea и vault.
# На эти адреса ссылаются k8s-Endpoints DNS-мостов внутри k3s (namespace
# flux-system и vault): у кластера своя DNS, и имена compose-сети в ней не
# резолвятся, а поды дотягиваются до 172.28.0.0/16 через SNAT на ноде.
# Данные PVC (postgres, minio, rabbitmq) живут в k3s и томов здесь не имеют.
networks:
default:
ipam:

View File

@ -1,8 +0,0 @@
{
"host": "http://minio:9000",
"bucket": "sarex-media-storage",
"access_key_id": "sarex-app",
"secret_access_key": "sarex-app-secret",
"region": "us-east-1",
"verify": false
}

View File

@ -1,26 +0,0 @@
# DNS-мост: имя `minio` в namespace processing → compose-контейнер minio
# (статический IP 172.28.0.11, прямой S3 на :9000). Service без селектора + Endpoints.
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: processing
spec:
ports:
- name: s3
port: 9000
targetPort: 9000
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: minio
namespace: processing
subsets:
- addresses:
- ip: 172.28.0.11
ports:
- name: s3
port: 9000
protocol: TCP

View File

@ -1,26 +0,0 @@
# DNS-мост: имя `postgres` в namespace processing → compose-контейнер postgres
# (статический IP 172.28.0.10). Service без селектора + ручной Endpoints.
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: processing
spec:
ports:
- name: pg
port: 5432
targetPort: 5432
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: postgres
namespace: processing
subsets:
- addresses:
- ip: 172.28.0.10
ports:
- name: pg
port: 5432
protocol: TCP

View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: processing

View File

@ -1,36 +0,0 @@
#!/usr/bin/env bash
# Генерация самоподписанного TLS-сертификата для верхнего nginx.
#
# Использование:
# ./nginx/gen-cert.sh # домены по умолчанию
# ./nginx/gen-cert.sh sarex.local api.sarex.local # свои домены (первый → CN)
# DAYS=730 ./nginx/gen-cert.sh # свой срок действия
#
# Результат: nginx/certs/selfsigned.{crt,key} (в .gitignore).
# MSYS_NO_PATHCONV=1 нужен для Git Bash на Windows; на Linux он безвреден.
set -euo pipefail
CERT_DIR="$(cd "$(dirname "$0")" && pwd)/certs"
DAYS="${DAYS:-365}"
if [ "$#" -gt 0 ]; then
DOMAINS=("$@")
else
DOMAINS=(sarex.local admin.sarex.local minio.sarex.local)
fi
# subjectAltName из списка доменов, CN — первый домен
SAN=""
for d in "${DOMAINS[@]}"; do SAN="${SAN}${SAN:+,}DNS:${d}"; done
CN="${DOMAINS[0]}"
mkdir -p "$CERT_DIR"
cd "$CERT_DIR" # пишем относительными именами: не ломается конверсия путей в Git Bash
MSYS_NO_PATHCONV=1 openssl req -x509 -nodes -newkey rsa:2048 -days "$DAYS" \
-keyout selfsigned.key \
-out selfsigned.crt \
-subj "/CN=${CN}" \
-addext "subjectAltName=${SAN}"
echo "OK: сертификат на ${DAYS} дней → $CERT_DIR/selfsigned.{crt,key}"
echo "SAN: ${SAN}"

View File

@ -1,166 +0,0 @@
# Рендерится образом nginx: envsubst подставляет ${PLATFORM_DOMAIN} и т.п.
# из окружения и кладёт результат в /etc/nginx/conf.d/default.conf (внутри http{}).
# Переменные вида $host / $http_upgrade — НЕ окруженческие, envsubst их не трогает.
large_client_header_buffers 8 128k;
client_max_body_size 5000M; # крупные загрузки (снимки, облака точек)
tcp_nopush on;
# TLS (самоподписанный сертификат на все домены). Задаём на http-уровне —
# наследуется всеми server-блоками. Файлы монтируются из ./nginx/certs.
ssl_certificate /etc/nginx/certs/selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
http2 on;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream backend { server backend:8000; }
upstream frontend { server frontend:80; }
upstream minio_s3 { server minio:9000; }
upstream minio_console { server minio:9001; }
upstream rabbitmq_mgmt { server rabbitmq:15672; }
upstream s3_proxy { server s3-proxy:80; } # media (/media/) с CORS+Range
upstream processing_api { server processing-api:8080; }
upstream processing_frontend { server processing-frontend:8080; }
# Общие проксирующие заголовки. ВАЖНО: если в location задан свой proxy_set_header,
# эти НЕ наследуются — там заголовки продублированы явно.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ============================ Платформа (основной домен) ====================
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name ${PLATFORM_DOMAIN};
location = /healthz {
access_log off;
add_header Content-Type text/plain;
return 200 "ok\n";
}
# Рантайм-конфиг фронта. Прод-сборка по умолчанию уходит в zitadel, которого
# тут нет, — форсим классический вход через backend (auth_type=original).
# Отдаём сами: в образе frontend этого файла нет (был бы 404).
location = /static/config.json {
default_type application/json;
add_header Cache-Control "no-store";
return 200 '{"auth_type":"original"}';
}
# Django backend: REST API + админка
location /api/ { proxy_pass http://backend; }
location /admin/ { proxy_pass http://backend; }
# --- processing (workflows) под /workflows ---
# Префикс /workflows срезаем: api ждёт пути /api/v1/..., фронт — от корня.
location = /workflows { return 301 /workflows/; }
location /workflows/api/ {
rewrite ^/workflows/(.*)$ /$1 break;
proxy_pass http://processing_api;
}
location /workflows/ {
rewrite ^/workflows/(.*)$ /$1 break;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://processing_frontend;
}
# Media: s3-proxy раздаёт объекты S3/MinIO по URI /media/ (CORS + Range).
# Префикс /media/ передаём как есть — s3-proxy сам мапит его на бакет.
location /media/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_buffering off;
proxy_request_buffering off;
proxy_max_temp_file_size 0;
proxy_pass http://s3_proxy;
}
# Frontend SPA (host-приложение само отдаёт статику и роутинг)
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://frontend;
}
}
# ======================= Admin: RabbitMQ management UI ======================
server {
listen 80;
listen 443 ssl;
server_name ${ADMIN_DOMAIN};
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://rabbitmq_mgmt;
}
}
# ============================== MinIO (отдельный домен) =====================
# Консоль отдаётся с КОРНЯ домена (порт 9001) — так её статика (/static, /api,
# websocket) резолвится без сабпути. У сервиса minio:
# MINIO_BROWSER_REDIRECT_URL = https://<MINIO_DOMAIN>
# S3 API (:9000) здесь НЕ публикуется: backend ходит в minio:9000 по внутренней
# сети. Понадобится S3 наружу — заведи отдельный домен (напр. s3.<...>) на minio_s3.
server {
listen 80;
listen 443 ssl;
server_name ${MINIO_DOMAIN};
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-NginX-Proxy true;
chunked_transfer_encoding off;
# крупные загрузки объектов через консоль
proxy_buffering off;
proxy_request_buffering off;
proxy_max_temp_file_size 0;
proxy_pass http://minio_console;
}
}