s3-proxy не срезает префикс сам — он отображает путь URL прямо в ключ
объекта. Маршрут отдавал ему путь как есть, поэтому он искал
media/orthophotos/..., тогда как в бакете лежит orthophotos/..., и отвечал
NoSuchKey (404).
Проверено на существующем объекте через сам s3-proxy:
/orthophotos/NTG230721_DEM/14/10920/4937.png -> 200, 17831 байт
/media/orthophotos/NTG230721_DEM/14/10920/4937.png -> 404 NoSuchKey
В корне бакета лежат geotiff_dems/, las_pointclouds/, orthophotos/,
pointclouds/ — префикса media/ нет вовсе.
Адрес вида /workflows/<uuid> — это путь ВНУТРИ SPA платформы, который
должна обработать она сама, отдав index.html. Маршрут /workflows/ на
frontend-svc.processing уводил его в nginx микрофронтенда, где такого файла
нет, и браузер получал
404 Not Found — nginx/1.19.6
Статика микрофронтенда доезжает и без этого маршрута: nginx платформы сам
проксирует /workflows/*.js в processing (apps/django/aero/nginx-configmap).
Это уже проверялось — 200, 282339 байт.
Маршрут /workflows/api/ оставлен: его nginx платформы не обслуживает, а
бандл ходит по нему относительным путём.
Разворачивается ИМЕННО bimbackend (Python, пара api + worker), а не
platform/bim-backend-v2 из apps/bim/base — тот откачен коммитом 285255e.
../base здесь намеренно не подключён.
Различить два приложения по образу нельзя: оба пушат bim-api. Различие в
тегах — у bimbackend полный SHA коммита (CI пушит bim-api:${CI_COMMIT_SHA}
с master), у bim-backend-v2 contour_*. Взят 870965d1..., тот же, что
крутится в dsinv.
Манифесты обычные, а не universal-chart: в репозитории сервис так и описан
(apps/bim/dsinv), своего чарта у него нет.
Отличия от dsinv:
* секреты из Vault вместо secretKeyRef на несуществующие Secret'ы;
штатный entrypoint файлы читать не умеет, поэтому обёрнут в sh -ec;
* PROCESSING_API_URL и WORKSPACES_API_URL приведены к именам контура —
в dsinv это workflows-service:8000 и workspaces-service:8000, здесь
оба сервиса называются backend-svc и слушают 80;
* RABBIT_HOST у воркера в dsinv указывает на rabbitmq-service.bim —
брокера в этом namespace нет ни там, ни здесь; у api в том же файле
адрес правильный. Поставлен общий брокер контура, vhost корневой;
* S3_BUCKET переведён на общее медиахранилище sarex-media-storage;
* убраны GOOGLE_APPLICATION_CREDENTIALS и том с ключом — Google в
контуре нет, флаг FEATURE_ENABLE_GCLOUD и так выключен;
* NodePort с прибитым 31352 заменён на ClusterIP: наружу сервис выходит
через общий Gateway;
* снят nodeSelector name=generic, imagePullSecrets переведён на regcred.
ИЗВЕСТНОЕ ОГРАНИЧЕНИЕ, принято осознанно. S3 у этого приложения на MinIO
не настраивается: в storage/s3.py адрес зашит в код —
endpoint_url='https://storage.yandexcloud.net'
переменной окружения для него нет. Из контура он недостижим, поэтому
операции с файлами будут падать по таймауту, а остальные эндпоинты живы.
Альтернативы — пересборка образа с вынесенным endpoint либо перевод на
BIM_CURRENT_STORAGE=local (тип есть в storage/storages.py).
Наружу выведен на bim.sarex.local.lonsdaleites.ru, имя добавлено в
dnsNames сертификата. Тот же адрес приложение знает про себя через
BIM_API_EXTERNAL_HOST и строит по нему ссылки.
Коммит c8eefab развернул platform/bim-backend-v2 (Go, один httpserver) —
он приезжает из apps/bim/base. Нужен же aero/bimbackend: Python, пара
api + worker, S3-бакет sarex-bim-storage, связки с processing и
workspaces. В iac он лежит не в base, а отдельными манифестами
bim-api.yaml и bim-worker.yaml (см. apps/bim/dsinv).
Спутать их легко: ОБА пушат в реестр один и тот же образ bim-api, и
различить можно только по содержимому — v2 пишет в лог
"Starting BIMv2 API server", а у bimbackend точки входа
entrypoint_api.sh и entrypoint_worker.sh.
Откатывается всё: релиз, маршрут наружу, имя в сертификате, адрес
BIMV2_INTERNAL_HOST у django и обвязка (роль Vault, секрет, regcred,
путь синхронизации). Заводить их заново под bimbackend дешевле, чем
править остатки под другой сервис — у него могут отличаться и namespace,
и имя базы.
База bim_db при этом НЕ трогается и пересоздания не требует: проверено
до отката — ноль таблиц, 9 МБ пустого шаблона. v2 к ней подключился, но
схему не создал, миграции не отработали.
Релиз идёт из base без единого патча, и это проверено по исходникам
bim-backend-v2, а не принято на веру:
* DJANGO_HOST в base уже правильный (backend-svc.django:80) — редкость,
у processing и workspaces там лежал несуществующий backend:8000;
* DB_CERT_PATH_2/3/4 указывают на сертификат управляемой СУБД Яндекса,
которого в контуре нет, но зануления не требуют: файл читается только
под флагом ENABLE_SSL, а при нуле к строке подключения дописывается
?sslmode=disable и путь не трогается;
* пять комплектов POSTGRES_*_N в vault-шаблоне смотрят на один хост —
в проде это реплики, в контуре СУБД одна.
Django ходил в никуда: BIMV2_INTERNAL_HOST в base ведёт в namespace
bim-api к сервису bim-backend-v2-service, а релиз разворачивается в
namespace bim и называет сервис backend-svc. Поправлено в обоих релизах
django — backend и celery.
Наружу выведен отдельным поддоменом bim.sarex.local.lonsdaleites.ru, а не
префиксом на домене платформы: своего фронтенда у сервиса нет, общего с
платформой origin тоже, а его пути конфликтовали бы с маршрутом /api/
django. Имя добавлено в dnsNames сертификата — без этого браузер получил
бы сертификат без него.
Обвязка: regcred и роль Vault для namespace bim, apps/bim в
gitea_sync_paths, секрет secrets/apps/bim/postgres (база bim_db и роль bim
уже заведены в contour.databases).
ИЗВЕСТНЫЙ РИСК, проверяется логами первого пода. В ветке develop у
bim-backend-v2 миграции катятся при старте httpserver, ошибка фатальна, а
адрес БД внутри RunOnStartup взят не из конфига, а из константы
rc1b-sse4o3n9vea392g4.mdb.yandexcloud.net:6432. Из контура он недостижим.
Если этот код попал в образ contour_f9f2a39-dirty, под уйдёт в
CrashLoopBackOff и лечиться это будет только пересборкой образа.
Фронтенд был исключён из-за адресов, вшитых в бандл на этапе сборки через
константу __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, и часть
правил молча потерялась бы.
Первый прикладной сервис переехал из 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.
PostgreSQL разворачивается пустым инстансом, состав баз повторяет то, что
сейчас провижинит postgres-init в compose:
sarex_db -> django (ltree)
workflow_db -> processing (uuid-ossp, ltree, hstore)
bim_db -> bim
workspace_db -> workspace
Пароли администратора и владельцев берутся из Vault через
vault-agent-injector, в репозитории их нет. Расширения создаёт бутстрап
заранее: роли не суперпользователи и CREATE EXTENSION в миграциях им
недоступен. timescaledb включён в shared_preload_libraries сразу, хотя этим
базам не нужен, — параметр читается только при старте, и добавить его позже
означает перезапуск СУБД.
nodeSelector снимается postRenderers'ом. Чарт по умолчанию ставит
dedicated: sts в расчёте на выделенный пул нод; в контуре таких нет, и под
навсегда повисал в Pending, а следом застревал PVC, потому что у local-path
режим WaitForFirstConsumer. Через values это недостижимо: Helm сливает карты
по ключам, поэтому ни {}, ни другая метка dedicated: sts не убирают.
Цикл реконсиляции сокращён с 10 до 2 минут у всех Kustomization и
HelmRelease контура. Корневая Kustomization патчится из clusters/aero:
её файл генерирует flux bootstrap с пометкой DO NOT EDIT, а флаг --interval
задаёт периодичность только GitRepository. Таймаут установки postgresql
снижен с 20 до 10 минут — он определяет цену одной неудачной итерации.
Отключение собственных Gateway/VirtualService/Certificate чарта rabbitmq
переведено с JSON6902 на postRenderers: kustomize нормализует null в {}, а
пустую карту Helm сливает с дефолтами, поэтому настоящий null до Helm не
доходит ни одним путём.
Топология k3s: 1 мастер + 2 воркера (убран k3s-worker-3 с томом).
Vault ↔ k3s. Vault живёт в compose, вне кластера, поэтому получает
статический IP 172.28.0.13, DNS-мост в namespace vault и token reviewer
(SA vault-auth + system:auth-delegator). Ansible включает auth/kubernetes,
передавая адрес apiserver, CA и JWT ревьюера явно, и заводит политику с
ролями. Секрет token reviewer'а читается целиком в JSON: вариант
-o jsonpath={.data.ca\.crt} НЕ работает — модуль command разбирает строку
через shlex, съедает обратный слэш, kubectl возвращает пустую строку с
кодом 0, и отказ остаётся незамеченным до падения vault write.
Синхронизация репозитория в gitea. Нужное подмножество путей (замыкание
ссылок clusters/aero) едет архивом на хост и коммитится там: gitea не
публикуется дальше самого хоста, контур остаётся замкнутым. flux-bootstrap
вынесен в отдельную волну — до неё Vault получает auth/kubernetes, а
репозиторий наполняется, иначе первая реконсиляция падает на пустом репо.
Инфраструктура: vault-agent-injector (чарт vault-contour в режиме внешнего
Vault), local-path-provisioner взамен встроенного в k3s (--disable=
local-storage; путь данных прежний), rabbitmq и minio с кредами из Vault.
Выход наружу: порты 80/443 k3s-server опубликованы (istio ingressgateway
занимает эти hostPort), общий contour-gateway на wildcard-хост, сертификат
Let's Encrypt через http01. Имена в сертификате перечислены явно —
http01 не выдаёт wildcard, для них нужен dns01.
Разорваны два дедлока Flux: infra-configs больше не зависит от
infra-controllers (издатели не должны зависеть от здоровья чартов, которые
их используют), istio-config получил disableWait.
Модули ядра iptable_nat и смежные грузятся на хосте: ноды k3s —
контейнеры, istio-init правит iptables через ядро хоста, а на RED OS 8
(nf_tables) legacy-модули не загружены, из-за чего любой под с
istio-injection навсегда вставал в Init:Error.
Собственные Gateway/VirtualService/Certificate чартов rabbitmq и dashboard
отключены — маршрутизация описана централизованно. У rabbitmq это сделано
postRenderers, а не values: kustomize нормализует null в {}, а пустую карту
Helm сливает с дефолтами, возвращая их целиком.