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