Как это работает
Это руководство объясняет внутреннюю архитектуру и поток данных TerraCi.
Обзор
TerraCi обрабатывает ваш Terraform проект в четыре этапа:
Этап 1: Обнаружение модулей
TerraCi сканирует структуру директорий для поиска Terraform модулей.
Как это работает
- Обход дерева директорий от корня проекта
- Поиск директорий на настроенной глубине, содержащих
.tfфайлы - Парсинг пути по именованным сегментам на основе настроенного паттерна
Паттерн настраивается (например, {service}/{environment}/{region}/{module}), и имена сегментов определяют ключи в components карте модуля.
Пример
platform/stage/eu-central-1/vpc/main.tf
│ │ │ │
│ │ │ └── сегмент "module": vpc
│ │ └── сегмент "region": eu-central-1
│ └── сегмент "environment": stage
└── сегмент "service": platformID модуля
Каждый модуль получает уникальный ID: platform/stage/eu-central-1/vpc
Этот ID используется для:
- Сопоставления зависимостей
- Именования джобов
- Разрешения пути к state-файлу
Этап 2: Парсинг HCL
TerraCi парсит .tf файлы каждого модуля для извлечения зависимостей.
Что парсится
- Блоки
terraform_remote_state- основной источник зависимостей - Блоки
locals- разрешение переменных для динамических путей
Пример Remote State
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-state"
key = "platform/stage/eu-central-1/vpc/terraform.tfstate"
}
}TerraCi извлекает:
- Тип backend:
s3 - Путь к state:
platform/stage/eu-central-1/vpc/terraform.tfstate - Разрешённый модуль:
platform/stage/eu-central-1/vpc
Разрешение путей
TerraCi разрешает переменные в путях state:
locals {
env = "stage"
}
data "terraform_remote_state" "vpc" {
config = {
key = "platform/${local.env}/eu-central-1/vpc/terraform.tfstate"
}
}Становится: platform/stage/eu-central-1/vpc/terraform.tfstate
Обработка for_each
При наличии for_each TerraCi раскрывает в несколько зависимостей:
data "terraform_remote_state" "services" {
for_each = toset(["auth", "api", "web"])
config = {
key = "platform/stage/eu-central-1/${each.key}/terraform.tfstate"
}
}Создаёт зависимости от модулей: auth, api, web.
Этап 3: Построение графа
TerraCi строит направленный ациклический граф (DAG) зависимостей модулей.
Алгоритм
- Создание узла для каждого обнаруженного модуля
- Добавление рёбер от каждого модуля к его зависимостям
- Обнаружение циклов (ошибка если найдены)
- Топологическая сортировка алгоритмом Кана
Топологическая сортировка
Алгоритм Кана гарантирует порядок где зависимости идут первыми:
Уровни выполнения
Модули группируются по уровням для параллельного выполнения:
| Уровень | Модули | Параллельно |
|---|---|---|
| 0 | vpc | Да (нет зависимостей) |
| 1 | eks, rds | Да (одинаковые зависимости) |
| 2 | app | После уровня 1 |
Обнаружение циклов
TerraCi обнаруживает циклические зависимости:
Сообщение об ошибке:
Error: circular dependency detected
vpc -> eks -> app -> vpcЭтап 4: Генерация пайплайна
TerraCi генерирует конфигурацию CI пайплайна из отсортированного графа модулей. Провайдер выбирается через TERRACI_PROVIDER, автоматически определяется из окружения (переменная GITLAB_CI выбирает GitLab, GITHUB_ACTIONS выбирает GitHub Actions) или выводится из единственного активного провайдера.
Генерация джобов
Для каждого модуля TerraCi генерирует:
- Выполняет
terraform plan -out=plan.tfplan - Сохраняет план как артефакт
- Apply джоб
- Зависит от plan джоба (
needs) - Выполняет
terraform apply plan.tfplan - Может быть ручным или условным через provider overwrites
- Зависит от plan джоба (
Маппинг DAG-стейджей
Зависимости джобов раскладываются в топологические DAG-слои. GitLab рендерит эти слои как стейджи:
stages:
- deploy-0 # первый DAG-слой
- deploy-1 # второй DAG-слой
- deploy-2 # третий DAG-слой
- deploy-3 # четвёртый DAG-слойЦепочка зависимостей
plan-vpc:
stage: deploy-0
apply-vpc:
stage: deploy-1
needs: [plan-vpc]
plan-eks:
stage: deploy-2
needs: [apply-vpc] # Ждёт применения vpc
apply-eks:
stage: deploy-3
needs: [plan-eks]Диаграмма потока данных
Описание каждого этапа:
| Шаг | Функция | Что делает |
|---|---|---|
| 1 | workflow.PlanProject() | Сканирование файловой системы, применение фильтров, парсинг HCL, построение графа зависимостей, разрешение optional targets |
| 2 | contributions из runflow.Prepare(...) + Terraform runtime | Сбор contributed DAG jobs и нормализация Terraform/OpenTofu runtime; invalid contributions останавливают сборку до IR |
| 3 | pipeline.BuildProjectIR(req) | Построение провайдер-агностичного immutable job DAG (*pipeline.IR) — единый вход для исполнения |
| 4 | provider.NewGenerator(ir) + Generate() | Привязка IR к провайдеру; преобразование IR в YAML GitLab CI или воркфлоу GitHub Actions |
IR — единый источник как для генерации пайплайнов, так и для terraci local-exec: провайдеры не обращаются отдельно к графу зависимостей или списку контрибуций — IR уже их в себе содержит и читается через getters.
Ключевые типы
Module
Представляет обнаруженный Terraform модуль. Вместо жёстко заданных полей модуль использует components карту с ключами из именованных сегментов настроенного паттерна:
type Module struct {
components map[string]string // {"service": "platform", "environment": "stage", ...}
segments []string // упорядоченные имена сегментов из паттерна
Path string // /abs/path/to/vpc
RelativePath string // platform/stage/eu-central-1/vpc
Parent *Module
Children []*Module
}
func (m *Module) Get(name string) string // m.Get("service") → "platform"
func (m *Module) ID() string // возвращает RelativePathТакой дизайн позволяет использовать полностью настраиваемые паттерны. Например, с паттерном {team}/{env}/{module} вы используете m.Get("team") и m.Get("env") вместо фиксированных имён полей.
RemoteStateRef
Представляет зависимость terraform_remote_state:
type RemoteStateRef struct {
Name string // "vpc"
Backend string // "s3"
Config map[string]string // bucket, key, region
WorkspaceDir string // разрешённый путь модуля
}DependencyGraph
Управляет связями между модулями:
type DependencyGraph struct {
nodes map[string]*Module
edges map[string][]string // from -> [to, to, ...]
}
func (g *DependencyGraph) AddEdge(from, to *Module)
func (g *DependencyGraph) TopologicalSort() ([]*Module, error)
func (g *DependencyGraph) ExecutionLevels() [][]*Module
func (g *DependencyGraph) DetectCycles() [][]stringПроизводительность
TerraCi оптимизирован для скорости:
| Размер проекта | Модулей | Время парсинга | Время генерации |
|---|---|---|---|
| Маленький | 10 | ~100мс | ~50мс |
| Средний | 50 | ~300мс | ~100мс |
| Большой | 200 | ~1с | ~300мс |
Советы для больших проектов:
- Используйте паттерны
excludeдля пропуска ненужных директорий - Используйте
--changed-onlyдля инкрементальных пайплайнов - Включите кэширование в сгенерированных пайплайнах
Смотрите также
- Структура проекта — требования к структуре директорий
- Зависимости — детали обнаружения зависимостей
- Генерация пайплайнов — формат сгенерированного вывода