mirror of
https://gitlab.sarex.io/infra/terraform-contour-mirror.git
synced 2026-08-06 18:51:36 +03:00
- Add modules: yc-s3, yc-postgresql, k8s-namespace, k8s-secrets - Add live configuration for stage environment - Add GitLab CI with downstream pipelines - Implement best practices from theory.md
926 lines
63 KiB
Markdown
926 lines
63 KiB
Markdown
Архитектурные паттерны IaC на основе источников
|
||
|
||
1.1. Декларативный подход и идемпотентность
|
||
|
||
Terraform использует декларативный язык HCL, где инженер описывает целевое конечное состояние («что должно существовать»), в то время как инструмент самостоятельно вычисляет граф зависимостей и шаги для его достижения [22], [4]. Это обеспечивает идемпотентность: многократный запуск одного и того же кода приводит к идентичному результату, исключая риск дублирования ресурсов [21]. В отличие от процедурных инструментов (Ansible, Bash), где повторный запуск может потребовать дополнительных проверок, Terraform сравнивает текущее состояние (state) со свежим кодом и вносит только необходимые изменения [21], [16]. Начиная с версии 0.12 (HCL 2.0), синтаксис был упрощен: переменные больше не требуют обязательной интерполяции ${} везде, где они используются, что сделало код более читаемым для человека [1], [4]. Декларативный подход позволяет уйти от ручного управления («ClickOps») и превратить инфраструктуру в «живую» документацию [1], [5], [29].
|
||
|
||
1.2. Минимизация «радиуса поражения» (Blast Radius)
|
||
|
||
Монолитная инфраструктура, где все ресурсы описаны в одном файле или управляются единым файлом состояния, признана критическим антипаттерном [2]. Научное исследование SMELLS-SUS показывает, что «монолитный запах» (Monolithic Infrastructure smell) является наиболее распространенной проблемой и встречается в 9,67% проанализированных сценариев [2]. Ошибка в коде одного компонента (например, опечатка в Security Group) в монолитном стейте может привести к каскадному удалению или повреждению критически важных ресурсов, таких как базы данных всего ландшафта [15]. Эксперты рекомендуют разделять инфраструктуру на мелкие независимые стейты — «переборки» [16]. Опыт компании Selectel подтверждает, что распилка монолита на гранулярные компоненты позволяет ускорить деплой инфраструктуры в 20 раз за счет уменьшения времени опроса API облачного провайдера [5].
|
||
|
||
1.3 Слоистая архитектура (Layered Architecture)
|
||
|
||
Рекомендуется разделение ресурсов на независимые логические уровни (слои), каждый из которых имеет свой жизненный цикл и границы ответственности [17], [4609. «Владимир Дроздецкий — Эффективное управление инфрой»], [29]. Типичная иерархия включает следующие слои: Global (IAM, DNS), Network (VPC, подсети), Platform (K8s, базы данных) и Application (микросервисы) [17], [20]. Слой сети является фундаментальным, так как без него невозможна работа большинства других компонентов [29]. Связь между слоями осуществляется через передачу выходных данных (outputs), что в Terragrunt реализуется через блоки dependency, обеспечивающие автоматическую оркестрацию и соблюдение порядка развертывания [16], [18], [29]. Использование команды run-all позволяет развернуть всю иерархию модулей в правильной последовательности одним действием [1], [18], [27].
|
||
|
||
2. Организация и структура репо
|
||
|
||
1. Стратегия разделения: Modules и Live
|
||
Общепринятым корпоративным стандартом является разделение реализации инфраструктуры от ее непосредственного развертывания по разным репозиториям [20], [30]:
|
||
|
||
Repository «Modules»: Библиотека универсальных, версионируемых «чертежей» (blueprints). Они не содержат специфических данных окружения (IP-адресов, имен) и предназначены для многократного использования [20], [7].
|
||
|
||
Repository «Live»: Описание реальных «зданий», построенных по чертежам из модулей. Здесь фиксируются конкретные параметры для каждого ландшафта (dev, stage, prod) [7], [20].
|
||
|
||
2. Иерархическая структура каталогов (Live-репозиторий)
|
||
Для обеспечения изоляции и прозрачности рекомендуется древовидная структура, отражающая логику облака [20], [30]:
|
||
Account (Учетная запись) → Region (Регион) → Environment (Окружение) → Category (Категория) → Component (Компонент)
|
||
|
||
Account: На верхнем уровне разделяются облачные аккаунты (например, prod-account, stage-account), что обеспечивает максимальную изоляцию в рамках безопасности [7], [20].
|
||
|
||
Region: Внутри аккаунта создаются папки регионов (например, us-east-1, eu-central-1) [20]. Папка _global на этом уровне используется для ресурсов, доступных во всех регионах аккаунта (IAM, DNS) [7].
|
||
|
||
Environment: Разделение на dev, qa, stage и prod [20], [6]. Каждое окружение обычно соответствует отдельному VPC [7].
|
||
|
||
Category: Группировка ресурсов по назначению: networking (VPC, подсети), services (приложения, K8s), data-stores (БД, кэши) [20], [29].
|
||
|
||
Component: Конечная директория с файлом terragrunt.hcl или main.tf, управляющая конкретным ресурсом (например, mysql или vpc) [20].
|
||
|
||
3. Организация файлов внутри компонента
|
||
Для поддержания чистоты кода (принцип DRY) и удобства навигации рекомендуется разделение на функциональные файлы [6], [23]:
|
||
|
||
main.tf: Вызов модулей и описание основных ресурсов [6].
|
||
|
||
variables.tf: Объявление входных переменных с обязательным описанием (description) и типами [6].
|
||
|
||
outputs.tf: Описание выходных данных для передачи их между слоями или модулями [6].
|
||
|
||
providers.tf: Конфигурация провайдеров (AWS, Azure, Yandex) и их версий [23].
|
||
|
||
backend.tf: Настройка удаленного хранения стейта (S3, GCS) [6].
|
||
|
||
versions.tf: Фиксация версий Terraform и провайдеров для стабильности сборок [6].
|
||
|
||
4. Иерархическое управление переменными в Terragrunt
|
||
Terragrunt позволяет избежать дублирования за счет автоматического наследования конфигураций по иерархии папок [27], [30]:
|
||
|
||
root.hcl: Находится в корне и содержит общую конфигурацию remote_state и провайдеров для всех ландшафтов [27].
|
||
|
||
env.hcl / environment.yaml: Хранит переменные, общие для всего окружения (например, env = "prod") [18], [30].
|
||
|
||
region.hcl / region.yaml: Переменные конкретного региона (например, maintenance_window) [27], [30].
|
||
Для автоматического поиска этих файлов используется функция find_in_parent_folders() [30].
|
||
|
||
5. Изоляция состояний (State Isolation)
|
||
Иерархическая структура напрямую влияет на управление состоянием. Gruntwork и эксперты рекомендуют управлять стейтом на уровне каждого отдельного юнита (директории) [20], [30]. Использование Workspaces для Production-сред не рекомендуется, так как они используют один бэкенд и скрывают структуру в CLI [27]. Разделение по папкам гарантирует, что каждый компонент имеет свой изолированный файл состояния, что критически снижает «радиус поражения» (blast radius) в случае ошибки [16], [24].
|
||
|
||
6. Нововведения в структуре (Stacks)
|
||
В 2025 году появилась концепция Terragrunt Stacks, позволяющая упаковывать коллекции связанных юнитов в переиспользуемые стеки [27], [19]. Это переводит переиспользование с уровня отдельных модулей на уровень целых инфраструктурных паттернов (например, «App + DB + Monitoring»), полностью устраняя копипаст конфигураций между ландшафтами [27].
|
||
|
||
|
||
3. Модульность и стратегии переиспользования
|
||
|
||
Согласно исследованиям и best-practices, модульность является краеугольным камнем масштабируемой и поддерживаемой инфраструктуры. Она позволяет инкапсулировать ее сложность, обеспечивать переиспольщование и минимизировать дублирование кода, что напрямую снижает риск возникновения «монолитного запаха» (Monolithic Infrastructure smell) в проектах [2, 6].
|
||
|
||
3.1. Понятие и структура модуля
|
||
|
||
Как описывается в руководствах, в Terraform модулем может считаеться любая директория, содержащая конфигурационные файлы .tf [6]. Модули работают по принципу функций в программировании: они инкапсулируют логику создания определенного набора ресурсов (например, виртуальной машины или кластера БД), принимают входные параметры через переменные и возвращают результаты через выходные значения [21]. Это позволяет абстрагироваться от деталей реализации.
|
||
Как показано в примерах, для создания переиспользуемого компонента его необходимо выделить в отдельную директорию. Например:
|
||
|
||
modules/object-storage/bucket/main.tf:
|
||
|
||
resource "yandex_storage_bucket" "this" {
|
||
bucket = var.bucket_name
|
||
acl = var.acl
|
||
|
||
|
||
dynamic "server_side_encryption_configuration" {
|
||
for_each = var.kms_key_id != null ? [1] : []
|
||
content {
|
||
rule {
|
||
apply_server_side_encryption_by_default {
|
||
kms_master_key_id = var.kms_key_id
|
||
sse_algorithm = "aws:kms"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
lifecycle_rule {
|
||
enabled = true
|
||
expiration {
|
||
days = var.object_expiration_days
|
||
}
|
||
}
|
||
}
|
||
|
||
Дабы вызвать данный модуль и корневого каталога, можно сделать следующее:
|
||
|
||
module "static_assets_bucket" {
|
||
source = "./modules/object-storage/bucket"
|
||
bucket_name = "myapp-static-assets-prod"
|
||
acl = "private"
|
||
kms_key_id = yandex_kms_symmetric_key.bucket_key.id
|
||
}
|
||
|
||
Источники подчеркивают, что такой подход превращает инфраструктуру в набор переиспользуемых «кирпичиков лего» [6, 21].
|
||
|
||
3.2. Использование публичных и готовых модулей
|
||
|
||
Для ускорения разработки и следования лучшим практикам настоятельно рекомендуется использовать готовые модули. Хотя публичный реестр Terraform для Yandex Cloud менее обширен, чем для AWS, существуют как официальные модули от Yandex, так и проверенные сообществом, которые можно найти на GitHub. Использование таких модулей, как отмечается в источниках, позволяет избежать написания сотен строк стандартного кода для настройки, например, виртуальных частных облаков (VPC) или групп безопасности [6, 23]. Тем самым нам не нужно в большинстве случаев изобретать вилосипед.
|
||
|
||
Адаптированный пример того, как можно использовать структурированный модуль для быстрого развертывания сети:
|
||
|
||
module "vpc" {
|
||
source = "terraform-yc-modules/vpc/yc"
|
||
version = "~> 1.0"
|
||
|
||
name = "production-network"
|
||
description = "VPC for production environment"
|
||
|
||
# Определение подсетей в разных зонах доступности
|
||
subnets = {
|
||
"ru-central1-a" = "10.0.1.0/24",
|
||
"ru-central1-b" = "10.0.2.0/24",
|
||
"ru-central1-c" = "10.0.3.0/24"
|
||
}
|
||
|
||
# Включение NAT для выхода в интернет из приватных подсетей
|
||
enable_nat = true
|
||
}
|
||
|
||
Использование версионированных модулей из проверенных источников является всеобщей стандартной практикой [6, 23].
|
||
|
||
3.3. Стратегии проектирования
|
||
|
||
Сложные инфраструктурные решения строятся по принципу композиции («Lego bricks»). Как описывается в литературе, выходные данные одного модуля (например, идентификаторы подсетей) должны передаваться на вход другому (например, модулю создания виртуальной машины), формируя явные и управляемые зависимости [6, 30]. Это обеспечивает слабую, но хоть какую-то связанность компонентов.
|
||
|
||
# 1. Модуль создания кластера Managed PostgreSQL
|
||
module "prod_postgresql" {
|
||
source = "../../../modules/data-stores/postgresql"
|
||
|
||
cluster_name = "app-db-prod"
|
||
network_id = data.yandex_vpc_network.default.id
|
||
subnet_ids = [yandex_vpc_subnet.private_a.id]
|
||
host_class = "s2.micro"
|
||
}
|
||
|
||
# 2. Модуль развертывания приложения, использующий вывод модуля БД
|
||
module "backend_app" {
|
||
source = "../../../modules/services/backend-app"
|
||
|
||
name = "backend-api"
|
||
subnet_id = yandex_vpc_subnet.public_a.id
|
||
image_id = "blahblahblah"
|
||
db_host = module.prod_postgresql.cluster_hosts_fqdn[0]
|
||
db_name = module.prod_postgresql.database_name
|
||
}
|
||
|
||
Такой подход исключает хардкод значений и делает инфраструктурный код декларативным и прозрачным [30].
|
||
|
||
3.4. Динамическое создание ресурсов (итерирование)
|
||
|
||
Для массового создания однотипных ресурсов без копирования кода используются count и for_each. В руководствах отмечается, что for_each предпочтительнее для работы с коллекциями (map, set), так как он создает ресурсы с уникальными идентификаторами в стейте, что делает их безопасными для последующих изменений [6, 10].
|
||
|
||
# variables.tf
|
||
variable "vm_instances" {
|
||
description = "Конфигурация создаваемых виртуальных машин"
|
||
type = map(object({
|
||
cores = number
|
||
memory = number
|
||
boot_disk_gb = number
|
||
image_family = string
|
||
user_data = string # Cloud-init
|
||
}))
|
||
}
|
||
|
||
# main.tf
|
||
resource "yandex_compute_instance" "vm" {
|
||
for_each = var.vm_instances
|
||
|
||
name = each.key
|
||
platform_id = "standard-v3"
|
||
zone = "ru-central1-a"
|
||
|
||
resources {
|
||
cores = each.value.cores
|
||
memory = each.value.memory
|
||
}
|
||
|
||
boot_disk {
|
||
initialize_params {
|
||
size = each.value.boot_disk_gb
|
||
image_id = data.yandex_compute_image.this[each.key].id
|
||
}
|
||
}
|
||
|
||
network_interface {
|
||
subnet_id = yandex_vpc_subnet.app.id
|
||
nat = true # Для ВМ, которым нужен внешний IP
|
||
}
|
||
|
||
metadata = {
|
||
user-data = each.value.user_data
|
||
}
|
||
}
|
||
|
||
# Использование data source для поиска актуального образа по семейству
|
||
data "yandex_compute_image" "this" {
|
||
for_each = var.vm_instances
|
||
family = each.value.image_family
|
||
}
|
||
|
||
Это позволяет позволяет гибко управлять множеством ресурсов через изменение одной переменной [10].
|
||
Как это работает?
|
||
|
||
Допустим, в файле terraform.tfvars или в Terragrunt inputs мы передадим
|
||
|
||
vm_instances = {
|
||
"web-server-01" = {
|
||
cores = 2
|
||
memory = 4
|
||
boot_disk_gb = 30
|
||
image_family = "ubuntu-2204-lts"
|
||
user_data = "#cloud-config\npackage_update: true"
|
||
},
|
||
"database-01" = {
|
||
cores = 4
|
||
memory = 8
|
||
boot_disk_gb = 100
|
||
image_family = "centos-7"
|
||
user_data = "#cloud-config\ndisable_root: false"
|
||
},
|
||
"monitoring-01" = {
|
||
cores = 2
|
||
memory = 2
|
||
boot_disk_gb = 20
|
||
image_family = "debian-11"
|
||
user_data = "#cloud-config\npackages: ['prometheus']"
|
||
}
|
||
}
|
||
|
||
Terraform возьмет каждый элемент этой мапы и создаст отдельный ресурс yandex_compute_instance:
|
||
|
||
Итерация 1:
|
||
|
||
Создается ВМ с именем web-server-01, 2 ядрами, 4 ГБ памяти
|
||
|
||
Итерация 2:
|
||
|
||
Создается ВМ с именем database-01, 4 ядрами, 8 ГБ памяти
|
||
|
||
Итерация 3:
|
||
|
||
Создается ВМ с именем monitoring-01 и т.д
|
||
|
||
3.5. Версионирование как гарантия стабильности
|
||
|
||
Для надежности production-сред в особненности, источники категорически настаивают на фиксировании версии как внешних провайдеров, так и собственных модулей. Это защищает от непреднамеренных изменений, которые могут быть внесены в апстрим репозитории [6, 21]. Семантическое версионирование (SemVer) позволяет контролировать процесс обновлений.
|
||
|
||
module "network" {
|
||
source = "git::https://gitlab.sarex.io/terraform/yc-network-module.git?ref=v2.5.1"
|
||
|
||
vpc_name = "production"
|
||
cidr = "10.100.0.0/16"
|
||
}
|
||
|
||
3.6. Оркестрация и соблюдение принципа DRY через Terragrunt
|
||
|
||
Для управления сложными, многоуровневыми деплойментами в облаках и соблюдения принципа DRY рекомендуется использовать инструменты оркестрации, такие как Terragrunt [27, 30]. Он позволяет устранить дублирование в конфигурациях бэкенда и провайдеров, а также явно управлять зависимостями между компонентами.
|
||
|
||
# Автоматическое включение конфигураций из родительских папок
|
||
include "root" {
|
||
path = find_in_parent_folders("root.hcl")
|
||
}
|
||
|
||
# Объявление зависимости от модуля сети
|
||
dependency "vpc" {
|
||
config_path = "../network"
|
||
|
||
# Mock-выходы для команд `plan` без доступа к реальному state
|
||
mock_outputs = {
|
||
subnet_ids = ["mock-subnet-id-a", "mock-subnet-id-b"]
|
||
network_id = "mock-network-id"
|
||
}
|
||
}
|
||
|
||
# Локальные переменные для удобства
|
||
locals {
|
||
common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl")).locals
|
||
}
|
||
|
||
# Входные переменные для Terraform-модуля
|
||
inputs = {
|
||
environment = local.common_vars.environment
|
||
region = local.common_vars.region
|
||
subnet_id = dependency.vpc.outputs.subnet_ids[0]
|
||
# ... другие переменные
|
||
}
|
||
|
||
Файл root.hcl:
|
||
|
||
hcl
|
||
|
||
# Настройка удаленного бэкенда в Yandex Object Storage с блокировкой через YDB
|
||
remote_state {
|
||
backend = "s3"
|
||
config = {
|
||
endpoint = "storage.yandexcloud.net"
|
||
bucket = "company-terraform-state-prod"
|
||
key = "${path_relative_to_include()}/terraform.tfstate"
|
||
region = "ru-central1"
|
||
skip_region_validation = true
|
||
skip_credentials_validation = true
|
||
skip_requesting_account_id = true
|
||
skip_metadata_api_check = true
|
||
force_path_style = true
|
||
|
||
# Настройка блокировок состояний через Yandex Database (YDB)
|
||
dynamodb_endpoint = "https://docapi.serverless.yandexcloud.net/ru-central1/b1gxxxxxxxx/etn03j3j45678"
|
||
dynamodb_table = "terraform_locks"
|
||
}
|
||
}
|
||
|
||
# Генерация общего провайдера для Yandex Cloud
|
||
generate "provider" {
|
||
path = "provider.tf"
|
||
if_exists = "overwrite_terragrunt"
|
||
contents = <<EOF
|
||
provider "yandex" {
|
||
folder_id = "${local.folder_id}"
|
||
zone = "ru-central1-a"
|
||
}
|
||
EOF
|
||
}
|
||
|
||
В источниках отмечается, что такой подход, реализуемый Terragrunt, позволяет централизованно управлять настройками для сотен компонентов, обеспечивая согласованность и безопасность [27, 30].
|
||
|
||
|
||
4. Управление состоянием (State Management)
|
||
|
||
Файл состояния (state) является критически важным компонентом Terraform, который выполняет роль «памяти» для IaC. Этот JSON-файл содержит точное сопоставление декларативных определений в файлах .tf и реальными физическими ресурсами в облачной среде [6, 16, 24]. Без корректного файла состояния Terraform не может определить, какие ресурсы уже развернуты, какие требуют обновления, а какие должны быть удалены, что ведет к катастрофическим последствиям для инфраструктуры [6, 16, 21].
|
||
|
||
4.1. Состав и структура файла состояния
|
||
|
||
В источниках подробно описывается, что файл состояния (обычно terraform.tfstate) содержит не только перечень ресурсов, но и метаданные, необходимые для отслеживания зависимостей между ними и поддержания целостности инфраструктурного графа [6, 16, 21].
|
||
|
||
{
|
||
"version": 4,
|
||
"terraform_version": "1.6.0",
|
||
"serial": 5,
|
||
"lineage": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||
"outputs": {
|
||
"vpc_id": {
|
||
"value": "enp0s9d2v1abc",
|
||
"type": "string"
|
||
}
|
||
},
|
||
"resources": [
|
||
{
|
||
"mode": "managed",
|
||
"type": "yandex_compute_instance",
|
||
"name": "app_server",
|
||
"provider": "provider[\"registry.terraform.io/yandex-cloud/yandex\"]",
|
||
"instances": [
|
||
{
|
||
"attributes": {
|
||
"id": "fhm0b28lgm4qabcde",
|
||
"name": "app-prod-01",
|
||
"platform_id": "standard-v3",
|
||
"resources": {
|
||
"cores": 2,
|
||
"memory": 4
|
||
},
|
||
"boot_disk": [
|
||
{
|
||
"disk_id": "fhm0b28lgm4qabcdefg"
|
||
}
|
||
]
|
||
},
|
||
"private": "bnR5cGU6IHN0cmluZwp2YWx1ZTogaGFzaGljb3JwL2xpbnV4LWFtaQ=="
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
|
||
Как разъясняется в источниках, ключевыми элементами структуры являются:
|
||
|
||
version: версия формата данных состояния, которая меняется при обновлении Terraform [21].
|
||
|
||
serial: возрастающий номер, увеличивающийся при каждом успешном применении изменений (terraform apply), что позволяет отслеживать историю изменений [6, 21].
|
||
|
||
lineage: уникальный идентификатор UUID, генерируемый при инициализации бэкенда и предотвращающий случайное смешивание разных состояний проекта [6, 16].
|
||
|
||
resources: детализированный список всех управляемых ресурсов с их полными атрибутами, включая чувствительные данные, которые могут храниться в открытом виде [6, 16, 24].
|
||
|
||
4.2. Remote Backends
|
||
|
||
Хранение файла состояния локально на ПК допустимо только в тестовых целях. В командной работе это неизбежно ведет к потере данных, конфликтам версий [3, 6, 16]. Рекомендуемой практикой является использование удаленных бэкендов (remote backends), которые обеспечивают безопасное хранение, шифрование данных, версионирование и совместный доступ к состоянию [6, 16, 23].
|
||
Как показано в официальной документации Yandex Cloud и практических примерах, для надежного хранения состояния необходимо использовать Yandex Object Storage в сочетании с Yandex Database (YDB) для локов [10, 28].
|
||
|
||
terraform {
|
||
backend "s3" {
|
||
endpoint = "storage.yandexcloud.net"
|
||
bucket = "company-name-terraform-state-prod"
|
||
key = "global/network/terraform.tfstate"
|
||
region = "ru-central1"
|
||
|
||
# Настройки для работы с Yandex Cloud S3-совместимым хранилищем
|
||
skip_region_validation = true
|
||
skip_credentials_validation = true
|
||
skip_requesting_account_id = true
|
||
skip_metadata_api_check = true
|
||
force_path_style = true
|
||
|
||
# Блокировка состояния через Yandex Database (YDB)
|
||
dynamodb_endpoint = "https://serverless.yandexcloud.net/ru-central1/b1g8..."
|
||
dynamodb_table = "terraform-state-locks"
|
||
encrypt = true
|
||
}
|
||
}
|
||
|
||
В источниках отмечается, что использование YDB для блокировок является обязательным для production-сред, так как предотвращает одновременное изменение состояния несколькими пользователями [10, 28].
|
||
|
||
4.3. Блокировка состояния (State Locking)
|
||
|
||
Механизм блокировки состояния предотвращает состояние race condition, когда несколько инженеров или CI/CD-пайплайнов одновременно пытаются изменить одну и ту же инфраструктуру [3, 16, 28]. Если процесс выполнения terraform apply прервется аварийно (например, из-за сетевого сбоя), блокировка может остаться активной. Для ее снятия вручную, согласно документации, используется команда:
|
||
|
||
terraform force-unlock <LOCK_ID>
|
||
|
||
В руководства=х предостерегают от частого использования этой команды и рекомендуют сначала убедиться, что ни один другой процесс не использует файл стейта [16, 21].
|
||
|
||
4.4. Изоляция окружений (Isolation)
|
||
|
||
Для обеспечения надежного разделения сред разработки (dev), тестирования (stage) и production (prod) в источниках рассматриваются два основных подхода:
|
||
|
||
Workspaces: Позволяют использовать одну и ту же конфигурацию кода для нескольких окружений, но хранят состояния всех окружений в одном бэкенде. Этот подход может подходить для небольших проектов, но не рекомендуется для production из-за риска случайного воздействия на несколько сред и отсутствия полной изоляции [6, 23].
|
||
|
||
Структура каталогов (Рекомендуемый подход): Обеспечивает полную изоляцию за счет использования разных директорий и, соответственно, разных файлов состояния для каждого окружения. В сочетании с Terragrunt этот подход автоматизируется с помощью функции path_relative_to_include() для динамического формирования ключей (key) в Object Storage [7, 20, 27].
|
||
|
||
Пример структуры каталогов с Terragrunt:
|
||
|
||
live/
|
||
├── prod/
|
||
│ ├── ru-central1/
|
||
│ │ ├── vpc/
|
||
│ │ │ └── terragrunt.hcl # key = "prod/ru-central1/vpc/terraform.tfstate"
|
||
│ │ └── k8s/
|
||
│ │ └── terragrunt.hcl # key = "prod/ru-central1/k8s/terraform.tfstate"
|
||
│ └── _global/
|
||
│ └── iam/
|
||
│ └── terragrunt.hcl # key = "prod/_global/iam/terraform.tfstate"
|
||
└── dev/
|
||
└── ru-central1/
|
||
└── vpc/
|
||
└── terragrunt.hcl # key = "dev/ru-central1/vpc/terraform.tfstate"
|
||
|
||
Как подчеркивается в источниках Gruntwork, такая структура минимизирует «радиус поражения» (blast radius) и обеспечивает максимальную безопасность [7, 20].
|
||
|
||
4.5. Работа с данными из других стейтов
|
||
|
||
Для создания зависимостей между изолированными компонентами инфраструктуры (например, когда модулю приложений нужен ID VPC из модуля сети) в источниках описывается два подхода:
|
||
|
||
Нативный подход Terraform: Использование data source terraform_remote_state для чтения выходных значений из состояния другого модуля.
|
||
|
||
data "terraform_remote_state" "network" {
|
||
backend = "s3"
|
||
config = {
|
||
endpoint = "storage.yandexcloud.net"
|
||
bucket = "company-name-terraform-state-prod"
|
||
key = "prod/ru-central1/vpc/terraform.tfstate"
|
||
region = "ru-central1"
|
||
skip_region_validation = true
|
||
skip_credentials_validation = true
|
||
}
|
||
}
|
||
|
||
resource "yandex_compute_instance" "app" {
|
||
name = "app-server"
|
||
platform_id = "standard-v3"
|
||
zone = "ru-central1-a"
|
||
|
||
network_interface {
|
||
subnet_id = data.terraform_remote_state.network.outputs.subnet_ids[0]
|
||
}
|
||
}
|
||
|
||
Однако этот подход требует жесткого указания путей к бэкенду и может создать скрытые зависимости [6, 16].
|
||
|
||
Подход с использованием Terragrunt: Использование блока dependency, который автоматически управляет зависимостями и порядком развертывания.
|
||
|
||
# terragrunt.hcl для модуля приложения
|
||
dependency "vpc" {
|
||
config_path = "../vpc"
|
||
}
|
||
|
||
inputs = {
|
||
subnet_id = dependency.vpc.outputs.subnet_ids[0]
|
||
}
|
||
|
||
4.6. Манипуляции со стейтом через CLI и код
|
||
|
||
Никогда не следует редактировать файл состояния вручную с помощью текстового редактора, так как это почти гарантированно приведет к повреждению данных и неработоспособности инфры [3, 6, 16]. Для безопасного управления состоянием предназначены CLI-команды Terraform:
|
||
|
||
Импорт существующих ресурсов: Привязка ресурсов, созданных вручную или другим способом, под управление Terraform.
|
||
|
||
terraform import yandex_compute_instance.app fhm0b28lgm4qabcde
|
||
|
||
Как описывается в источниках, эта команда добавляет ресурс в состояние, но не создает код, который нужно написать отдельно [6, 21].
|
||
|
||
Безопасный рефакторинг с moved: Современный способ переименования ресурсов или изменения их адреса без физического пересоздания.
|
||
|
||
# В конфигурации Terraform
|
||
moved {
|
||
from = yandex_compute_instance.old_name
|
||
to = yandex_compute_instance.new_name
|
||
}
|
||
|
||
Этот блок в книге Robert Hafner'а указывает Terraform на необходимость перенести состояние, а не удалять и создавать заново [21].
|
||
|
||
Удаление ресурса из управления Terraform: Команда state rm удаляет ресурс из состояния, но не удаляет его физически в облаке.
|
||
|
||
bash
|
||
|
||
terraform state rm yandex_compute_instance.temporary
|
||
|
||
Эта операция должна выполняться с крайней осторожностью!! [16, 21].
|
||
|
||
|
||
Downstream Pipelines и автоматизация в CI/CD
|
||
|
||
При управлении крупной инфраструктурой, состоящей из сотен компонентов, классический подход с единым CI/CD-пайплайном становится неприемлемым. Его выполнение превращается в многочасовую процедуру, а ошибка в одном компоненте приводит к остановке всего процесса и сложностям в локализации проблемы. Как показывают практические кейсы, Downstream (дочерние) Pipelines решают эту проблему, превращая монолитный деплой в набор независимых, параллельных задач, что сокращает время развертывания с часов до минут и кардинально упрощает операционную деятельность [5, 29].
|
||
|
||
5.1. Динамическая генерация пайплайна: принцип работы и практическая реализация
|
||
|
||
Суть динамической генерации заключается в том, что структура пайплайна не задается жестко в YAML-файле, а создается скриптом на лету на основе актуальной файловой структуры репозитория live/, как было уже указано выше. Это обеспечивает полное соответствие между инфраструктурным кодом и процессом его доставки.
|
||
|
||
Контекст и исходная структура:
|
||
Предположим, инфраструктура организована по принципу, описанному в материале Gruntworks - Folder Structure, и структурно выглядит как Окружение/Регион/Компонент:
|
||
|
||
live/
|
||
├── prod/
|
||
│ ├── ru-central1-a/
|
||
│ │ ├── network/ # Компонент A: VPC, subnets
|
||
│ │ │ └── terragrunt.hcl
|
||
│ │ └── k8s-cluster/ # Компонент B: k8s кластер
|
||
│ │ └── terragrunt.hcl
|
||
│ └── _global/
|
||
│ └── object-storage/ # Компонент C: s3 buckets
|
||
│ └── terragrunt.hcl
|
||
└── dev/... # Аналогичная структура для других каталогов
|
||
|
||
Шаг 1: Создание скрипта-генератора
|
||
Скрипт generate-pipeline.sh выполняет ключевую роль. Он:
|
||
|
||
Сканирует всю иерархию live/ в поисках файлов terragrunt.hcl, игнорируя служебные директории (например, .terragrunt-cache/).
|
||
|
||
Для каждого найденного компонента извлекает из пути его метаданные: окружение (prod, dev), регион (ru-central1-a, _global) и имя компонента (network).
|
||
|
||
Генерирует три стандартные job GitLab CI для каждого компонента: validate, plan и apply.
|
||
|
||
Автоматически назначает переменные окружения (например, TG_ROOT) и, что критически важно, настраивает правило when: manual для стадии apply в production-окружениях. Это обязательная мера безопасности, чтобы production-изменения не катились автоматически.
|
||
|
||
#!/bin/bash
|
||
echo "stages: [validate, plan, apply]" > .gitlab-ci.generated.yml
|
||
|
||
find live -name "terragrunt.hcl" -not -path "*/.terragrunt-cache/*" | while read CONFIG_FILE; do
|
||
COMPONENT_DIR=$(dirname "$CONFIG_FILE")
|
||
ENV=$(echo "$COMPONENT_DIR" | cut -d'/' -f2)
|
||
REGION=$(echo "$COMPONENT_DIR" | cut -d'/' -f3)
|
||
COMPONENT_NAME=$(basename "$COMPONENT_DIR")
|
||
|
||
for STAGE in validate plan apply; do
|
||
JOB_NAME="${STAGE}-${ENV}-${REGION}-${COMPONENT_NAME}"
|
||
cat >> .gitlab-ci.generated.yml << EOF
|
||
${JOB_NAME}:
|
||
stage: ${STAGE}
|
||
variables:
|
||
TG_ROOT: "${COMPONENT_DIR}"
|
||
ENVIRONMENT: "${ENV}"
|
||
script:
|
||
- cd \${TG_ROOT}
|
||
- terragrunt init -reconfigure -input=false
|
||
- terragrunt \${CI_JOB_STAGE} -input=false --terragrunt-non-interactive
|
||
EOF
|
||
# Ключевое правило безопасности для production
|
||
if [[ "$STAGE" == "apply" && "$ENV" == "prod" ]]; then
|
||
echo " when: manual" >> .gitlab-ci.generated.yml
|
||
fi
|
||
echo "" >> .gitlab-ci.generated.yml
|
||
done
|
||
done
|
||
|
||
Шаг 2: Оркестрация в основном пайплайне
|
||
Основной файл .gitlab-ci.yml становится минимальным и выполняет лишь две функции — запуск генератора и триггер downstream пайплайна.
|
||
|
||
yaml
|
||
|
||
stages:
|
||
- generate
|
||
- trigger
|
||
|
||
generate-config:
|
||
stage: generate
|
||
script: ./generate-pipeline.sh
|
||
artifacts:
|
||
paths:
|
||
- .gitlab-ci.generated.yml
|
||
expire_in: 1 hour
|
||
|
||
trigger-downstream:
|
||
stage: trigger
|
||
trigger:
|
||
include:
|
||
- artifact: .gitlab-ci.generated.yml
|
||
job: generate-config
|
||
strategy: depend
|
||
|
||
Следовательно, при каждом пуше в репозиторий сначала выполняется джоба generate-config, которая создает актуальный файл .gitlab-ci.generated.yml и сохраняет его как артефакт. Затем job trigger-downstream запускает новый пайплайн, используя сгенерированный файл в качестве .gitlab-ci.yml.
|
||
|
||
Шаг 3: Что происходит в сгенерированном downstream пайплайне
|
||
В результате для структуры из трех компонентов будет создано 9 независимых job, организованных в трех стейджах:
|
||
|
||
-=ВСЕ ДЖОБЫ ВЫПОЛНЯЮТСЯ ПАРАЛЛЕЛЬНО
|
||
validate:
|
||
• validate-prod-ru-central1-a-network
|
||
• validate-prod-ru-central1-a-k8s-cluster
|
||
• validate-prod-global-object-storage
|
||
|
||
plan (если validate завершился успешно):
|
||
• plan-prod-ru-central1-a-network
|
||
• plan-prod-ru-central1-a-k8s-cluster
|
||
• plan-prod-global-object-storage
|
||
|
||
Стадия 'apply' (manual для prod или manual для всех apply):
|
||
• apply-prod-ru-central1-a-network (manual)
|
||
• apply-prod-ru-central1-a-k8s-cluster (manual)
|
||
• apply-prod-global-object-storage (manual)
|
||
|
||
А вот так будет выглядеть сгенерированный gitlab-ci для компонента network:
|
||
|
||
validate-prod-ru-central1-a-network:
|
||
stage: validate
|
||
variables:
|
||
TG_ROOT: "live/prod/ru-central1-a/network"
|
||
ENVIRONMENT: "prod"
|
||
YC_FOLDER_ID: "${YC_PROD_FOLDER_ID}"
|
||
before_script:
|
||
- cd ${TG_ROOT}
|
||
script:
|
||
- terragrunt init -reconfigure --terragrunt-non-interactive
|
||
- terragrunt validate -input=false --terragrunt-non-interactive
|
||
rules:
|
||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||
tags:
|
||
- yc
|
||
interruptible: true
|
||
|
||
plan-prod-ru-central1-a-network:
|
||
stage: plan
|
||
variables:
|
||
TG_ROOT: "live/prod/ru-central1-a/network"
|
||
ENVIRONMENT: "prod"
|
||
YC_FOLDER_ID: "${YC_PROD_FOLDER_ID}"
|
||
before_script:
|
||
- cd ${TG_ROOT}
|
||
script:
|
||
- terragrunt init -reconfigure --terragrunt-non-interactive
|
||
- terragrunt plan -input=false --terragrunt-non-interactive -out=tfplan
|
||
rules:
|
||
- if: '$CI_COMMIT_BRANCH == "master"'
|
||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||
tags:
|
||
- yc
|
||
interruptible: true
|
||
artifacts:
|
||
paths:
|
||
- ${TG_ROOT}/*.plan
|
||
when: always
|
||
expire_in: 1 week
|
||
|
||
apply-prod-ru-central1-a-network:
|
||
stage: apply
|
||
variables:
|
||
TG_ROOT: "live/prod/ru-central1-a/network"
|
||
ENVIRONMENT: "prod"
|
||
YC_FOLDER_ID: "${YC_PROD_FOLDER_ID}"
|
||
before_script:
|
||
- cd ${TG_ROOT}
|
||
script:
|
||
- terragrunt init -reconfigure --terragrunt-non-interactive
|
||
- terragrunt apply -input=false --terragrunt-non-interactive tfplan
|
||
rules:
|
||
- if: '$CI_COMMIT_BRANCH == "master"'
|
||
when: manual
|
||
tags:
|
||
- yc
|
||
interruptible: true
|
||
|
||
Такой подход обеспечивает максимальный параллелизм. Например, пока выполняется plan для network, одновременно может идти validate для object-storage. Стадия apply для всего production-окружения блокируется и требует ручного нажатия кнопки деплоя для каждого компонента в отдельности, что дает полный контроль над разверткой.
|
||
|
||
5.2. Ключевые преимущества и операционные выгоды подхода
|
||
|
||
Экспоненциальный рост скорости: Вместо последовательного выполнения команд для всех компонентов, они обрабатываются параллельно. Это преобразует время деплоя из линейной зависимости O(n) в константную O(1). Яркий пример — кейс компании Selectel, где внедрение Downstream Pipelines сократило время полного развертывания инфраструктуры с 8 часов до 24 минут [5].
|
||
|
||
Минимизация «радиуса поражения» (Blast Radius): При использовании единого пайплайна с командой terragrunt run-all ошибка в любом модуле приводит к остановке всего процесса, а откат изменений может быть проблематичным занятием. При гранулярном подходе падение джобы типа apply-prod-ru-central1-a-k8s-cluster никак не повлияет на уже завершенное или еще не начатое развертывание компонентов network или object-storage. Можно точечно исправить ошибку в одном компоненте и перезапустить только его, не затрагивая остальные стабильные части системы [5, 16].
|
||
|
||
Прозрачность и простой траблшутинг: В интерфейсе GitLab CI/CD каждый компонент представлен отдельной джобой с собственным логом. Это кардинально отличается от гаргантюанского вывода run-all, где логи десятков модулей идут единым потоком. При возникновении проблемы инженер мгновенно видит, в каком именно компоненте и на каком стейдже произошел сбой, и вследствие может посмотреть его лог предметно. [5, 29].
|
||
|
||
<езопасность и контроль: Правило when: manual для apply в production является не просто рекомендацией, а обязательным guardrail, согласно истоичникам. Оно исключает возможность автоматического применения потенциально разрушительных изменений из-за ошибки в коде или сбоя в CI-системе. Каждое изменение подтвержадется руками, что соответствует принципам GitOps и compliance-требованиям для сред, критичных к аптайму.
|
||
|
||
Управление зависимостями и порядком выполнения: Важным аспектом, который решается уже на уровне конфигурации Terragrunt, а не самого пайплайна, являются зависимости между компонентами. Например, k8s-cluster зависит от выходных данных network (ID подсетей). В файле terragrunt.hcl компонента k8s-cluster это описывается через блок dependency. Во время выполнения terragrunt apply в сгенерированной джобе Terragrunt автоматически прочитает состояние network из remote стейта, получит необходимые выходные переменные и передаст их в модуль. Таким образом, оркестрация зависимостей и параллелизм не конфликтуют: пайплайн может запустить джобы параллельно, а Terragrunt внутри джобы будет ждать, пока состояние зависимого компонента (network) не станет актуальным [18, 27].
|
||
|
||
5.3. Статический анализ и валидация
|
||
|
||
Динамическую генерацию можно расширить для создания предварительных стадий проверки. Например, можно создать отдельную стадию lint, которая для каждого компонента запускает:
|
||
|
||
terragrunt validate — проверка синтаксиса Terraform.
|
||
|
||
terragrunt hclfmt --terragrunt-check — проверка соответствия формату.
|
||
|
||
checkov -d . — статический анализ безопасности на наличие уязвимых конфигураций (например, открытых security groups, незашифрованных дисков) [5, 23].
|
||
|
||
Эти проверки выполняются за секунды и позволяют «отсеять» очевидные проблемы до запуска длительных операций plan и apply, экономя время и ресурсы.
|
||
|
||
|
||
6. Рефакторинг инфры
|
||
|
||
Рефакторинг в Terraform долгое время был доовльно рискованной операцией. Изменение идентификатора ресурса в коде (например, с aws_instance.web на aws_instance.server) воспринималось TF как удаление старого объекта и создание нового, что приводило к простоям и потере данных у ресурсов с состоянием (БД, S3-бакеты) [21, 24]. С выходом версии 1.1 Terraform представил блоки moved — декларативный способ управления состоянием прямо в коде, который пришел на замену ручным и опасным командам в CLI [6, 21].
|
||
|
||
6.1. Деструктивный рефакторинг
|
||
|
||
Terraform связывает идентификатор ресурса в файлах .tf с уникальным ID в облаке через стейт [16, 21]. Если мы просто переименуем ресурс в коде, Terraform интерпретирует это как удаление ресурса и создание другого. Это приводит к печальным последствиям — удалению работающего инстанса виртуальной машины, базы данных или бакета [16, 21, 24].
|
||
|
||
6.2. Блок moved
|
||
|
||
Блок moved сообщает Terraform, что ресурс сменил свое расположение или имя внутри кода. Это позволяет перепривязать существующую запись в стейте к новому имени без негативных последствий на реальную инфраструктуру [6, 21]. Синтаксис блока прост:
|
||
|
||
hcl
|
||
|
||
moved {
|
||
from = <СТАРЫЙ_АДРЕС_РЕСУРСА>
|
||
to = <НОВЫЙ_АДРЕС_РЕСУРСА>
|
||
}
|
||
|
||
6.3. Основные сценарии использования
|
||
|
||
В руководствах выделяют несколько ключевых сценариев, где блок moved становится незаменимым.
|
||
|
||
Сценарий 1: Простое переименование ресурса
|
||
Наиболее частый случай — приведение имен ресурсов к единому стандарту.
|
||
|
||
# Было: resource "yandex_compute_instance" "vm" { ... }
|
||
# Стало:
|
||
resource "yandex_compute_instance" "app_server" {
|
||
name = "production-app-01"
|
||
platform_id = "standard-v3"
|
||
# ... остальная конфигурация
|
||
}
|
||
|
||
moved {
|
||
from = yandex_compute_instance.vm
|
||
to = yandex_compute_instance.app_server
|
||
}
|
||
|
||
После применения terraform apply в стейте Terraform запись для ВМ будет обновлена, а сама ВМ в облаке останется нетронутой.
|
||
|
||
Сценарий 2: Перемещение ресурса внутрь модуля
|
||
В процессе развития проекта монолитный код разбивается на модули. Блок moved позволяет безопасно инкапсулировать ресурс.
|
||
|
||
# В корневом main.tf (старая структура)
|
||
resource "yandex_vpc_network" "this" {
|
||
name = "my-network"
|
||
}
|
||
|
||
# В новом модуле modules/network/main.tf
|
||
resource "yandex_vpc_network" "this" {
|
||
name = var.network_name
|
||
}
|
||
|
||
# В корневом main.tf (новая структура)
|
||
module "network" {
|
||
source = "./modules/network"
|
||
network_name = "my-network"
|
||
}
|
||
|
||
moved {
|
||
from = yandex_vpc_network.this
|
||
to = module.network.yandex_vpc_network.this
|
||
}
|
||
|
||
Сценарий 3: Рефакторинг вызова модулей
|
||
Можно переименовывать не только ресурсы, но и вызовы самих модулей для улучшения читаемости кода.
|
||
|
||
moved {
|
||
from = module.obscure_name_xyz
|
||
to = module.kubernetes_cluster
|
||
}
|
||
|
||
6.4. Разница между moved и terraform state mv?
|
||
|
||
До появления блоков moved использовали команду terraform state mv. Однако у декларативного подхода есть большие преимущества:
|
||
|
||
Автоматизация для всей команды: Блок moved является частью кода. Когда разработчик обновляет репозиторий и запускает terraform apply, миграция состояния происходит автоматически. Не нужно запоминать и выполнять последовательность команд вручную [6, 21].
|
||
|
||
Безопасность и проверка через plan: Изменения стейта отображаются в выводе terraform plan. Вы можете заранее убедиться, что Terraform планирует именно перемещение (в логе будет указано # resource has moved), а не удаление и создание (# resource will be destroyed, # resource will be created) [6, 21].
|
||
|
||
История изменений в Git: Факт рефакторинга фиксируется в системе контроля версий. Любой член команды, просматривая историю коммитов, увидит, когда и почему ресурс был переименован, что делает эволюцию инфраструктуры полностью прозрачной [21].
|
||
|
||
6.5. Интеграция рефакторинга в рабочий процесс с Terragrunt
|
||
|
||
Terragrunt, выступая оркестратором, накладывает строгие ограничения на структуру проекта, что делает рефакторинг более предсказуемым и безопасным [27, 30].
|
||
|
||
1. Изоляция состояния и минимизация Blast Radius
|
||
Как уже было сказано, Terragrunt стимулирует дробление инфраструктуры на мелкие, независимые компоненты, каждый со своим файлом состояния [7, 20, 27].
|
||
|
||
Преимущество: Опять же, при ошибке в рефакторинге (например, ошибка в блоке moved) затронет состояние только одного компонента. Это предотвращает каскадные сбои во всей инфраструктуре [16, 24, 30].
|
||
|
||
Пример: При переименовании ресурса в модуле k8s-cluster состояние модулей network и database останутся полностью нетронутыми, что обеспечивает спокойный сон запускающего Terragrunt.
|
||
|
||
2. Согласованный рефакторинг в нескольких окружениях
|
||
При использовании иерархической структуры live/ один и тот же модуль может использоваться в dev, stage и prod. Блок moved, добавленный в исходный код модуля, будет автоматически применен во всех окружениях при их следующем обновлении. Terragrunt позволяет выполнить массовую проверку с помощью terragrunt run-all plan и после аппрува terragrunt run-all apply, обеспечивая консистентность изменений во всех средах [7, 20].
|
||
|
||
3. Управление зависимостями во время рефакторинга
|
||
Если рефакторинг изменяет outputs модуля (например, переименовывает выходную переменную vpc_id в network_id), зависимые модули могут временно сломаться. В Terragrunt можно решить эту проблему через mock_outputs [29].
|
||
|
||
# В конфигурации модуля приложения, зависящего от network
|
||
dependency "network" {
|
||
config_path = "../network"
|
||
|
||
mock_outputs = {
|
||
# Предполагаем, что после рефакторинга vpc_id стал называться network_id
|
||
network_id = "mock-temp-id"
|
||
subnet_ids = ["mock-subnet-a", "mock-subnet-b"]
|
||
}
|
||
mock_outputs_allowed_terraform_commands = ["plan", "validate"]
|
||
}
|
||
|
||
inputs = {
|
||
# Используем новое имя переменной
|
||
vpc_id = dependency.network.outputs.network_id
|
||
}
|
||
|
||
Это позволяет выполнить terragrunt plan в модуле приложения до того, как рефакторинг в модуле сети будет применен, и проверить, корректно ли обновлены ссылки на выходные данные.
|
||
|
||
6.6. Рекомендации по использованию moved
|
||
|
||
Не удалять блоки moved сразу: В отличие от блоков import, блоки moved рекомендуется оставлять в коде на длительный срок, особенно в публичных модулях. Это позволяет пользователям, которые обновляются нерегулярно, бесшовно мигрировать со старых версий [21].
|
||
|
||
Использовать модули публичные: Включение блоков moved в публичные и внутренние модули — это золотой стандарт, который обеспечивает обратную совместимость и беспроблемное обновление для консьюмеров модуля [6, 21].
|
||
|
||
Всегда внимательно проверять план (plan): Перед применением рефакторинга с помощью terraform apply или terragrunt apply обязательно нужно запустить terraform plan. Убедиться, что в выводе присутствует только запись о перемещении ресурсов (# ... has moved to ...) и отсутствуют операции destroy [16, 21].
|
||
|
||
6.7. Практический пример: комплексный рефакторинг
|
||
|
||
Рассмотрим сценарий, где необходимо переименовать несколько ресурсов и перенести конфигурацию сети в отдельный модуль.
|
||
|
||
Исходная конфигурация:
|
||
|
||
# main.tf (устаревший монолит)
|
||
resource "yandex_vpc_network" "main" {
|
||
name = "prod-net"
|
||
}
|
||
resource "yandex_vpc_subnet" "private" {
|
||
name = "private-subnet"
|
||
v4_cidr_blocks = ["10.0.1.0/24"]
|
||
}
|
||
resource "yandex_compute_instance" "app" {
|
||
name = "app-old-name"
|
||
# ... ссылка на subnet: yandex_vpc_subnet.private.id
|
||
}
|
||
|
||
Целевая конфигурация после рефакторинга:
|
||
|
||
# Модуль: modules/network/main.tf
|
||
resource "yandex_vpc_network" "this" {
|
||
name = var.name
|
||
}
|
||
resource "yandex_vpc_subnet" "private" {
|
||
name = "${var.name}-private"
|
||
v4_cidr_blocks = var.private_subnets
|
||
}
|
||
|
||
# Корневой main.tf
|
||
module "network" {
|
||
source = "./modules/network"
|
||
name = "prod-network"
|
||
private_subnets = ["10.0.1.0/24"]
|
||
}
|
||
|
||
resource "yandex_compute_instance" "application_server" {
|
||
name = "prod-app-01"
|
||
# ... ссылка теперь на выход модуля: module.network.private_subnet_id
|
||
}
|
||
|
||
Блоки moved для безопасного перехода:
|
||
|
||
# В корневом main.tf, рядом с вызовом модуля
|
||
moved {
|
||
from = yandex_vpc_network.main
|
||
to = module.network.yandex_vpc_network.this
|
||
}
|
||
moved {
|
||
from = yandex_vpc_subnet.private
|
||
to = module.network.yandex_vpc_subnet.private
|
||
}
|
||
moved {
|
||
from = yandex_compute_instance.app
|
||
to = yandex_compute_instance.application_server
|
||
}
|
||
|
||
После выполнения terraform apply все существующие ресурсы будут корректно перенесены в стейт без их пересоздания в облаке.
|
||
|
||
|
||
|