Measurements пишет в общий бакет контура + перекат нагрузок в ansible

--- Бакет ---

В base у measurements имя бакета ЗАШИТО в самом vault-шаблоне:
  "buckets":["measurements"]
Из-за этого он складывал файлы в собственный бакет, тогда как
медиахранилище контура одно — sarex-media-storage: туда пишет django и
оттуда же раздаёт s3-proxy на маршруте /media/. Ссылка на файл
измерения, отданная через django, вела бы в пустоту.

Шаблон переопределён в apps/measurements/aero так, чтобы имя бакета
читалось из секрета — как уже читается endpoint. Значение задаётся в
одном месте, vault_app_secrets, и совпадает с sarex_django_s3_bucket.
Заменить пришлось аннотацию целиком: значение внутри шаблона, через
values его не перекрыть.

Из Job'а бакетов убран measurements. Это не удаление: Job умеет только
mc mb --ignore-existing, ранее созданный бакет остаётся на месте — мы
лишь перестаём его заводить.

Порядок применения безопасен: в platform.yml vault-k8s.yml идёт до
gitea-sync.yml, то есть секрет получит ключ bucket раньше, чем Flux
пересоздаст под. Иначе vault-agent-init не отрендерил бы шаблон с
несуществующим ключом и под ушёл бы в Init:Error.

--- Перекат нагрузок ---

Единственное действие, которое до сих пор делалось руками мимо роли, —
rollout restart после правки ConfigMap. Оно понадобилось дважды (порядок
set/rewrite в nginx и auth_type) и оба раза выглядело как «правка не
поехала»: Flux рапортует Applied revision, а поведение старое.

Причина в том, что у релизов universal-chart в шаблоне пода нет
контрольной суммы конфига: ConfigMap меняется, спецификация пода — нет,
Kubernetes не видит причин пересоздавать под. Декларативно это здесь не
лечится — имя ConfigMap передаётся чарту строкой, монтирует он его сам, а
configMapGenerator переименовал бы ресурс, не поправив ссылку внутри
чарта, который рендерится уже в кластере.

Добавлены tasks/reload.yml, список sarex_reload_workloads в defaults и
задача `uv run poe reload`. По умолчанию выключено: перезапуск подов
посреди обычного деплоя происходить сам не должен.
This commit is contained in:
emelinda 2026-08-09 11:58:06 +03:00
parent 0a69d528b3
commit c750a08f8d
6 changed files with 113 additions and 11 deletions

View File

@ -47,6 +47,11 @@ login = "ansible-playbook deploy.yml --tags login"
# Показать логин+пароль администратора платформы. Саму учётку создаёт под
# backend при старте (apps/django/aero), эта задача только печатает креды.
superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags superuser"
# Перекатить нагрузки, конфигурация которых лежит в ConfigMap. Нужно после
# правки nginx-configmap: Flux обновит ConfigMap, но под продолжит работать со
# старым содержимым — в шаблоне пода нет хеша конфига, спецификация не меняется
# и Kubernetes не видит причин его пересоздавать. Список — sarex_reload_workloads.
reload = "ansible-playbook deploy.yml -e sarex_reload=true --tags reload"
lint = "ansible-lint"
# --- Операционные задачи на хосте (без полного деплоя) ----------------------

View File

