Skip to content

Начало работы с Nubes Terraform

Это руководство поможет вам установить инструменты, настроить доступ и развернуть вашу первую инфраструктуру в облаке Nubes, используя Terraform.

ИИ-помощники на этом сайте

В правом нижнем углу сайта документации есть две кнопки:

  • «LLM» — ассистент документации. Задайте вопрос своими словами — получите ответ со ссылками на страницы документации.
  • «TF» — проверка файла. Вставьте содержимое своего terraform.tfvars — ИИ вернёт исправленный файл и список изменений (что и почему поменял). Секреты (api_token, пароли) в поле не вставляйте — держите их в отдельном файле secrets.auto.tfvars.

ИИ-агент в вашей IDE

В репозитории примеров (Nail/tf_examples) есть файл AGENTS.md — инструкция для ИИ-агентов (GitHub Copilot, Claude, Cursor и др.). Агент прочитает её сам и поможет писать и проверять конфигурацию, опираясь на реальные схемы ресурсов, а не на догадки.

Что ИИ не делает

Ассистенты только помогают: не деплоят (terraform plan/apply запускаете вы), не читают ваши секреты и не заменяют проверку конфигурации по документации.

0. Установка Terraform

Для работы с инфраструктурой как кодом (IaC) потребуется утилита Terraform (версии 1.5 или выше).

  1. Скачивание:
  2. Установка:
    • Распакуйте скачанный архив.
    • Поместите исполняемый файл (terraform или terraform.exe) в папку, доступную в переменной окружения PATH.
  3. Проверка:
    • Откройте терминал (PowerShell, CMD, Bash или Terminal в macOS).
    • Введите команду terraform -version. Вы должны увидеть номер версии.

1. Подготовка конфигурации

Создайте рабочую директорию для вашего проекта и создайте основной файл конфигурации main.tf.

main.tf
terraform {
  required_providers {
    nubes = {
      source  = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
      version = "2.1.0"
    }
  }
}

provider "nubes" {
  api_endpoint = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
  api_token = var.api_token
}

variable "api_token" {
  type      = string
  sensitive = true
}

Стенд nubes-dev (обязательные адреса)

Эта документация относится к стенду nubes-dev.

  • Личный кабинет: https://deck-dev.ngcloud.ru/dashboard/
  • API endpoint: https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc

Безопасность

Никогда не храните токен прямо в файле main.tf, если планируете загружать код в систему контроля версий (git). Используйте variables.tf или файл terraform.tfvars.

2. Получение API токена

Токен (Access Token) необходим провайдеру для авторизации ваших действий в облаке.

Если нет ТОКЕНА доступа или хотите создать новый - В Личном Кабинете - на странице Профиля пользователя https://deck-dev.ngcloud.ru/authorization/profile во вкладке Токены - нажать "Выпустить тех-токен" Значение токена показывается только при его создании, надо его сохранить

Создайте файл terraform.tfvars и сохраните токен там:

terraform.tfvars
api_token = "eyJhbGciOiJ..." # Ваш длинный токен

3. Описание ресурсов

Добавьте ресурсы, которые вы хотите создать, в файл main.tf или resources.tf.

Пример создания S3 бакета:

resource "nubes_s3bucket" "my_files" {
  resource_name = "bucket_0"
  bucket_name   = "my-unique-bucket-name"
    s3_user_uid   = "235e0546-..." # UID Корневой услуги S3
}

Или Postgres кластера:

resource "nubes_postgres" "db" {
  resource_name = "pg-tst0"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru" # платформа развёртывания
  }
  cluster_configuration = {
    cpu      = 500 # milicores (1000 = 1 ядро)
    memory   = 512 # MB
    replicas = 1
    disk     = 10  # GB
  }
  access_configuration = {
    master_ip_space    = "no-needed"
    master_access_list = jsonencode([])
    slave_ip_space     = "no-needed"
    slave_access_list  = jsonencode([])
  }
  postgres_configuration = {
    version       = "17"
    ssl_required  = true
    pooler_master = false
    pooler_slave  = false
  }
  backup_configuration = {
    s3_uid   = "my-s3" # ИМЯ экземпляра S3 (не UUID)
    retain   = 7
    schedule = "0 0 * * *"
  }
  autoscale_configuration = {
    enabled  = false
    schedule = 0
    percent  = 10
    quota    = 100
  }
}

