diff --git a/.env.example b/.env.example index 144fbe5..035f6d7 100644 --- a/.env.example +++ b/.env.example @@ -58,6 +58,8 @@ SAREX_MC_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/mc:250724 # Образы приложения (приватный реестр cr.yandex — нужен docker login) SAREX_BACKEND_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/backend:production_80d1d847 SAREX_FRONTEND_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/frontend:contour_42138486 +# s3-proxy: отдаёт объекты MinIO/S3 браузеру с CORS и Range (media в namespace django) +SAREX_S3_PROXY_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/s3-proxy:vault_6e586217 # Базы данных приложений (django использует SAREX_POSTGRES_DB) SAREX_PROCESSING_DB=workflow_db diff --git a/aero/roles/sarex_stack/defaults/main.yml b/aero/roles/sarex_stack/defaults/main.yml index c7c8bdd..556afaa 100644 --- a/aero/roles/sarex_stack/defaults/main.yml +++ b/aero/roles/sarex_stack/defaults/main.yml @@ -47,7 +47,7 @@ sarex_compose_up: false compose_cmd: "docker compose" # Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет -# зависимости и порядок). k3s/gitea/processing намеренно не поднимаем. +# зависимости и порядок). k3s/gitea намеренно не поднимаем. sarex_services: - postgres - postgres-init @@ -56,7 +56,10 @@ sarex_services: - minio - minio-init - measurements + - s3-proxy - backend - celery - frontend + - processing-api + - processing-frontend - nginx diff --git a/backend/production.py b/backend/production.py index bdcb051..b6a274f 100644 --- a/backend/production.py +++ b/backend/production.py @@ -15,6 +15,16 @@ 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' diff --git a/docker-compose.yaml b/docker-compose.yaml index f09c7d3..e0065ee 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -296,6 +296,15 @@ services: 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" tty: true ports: - "8000:8000" # HTTP API (uWSGI http-socket), migrate выполняется в entrypoint @@ -346,6 +355,38 @@ services: 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. @@ -353,6 +394,10 @@ services: # --------------------------------------------------------------------------- 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"] @@ -369,8 +414,7 @@ services: ENABLE_SQL_QUERY: "0" DJANGO_HOST: http://backend:8000 # django-backend в этом же compose PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} # JWT public key (общий с backend) - ports: - - "8081:8080" + # Порт наружу не публикуем — доступ через верхний nginx (/workflows/api/). depends_on: postgres: condition: service_healthy @@ -379,10 +423,17 @@ services: 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 - ports: - - "8082:8080" # web UI (контейнер слушает 8080) + # Порт наружу не публикуем — доступ через верхний nginx (/workflows/). depends_on: - processing-api @@ -428,8 +479,9 @@ services: # - frontend - minio - rabbitmq - # - processing-api # processing пока не поднимаем (закомментирован в шаблоне nginx) - # - processing-frontend + - s3-proxy + - processing-api + - processing-frontend healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"] interval: 10s diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template index 4b33573..233e5f2 100644 --- a/nginx/templates/default.conf.template +++ b/nginx/templates/default.conf.template @@ -31,10 +31,9 @@ upstream frontend { server frontend:80; } upstream minio_s3 { server minio:9000; } upstream minio_console { server minio:9001; } upstream rabbitmq_mgmt { server rabbitmq:15672; } -# processing пока не поднимаем — upstream'ы отключены (иначе nginx падает на -# "host not found in upstream" при старте) -# upstream processing_api { server processing-api:8080; } -# upstream processing_frontend { server processing-frontend:8080; } +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, # эти НЕ наследуются — там заголовки продублированы явно. @@ -68,42 +67,43 @@ server { location /api/ { proxy_pass http://backend; } location /admin/ { proxy_pass http://backend; } - # --- processing (workflows) под /workflows --- (пока не поднимаем) + # --- processing (workflows) под /workflows --- # Префикс /workflows срезаем: api ждёт пути /api/v1/..., фронт — от корня. - # location = /workflows { return 301 /workflows/; } + location = /workflows { return 301 /workflows/; } - # location /workflows/api/ { - # rewrite ^/workflows/(.*)$ /$1 break; - # proxy_pass http://processing_api; - # } + 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; - # } + 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; + } - # S3-объекты (media) по относительному пути, с поддержкой Range (TODO: в работе) - # location /sarex-media-storage/ { - # 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://minio_s3; - # } + # 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 / {