Проверено по API 2026-10-01 (pg4crud2, TEST):
GET /instances/<uid>/vault/users -> {"users":{"user4crudpg":{"password":"..."}}}.
Пароль ЕСТЬ; state.out.users — метаданные без пароля (их легко перепутать).
- README.md: строка навигации «Пароль БД / секреты Vault» + раздел «Грабли, на которые уже наступали»
(Invalid index из-за одного apply; errorLog врёт — смотреть stages; лишний sensitive).
- docs/curated/postgres/pg_user_db.md: раздел «Пароль пользователя БД и секреты Vault» (ловушки,
два apply, диагностика через /instanceOperations?fields=stages); исправлено утверждение «все 4 ресурса
за один apply» — для приложений, читающих пароль, нужен второй apply.
- docs/30_registry/guides/getting-started.md: помечены устаревшие ключи adminUser/adminPass (сейчас 404).
- HISTORY/2026-10-01_...: дополнение с фактами и указанием, что первый разбор ошибся.
7.8 KiB
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/2026-10-01_test_crud_pg_create_failure.md.