Подробные примеры конфигураций смотрите в разделе Resources документации.

4. Запуск

Теперь вы готовы применить конфигурацию:

  1. Инициализация: terraform init (загружает плагин провайдера).
  2. План: terraform plan (показывает, что будет сделано).
  3. Применение: terraform apply (создает инфраструктуру).

5. Почему провайдер использует Suspend/Adopt

Для этой логики используются два флага.

  • suspend_on_destroy
  • adopt_existing_on_create

suspend_on_destroy

  • true (по умолчанию)
  • при terraform destroy инстанс уходит в Suspend;
  • при удалении ресурса из манифеста — тоже Suspend.
  • false
  • Terraform удаляет ресурс только из state;
  • в облаке инстанс не меняется.

adopt_existing_on_create

  • false (по умолчанию)
  • если инстанс уже существует, Terraform вернёт ошибку.
  • true
  • Terraform может взять существующий инстанс под управление.

Как это связано с terraform import

Это похоже на terraform import, но срабатывает в обычном apply.

  • найден running → adopt;
  • найден suspended + обязательные параметры совпадают → resume + adopt.

Важно

Один инстанс должен быть только в одном state.

Если подключить один инстанс к двум манифестам, получится конфликт управления:

  • первый apply меняет ресурс;
  • второй apply откатывает или перезаписывает изменения.

Следующий шаг: изучите Справочник по командам Terraform для уверенной работы.

Примеры

Lucee & PostgreSQL

Lucee и NodeJS — это два отдельных UI для CRUD‑операций, оба работают с одной и той же таблицей в Postgres.

resources.tf
resource "nubes_postgres" "db2" {
  # Основной Postgres-кластер для демо.
  resource_name = "pg-crud"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 500
    memory   = 512
    replicas = 1
    disk     = 10
  }
  access_configuration = {
    master_ip_space    = "no-needed"
    master_access_list = jsonencode([])
    slave_ip_space     = "no-needed"
    slave_access_list  = jsonencode([])
  }
  postgres_configuration = {
    version       = "17"
    ssl_required  = true
    pooler_master = false
    pooler_slave  = false
  }
  postgres_conf = {
    # Доп. настройки postgresql.conf (map-fixed).
    log_connections    = "off"
    log_disconnections = "off"
  }
  backup_configuration = {
    s3_uid   = "my-s3" # ИМЯ экземпляра S3 (не UUID)
    retain   = 7
    schedule = "0 0 * * *"
  }
  autoscale_configuration = {
    enabled  = false
    schedule = 0
    percent  = 10
    quota    = 100
  }
}

# Пользователь и база для приложений.
resource "nubes_postgres_user" "demo" {
  postgres_id = nubes_postgres.db2.id
  username    = "crud_user"
  role        = "ddl_user"
}

resource "nubes_postgres_database" "demo_db" {
  postgres_id = nubes_postgres.db2.id
  db_name     = "postgres"
  db_owner    = nubes_postgres_user.demo.username
}

