diff --git a/aero/README.md b/aero/README.md index 0d631b7..9058c32 100644 --- a/aero/README.md +++ b/aero/README.md @@ -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//`. 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/ && 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-коллекции (опционально) diff --git a/aero/pyproject.toml b/aero/pyproject.toml index e5b19c0..74ad14b 100644 --- a/aero/pyproject.toml +++ b/aero/pyproject.toml @@ -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"] diff --git a/aero/roles/sarex_stack/defaults/main.yml b/aero/roles/sarex_stack/defaults/main.yml index d3e5555..d6ff5b1 100644 --- a/aero/roles/sarex_stack/defaults/main.yml +++ b/aero/roles/sarex_stack/defaults/main.yml @@ -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 diff --git a/aero/roles/sarex_stack/handlers/main.yml b/aero/roles/sarex_stack/handlers/main.yml deleted file mode 100644 index 5d30e18..0000000 --- a/aero/roles/sarex_stack/handlers/main.yml +++ /dev/null @@ -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 diff --git a/aero/roles/sarex_stack/tasks/main.yml b/aero/roles/sarex_stack/tasks/main.yml index 19e0e21..441a15d 100644 --- a/aero/roles/sarex_stack/tasks/main.yml +++ b/aero/roles/sarex_stack/tasks/main.yml @@ -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 стартового скрипта). Так учётка diff --git a/aero/roles/sarex_stack/tasks/platform.yml b/aero/roles/sarex_stack/tasks/platform.yml index 6c335d6..e7a64a0 100644 --- a/aero/roles/sarex_stack/tasks/platform.yml +++ b/aero/roles/sarex_stack/tasks/platform.yml @@ -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 создаёт только при первом старте. diff --git a/backend/production.py b/backend/production.py deleted file mode 100644 index b6a274f..0000000 --- a/backend/production.py +++ /dev/null @@ -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"}, -] diff --git a/backend/uwsgi.ini b/backend/uwsgi.ini deleted file mode 100644 index fde4cbb..0000000 --- a/backend/uwsgi.ini +++ /dev/null @@ -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 diff --git a/docker-compose.yaml b/docker-compose.yaml index 90e2161..984ea9c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -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 - 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 - 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/, верхний 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: diff --git a/engine/yc-s3-service-account.json b/engine/yc-s3-service-account.json deleted file mode 100644 index 7d05db1..0000000 --- a/engine/yc-s3-service-account.json +++ /dev/null @@ -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 -} diff --git a/k3s/manifests/processing/bridge-minio.yaml b/k3s/manifests/processing/bridge-minio.yaml deleted file mode 100644 index 983e643..0000000 --- a/k3s/manifests/processing/bridge-minio.yaml +++ /dev/null @@ -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 diff --git a/k3s/manifests/processing/bridge-postgres.yaml b/k3s/manifests/processing/bridge-postgres.yaml deleted file mode 100644 index 8f0f189..0000000 --- a/k3s/manifests/processing/bridge-postgres.yaml +++ /dev/null @@ -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 diff --git a/k3s/manifests/processing/namespace.yaml b/k3s/manifests/processing/namespace.yaml deleted file mode 100644 index 7b7b96e..0000000 --- a/k3s/manifests/processing/namespace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: processing diff --git a/nginx/gen-cert.sh b/nginx/gen-cert.sh deleted file mode 100644 index 0fbffb6..0000000 --- a/nginx/gen-cert.sh +++ /dev/null @@ -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}" diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template deleted file mode 100644 index 233e5f2..0000000 --- a/nginx/templates/default.conf.template +++ /dev/null @@ -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:// -# 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; - } -}