Files
tf_provider/docs/curated/postgres/pg_user_db.md
T
Repinoid 2aa2946700 docs(history): обновлены перекрёстные ссылки на файлы HISTORY после переноса
Перенос в тематические папки сломал бы все ссылки, поэтому обновлены пути:
- HISTORY/OPUS/ -> HISTORY/90_llm/OPUS/, HISTORY/SONNET/ -> HISTORY/90_llm/SONNET/;
- 15 целевых файлов из корня получили свой тематический префикс
  (HISTORY/<файл>.md -> HISTORY/<папка>/<файл>.md) — в 33 файлах репозитория.

Затронуто вне HISTORY: README.md, VERSIONS.md, HOW_TO/DEVOPS_BUILD_PIPELINE.md,
NOTES/README.md, NOTES/10_plans/, NOTES/20_prompts/, NOTES/30_analysis/,
NOTES/40_chat_summaries/, docs/curated/{crud,postgres}, docs/help/dev-reference/,
docs/ops/TESTING.md.

Проверки после правки:
- ссылок вида HISTORY/<дата> без тематической папки не осталось;
- все пути HISTORY/*.md из markdown-ссылок существуют (кроме трёх упоминаний,
  которые не были файлами и до переноса: HISTORY/90_llm/OPUS/3006_1.md,
  3006_0.md — планировавшиеся имена в старых транскриптах,
  и HISTORY/HOWTO-UPLOAD.md — ссылка на старый внешний репозиторий tf_registry);
- ссылки по «голому» имени внутри 90_llm/OPUS/ остались корректными (соседние файлы).
2026-10-02 07:36:09 +03:00

7.8 KiB
Raw Blame History

PostgreSQL: кластер + пользователь + база + S3-бэкап

Проверенный сценарий. Протестировано 2026-08-10 на TEST-стенде.

Что создаётся

Ресурс Имя в манифесте Назначение
nubes_postgres main_pg Кластер PostgreSQL
nubes_postgres_user app_user Пользователь БД
nubes_postgres_database app_db База данных
nubes_s3bucket backups S3-бакет для бэкапов

Переменные

Создай terraform.tfvars:

api_token = "***"                    # токен из Личного кабинета
realm     = "k8s-3-sandbox-nubes-ru" # кластер Kubernetes (из организации)
s3_name   = "my-s3"                  # ИМЯ экземпляра S3 (не UUID!)

Провайдер

terraform {
  required_providers {
    nubes = {
      source  = "{{PROVIDER_SOURCE}}"
      version = "{{VERSION}}"
    }
  }
}

variable "api_token" { type = string; sensitive = true }
variable "realm"     { type = string }
variable "s3_name"   { type = string }

provider "nubes" {
  api_token    = var.api_token
  api_endpoint = "{{NUBES_API_ENDPOINT}}"
}

Кластер PostgreSQL

resource "nubes_postgres" "main_pg" {
  resource_name = "my-postgres"          # любое имя

  # На каком кластере Kubernetes развернуть
  startup_configuration = {
    resource_realm = var.realm           # имя кластера
  }

  # Ресурсы пода
  cluster_configuration = {
    cpu      = 500                       # milicores (1000 = 1 ядро)
    memory   = 512                       # megabytes
    replicas = 1                         # 1, 3, 5 или 7
    disk     = 10                        # gigabytes
  }

  # Сетевой доступ
  access_configuration = {
    master_ip_space    = "no-needed"     # Ip-Space для внешнего IP мастера
    master_access_list = jsonencode(["10.0.0.0/8"]) # кому доступ к мастеру
    slave_ip_space     = "no-needed"     # Ip-Space для внешнего IP слейва
    slave_access_list  = jsonencode([])  # кому доступ к слейву ([] = всем)
  }

  # Версия и настройки PostgreSQL
  postgres_configuration = {
    version       = "17"                 # 16 или 17
    ssl_required  = true                 # требовать SSL
    pooler_master = false                # PgBouncer для записи
    pooler_slave  = false                # PgBouncer для чтения
  }

  # Резервное копирование
  backup_configuration = {
    s3_uid   = var.s3_name              # ИМЯ S3 (не UUID!)
    retain   = 14                        # сколько бэкапов хранить
    schedule = "0 0 * * *"               # cron (ежедневно в полночь)
  }

  # Автоскейлинг диска
  autoscale_configuration = {
    enabled  = false                     # выключен
    schedule = 0                         # час окна (0-23)
    percent  = 10                        # % расширения
    quota    = 100                       # макс. размер диска (GB)
  }

  # Mutual TLS
  mtls_configuration = {
    type            = "off"              # off | manual | auto
    duration_ca     = 175200             # время жизни CA (часы)
    duration_server = 87600              # время жизни сертификата (часы)
  }

  operation_timeout = "11m"              # таймаут операции
}

Пользователь и база данных

resource "nubes_postgres_user" "app_user" {
  postgres_id = nubes_postgres.main_pg.id  # ссылка на кластер
  username    = "app_user"                 # имя пользователя
  role        = "ddl_user"                 # app_user (DML) | ddl_user (DDL)
  mtls_access = false                      # mTLS для пользователя
}

resource "nubes_postgres_database" "app_db" {
  postgres_id = nubes_postgres.main_pg.id  # ссылка на кластер
  db_name     = "my_app_db"                # имя базы данных
  db_owner    = nubes_postgres_user.app_user.username  # владелец
}

S3-бакет для бэкапов

resource "nubes_s3bucket" "backups" {
  resource_name = "pg-backup-bucket"     # любое имя
  s3_user_uid   = var.s3_name            # ИМЯ S3 (не UUID!)
  bucket_name   = "my-app-backups"       # имя бакета
}

Запуск

terraform init
terraform apply

Все 4 ресурса за один apply. Terraform сам выстроит порядок: кластер → пользователь → БД → бакет.

⚠️ Исключение — если приложение берёт пароль БД из vault_secrets (как в CRUD-стенде): нужен второй apply. В первом создаются кластер и пользователь, во втором приложения получают уже появившийся пароль. Иначе — Invalid index ... does not identify an element in this collection value.

Пароль пользователя БД и секреты Vault

Проверено 2026-10-01 на TEST-стенде (инстанс pg4crud2).

Пароль пользователя, созданного через nubes_postgres_user, платформа пишет в Vault и отдаёт в выходе vault_secrets["users"] — это JSON-строка:

{ "<имя_пользователя>": { "password": "<пароль>" } }

Как брать в конфиге:

locals {
  pg_host = nubes_postgres.main_pg.state_out_flat["internalMaster"]
  pg_user = nubes_postgres_user.crud_user_0.username
  pg_pass = nonsensitive(jsondecode(nubes_postgres.main_pg.vault_secrets["users"])[local.pg_user].password)
}

Проверить вручную (то же, что читает провайдер):

GET {api_endpoint}/instances/<instanceUid>/vault/users

Ловушки

  • state.out.users — это метаданные (role, rights, username, mtlsAccess), пароля там нет. Пароль лежит только в vault_secrets["users"]. Это два разных объекта, их легко перепутать.
  • Список доступных имён секретов — выход vault_fields (у PostgreSQL это ["users"]).
  • Ключей adminUser / adminPass / standbyUser / standbyPass у новых инстансов нет — эндпоинт /vault/<имя> отдаёт 404. Они встречаются в старых материалах (например, в docs/30_registry/guides/getting-started.md).
  • ⚠️ Нужно два apply, если PG и пользователь создаются в одном прогоне, а приложения читают пароль: см. предупреждение в разделе «Запуск» выше.

Диагностика сбоя операции

errorLog у платформы не отражает суть. Реальная причина — в stages:

GET {api_endpoint}/instanceOperations/<UID>?fields=cfsParams,errorLog,stages

stages[].stageMsg — JSON-массив пар [заголовок, лог], смотреть последнюю запись. Там же cfsParams — фактический payload, ушедший на платформу. Разбор реального случая: HISTORY/60_stands/2026-10-01_test_crud_pg_create_failure.md.