resource "nubes_lucee" "app1" {
  # Lucee UI, который читает/пишет в Postgres.
  resource_name = "lucy1"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 300
    memory   = 512
    replicas = 1
  }
  access_configuration = {
    domain = "web03" # Пример домена приложения
  }
  app_configuration = {
    version  = "5.4"
    git_path = "https://<ваш-репозиторий>/<приложение>.git"
  }

  json_env = jsonencode({
    # JDBC datasource для Lucee. Пароль БД платформа кладёт в vault_secrets["users"].
    testds_class            = "org.postgresql.Driver"
    testds_bundleName       = "org.postgresql.jdbc"
    testds_bundleVersion    = "42.6.0"
    testds_connectionString = "jdbc:postgresql://${nubes_postgres.db2.state_out_flat["internalMaster"]}:5432/${nubes_postgres_database.demo_db.db_name}"
    testds_username         = nubes_postgres_user.demo.username
    testds_password         = nonsensitive(jsondecode(nubes_postgres.db2.vault_secrets["users"])[nubes_postgres_user.demo.username].password)
    testds_connectionLimit  = "5"
    testds_liveTimeout      = "15"
    testds_validate         = "false"
  })

  depends_on = [nubes_postgres.db2]
}

resource "nubes_nodejs" "app3" {
  # NodeJS демо, работающий с тем же Postgres.
  resource_name = "node_0"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 300
    memory   = 256
    replicas = 1
  }
  access_configuration = {
    domain = "node"
  }
  app_configuration = {
    version     = "23"
    git_path    = "https://<ваш-репозиторий>/<приложение>.git"
    health_path = "/healthz"
  }

  json_env = jsonencode({
    PGHOST     = nubes_postgres.db2.state_out_flat["internalMaster"]
    PGPORT     = "5432"
    PGUSER     = nubes_postgres_user.demo.username
    PGPASSWORD = nonsensitive(jsondecode(nubes_postgres.db2.vault_secrets["users"])[nubes_postgres_user.demo.username].password)
    PGDATABASE = nubes_postgres_database.demo_db.db_name
    PGSSLMODE  = "require"
  })

  depends_on = [nubes_postgres.db2]
}

Пример: RabbitMQ + Lucee UI + NodeJS воркер

Ниже полный пример resources.tf для RabbitMQ + Lucee UI + NodeJS воркера.

Комментарий: UI Lucee отправляет CRUD‑запросы в RabbitMQ, а применение изменений в Postgres выполняет отдельный воркер на NodeJS. Сервис Postgres должен быть запущен заранее (пример развертывания — выше в этом же руководстве, см. раздел про Lucee + Postgres).

resources.tf
# RabbitMQ кластер для демо.
resource "nubes_rabbitmq" "rb1" {
  resource_name = "rabbit_0"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 500
    memory   = 512
    disk     = 5
    replicas = 1
  }
  access_configuration = {
    master_ip_space    = "no-needed"
    master_access_list = jsonencode([])
  }
  mq_configuration = {
    max_message_size = 4
  }
}

# Lucee UI, который пишет в Rabbit и читает из Postgres.
resource "nubes_lucee" "rabbit_ui" {
  resource_name = "rb-lucee-ui"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 300
    memory   = 512
    replicas = 1
  }
  access_configuration = {
    domain = "rb-ui"
  }
  app_configuration = {
    version  = "5.4"
    git_path = "https://<ваш-репозиторий>/<приложение>.git"
  }

  # Переменные окружения для datasource, Rabbit и UI.
  json_env = jsonencode({
    testds_bundleName       = "org.postgresql.jdbc"
    testds_bundleVersion    = "42.6.0"
    testds_class            = "org.postgresql.Driver"
    testds_connectionLimit  = "5"
    testds_connectionString = "jdbc:postgresql://${var.PGHOST}:5432/postgres"
    testds_liveTimeout      = "15"
    testds_username         = var.PGUSER
    testds_password         = var.PGPASSWORD
    testds_validate         = "false"
    PG_TABLE                = "rabbit_messages"
    UI_LOG_TABLE            = "rabbit_ui_log"
    RABBIT_HOST             = nubes_rabbitmq.rb1.state_out.internalConnect
    RABBIT_PORT             = "5672"
    RABBIT_USER             = nubes_rabbitmq.rb1.vault_secrets["adminUser"]
    RABBIT_PASSWORD         = nubes_rabbitmq.rb1.vault_secrets["adminPass"]
    RABBIT_VHOST            = "/"
    RABBIT_QUEUES           = "crud_queue"
    RABBIT_DURABLE          = "true"
    RABBIT_ADMIN_URL         = "https://${jsondecode(nubes_rabbitmq.rb1.state_out.externalConnect).admin}/#/"
    NODEWORKER_URL           = "https://${nubes_nodejs.rabbit_nodeworker.state_out.connectionUrl}/"
  })

  depends_on = [nubes_rabbitmq.rb1]
}