@ -94,6 +94,28 @@ registry_secrets:
sarex_compose_up: false
compose_cmd: "docker compose"
# --- Перекат нагрузок после правки конфигурации -----------------------------
# Выключено по умолчанию: перезапуск подов посреди обычного деплоя — не то, что
# должно происходить само. Включается задачей `uv run poe reload`.
#
# Список — это нагрузки, конфигурация которых лежит в ConfigMap и потому НЕ
# подхватывается без пересоздания пода: спецификация пода не меняется, значит
# Kubernetes не видит причин его перекатывать. Всё остальное перекатывается
# само — правка envs или podAnnotations меняет шаблон пода.
#
# Добавляешь ConfigMap, который читает приложение, — добавь нагрузку сюда,
# иначе правка будет уезжать в кластер и не применяться, а выглядеть это будет
# как «изменение не сработало».
sarex_reload: false
sarex_reload_timeout: 240s
sarex_reload_workloads:
# nginx главного фронтенда: apps/django/aero/nginx-configmap.yaml. Здесь
# маршруты на микрофронтенды workspaces и workflows и подмена
# /static/config.json, которой выключается Zitadel.
- namespace: django
deployment: frontend
why: nginx-configmap
# --- GitOps-подложка контура (k3s + gitea + vault + flux) -------------------
# Параметры gitea и Flux. Попадают в .env (их читает docker-compose) — менять
# нужно здесь, а не в .env.example, иначе перезапишется при следующем деплое.
@ -309,8 +331,11 @@ vault_app_secrets:
# собирает из этих ключей одну переменную S3_JSON_SETTINGS и читает endpoint
# ВЛОЖЕННЫМ ключом .Data.data.client.endpoint — отсюда лишний уровень client.
#
# Бакет measurements зашит в том же шаблоне и заводится Job'ом
# infrastructure/minio/aero/buckets-job.yaml.
# Бакет — ОБЩЕЕ медиахранилище контура, то же, куда пишет django и откуда
# раздаёт s3-proxy на маршруте /media/. В base у measurements имя бакета
# зашито в самом vault-шаблоне ("buckets":["measurements"]), из-за чего он
# складывал файлы отдельно и ссылка, отданная через django, вела бы в пустоту.
# Шаблон переопределён в apps/measurements/aero, чтобы имя читалось отсюда.
#
# Отдельного пользователя MinIO нет — тот же временный компромисс, что и у
# django: берём root-креды.
@ -318,6 +343,7 @@ vault_app_secrets:
data:
client:
endpoint: http://minio.minio.svc.cluster.local:9000
bucket: "{{ sarex_django_s3_bucket }}"
access_key: "{{ sarex_minio_k8s_user }}"
secret_key: "{{ sarex_secrets.SAREX_MINIO_ROOT_PASSWORD }}"
# Учётка администратора платформы. Её разбирает не приложение, а стартовый

View File

@ -194,6 +194,14 @@
when: aero_platform_up | bool
tags: [platform]
# --- Перекат нагрузок с изменённой конфигурацией --------------------------
# Только по тегу reload: правка ConfigMap сама под не перезапускает, а делать
# это на каждом прогоне нельзя. Обоснование — в шапке reload.yml.
- name: Перекатить нагрузки с изменённой конфигурацией
ansible.builtin.include_tasks: reload.yml
when: sarex_reload | bool
tags: [reload]
# --- Поднять сервисы приложения ------------------------------------------
- name: Поднять сервисы приложения (docker compose up -d)
ansible.builtin.command:

View File

@ -0,0 +1,41 @@
---
# Перекат нагрузок, которые не перезапускаются сами при правке конфигурации.
#
# ЗАЧЕМ ЭТО ОТДЕЛЬНАЯ ЗАДАЧА. Flux обновляет ConfigMap в кластере за пару минут,
# но под продолжает работать со старым содержимым: у релизов universal-chart в
# шаблоне пода нет контрольной суммы конфига, поэтому спецификация не меняется
# и Kubernetes не видит причин пересоздавать под. Всё, что задаётся через
# ConfigMap, применяется только после явного переката.
#
# Так это выглядит на практике: правишь nginx.conf, Flux рапортует Applied
# revision, проверяешь — поведение старое. Легко принять за «правка не поехала»
# и пойти искать несуществующую ошибку. Дважды так и было: сначала с порядком
# set/rewrite, потом с auth_type.
#
# ПОЧЕМУ НЕ ДЕКЛАРАТИВНО. Правильное лечение — аннотация с хешем конфига в
# шаблоне пода, тогда изменение ConfigMap меняет спецификацию и перекат
# происходит сам. Здесь так не выходит: имя ConfigMap чарту передаётся строкой,
# монтирование он делает сам, и посчитать хеш на этапе kustomize нечем —
# configMapGenerator переименовал бы ресурс, но ссылку внутри чарта, который
# рендерится уже в кластере, не поправил бы. Так что действие остаётся
# императивным, и его место — здесь, а не в чьей-то памяти.
#
# ЗАПУСК ТОЛЬКО ЯВНЫЙ. Задача не выполняется при обычном прогоне: перекат
# нагрузок посреди деплоя — не то, что должно случаться само.
# uv run poe reload
# либо точечно:
# uv run poe reload -- -e "sarex_reload_workloads=[{'namespace':'django','deployment':'frontend'}]"
- name: Перекатить нагрузки с изменённой конфигурацией
ansible.builtin.shell:
cmd: >-
{{ compose_cmd }} exec -T k3s-server sh -ec '
kubectl -n {{ item.namespace }} rollout restart deploy/{{ item.deployment }}
kubectl -n {{ item.namespace }} rollout status deploy/{{ item.deployment }} --timeout={{ sarex_reload_timeout }}
'
chdir: "{{ deploy_dir }}"
loop: "{{ sarex_reload_workloads }}"
loop_control:
label: "{{ item.namespace }}/{{ item.deployment }} — {{ item.why | default('правка конфигурации') }}"
changed_when: true
tags: [reload]

View File

@ -19,8 +19,23 @@ patches:
path: /spec/values/services/backend/service/name/_default
value: measurements-service
# S3 внутри кластера. В base endpoint приезжает из самого секрета
# (.Data.data.client.endpoint), поэтому шаблон править не нужно — адрес
# задаёт ansible в vault_app_secrets. Бакет measurements заводит
# infrastructure/minio/aero/buckets-job.yaml: имя зашито в шаблоне
# аннотации, и без готового бакета приложение не поднимется.
# S3: бакет берём из секрета, а не из шаблона.
#
# В base имя бакета ЗАШИТО прямо в vault-шаблоне — "buckets":["measurements"].
# Из-за этого measurements складывал файлы в собственный бакет, тогда как
# медиахранилище контура одно и называется sarex-media-storage: туда пишет
# django (S3_BUCKET) и оттуда же раздаёт s3-proxy на маршруте /media/.
# Разные бакеты означали, что ссылка на файл измерения, отданная через
# django, вела бы в пустоту.
#
# Endpoint в base уже приезжает из секрета (.Data.data.client.endpoint) —
# достраиваем до той же схемы и бакет, чтобы имя задавалось в ОДНОМ месте
# (vault_app_secrets в aero/roles/sarex_stack/defaults/main.yml), а не
# дублировалось в манифесте. Заменить можно только аннотацию целиком:
# значение зашито внутри шаблона, через values его не перекрыть.
- op: replace
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-measurements-s3
value: |-
{{- with secret "secrets/data/minio/apps/measurements" -}}
S3_JSON_SETTINGS='{"host":"{{ index .Data.data.client "endpoint" }}","login":"{{ index .Data.data "access_key" }}","password":"{{ index .Data.data "secret_key" }}","verify":false,"buckets":["{{ index .Data.data "bucket" }}"]}'
{{- end -}}

View File

@ -1,8 +1,7 @@
---
# Заведение бакетов. Чарт MinIO этого не умеет — форк не принимает ни buckets,
# ни provisioning, — а приложения ждут бакеты готовыми: django падает на
# загрузке файлов, measurements — на старте, потому что имя бакета зашито в
# его vault-шаблоне.
# ни provisioning, — а приложения ждут бакет готовым: django падает на загрузке
# файлов, measurements — на старте, если бакета из его vault-шаблона нет.
#
# До этого Job'а бакеты заводились руками через mc. Такой бакет не переживает
# пересоздание контура и не виден в репозитории: развернув всё с нуля, ты
@ -66,7 +65,15 @@ spec:
sleep 5
done
for b in sarex-media-storage measurements; do
# Бакет в контуре ОДИН: в него пишет django, из него раздаёт
# s3-proxy на маршруте /media/, туда же переведён measurements.
# Раньше у measurements в vault-шаблоне был зашит собственный
# бакет, и файл, положенный им, по ссылке из django не отдавался.
#
# Ранее созданный бакет measurements этой правкой НЕ удаляется:
# Job умеет только mc mb --ignore-existing. Убирать его — отдельное
# осознанное решение, здесь мы лишь перестаём его заводить.
for b in sarex-media-storage; do
mc mb --ignore-existing "contour/$b"
echo "бакет $b готов"
done