BIM: расширение ltree и задача досоздания баз в живой СУБД

Продолжение разбора: после появления bimapidb оба пода упали на
  psycopg2.ProgrammingError: ltree type not found in the database
а миграция api — на
  type "ltree" does not exist
  LINE 2: ALTER TABLE bimelement ADD hierarchy ltree null;
models/database.py при инициализации зовёт register_ltree(conn), без
расширения приложение не стартует. Роль bim не суперпользователь и
CREATE EXTENSION сама не сделает — расширение обязан завести бутстрап,
как у sarex_db и workflow_db. Добавлено в contour.databases.

--- Почему понадобилась отдельная задача ansible ---

Базы и расширения описаны декларативно, но заводит их скрипт бутстрапа
ТОЛЬКО при старте пода postgresql. На живом кластере правка списка
доезжает до Kubernetes и не делает ничего: спецификация пода не меняется,
пересоздания нет. Flux при этом полностью зелёный, а приложение падает с
"database does not exist" — связь между причиной и симптомом неочевидна.

Альтернатива — перезапуск postgresql-0, но это обрыв коннектов django,
processing и workspaces ради одной базы.

Добавлены tasks/databases.yml, список sarex_databases в defaults и задача
`uv run poe dbsync`. Делает ровно то же, что бутстрап, но без простоя, и
идемпотентно: существующие базы пропускаются, расширения создаются через
IF NOT EXISTS. Расширения обрабатываются отдельным шагом и всегда, а не
только для новых баз, — расширение может появиться в списке позже самой
базы, ровно так и вышло с ltree.

Проверено на живом контуре: bim-api 2/2 (uWSGI поднял воркеров),
bim-worker 2/2 (celery подключился к брокеру, 8 задач зарегистрировано).
This commit is contained in:
emelinda 2026-08-09 21:55:27 +03:00
parent 3d205fa7e0
commit 6d6703ed2d
5 changed files with 129 additions and 1 deletions

View File

@ -52,6 +52,11 @@ superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags s
# старым содержимым — в шаблоне пода нет хеша конфига, спецификация не меняется # старым содержимым — в шаблоне пода нет хеша конфига, спецификация не меняется
# и Kubernetes не видит причин его пересоздавать. Список — sarex_reload_workloads. # и Kubernetes не видит причин его пересоздавать. Список — sarex_reload_workloads.
reload = "ansible-playbook deploy.yml -e sarex_reload=true --tags reload" reload = "ansible-playbook deploy.yml -e sarex_reload=true --tags reload"
# Досоздать базы, появившиеся в contour.databases после запуска СУБД. Бутстрап
# заводит базы только при старте пода postgresql, поэтому на живом кластере
# новая база иначе не появится: Flux зелёный, а приложение падает с
# FATAL: database "..." does not exist. Идемпотентно.
dbsync = "ansible-playbook deploy.yml -e sarex_dbsync=true --tags dbsync"
lint = "ansible-lint" lint = "ansible-lint"
# --- Операционные задачи на хосте (без полного деплоя) ---------------------- # --- Операционные задачи на хосте (без полного деплоя) ----------------------

View File

@ -107,6 +107,34 @@ compose_cmd: "docker compose"
# Добавляешь ConfigMap, который читает приложение, — добавь нагрузку сюда, # Добавляешь ConfigMap, который читает приложение, — добавь нагрузку сюда,
# иначе правка будет уезжать в кластер и не применяться, а выглядеть это будет # иначе правка будет уезжать в кластер и не применяться, а выглядеть это будет
# как «изменение не сработало». # как «изменение не сработало».
# --- Досоздание баз в работающей СУБД ---------------------------------------
# Выключено по умолчанию, включается задачей `uv run poe dbsync`.
#
# Список ДУБЛИРУЕТ contour.databases из infrastructure/postgresql/aero — и это
# осознанно. Тот список читает скрипт бутстрапа при старте пода, этот нужен
# для уже работающей СУБД, где бутстрап больше не выполнится. Имена берутся из
# тех же переменных, что и секреты приложений, поэтому разъехаться они могут
# только вместе с секретами, то есть заметно.
sarex_dbsync: false
#
# extensions повторяют то же поле в contour.databases. Их тоже создаёт только
# бутстрап, и роли приложений не суперпользователи — сами CREATE EXTENSION не
# сделают. Пропуск расширения выглядит не как отсутствие расширения, а как
# «тип не найден»: ltree type not found in the database.
sarex_databases:
- name: "{{ sarex_postgres_django_db }}"
owner: "{{ sarex_postgres_django_user }}"
extensions: [ltree]
- name: "{{ sarex_postgres_processing_db }}"
owner: "{{ sarex_postgres_processing_user }}"
extensions: [uuid-ossp, ltree, hstore]
- name: "{{ sarex_postgres_workspaces_db }}"
owner: "{{ sarex_postgres_workspaces_user }}"
extensions: []
- name: "{{ sarex_postgres_bim_db }}"
owner: "{{ sarex_postgres_bim_user }}"
extensions: [ltree]
sarex_reload: false sarex_reload: false
sarex_reload_timeout: 240s sarex_reload_timeout: 240s
sarex_reload_workloads: sarex_reload_workloads:

View File

