Terragrunt ile Terraform Yapılandırma Yönetimi
Terraform ile ciddi altyapı yönetimi yapıyorsanız, bir noktada şu soruyla mutlaka yüzleşiyorsunuz: “Aynı modülü birden fazla ortam için nasıl yöneteyim?” Dev, staging ve production ortamlarınız var, her birinin biraz farklı değişkenleri var, ve siz kendinizi aynı terraform.tfvars dosyasını kopyalayıp yapıştırırken buluyorsunuz. İşte tam bu noktada Terragrunt devreye giriyor.
Terragrunt, Gruntwork tarafından geliştirilen ve Terraform’un üzerine oturan ince bir wrapper araç. “Thin wrapper” diyorlar ama pratikte hayatınızı epey kolaylaştırıyor. DRY (Don’t Repeat Yourself) prensibini Terraform konfigürasyonlarına uygulamanızı sağlıyor, remote state yönetimini otomatize ediyor ve bağımlılık yönetimi konusunda Terraform’un eksikliklerini kapatıyor.
Neden Terragrunt?
Saf Terraform kullanırken karşılaştığınız tipik problemleri düşünün. Her ortam için ayrı backend.tf dosyası tutuyorsunuz, remote state bucket’ı elle oluşturuyorsunuz, ve modül çağrılarını her environments/dev, environments/staging, environments/prod klasöründe tekrarlıyorsunuz. Bu durum zamanla bakım kabusuna dönüşüyor.
Terragrunt şu problemleri çözüyor:
- DRY konfigürasyon: Aynı backend konfigürasyonunu tek bir yerde tanımlayıp her yerde kullanabiliyorsunuz
- Otomatik remote state: S3 bucket ve DynamoDB tablosunu otomatik oluşturuyor
- Bağımlılık yönetimi: Modüller arası bağımlılıkları açıkça tanımlayıp sıralı apply yapabiliyorsunuz
- Hooks: Before/after hook’larla özel komutlar çalıştırabiliyorsunuz
run-allkomutu: Tüm altyapıyı tek komutla yönetebiliyorsunuz
Kurulum ve İlk Adımlar
Terragrunt kurulumu oldukça basit. Linux üzerinde:
# Binary indirme yöntemi
wget https://github.com/gruntwork-io/terragrunt/releases/download/v0.54.0/terragrunt_linux_amd64
chmod +x terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt
# Homebrew ile (macOS veya Linux)
brew install terragrunt
# Versiyon kontrolü
terragrunt --version
Terragrunt, Terraform binary’sine ihtiyaç duyuyor, dolayısıyla Terraform’un da kurulu olması gerekiyor. tfenv veya asdf kullanıyorsanız versiyonları kolayca yönetebilirsiniz.
Proje Yapısı
Terragrunt projelerinde klasör yapısı kritik önem taşıyor. İyi tasarlanmış bir yapı şuna benziyor:
infrastructure/
├── terragrunt.hcl # Root konfigürasyon (provider, backend şablonu)
├── modules/ # Terraform modülleri
│ ├── vpc/
│ ├── eks/
│ └── rds/
└── environments/
├── dev/
│ ├── terragrunt.hcl # Ortam bazlı override
│ ├── vpc/
│ │ └── terragrunt.hcl
│ ├── eks/
│ │ └── terragrunt.hcl
│ └── rds/
│ └── terragrunt.hcl
├── staging/
│ ├── terragrunt.hcl
│ └── ...
└── prod/
├── terragrunt.hcl
└── ...
Bu yapıda her terragrunt.hcl dosyası kendi seviyesindeki konfigürasyonu tanımlıyor ve parent konfigürasyonları miras alabiliyor.
Root terragrunt.hcl Yapılandırması
En kritik dosya root terragrunt.hcl. Burada backend şablonunu ve ortak provider konfigürasyonunu tanımlıyorsunuz:
# infrastructure/terragrunt.hcl
locals {
# Klasör yapısından ortam ve region bilgisini otomatik çekiyoruz
path_components = split("/", path_relative_to_include())
environment = local.path_components[0]
region = "eu-west-1"
account_id = get_aws_account_id()
# Ortak etiketler
common_tags = {
Environment = local.environment
ManagedBy = "Terragrunt"
Repository = "infrastructure"
}
}
# Remote state backend şablonu
remote_state {
backend = "s3"
config = {
bucket = "mycompany-terraform-state-${local.account_id}"
key = "${path_relative_to_include()}/terraform.tfstate"
region = local.region
encrypt = true
dynamodb_table = "terraform-state-lock"
}
# Bu ayar ile Terragrunt bucket ve DynamoDB tablosunu otomatik oluşturuyor
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
}
# Provider konfigürasyonu şablonu
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "${local.region}"
default_tags {
tags = {
Environment = "${local.environment}"
ManagedBy = "Terragrunt"
}
}
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.5.0"
}
EOF
}
# Tüm child modüllere geçilecek ortak inputlar
inputs = merge(
local.common_tags,
{
region = local.region
account_id = local.account_id
}
)
Bu root konfigürasyon sayesinde her modül kendi backend ve provider dosyasını manuel oluşturmak zorunda kalmıyor.
Ortam Bazlı Yapılandırma
Dev ortamı için bir örnek terragrunt.hcl:
# infrastructure/environments/dev/terragrunt.hcl
locals {
environment = "dev"
}
# Root konfigürasyonu dahil et
include "root" {
path = find_in_parent_folders()
expose = true
}
inputs = {
environment = local.environment
# Dev ortamında küçük instance tipleri kullanalım
instance_type = "t3.small"
min_capacity = 1
max_capacity = 3
deletion_protection = false
}
# infrastructure/environments/dev/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
include "env" {
path = find_in_parent_folders("terragrunt.hcl")
}
# Hangi Terraform modülünü kullanacağımızı belirtiyoruz
terraform {
source = "../../../modules/vpc"
}
inputs = {
vpc_cidr = "10.0.0.0/16"
availability_zones = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # Dev'de tek NAT gateway yeterli, maliyet avantajı
}
Production için aynı modül farklı değerlerle:
# infrastructure/environments/prod/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/vpc"
}
inputs = {
vpc_cidr = "10.2.0.0/16"
availability_zones = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.2.1.0/24", "10.2.2.0/24", "10.2.3.0/24"]
public_subnets = ["10.2.101.0/24", "10.2.102.0/24", "10.2.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false # Prod'da her AZ için NAT gateway
one_nat_gateway_per_az = true
}
Gördüğünüz gibi aynı modülü farklı parametrelerle çağırıyoruz, herhangi bir kod tekrarı yok.
Bağımlılık Yönetimi
Terragrunt’ın en güçlü özelliklerinden biri bağımlılık yönetimi. Örneğin EKS cluster’ı VPC’ye bağımlı, RDS de hem VPC’ye hem de EKS’e bağımlı olsun:
# infrastructure/environments/dev/eks/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/eks"
}
# VPC bağımlılığı tanımlıyoruz
dependency "vpc" {
config_path = "../vpc"
# Plan aşamasında mock değerler kullan (VPC henüz oluşturulmamışsa)
mock_outputs = {
vpc_id = "vpc-mock-12345"
private_subnets = ["subnet-mock-1", "subnet-mock-2"]
}
mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}
inputs = {
cluster_name = "myapp-dev"
cluster_version = "1.28"
# VPC output'larını otomatik olarak alıyoruz
vpc_id = dependency.vpc.outputs.vpc_id
subnet_ids = dependency.vpc.outputs.private_subnets
node_groups = {
general = {
instance_types = ["t3.medium"]
min_size = 1
max_size = 3
desired_size = 2
}
}
}
Bu yapıyla terragrunt apply çalıştırdığınızda Terragrunt önce VPC’nin output’larını alıp EKS modülüne besliyor. Manuel terraform output ve kopyala-yapıştır dönemini kapatıyorsunuz.
run-all ile Toplu İşlemler
Tek bir komutla tüm ortamı yönetmek için run-all kullanıyorsunuz:
# Dev ortamındaki tüm modülleri plan et
cd infrastructure/environments/dev
terragrunt run-all plan
# Tüm modülleri apply et (bağımlılık sırasına göre)
terragrunt run-all apply
# Sadece belirli dizinleri dahil et
terragrunt run-all apply --terragrunt-include-dir "vpc" --terragrunt-include-dir "eks"
# Belirli dizinleri hariç tut
terragrunt run-all plan --terragrunt-exclude-dir "rds"
# Paralel işlem sayısını sınırla
terragrunt run-all apply --terragrunt-parallelism 2
run-all bağımlılıkları analiz ediyor ve doğru sırayla apply yapıyor. Bağımsız modülleri paralel çalıştırıyor, bu da büyük altyapılarda ciddi zaman kazandırıyor.
Hooks Kullanımı
Before/after hook’ları güçlü bir özellik. Örneğin apply öncesi bir script çalıştırabilir veya sonrasında bildirim gönderebilirsiniz:
# Helm chart'larını Terraform apply sonrası güncelleyen hook örneği
terraform {
source = "../../../modules/eks"
before_hook "validate_kubeconfig" {
commands = ["apply"]
execute = ["bash", "-c", "aws eks update-kubeconfig --name myapp-dev --region eu-west-1"]
}
after_hook "update_helm_repos" {
commands = ["apply"]
execute = ["helm", "repo", "update"]
run_on_error = false
}
after_hook "notify_slack" {
commands = ["apply"]
execute = [
"bash", "-c",
"curl -X POST -H 'Content-type: application/json' --data '{"text":"Dev EKS apply tamamlandi!"}' $SLACK_WEBHOOK_URL"
]
run_on_error = false
}
# Error durumunda çalışacak hook
error_hook "alert_on_failure" {
commands = ["apply"]
execute = ["bash", "-c", "echo 'Apply basarisiz oldu!' | mail -s 'Terragrunt Alert' [email protected]"]
on_errors = [".*"]
}
}
CI/CD Pipeline Entegrasyonu
Gerçek dünya senaryosunda Terragrunt’ı CI/CD pipeline’ınıza entegre etmeniz gerekiyor. GitHub Actions örneği:
# .github/workflows/terragrunt.yml
name: Terragrunt CI/CD
on:
push:
branches: [main]
paths:
- 'infrastructure/**'
pull_request:
branches: [main]
paths:
- 'infrastructure/**'
env:
TF_VERSION: "1.6.0"
TG_VERSION: "0.54.0"
AWS_REGION: "eu-west-1"
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
changed-envs: ${{ steps.detect.outputs.environments }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Degisen ortamlari tespit et
id: detect
run: |
CHANGED=$(git diff --name-only HEAD~1 HEAD | grep '^infrastructure/environments/' | cut -d'/' -f3 | sort -u | tr 'n' ',')
echo "environments=$CHANGED" >> $GITHUB_OUTPUT
plan:
needs: detect-changes
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, staging]
steps:
- uses: actions/checkout@v4
- name: Terraform kur
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terragrunt kur
run: |
wget -q https://github.com/gruntwork-io/terragrunt/releases/download/v${{ env.TG_VERSION }}/terragrunt_linux_amd64
chmod +x terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt
- name: AWS kimlik dogrulama
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets[format('AWS_ROLE_{0}', matrix.environment)] }}
aws-region: ${{ env.AWS_REGION }}
- name: Terragrunt Plan
working-directory: infrastructure/environments/${{ matrix.environment }}
run: |
terragrunt run-all plan
--terragrunt-non-interactive
-out=tfplan
apply:
needs: plan
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production # GitHub environment onayı
steps:
- uses: actions/checkout@v4
- name: Terragrunt Apply (Prod)
working-directory: infrastructure/environments/prod
run: |
terragrunt run-all apply
--terragrunt-non-interactive
-auto-approve
env:
AWS_DEFAULT_REGION: ${{ env.AWS_REGION }}
Ortak Sorunlar ve Çözümleri
Terragrunt kullanırken karşılaşacağınız tipik durumlar:
State lock problemi: Uzun süren bir apply yarıda kesilirse DynamoDB lock’u kalmış olabilir.
# Lock ID'yi öğren
terragrunt force-unlock LOCK_ID
# Ya da doğrudan DynamoDB'den sil
aws dynamodb delete-item
--table-name terraform-state-lock
--key '{"LockID": {"S": "mycompany-terraform-state/environments/dev/vpc/terraform.tfstate-md5"}}'
Cache temizleme: Modül kaynak kodunda değişiklik yaptığınızda cache’i temizlemeniz gerekebilir.
# Terragrunt cache'i temizle
find . -type d -name ".terragrunt-cache" -exec rm -rf {} +
# Terraform plugin cache'i koru ama Terragrunt cache'i temizle
terragrunt run-all init --terragrunt-no-auto-init
Modül versiyonlama: Üretimde modül versiyonlarını pin’lemek kritik.
# Versiyonlu modül referansı
terraform {
source = "git::https://github.com/mycompany/terraform-modules.git//modules/vpc?ref=v2.1.0"
}
# Ya da local path için (mono-repo yaklaşımı)
terraform {
source = "${get_repo_root()}/modules/vpc"
}
Terragrunt Catalog ve Scaffold
Terragrunt 0.52 ile gelen yeni özelliklerden biri catalog ve scaffold komutları. Yeni bir modül eklemeniz gerektiğinde şablondan başlayabiliyorsunuz:
# Mevcut catalog'u görüntüle
terragrunt catalog
# Yeni modül için scaffolding oluştur
terragrunt scaffold github.com/gruntwork-io/terragrunt-infrastructure-modules//modules/vpc
# Bu komut otomatik olarak terragrunt.hcl dosyası oluşturuyor
# ve modülün gerekli input'larını doldurmanız için şablon hazırlıyor
Pratik İpuçları
Uzun vadeli kullanımda öğrendiğim bazı önemli noktalar:
get_env()fonksiyonunu kullanın: Secrets’ları konfigürasyon dosyasına gömmeyin, environment variable’lardan çekinlocalsbloğunu verimli kullanın: Hesaplanmış değerleri locals’da tanımlayın, input bloğunu temiz tutun- Modül kaynaklarını tag’leyin:
source = "git::...?ref=v1.0.0"şeklinde her zaman belirli bir versiyon pin’leyin validatehook’u ekleyin: Her plan öncesitfsecveyacheckovçalıştırın--terragrunt-log-level debug: Sorun giderirken debug log’larını açın, neler olduğunu netçe görürsünüz- Mock output’ları gerçekçi tutun:
mock_outputsdeğerleri gerçek formata benzemelidir, aksi halde plan aşamasında yanıltıcı sonuçlar alırsınız - Paralel apply sayısına dikkat edin: AWS API rate limit’lerine takılmamak için
--terragrunt-parallelism 4ile sınır koyun
Sonuç
Terragrunt, büyüyen Terraform altyapılarında kaçınılmaz hale geliyor. Başlangıçta “sadece bir wrapper” gibi görünüyor ama DRY prensibini gerçekten uygulayabilmek, remote state yönetimini otomatize etmek ve bağımlılıkları düzgünce yönetmek için sağlam bir çözüm sunuyor.
Küçük bir altyapınız varsa ve tek ortamla çalışıyorsanız belki gereksiz bir karmaşıklık ekliyor. Ama birden fazla ortam, onlarca modül ve ekip bazlı çalışma söz konusuysa Terragrunt’ı olmadan nasıl çalıştığınızı bir süre sonra kendinize soracaksınız.
Geçiş yaparken tüm altyapıyı bir anda dönüştürmeye çalışmayın. Yeni modülleri Terragrunt ile yazın, eskilerini yavaş yavaş taşıyın. Root terragrunt.hcl yapısını iyi tasarlarsanız ileride pişman olmazsınız, bu dosya tüm altyapınızın temeli haline geliyor.
