services: k3s-server: image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1} container_name: k3s-server command: - server - --tls-san=127.0.0.1 - --tls-san=k3s-server - --tls-san=${K3S_API_HOST:-127.0.0.1} # внешний IP/домен для доступа к apiserver извне - --disable=traefik privileged: true cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS) restart: unless-stopped environment: K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken} K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml K3S_KUBECONFIG_MODE: "644" #ports: # - "6443:6443" # kube-apiserver — доступен извне; ОБЯЗАТЕЛЬНО ограничь фаерволом до доверенных IP # - "80:80" # ingress http — задействуются после установки ingress-контроллера # - "443:443" # ingress https volumes: - k3s-server-data:/var/lib/rancher/k3s # etcd/state, образы, манифесты — только named volume - ./k3s:/output:z # отсюда забираем контекст k8s (:z — SELinux label для RedOS) tmpfs: - /run - /var/run ulimits: nproc: 65535 nofile: soft: 65535 hard: 65535 healthcheck: test: ["CMD", "kubectl", "get", "--raw=/readyz"] interval: 10s timeout: 5s retries: 30 start_period: 30s k3s-worker: image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1} container_name: k3s-worker command: - agent privileged: true cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS) restart: unless-stopped depends_on: k3s-server: condition: service_healthy environment: K3S_URL: https://k3s-server:6443 K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken} volumes: - k3s-worker-data:/var/lib/rancher/k3s tmpfs: - /run - /var/run ulimits: nproc: 65535 nofile: soft: 65535 hard: 65535 gitea: image: ${GITEA_IMAGE:-gitea/gitea:1.22} container_name: gitea restart: unless-stopped environment: USER_UID: 1000 USER_GID: 1000 GITEA__server__ROOT_URL: ${GITEA_ROOT_URL:-http://localhost:3000/} GITEA__server__SSH_PORT: ${GITEA_SSH_PORT:-2222} ports: - "3000:3000" # web UI (80/443 заняты k3s) - "${GITEA_SSH_PORT:-2222}:22" # git over ssh volumes: - ./gitea-data:/data:z # :z — SELinux label для RedOS healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"] interval: 10s timeout: 5s retries: 12 start_period: 20s # --------------------------------------------------------------------------- # Инфраструктурные зависимости 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 # Одноразовый 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 # Одноразовый 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" DJANGO_HOST: http://backend:8000 # django-backend в этом же compose PUBLIC_KEY: ${SAREX_JWT_PUBLIC_KEY:-JPK} # JWT public key (общий с backend) # Порт наружу не публикуем — доступ через верхний 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 # --------------------------------------------------------------------------- # 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-data: gitea-data: sarex-postgres-data: sarex-redis-data: sarex-rabbitmq-data: sarex-minio-data: