feat(aero): PostgreSQL с четырьмя базами и ускоренный цикл реконсиляции

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 не
доходит ни одним путём.
This commit is contained in:
emelinda 2026-08-08 11:33:43 +03:00
parent 565faa0e17
commit 8e14cd1e0b
14 changed files with 258 additions and 25 deletions

View File

@ -80,6 +80,7 @@ registry_secrets:
# Образы vault-k8s и vault-agent для injector'а тоже лежат в cr.yandex.
- {name: regcred, namespace: vault}
- {name: regcred, namespace: local-path-provisioner}
- {name: regcred, namespace: postgresql}
- {name: regcred, namespace: rabbitmq}
- {name: regcred, namespace: minio}
@ -121,6 +122,7 @@ gitea_sync_paths:
- infrastructure/istio-pilot
- infrastructure/vault-injector
- infrastructure/local-path-provisioner
- infrastructure/postgresql
- infrastructure/rabbitmq
- infrastructure/minio
@ -202,6 +204,7 @@ vault_role_ttl: 24h
# vault.hashicorp.com/role, namespace — где живут его поды. Пополняется по мере
# переезда приложений из compose в k3s.
vault_k8s_roles:
- {name: postgresql, namespaces: [postgresql]}
- {name: rabbitmq, namespaces: [rabbitmq]}
- {name: minio, namespaces: [minio]}
- {name: django, namespaces: [django]}
@ -214,6 +217,20 @@ vault_k8s_roles:
# Пароли не хранятся в репозитории: берутся из sarex_secrets, который
# генерирует и персистит в aero/.secrets задача "Сгенерировать/загрузить пароли".
vault_app_secrets:
# Пароль администратора PostgreSQL. Имя ключа задано чартом
# (contour.vault.secretKey) — менять в отрыве от values нельзя.
- path: secrets/postgresql/admin
data:
postgres-password: "{{ sarex_secrets.SAREX_POSTGRES_PASSWORD }}"
# Пароли владельцев баз. Ключи сопоставляются с passwordKey каждой базы в
# contour.databases (см. infrastructure/postgresql/aero). Добавляешь базу —
# добавь сюда ключ, иначе бутстрап не сможет задать роли пароль.
- path: secrets/postgresql/users
data:
django: "{{ sarex_secrets.SAREX_DJANGO_DB_PASSWORD }}"
processing: "{{ sarex_secrets.SAREX_PROCESSING_DB_PASSWORD }}"
bim: "{{ sarex_secrets.SAREX_BIM_DB_PASSWORD }}"
workspace: "{{ sarex_secrets.SAREX_WORKSPACE_DB_PASSWORD }}"
- path: secrets/rabbitmq/auth
data:
username: "{{ sarex_rabbitmq_k8s_user }}"

View File

@ -12,7 +12,7 @@ metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
interval: 2m0s
path: ./clusters/aero/apps
prune: true
wait: true

View File

@ -22,7 +22,10 @@ resources:
- ../../../infrastructure/dashboard/aero
# Только Vault Agent Injector: сам Vault живёт в compose, а не в кластере.
- ../../../infrastructure/vault-injector/aero
# Брокер и объектное хранилище. Креды берут из Vault через injector, поэтому
# идут после него. Админки выставлены наружу через istio-config/aero.
# СУБД, брокер и объектное хранилище. Креды берут из Vault через injector,
# поэтому идут после него. Админки rabbitmq и minio выставлены наружу через
# istio-config/aero; postgresql наружу не публикуется — доступ только изнутри
# кластера.
- ../../../infrastructure/postgresql/aero
- ../../../infrastructure/rabbitmq/aero
- ../../../infrastructure/minio/aero

View File

@ -37,7 +37,11 @@ metadata:
name: infra-controllers
namespace: flux-system
spec:
interval: 10m0s
# 2 минуты, а не дефолтные 10: интервал определяет, как быстро слой заметит
# новую ревизию и как быстро повторит попытку после неудачи. С 10 минутами
# цикл «правка → проверка» растягивался до получаса, а сломанный релиз
# держал слой всё это время.
interval: 2m0s
path: ./clusters/aero/controllers
prune: true
wait: true
@ -52,7 +56,7 @@ metadata:
name: infra-configs
namespace: flux-system
spec:
interval: 10m0s
interval: 2m0s
path: ./clusters/aero/configs
prune: true
# dependsOn намеренно отсутствует — см. комментарий в начале файла.

View File

@ -7,3 +7,26 @@ resources:
- ./infrastructure.yaml
# Слой приложений. Пока пуст — сюда приедут сервисы при переносе из compose.
- ./apps.yaml
# Корневая Kustomization применяет этот же каталог, поэтому здесь она правит
# сама себя — и это единственный способ задать ей интервал.
#
# Файл flux-system/gotk-sync.yaml генерирует `flux bootstrap` и помечает
# "DO NOT EDIT": правка руками затрётся на следующем прогоне. Флаг
# --interval=2m у bootstrap (см. docker-compose.yaml) тоже не помогает — он
# задаёт периодичность только GitRepository, а Kustomization оставляет
# дефолтные 10 минут. Патч же применяется поверх сгенерированного файла и
# переживает регенерацию.
#
# Без этого определения самих слоёв (infrastructure.yaml, apps.yaml)
# доезжали бы до кластера с задержкой до 10 минут, даже когда все нижние
# слои уже проверяются раз в 2 минуты.
patches:
- target:
kind: Kustomization
name: flux-system
namespace: flux-system
patch: |
- op: replace
path: /spec/interval
value: 2m0s

