Как это работает
Это руководство объясняет внутреннюю архитектуру и поток данных 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 пайплайна из отсортированного графа модулей. Провайдер автоматически определяется из окружения (переменная GITLAB_CI выбирает GitLab, GITHUB_ACTIONS выбирает GitHub Actions) или может быть задан явно через поле provider в .terraci.yaml.
Генерация джобов
Для каждого модуля TerraCi генерирует:
Plan джоб (если
plan_enabled: true)- Выполняет
terraform plan -out=plan.tfplan - Сохраняет план как артефакт
- Выполняет
Apply джоб
- Зависит от plan джоба (
needs) - Выполняет
terraform apply plan.tfplan - Ручной запуск (если
auto_approve: false)
- Зависит от plan джоба (
Маппинг стейджей
Уровни выполнения отображаются на GitLab стейджи:
stages:
- deploy-plan-0 # Планы уровня 0
- deploy-apply-0 # Применение уровня 0
- deploy-plan-1 # Планы уровня 1
- deploy-apply-1 # Применение уровня 1Цепочка зависимостей
plan-vpc:
stage: deploy-plan-0
apply-vpc:
stage: deploy-apply-0
needs: [plan-vpc]
plan-eks:
stage: deploy-plan-1
needs: [apply-vpc] # Ждёт применения vpc
apply-eks:
stage: deploy-apply-1
needs: [plan-eks]Диаграмма потока данных
Описание каждого этапа:
| Шаг | Функция | Что делает |
|---|---|---|
| 1 | Scanner.Scan() | Обход дерева директорий, поиск .tf файлов на настроенной глубине, создание Module с картой компонентов |
| 2 | Parser.ParseModule() | Парсинг HCL, извлечение locals, поиск terraform_remote_state, разрешение переменных |
| 3 | DependencyGraph.Build() | Добавление узлов/рёбер, детекция циклов, топологическая сортировка → уровни |
| 4 | Generator.Generate() | Создание стадий, генерация plan/apply джобов, применение overwrites, вывод YAML (GitLab или GitHub Actions) |
Ключевые типы
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для инкрементальных пайплайнов - Включите кэширование в сгенерированных пайплайнах
Смотрите также
- Структура проекта — требования к структуре директорий
- Зависимости — детали обнаружения зависимостей
- Генерация пайплайнов — формат сгенерированного вывода