# NodeJS воркер, который переносит CRUD из очереди в Postgres.
resource "nubes_nodejs" "rabbit_nodeworker" {
  resource_name = "nodeworker"

  startup_configuration = {
    resource_realm = "k8s-4-sandbox-nubes-ru"
  }
  cluster_configuration = {
    cpu      = 100
    memory   = 256
    replicas = 1
  }
  access_configuration = {
    domain = "nodeworker"
  }
  app_configuration = {
    version     = "23"
    git_path    = "https://<ваш-репозиторий>/<приложение>.git"
    health_path = "/healthz"
  }

  # Параметры подключения к Rabbit и Postgres.
  json_env = jsonencode({
    RABBIT_HOST      = nubes_rabbitmq.rb1.state_out.internalConnect
    RABBIT_PORT      = "5672"
    RABBIT_USER      = nubes_rabbitmq.rb1.vault_secrets["adminUser"]
    RABBIT_PASSWORD  = nubes_rabbitmq.rb1.vault_secrets["adminPass"]
    RABBIT_VHOST     = "/"
    RABBIT_QUEUES    = "crud_queue"
    RABBIT_DURABLE   = "true"
    RABBIT_PREFETCH  = "1"
    REQUEUE_ON_ERROR = "true"
    PGHOST           = var.PGHOST
    PGPORT           = "5432"
    PGUSER           = var.PGUSER
    PGPASSWORD       = var.PGPASSWORD
    PGDATABASE       = "postgres"
    PGSSLMODE        = "require"
    PG_TABLE         = "rabbit_messages"
  })

  depends_on = [nubes_rabbitmq.rb1]
}

# Ручной запуск операций из Terraform.
#
# 1) redeploy: у ресурсов с операцией `redeploy` есть поле `git_revision`.
#    Отдельного ресурса `nubes_nodejs_redeploy` больше нет — меняйте значение
#    поля в самом `nubes_nodejs`, чтобы запустить пересборку из git:
#
#   resource "nubes_nodejs" "rabbit_nodeworker" {
#     # ... остальные параметры ...
#     git_revision = "manual-001"  # ← смена значения = redeploy
#   }
#
# 2) reconcile и любые другие операции — ресурсом `nubes_service_operation`
#    (доступен во всех опубликованных версиях). Меняйте `run_id`, чтобы
#    запустить операцию повторно:
#
#   resource "nubes_service_operation" "pg_reconcile" {
#     instance_uid = nubes_postgres.db2.id
#     operation    = "reconcile"
#     run_id       = "manual-001"  # ← смена значения = повторный запуск
#   }
#
#    Операция выполняется, только если она есть в `availableOperations`
#    инстанса; иначе — ошибка «операция … недоступна для экземпляра …».
variables.tf
// Postgres уже запущен, значения берутся из UI Личного кабинета.
variable "PGUSER" {
  type    = string
  default = "postgres"
}

variable "PGPASSWORD" {
  type    = string
  default = "" # пароль укажите вручную, в примере не публикуем
}

variable "PGHOST" {
  type    = string
  default = "postgresqlk8s-master.<id>.svc.k8s-3.ext.nubes.ru" # оставьте префикс и суффикс, меняется только <id>
}
terraform.tfvars
# Инструкция по получению токена: раздел "Получение API токена"
api_token = "eyJhbGciOiJ..."