View File

@ -379,6 +379,13 @@ services:
# Адрес источника — имя Service'а моста (flux-k8s-init), а не IP: так
# адрес gitea задан ровно в одном месте — переменной GITEA_BRIDGE_IP.
#
# --interval=2m задаёт периодичность проверок у GitRepository и корневой
# Kustomization в clusters/aero/flux-system/gotk-sync.yaml. Задаётся
# именно флагом, потому что этот файл генерирует сам bootstrap и правка
# руками затирается при следующем прогоне (в нём так и написано:
# "DO NOT EDIT"). Дефолтные 10 минут растягивали цикл «правка → проверка»
# до получаса.
flux bootstrap git \
--url="http://gitea.flux-system.svc.cluster.local:3000/$$GITEA_ORG/$$GITEA_REPO.git" \
--branch="$$AERO_GIT_BRANCH" \
@ -386,7 +393,8 @@ services:
--username="$$GITEA_ADMIN_USER" \
--password="$$GITEA_ADMIN_PASSWORD" \
--token-auth \
--allow-insecure-http
--allow-insecure-http \
--interval=2m
# ---------------------------------------------------------------------------
# Инфраструктурные зависимости apps/django (sarex-backend).

View File

@ -4,7 +4,7 @@ metadata:
name: dashboard
namespace: kubernetes-dashboard
spec:
interval: 5m
interval: 2m
timeout: 10m
values:
# Kong слушает 8000 открытым HTTP и ничего не знает про istio. Без этого

View File

@ -4,7 +4,7 @@ metadata:
name: istio-config
namespace: default
spec:
interval: 5m
interval: 2m
timeout: 10m
# disableWait РАЗРЫВАЕТ ДЕДЛОК, не украшательство.
#

View File

@ -4,7 +4,7 @@ metadata:
name: local-path-provisioner
namespace: local-path-provisioner
spec:
interval: 5m
interval: 2m
timeout: 10m
values:
replicaCount: 1

View File

@ -4,7 +4,7 @@ metadata:
name: minio
namespace: minio
spec:
interval: 5m
interval: 2m
timeout: 10m
values:
nameOverride: "minio"

View File

@ -0,0 +1,10 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
patches:
- path: postgresql.yaml
target:
kind: HelmRelease
name: postgresql

View File

