Комментарий в оверлее переписан с «в бандле есть srx_workspaces» на
проверяемые факты. Прежняя формулировка была получена grep'ом по
подстроке, которая матчит и srx_workspacesV2, — вывод оказался верным
по случайности.
Что проверено:
* в бандле платформы из 18 упоминаний remoteEntry.js воркспейсовое
одно, /workspaces-v2/..., и встречается только srx_workspacesV2;
* отдаваемый контуром /module/remoteEntry.js объявляет то же имя;
* в cr.yandex у workspaces-v2-frontend 61 тег contour_*, у v1
(workspaces-frontend) ни одного, образа workspace-frontend нет.
Тег contour_2a4ce3fd из base — самая свежая contour-сборка: по датам
создания образов в реестре она от 2026-07-24, предыдущая от 2026-07-19.
Собирает такие образы джоба build_contour из
generic/build-contour-frontend по правилу «ветка master и
ENABLE_BUILD_IMAGE_CONTOUR=true», то есть contour_* всегда с мастера.
Переопределять тег в оверлее не нужно.
apps/workspaces/aero — оверлей поверх base:
* DJANGO_HOST в base неверен дважды: сервиса backend не существует
(он backend-svc), и 8000 — это targetPort контейнера, а сервис
принимает на 80. Проверено в кластере: backend-svc:80 отдаёт 200,
backend-svc:8000 таймаутит, backend:8000 не резолвится. Та же
ошибка была в base у processing/engine-low.
* ENVIRONMENT/DJANGO_ORIGINATOR — «prod» заменён на aero: значение
уезжает в Sentry и в заголовки запросов к смежным сервисам.
* DOCUMENTATION_HOST переписан на честное имя внутри кластера.
Выключить интеграцию нечем: DOCUMENTATION_LOGGER_FEATURE гасит
только логирование, а GetDocumentByWS вызывается из обработчика
GET workspace безусловно. Сервиса documentations в контуре нет и
не будет — карточка воркспейса отдаётся без документа.
Фронтенд идёт из base без патчей и наружу НЕ публикуется: это Module
Federation remote srx_workspaces, его подгружает главный фронтенд
платформы. Версия — v2, и это не выбор: в бандле
sarex-frontend-dev:contour_5.22.0 лежит строка
/workspaces-v2/module/remoteEntry.js и имя ремоута srx_workspaces,
обращений к v1 нет вовсе. У v1 (platform/workspaces-frontend) к тому же
нет ни одной contour-сборки.
nginx-configmap: исправлен порядок директив в трёх location'ах.
set обязан идти ДО rewrite ... break — обе принадлежат
ngx_http_rewrite_module, и флаг break прекращает обработку его
директив, поэтому set после него не выполнялся. Переменная оставалась
пустой, nginx писал «no host in upstream :80» и отдавал 500. Так падали
и /workspaces-v2/..., и /workflows/... — маршрут / при этом работал,
из-за чего дефект и не был замечен раньше.
Обвязка: regcred и роль Vault для namespace workspaces, apps/workspaces
в gitea_sync_paths, секреты secrets/apps/workspaces/postgres и
secrets/vault/common/django_auth. Последний — вопреки имени пути НЕ
токен Zitadel: workspaces читает оттуда ключ key и кладёт его в
DJANGO_BASIC_AUTH, то есть это base64 логина и пароля администратора
django. Совпадение проверено в кластере по sha256.
Фронтенд был исключён из-за адресов, вшитых в бандл на этапе сборки через
константу __BUILD_ENV__. Опасение не подтвердилось: сборка обращается к тому
же origin относительными путями — иначе в yc-k8s-test и d8-ugmk-prod не
понадобился бы отдельный маршрут /workflows/api/ перед /workflows/.
Маршруты добавлены на домен платформы, а не на отдельный поддомен: поддомен
потребовал бы пересборки образа с другим __BUILD_ENV__.
/workflows/api/ -> rewrite /api/ -> backend-svc.processing
/workflows/ -> rewrite / -> frontend-svc.processing
Порядок внутри пары значим: /workflows/api/ обязан идти перед /workflows/,
иначе запросы к API уйдут в раздачу статики. Оба стоят перед /api/ и / —
правила django их бы перехватили.
Маршруты положены в существующий VirtualService platform, а не в отдельный:
Istio не сливает несколько VirtualService на одной паре хост+gateway, и часть
правил молча потерялась бы.
Развёрнут частично, и это осознанно:
* engine удалён — в base у него replicaCount 0, то есть он выключен и там,
а образ прибит к тегу с опечаткой в имени (workflows-endigne_prod);
* frontend удалён — адреса бэкендов зашиты в образ на этапе сборки через
__BUILD_ENV__, и ни один готовый профиль не смотрит на домен контура.
Подключать его имеет смысл только вместе с пересборкой образа.
Отключены смежные сервисы, которых в контуре нет: documentations (PDM,
filestream), resources, bim-api, workspace, issues, comparisons, mailgun,
SMTP. Оставлен только S3. Вместе с флагом ENABLE_SMTP убрана аннотация
инжекции secrets/vault/common/smtp_auth — секрета нет, и vault-agent-init
оставил бы под в Init:Error, не дойдя до флага.
S3 приезжает из Vault отдельным файлом: приложение читает по пути из
S3_SERVICE_ACCOUNT целый JSON, а не пару переменных. Набор ключей повторяет
engine/yc-s3-service-account.json из compose-стека, который так же смотрел
в MinIO.
Планирование подов задач: engine работает kubernetes-исполнителем и сам
создаёт Job'ы, подставляя им nodeSelector dedicated=processing. Выделенных
нод в контуре нет, поды висли бы в Pending — как это уже было у postgresql.
ENABLE_TOLERATION и DEFAULT_NODE_SELECTOR_* сняты.
Заодно у celery поправлены четыре адреса на wb.sarex.io — чужой контур,
недостижимый из aero. У backend те же переменные уже вели внутрь кластера;
при WORKFLOWS_USE=1 расхождение давало бы зависания вместо явной ошибки.
Патчи адресуются по индексу env, поэтому перед каждым replace стоит op: test
на имя переменной: смена порядка в base уронит сборку явно, а не перезапишет
соседнее значение.
Django обращается к сервису измерений при MEASUREMENTS_USE_MEASUREMENTS=1
(значение по умолчанию в base), так что без него часть API отвечала ошибкой.
Сервис переименован в measurements-service, как в brusnika-*. В base он
называется measurements-svc, а django ходит на measurements-service —
переименовать сервис дешевле, чем патчить переменную в двух релизах django.
Бакеты теперь заводит Job в infrastructure/minio/aero, а не команда руками.
Чарт MinIO этого не умеет: форк не принимает ни buckets, ни provisioning.
Так бакет django был создан императивно и не пережил бы пересоздание
контура — развернув всё с нуля, получили бы работающий MinIO и приложения,
падающие на отсутствующем бакете. Measurements сломался бы на этом сразу:
имя бакета зашито в его vault-шаблоне. Идемпотентность даёт
mc mb --ignore-existing.
Сайдкар istio у Job отключён: контейнер задачи выходит, envoy продолжает
работать, и под навсегда остаётся Running.
Worker был отключён патчем удаления, пока не было очередей. RabbitMQ и MinIO
развёрнуты, схема БД накачена — включаем.
Расхождения с base у celery ровно те же, что у backend: нет Kafka, MinIO
живёт внутри кластера. Аннотации инжекции несуществующих секретов убраны —
иначе vault-agent-init не отрендерит шаблон и под навсегда останется в
Init:Error. SERVER_ZITADEL_ENABLED не трогаем: в base у celery он уже False.
Миграции и создание администратора в стартовый скрипт worker'а намеренно НЕ
добавлены: схемой владеет backend, а два процесса, катящих миграции при
одновременном старте, — гонка на ровном месте.
Тег образа выравнен с backend (production_f813140d вместо production_a96dead0).
Разные версии кода у web и worker ломаются не при старте, а на конкретной
задаче, когда worker десериализует аргументы по своей версии модели.
Учётка создавалась задачей ansible через kubectl exec — по явной команде
оператора и в обход GitOps. Теперь это делает сам под при каждом старте:
стартовый скрипт подхватывает креды из Vault и вызывает штатную
createsuperuser --noinput.
Идемпотентность обеспечена ветвлением, а не подавлением ошибки: на уже
существующем логине Django возвращает "That username is already taken" с
ненулевым кодом, что под set -e из command базы уронило бы под на втором
запуске. Ошибка ожидаема и гасится веткой else.
Заодно в стартовый скрипт добавлен migrate. В entrypoint.sh образа он уже
есть, но запущен без set -e — его падение не мешает uwsgi стартовать.
Именно так контур несколько часов отдавал 200 на /admin/ с пустой схемой:
backend не мог аутентифицироваться в СУБД, миграции падали, приложение
работало. Здесь команда идёт под set -e, поэтому отказ виден сразу.
Ansible остаётся владельцем значений: генерирует логин и пароль (теперь
безусловно, а не по флагу — их ждёт vault-agent) и кладёт в
secrets/apps/django/superuser. Задача poe superuser печатает креды.
Убраны временные диагностические задачи с ignore_errors, добавленные при
разборе отказа createsuperuser.
Первый прикладной сервис переехал из compose в кластер. Развёрнуты backend,
frontend, redis, srx-admin и s3-proxy; celery выключен до следующего шага.
Платформа доступна по https://sarex.local.lonsdaleites.ru — корень отдаёт
frontend, /api/ и /admin/ уходят в backend, /media/ в s3-proxy.
Kafka и Zitadel в контуре не развёрнуты, и приложению они не нужны:
SERVER_KAFKA_ENABLED в base уже False, а zitadel_enabled в sarex-backend
читается ровно в одном месте — update_ams_user(), которая при выключенном
флаге сразу выходит. Но аннотации инжекции этих секретов пришлось убрать:
vault-agent-init не рендерит шаблон для несуществующего секрета и роняет под
в Init:Error ещё до старта приложения.
nginx-configmap заменён целиком. В base зашиты upstream'ы на namespace pm,
workspaces и processing; nginx резолвит их при старте и падал с
"host not found in upstream". pm убран как ненужный, для остальных двух
адрес вынесен в переменную с resolver — их резолвинг откладывается до
запроса, поэтому появление сервисов позже не потребует правки конфига.
Заменять пришлось именно патчем: объект с тем же kind+name уже приходит из
base, и добавление его в resources валит сборку слоя целиком.
Секреты django в Vault: postgres, rabbitmq, minio и общие RSA-ключи JWT.
RabbitMQ и MinIO подключены под административными кредами — отдельных
пользователей приложения чарты создавать не умеют, это осознанный долг.
Задача создания суперпользователя переписана с docker compose exec на
kubectl exec. ВНИМАНИЕ: она пока НЕ РАБОТАЕТ — команда падает, вывод скрыт
no_log. Механизм проверен отдельно и исправен, значит ошибка на стороне
Django. Чтобы увидеть причину, нужно временно снять no_log.