diff --git a/dns_zones/main.tf b/dns_zones/main.tf index e9c79ca..1f30995 100644 --- a/dns_zones/main.tf +++ b/dns_zones/main.tf @@ -1,40 +1,72 @@ # ============================================================================= -# Пример: доменные зоны компании и записи в них — через Terraform. +# Пример: доменные зоны компании и записи DNS — целиком через Terraform. +# +# Задача: держать доменные зоны и их записи в коде (IaC), а не руками в ЛК. # # Ресурсы провайдера Nubes: -# - nubes_zones_v2 (сервис 109) — DNS-зона; -# - nubes_zones_v2_record (подресурс зоны, record) — запись внутри зоны. +# - nubes_zones_v2 (сервис 109) — DNS-зона; +# - nubes_zones_v2_record (подресурс зоны, record) — запись внутри зоны +# (A / CNAME / …). Записи — ОТДЕЛЬНЫЕ ресурсы, поэтому ими можно управлять +# адресно: добавить/изменить/удалить одну запись, не трогая зону целиком. # -# ⚠️ Имя поля в HCL = `name` из схемы (snake_case); `code` (camelCase) — имя в API. +# ⚠️ В HCL пишется `name` поля из схемы (snake_case): `zone_name`, `record_name`, +# `record_type`, `record_input`, `record_ttl`. `code` (camelCase) — внутреннее +# имя в API, в HCL НЕ используется. # -# Зоны задаются картой `var.zones` (ключ → zone_name + records). -# Чтобы добавить/убрать зону или запись — правьте `var.zones` и делайте `apply`. +# КАК РАСШИРЯТЬ: чтобы добавить/убрать зону или запись — правьте `var.zones` +# (в variables.tf или terraform.tfvars) и делайте `terraform apply`. +# Ключи карт (`a`, `b`, `www`, …) — это АДРЕСА объектов в state. Меняйте их +# осознанно: смена ключа = «удалить старый объект + создать новый», а не +# «переименовать». Само значение `zone_name`/`record_input` меняется обычным +# `apply`. # ============================================================================= +# ── locals.records: «уплощаем» вложенную структуру «зоны → записи» ──────────── +# +# var.zones — карта «зона → { zone_name, records }», где records — ещё одна +# карта «имя записи → { type, values }». +# +# Ресурсу записи нужен ОДИН плоский набор, где у каждого элемента УНИКАЛЬНЫЙ +# ключ: тогда `for_each` создаст ровно по одному ресурсу на запись и даст +# стабильные адреса вида `nubes_zones_v2_record.record["a:www"]`. +# +# `merge([ ... ]...)` — разворот (`...`) списка карт в один вызов merge — +# склеивает карты из каждой зоны в общую. Ключ собираем как "<зона>:<запись>", +# чтобы записи с одинаковыми именами в разных зонах не столкнулись. locals { - # Плоская карта записей «зона:имя» (вложенный цикл → merge/flatten), - # чтобы `for_each` у записей давал стабильные адреса. records = merge([ for zk, z in var.zones : { for rk, r in z.records : "${zk}:${rk}" => { - zone = zk - name = rk - type = r.type - values = r.values + zone = zk # какая зона (ключ карты var.zones) + name = rk # имя записи + type = r.type # тип записи (A / CNAME / …) + values = r.values # список значений записи } } ]...) } -# Зоны: N штук из var.zones (for_each по ключам карты — стабильные адреса). +# ── Зоны ───────────────────────────────────────────────────────────────────── +# `for_each = var.zones` — по одному ресурсу на каждый КЛЮЧ карты зон. +# `each.key` — короткое имя зоны (a, b, …), `each.value` — её параметры. +# `resource_name` — человекочитаемое имя ресурса в Nubes; `zone_name` — сам +# домен (FQDN зоны). resource "nubes_zones_v2" "zone" { for_each = var.zones resource_name = "dns-zone-${each.key}" zone_name = each.value.zone_name } -# Записи: record_input = [{record=<значение>}, ...] из списка values. +# ── Записи ─────────────────────────────────────────────────────────────────── +# `for_each = local.records` — по одному ресурсу на запись (плоская карта выше). +# `zones_v2_id` — ссылка на id СВОЕЙ зоны (через for_each зоны): Terraform сам +# построит зависимость «зона создаётся раньше своих записей». +# +# `record_input` — платформа ждёт JSON-массив объектов `[{ record = "<значение>" }, +# …]`, поэтому собираем его из плоского списка `values` через `jsonencode`. +# Одна запись может иметь НЕСКОЛЬКО значений (напр. два A-адреса) — это одна +# запись с массивом, а не две отдельные. resource "nubes_zones_v2_record" "record" { for_each = local.records zones_v2_id = nubes_zones_v2.zone[each.value.zone].id @@ -43,3 +75,4 @@ resource "nubes_zones_v2_record" "record" { record_input = jsonencode([for v in each.value.values : { record = v }]) record_ttl = 3600 } + diff --git a/dns_zones/provider.tf b/dns_zones/provider.tf index 9d91910..5c03a77 100644 --- a/dns_zones/provider.tf +++ b/dns_zones/provider.tf @@ -1,3 +1,8 @@ +# Конфигурация провайдера Nubes. +# api_token — секрет (передаётся из secrets.auto.tfvars); +# api_endpoint — адрес API Gateway стенда. +# ⚠️ Атрибута `realm` у провайдера НЕТ — ресурсная платформа задаётся параметром +# ресурса (напр. `startup_configuration.resource_realm`), а не здесь. provider "nubes" { api_token = var.api_token api_endpoint = var.api_endpoint diff --git a/dns_zones/variables.tf b/dns_zones/variables.tf index 1e05895..0b97b02 100644 --- a/dns_zones/variables.tf +++ b/dns_zones/variables.tf @@ -1,19 +1,36 @@ -# Переменные примера. api_token — СЕКРЕТ, держать в secrets.auto.tfvars (не коммитить). +# ============================================================================= +# Переменные примера «DNS-зоны и записи». +# +# ⚠️ api_token — СЕКРЕТ. НЕ кладите его в terraform.tfvars. Держите в отдельном +# файле `secrets.auto.tfvars` (Terraform сам подхватывает `*.auto.tfvars`), +# а этот файл закройте в .gitignore. Значение — JWT из ЛК (Профиль → Токены). +# ============================================================================= +# Токен доступа (JWT) к API Nubes. `sensitive = true` — Terraform не покажет +# значение в выводе плана/apply. variable "api_token" { description = "Nubes API token (JWT). Секрет — в secrets.auto.tfvars." type = string sensitive = true } +# Адрес API Gateway стенда. Для TEST — как в default. variable "api_endpoint" { description = "API Gateway URL стенда" type = string default = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" } -# Зоны: карта «короткое имя → { zone_name, records }». -# records — карта «имя записи → { type, values }». +# ── Зоны и записи ──────────────────────────────────────────────────────────── +# Карта «короткое имя зоны → { zone_name, records }»: +# - КЛЮЧ карты (a, b, …) — адрес зоны в state (стабильный id объекта в TF); +# - zone_name — сам домен (FQDN зоны); +# - records — карта «имя записи → { type, values }»: +# - type — тип записи (A / CNAME / TXT / …), по умолчанию "A"; +# - values — СПИСОК значений (одна запись может иметь их несколько, +# напр. два A-адреса у одного имени). +# +# Здесь задан дефолт (2 зоны × 3 записи). Можно переопределить в terraform.tfvars. variable "zones" { description = "Зоны компании: имя + записи (type / values[])" type = map(object({ diff --git a/dns_zones/versions.tf b/dns_zones/versions.tf index 65635b6..3d879c5 100644 --- a/dns_zones/versions.tf +++ b/dns_zones/versions.tf @@ -1,9 +1,13 @@ +# Версии Terraform и провайдера Nubes. terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.5.0" # минимальная версия Terraform required_providers { nubes = { - source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + # Реестр провайдера стенда TEST. Для другого стенда — свой namespace: + # PROD → .../nubes/nubes, DEV → .../nubes-dev/nubes + source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + # Фиксируем версию провайдера — для воспроизводимости. version = "3.4.0" } } diff --git a/dns_zones_generated/main.tf b/dns_zones_generated/main.tf index 49fa0a5..0b6e4fe 100644 --- a/dns_zones_generated/main.tf +++ b/dns_zones_generated/main.tf @@ -1,20 +1,35 @@ # ============================================================================= -# Пример: N DNS-зон генерируются ПРОГРАММНО (в цикле) — через Terraform. +# Пример: N DNS-зон генерируются ПРОГРАММНО (в цикле) — целиком через Terraform. # -# Отличие от ../dns_zones/: там зоны заданы явной картой `var.zones`, -# а здесь зоны генерируются из числа `var.zone_count` через `range(...)`. -# Удобно, когда зон много и они однотипны (tfzone-1 … tfzone-N). +# Отличие от ../dns_zones/: там зоны заданы ЯВНОЙ картой `var.zones`, а здесь — +# генерируются из числа `var.zone_count` через `range(1, N+1)`. Удобно, когда +# зон много и они ОДНОТИПНЫ (tfzone-1 … tfzone-N), и задавать каждую руками +# не хочется. # -# Ресурсы: nubes_zones_v2 (109) + подресурс nubes_zones_v2_record (записи). -# ⚠️ Имя поля в HCL = `name` из схемы (snake_case). +# Ресурсы провайдера Nubes: +# - nubes_zones_v2 (сервис 109) — DNS-зона; +# - nubes_zones_v2_record (подресурс зоны, record) — запись внутри зоны. +# +# ⚠️ В HCL пишется `name` поля из схемы (snake_case): `zone_name`, `record_name`, +# `record_type`, `record_input`, `record_ttl`. `code` (camelCase) — имя в API. +# +# КАК РАСШИРЯТЬ: измените `var.zone_count` (и `zone_prefix`/`zone_domain`) и +# сделайте `apply` — зоны добавятся/уберутся. Состав записей в шаблоне зоны +# правится в `locals.zones` ниже. # ============================================================================= +# ── locals.zones: ГЕНЕРИРУЕМ N однотипных зон ──────────────────────────────── +# `range(1, var.zone_count + 1)` даёт последовательность 1..N. Для каждого числа +# строим элемент карты: ключ "-", значение — { zone_name, records }. +# Ключ карты = адрес зоны в state (стабильное имя объекта), поэтому нумерация +# зон детерминирована: tfzone-1 … tfzone-N. locals { - # N зон генерируются программно: tfzone-1 … tfzone-N. zones = { for i in range(1, var.zone_count + 1) : "${var.zone_prefix}-${i}" => { zone_name = "${var.zone_prefix}-${i}.${var.zone_domain}" + # Однотипный набор записей для каждой зоны (пример). Здесь — www и api + # с адресами, зависящими от номера зоны (10.0..1 / .2). records = { www = { type = "A", values = ["10.0.${i}.1"] } api = { type = "A", values = ["10.0.${i}.2"] } @@ -22,28 +37,35 @@ locals { } } - # Плоская карта записей «зона:имя» (вложенный цикл → merge/flatten). + # ── locals.records: «уплощаем» карту зон в плоский набор записей ─────────── + # Записи — отдельные ресурсы, каждой нужен уникальный ключ в for_each. Собираем + # карту «<зона>:<запись>» (разворот `...` в merge склеивает карты всех зон). records = merge([ for zk, z in local.zones : { for rk, r in z.records : "${zk}:${rk}" => { - zone = zk - name = rk - type = r.type - values = r.values + zone = zk # какая зона + name = rk # имя записи + type = r.type # тип (A / CNAME / …) + values = r.values # список значений } } ]...) } -# Зоны: N штук, сгенерированных в locals (for_each по ключам — стабильные адреса). +# ── Зоны ───────────────────────────────────────────────────────────────────── +# `for_each = local.zones` — по одному ресурсу на сгенерированную зону. +# `each.key` = "-", `each.value.zone_name` = FQDN. resource "nubes_zones_v2" "zone" { for_each = local.zones resource_name = "dns-zone-${each.key}" zone_name = each.value.zone_name } -# Записи: record_input = [{record=<значение>}, ...] из списка values. +# ── Записи ─────────────────────────────────────────────────────────────────── +# `zones_v2_id` ссылается на id СВОЕЙ зоны → Terraform строит зависимость. +# `record_input` — JSON-массив `[{ record = "<значение>" }, …]` (jsonencode), +# одна запись может иметь несколько значений. resource "nubes_zones_v2_record" "record" { for_each = local.records zones_v2_id = nubes_zones_v2.zone[each.value.zone].id @@ -52,3 +74,4 @@ resource "nubes_zones_v2_record" "record" { record_input = jsonencode([for v in each.value.values : { record = v }]) record_ttl = 3600 } + diff --git a/dns_zones_generated/provider.tf b/dns_zones_generated/provider.tf index 9d91910..5c03a77 100644 --- a/dns_zones_generated/provider.tf +++ b/dns_zones_generated/provider.tf @@ -1,3 +1,8 @@ +# Конфигурация провайдера Nubes. +# api_token — секрет (передаётся из secrets.auto.tfvars); +# api_endpoint — адрес API Gateway стенда. +# ⚠️ Атрибута `realm` у провайдера НЕТ — ресурсная платформа задаётся параметром +# ресурса (напр. `startup_configuration.resource_realm`), а не здесь. provider "nubes" { api_token = var.api_token api_endpoint = var.api_endpoint diff --git a/dns_zones_generated/variables.tf b/dns_zones_generated/variables.tf index 7fabd90..969a2aa 100644 --- a/dns_zones_generated/variables.tf +++ b/dns_zones_generated/variables.tf @@ -1,29 +1,40 @@ -# Переменные примера. api_token — СЕКРЕТ, держать в secrets.auto.tfvars. +# ============================================================================= +# Переменные примера «N DNS-зон, сгенерированных программно». +# +# ⚠️ api_token — СЕКРЕТ: не в terraform.tfvars, а в secrets.auto.tfvars +# (Terraform подхватывает `*.auto.tfvars` сам), файл — в .gitignore. +# ============================================================================= +# Токен доступа (JWT) к API Nubes. `sensitive` — не показывать значение в выводе. variable "api_token" { description = "Nubes API token (JWT). Секрет — в secrets.auto.tfvars." type = string sensitive = true } +# Адрес API Gateway стенда (для TEST — как в default). variable "api_endpoint" { description = "API Gateway URL стенда" type = string default = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" } +# ── Параметры генерации зон ────────────────────────────────────────────────── +# Сколько однотипных зон создать: tfzone-1 … tfzone-. variable "zone_count" { description = "Сколько зон сгенерировать (tfzone-1 … tfzone-N)" type = number default = 10 } +# Префикс имени/домена зоны (первая часть FQDN). variable "zone_prefix" { description = "Префикс имени зоны" type = string default = "tfzone" } +# Домен (суффикс FQDN). Итоговое имя зоны = "-.". variable "zone_domain" { description = "Домен зон (суффикс FQDN)" type = string diff --git a/dns_zones_generated/versions.tf b/dns_zones_generated/versions.tf index 65635b6..3d879c5 100644 --- a/dns_zones_generated/versions.tf +++ b/dns_zones_generated/versions.tf @@ -1,9 +1,13 @@ +# Версии Terraform и провайдера Nubes. terraform { - required_version = ">= 1.5.0" + required_version = ">= 1.5.0" # минимальная версия Terraform required_providers { nubes = { - source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + # Реестр провайдера стенда TEST. Для другого стенда — свой namespace: + # PROD → .../nubes/nubes, DEV → .../nubes-dev/nubes + source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + # Фиксируем версию провайдера — для воспроизводимости. version = "3.4.0" } }