@ -0,0 +1,176 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: postgresql
namespace: postgresql
spec:
interval: 2m
# Разворачиваем пустой инстанс: базы создаются с нуля, дампы не
# восстанавливаются. Поэтому таймауты обычные, а не двухчасовые как в
# yc-k8s-test — там они заданы под restoreFromDump.
#
# 10 минут, а не 20: таймаут — это ещё и цена одной неудачной итерации.
# Пока стояло 20, цикл «install → таймаут → откат → install» занимал по
# 20 минут на круг, и правка, приехавшая в середине круга, подхватывалась
# только на следующем. Здоровый инстанс поднимается примерно за две минуты,
# так что 10 — с большим запасом.
timeout: 10m
install:
timeout: 10m
remediation:
retries: 3
upgrade:
timeout: 10m
remediation:
retries: 3
# Снимаем nodeSelector, который чарт ставит по умолчанию.
#
# В дефолтах чарта (primary.nodeSelector) прописано dedicated: sts — расчёт на
# кластер с выделенным пулом нод под StatefulSet'ы. В контуре aero таких нод
# нет, все три равнозначны, и под навсегда повисал в Pending:
# 0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector
# а следом застревал PVC, потому что у local-path режим WaitForFirstConsumer
# и том создаётся только после планирования пода.
#
# Через values это не снять: Helm сливает карты по ключам, поэтому и {}, и
# любая другая метка оставят dedicated: sts на месте. В yc-k8s-test дефолт не
# убирают, а перекрывают на dedicated: db — там выделенные ноды есть.
# Тут остаётся убрать ключ из уже отрендеренного манифеста.
#
# Tolerations чарта не трогаем: они разрешают taint dedicated=sts, которого на
# наших нодах нет, и ни на что не влияют.
postRenderers:
- kustomize:
patches:
- target:
kind: StatefulSet
name: postgresql
patch: |
- op: remove
path: /spec/template/spec/nodeSelector
values:
global:
security:
allowInsecureImages: true
defaultStorageClass: local-path
imagePullSecrets:
- regcred
postgresql:
auth:
# Пусто осознанно: пользователя и базу заводит не чарт-бутстрап
# bitnami, а contour-логика ниже (contour.databases). Оставь здесь
# значения — получишь лишнюю базу-пустышку.
username: ""
database: ""
secretKeys:
userPasswordKey: "postgres-password"
auth:
username: ""
database: ""
secretKeys:
userPasswordKey: "postgres-password"
image:
registry: cr.yandex/crp3ccidau046kdj8g9q
repository: contour/postgresql
tag: 17.0.7
pullPolicy: IfNotPresent
metrics:
enabled: false
prometheusRule:
enabled: false
primary:
# Токен ServiceAccount нужен vault-agent'у для логина в Vault.
automountServiceAccountToken: true
containerSecurityContext:
readOnlyRootFilesystem: false
persistence:
storageClass: local-path
size: 20Gi
# В yc-k8s-test здесь nodeSelector/tolerations dedicated=db. В контуре
# aero выделенных под БД нод нет — все три равнозначны, ограничивать
# планировщик нечем и незачем.
customLivenessProbe:
exec:
command:
- /bin/sh
- -c
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 6
customReadinessProbe:
exec:
command:
- /bin/sh
- -c
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 6
customStartupProbe:
exec:
command:
- /bin/sh
- -c
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 6
# contour-логика: создаёт роли, базы и расширения по списку ниже, пароли
# берёт из Vault через vault-agent-injector.
contour:
enabled: true
adminUser: "postgres"
vault:
enabled: true
role: postgresql
authPath: auth/kubernetes
# Пароль администратора.
secretPath: secrets/data/postgresql/admin
secretKey: postgres-password
# Пароли пользователей баз: ключи внутри секрета сопоставляются с
# passwordKey каждой базы. Чарт требует этот путь при vault.enabled.
# Секрет кладёт ansible (роль sarex_stack, vault_app_secrets).
usersSecretPath: secrets/data/postgresql/users
# timescaledb включён заранее, хотя нашим четырём базам он не нужен:
# shared_preload_libraries читается только при старте, и добавить его
# позже — значит перезапустить СУБД. Сервисы, которым он понадобится
# (issues, subscriptions, system-log), уже есть в списке на перенос.
sharedPreloadLibraries: "timescaledb,pg_stat_statements"
# Набор баз повторяет то, что сейчас провижинит postgres-init в
# docker-compose.yaml, — контур переезжает в k3s тем же составом.
# passwordKey должен совпадать с ключом в секрете Vault
# secrets/postgresql/users.
databases:
- name: sarex_db
user: django
passwordKey: django
extensions:
- ltree
restoreFromDump: false
- name: workflow_db
user: processing
passwordKey: processing
# Роль processing не суперпользователь, а её миграции делают
# CREATE EXTENSION — поэтому расширения создаёт бутстрап заранее.
extensions:
- uuid-ossp
- ltree
- hstore
restoreFromDump: false
- name: bim_db
user: bim
passwordKey: bim
extensions: []
restoreFromDump: false
- name: workspace_db
user: workspace
passwordKey: workspace
extensions: []
restoreFromDump: false

View File

@ -4,7 +4,7 @@ metadata:
name: rabbitmq
namespace: rabbitmq
spec:
interval: 5m
interval: 2m
timeout: 10m
# Вырезаем ресурсы ПОСЛЕ рендера чарта. Через values это недостижимо:
# * `null` до Helm не доезжает — kustomize нормализует его в {};
@ -55,24 +55,16 @@ spec:
global:
security:
allowInsecureImages: true
# Собственные VirtualService/Gateway/Certificate чарта отключаются НЕ здесь,
# а JSON6902-патчем в kustomization.yaml рядом. Причина — в противоположной
# семантике null у двух инструментов:
# Здесь НЕТ ключей virtualService/gateway/certificate, и это намеренно:
# через values дефолты чарта не убираются вообще никак.
#
# Helm : удалить дефолт можно ТОЛЬКО значением null;
# {} он глубоко сливает с дефолтом, и дефолт остаётся;
# kustomize : в strategic-merge-патче null означает УДАЛИТЬ КЛЮЧ,
# то есть до Helm он просто не доедет.
# в JSON6902 — нормализуется обратно в {}.
#
# Значит ни `virtualService: null`, ни `virtualService: {}` здесь не
# работают: первое стирается kustomize, второе игнорируется Helm. Нужен
# JSON6902, где null — это именно значение, а не команда удаления.
#
# Цена ошибки не косметическая: чарт создавал rmq-gateway, rmq-virt-service
# и сертификат rmq-tls на посторонний домен rabbitmq.infra.sarex.io.
# Последний не проходит проверку ACME никогда, впустую жжёт лимиты
# Let's Encrypt, а релиз висит в install, ожидая его выпуска, и блокирует
# весь слой infra-controllers.
# То есть до Helm настоящий null не доходит ни одним путём. Поэтому ресурсы
# вырезаются postRenderers'ом выше, уже из отрендеренного манифеста.
metrics:
serviceMonitor:
enabled: false

View File

@ -19,7 +19,7 @@ metadata:
name: vault-injector
namespace: vault
spec:
interval: 10m
interval: 2m
chart:
spec:
chart: vault-contour