@ -0,0 +1,82 @@
---
# Досоздание баз в уже работающей СУБД.
#
# ЗАЧЕМ ЭТО ОТДЕЛЬНАЯ ЗАДАЧА. Базы описаны декларативно — contour.databases в
# infrastructure/postgresql/aero. Но заводит их скрипт бутстрапа, который
# выполняется ТОЛЬКО при старте пода postgresql. На живом кластере правка
# списка доезжает до Kubernetes (HelmRelease обновляется, Flux рапортует
# успех) и не делает ничего: под не пересоздаётся, потому что его
# спецификация не изменилась.
#
# Выглядит это так: приложение падает с
# psycopg2.OperationalError: FATAL: database "bimapidb" does not exist
# при полностью зелёном Flux. Так и случилось с bim.
#
# Альтернатива — перезапуск postgresql-0, но это обрыв коннектов у django,
# processing и workspaces ради одной новой базы. Здесь мы делаем ровно то же,
# что сделал бы бутстрап, но без простоя.
#
# ИДЕМПОТЕНТНО: существующие базы пропускаются, повторный прогон ничего не
# меняет. Ничего не удаляет и не пересоздаёт — только добавляет недостающее.
#
# Пароли ролям здесь НЕ задаются: роли и их пароли остаются за бутстрапом и
# Vault (secrets/postgresql/users). Если роли-владельца ещё нет, создание
# базы упадёт явно — это правильнее, чем завести её от postgres.
#
# Запуск: uv run poe dbsync
- name: Получить список существующих баз
ansible.builtin.shell:
cmd: >-
{{ compose_cmd }} exec -T k3s-server
kubectl -n postgresql exec postgresql-0 -c postgresql --
sh -ec 'PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -tAc
"select datname from pg_database"'
chdir: "{{ deploy_dir }}"
register: existing_databases
changed_when: false
tags: [dbsync]
- name: Создать недостающие базы
ansible.builtin.shell:
cmd: >-
{{ compose_cmd }} exec -T k3s-server
kubectl -n postgresql exec postgresql-0 -c postgresql --
sh -ec 'PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres -c
"CREATE DATABASE \"{{ item.name }}\" OWNER \"{{ item.owner }}\""'
chdir: "{{ deploy_dir }}"
loop: "{{ sarex_databases }}"
loop_control:
label: "{{ item.name }} (владелец {{ item.owner }})"
when: item.name not in existing_databases.stdout_lines
changed_when: true
tags: [dbsync]
# Расширения создаются ОТДЕЛЬНЫМ шагом и всегда, а не только для новых баз:
# расширение могло появиться в списке позже самой базы — ровно так и вышло с
# ltree у bimapidb. CREATE EXTENSION IF NOT EXISTS идемпотентен.
#
# Выполняется от postgres, а не от владельца базы: роли приложений не
# суперпользователи и создать расширение не могут.
- name: Создать расширения в базах
ansible.builtin.shell:
cmd: >-
{{ compose_cmd }} exec -T k3s-server
kubectl -n postgresql exec postgresql-0 -c postgresql --
sh -ec 'PGPASSWORD=$POSTGRES_POSTGRES_PASSWORD psql -U postgres
-d "{{ item.0.name }}" -c
"CREATE EXTENSION IF NOT EXISTS \"{{ item.1 }}\""'
chdir: "{{ deploy_dir }}"
loop: "{{ sarex_databases | subelements('extensions', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }}: {{ item.1 }}"
changed_when: true
tags: [dbsync]
- name: Итог
ansible.builtin.debug:
msg: >-
Базы: {{ sarex_databases | map(attribute='name') | join(', ') }}.
Уже существовали: {{ sarex_databases | map(attribute='name')
| select('in', existing_databases.stdout_lines) | join(', ') | default('нет', true) }}
tags: [dbsync]

View File

@ -194,6 +194,13 @@
when: aero_platform_up | bool when: aero_platform_up | bool
tags: [platform] tags: [platform]
# --- Досоздание баз в работающей СУБД -------------------------------------
# Только по тегу dbsync. Обоснование — в шапке databases.yml.
- name: Досоздать недостающие базы
ansible.builtin.include_tasks: databases.yml
when: sarex_dbsync | bool
tags: [dbsync]
# --- Перекат нагрузок с изменённой конфигурацией -------------------------- # --- Перекат нагрузок с изменённой конфигурацией --------------------------
# Только по тегу reload: правка ConfigMap сама под не перезапускает, а делать # Только по тегу reload: правка ConfigMap сама под не перезапускает, а делать
# это на каждом прогоне нельзя. Обоснование — в шапке reload.yml. # это на каждом прогоне нельзя. Обоснование — в шапке reload.yml.

View File

@ -172,7 +172,13 @@ spec:
- name: bimapidb - name: bimapidb
user: bim user: bim
passwordKey: bim passwordKey: bim
extensions: [] # ltree обязателен: models/database.py при инициализации вызывает
# register_ltree(conn) и падает с
# psycopg2.ProgrammingError: ltree type not found in the database
# Роль bim не суперпользователь и CREATE EXTENSION сама не сделает,
# поэтому расширение заводит бутстрап — как у sarex_db и workflow_db.
extensions:
- ltree
restoreFromDump: false restoreFromDump: false
- name: workspace_db - name: workspace_db
user: workspace user: workspace