From 6d6703ed2dcda5d767895555fba7fa8a456972e9 Mon Sep 17 00:00:00 2001 From: emelinda Date: Sun, 9 Aug 2026 21:55:27 +0300 Subject: [PATCH] =?UTF-8?q?BIM:=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20ltree=20=D0=B8=20=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=20=D0=B4=D0=BE=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7=20=D0=B2=20=D0=B6=D0=B8?= =?UTF-8?q?=D0=B2=D0=BE=D0=B9=20=D0=A1=D0=A3=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Продолжение разбора: после появления 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 задач зарегистрировано). --- aero/pyproject.toml | 5 ++ aero/roles/sarex_stack/defaults/main.yml | 28 +++++++ aero/roles/sarex_stack/tasks/databases.yml | 82 +++++++++++++++++++ aero/roles/sarex_stack/tasks/main.yml | 7 ++ .../postgresql/aero/postgresql.yaml | 8 +- 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 aero/roles/sarex_stack/tasks/databases.yml diff --git a/aero/pyproject.toml b/aero/pyproject.toml index 48819a4..e5b19c0 100644 --- a/aero/pyproject.toml +++ b/aero/pyproject.toml @@ -52,6 +52,11 @@ superuser = "ansible-playbook deploy.yml -e sarex_create_superuser=true --tags s # старым содержимым — в шаблоне пода нет хеша конфига, спецификация не меняется # и Kubernetes не видит причин его пересоздавать. Список — sarex_reload_workloads. 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" # --- Операционные задачи на хосте (без полного деплоя) ---------------------- diff --git a/aero/roles/sarex_stack/defaults/main.yml b/aero/roles/sarex_stack/defaults/main.yml index c9887cd..85703d9 100644 --- a/aero/roles/sarex_stack/defaults/main.yml +++ b/aero/roles/sarex_stack/defaults/main.yml @@ -107,6 +107,34 @@ compose_cmd: "docker compose" # Добавляешь 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_timeout: 240s sarex_reload_workloads: diff --git a/aero/roles/sarex_stack/tasks/databases.yml b/aero/roles/sarex_stack/tasks/databases.yml new file mode 100644 index 0000000..2a28be6 --- /dev/null +++ b/aero/roles/sarex_stack/tasks/databases.yml @@ -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] diff --git a/aero/roles/sarex_stack/tasks/main.yml b/aero/roles/sarex_stack/tasks/main.yml index e63683a..19e0e21 100644 --- a/aero/roles/sarex_stack/tasks/main.yml +++ b/aero/roles/sarex_stack/tasks/main.yml @@ -194,6 +194,13 @@ when: aero_platform_up | bool tags: [platform] +# --- Досоздание баз в работающей СУБД ------------------------------------- +# Только по тегу dbsync. Обоснование — в шапке databases.yml. +- name: Досоздать недостающие базы + ansible.builtin.include_tasks: databases.yml + when: sarex_dbsync | bool + tags: [dbsync] + # --- Перекат нагрузок с изменённой конфигурацией -------------------------- # Только по тегу reload: правка ConfigMap сама под не перезапускает, а делать # это на каждом прогоне нельзя. Обоснование — в шапке reload.yml. diff --git a/infrastructure/postgresql/aero/postgresql.yaml b/infrastructure/postgresql/aero/postgresql.yaml index 01ae7bd..b42c9cf 100644 --- a/infrastructure/postgresql/aero/postgresql.yaml +++ b/infrastructure/postgresql/aero/postgresql.yaml @@ -172,7 +172,13 @@ spec: - name: bimapidb user: 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 - name: workspace_db user: workspace