Terraform Nedir: Altyapı Kod Olarak Temelleri

Sunucu kurarken elle yapılandırma dosyalarını düzenlemek, komutları tek tek çalıştırmak ve sonra “bu sunucuyu nasıl kurmuştum acaba?” diye düşünmek… Bunu yaşamayan sysadmin yoktur. İşte tam bu noktada Terraform devreye giriyor. Altyapını kod olarak tanımla, versiyon kontrol sistemine at, ekibinle paylaş ve her ortamda tutarlı şekilde deploy et. Bu yazıda Terraform’un ne olduğunu, nasıl çalıştığını ve gerçek dünya senaryolarında nasıl kullanacağını detaylıca ele alacağız.

Terraform Nedir ve Neden Kullanmalıyız?

Terraform, HashiCorp tarafından geliştirilen açık kaynaklı bir Infrastructure as Code (IaC) aracıdır. AWS, Azure, Google Cloud, DigitalOcean gibi bulut sağlayıcılarından tutun da on-premise VMware altyapısına kadar her şeyi kod olarak tanımlamanı sağlar.

Klasik yaklaşımda ne yapıyorduk? AWS konsoluna giriyorduk, EC2 instance açıyorduk, security group ayarlıyorduk, RDS veritabanı oluşturuyorduk. Her şey manuel, her şey tekrarlanabilir değil. Bir ortamı staging’de kuruyordun, production’da birebir aynısını yapmak için saatler harcıyordun. Üstelik neyi ne zaman değiştirdiğini takip etmek neredeyse imkansızdı.

Terraform ile bu süreç tamamen değişiyor. Altyapını HCL (HashiCorp Configuration Language) adı verilen, okunması son derece kolay bir dilde tanımlıyorsun. Bu dosyalar Git’e gidiyor, pull request açıyorsun, ekip arkadaşın inceliyor ve merge edince altyapı otomatik olarak oluşturuluyor.

Terraform’un öne çıkan özellikleri:

  • Deklaratif yapı: “Nasıl yapılacağını” değil, “ne istediğini” söylüyorsun
  • Idempotent: Aynı konfigürasyonu defalarca çalıştırsan da sonuç değişmiyor
  • State yönetimi: Mevcut altyapı durumuyla konfigürasyonu karşılaştırıyor
  • Provider ekosistemi: 1000’den fazla provider desteği
  • Plan ve Apply: Değişiklik yapmadan önce ne olacağını görebiliyorsun

Temel Kavramlar

Terraform’u anlamak için birkaç temel kavramı kafaya oturtmak gerekiyor.

Provider

Provider, Terraform’un hangi platformla konuşacağını belirliyor. AWS, Azure, GCP gibi büyük bulut sağlayıcılarının yanı sıra Kubernetes, Datadog, GitHub gibi servislerin de Terraform provider’ları var.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-1"
}

Resource

Resource, oluşturmak istediğin altyapı bileşenidir. Bir EC2 instance, bir S3 bucket, bir DNS kaydı… Bunların hepsi birer resource.

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name        = "web-sunucu"
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Variable

Değerleri sabit kodlamak yerine değişken kullanmak, konfigürasyonunu yeniden kullanılabilir yapıyor. Bir modülü hem staging hem production için kullanabiliyorsun.

variable "instance_type" {
  description = "EC2 instance tipi"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Ortam adi (staging, production)"
  type        = string
}

variable "allowed_cidrs" {
  description = "Izin verilen IP bloklari"
  type        = list(string)
  default     = ["10.0.0.0/8"]
}

Output

Output’lar, Terraform’un oluşturduğu kaynakların değerlerini dışa aktarmana yarıyor. Mesela oluşturulan EC2’nin IP adresini output olarak alabilirsin.

output "instance_public_ip" {
  description = "EC2 instance public IP adresi"
  value       = aws_instance.web_server.public_ip
}

output "instance_id" {
  description = "EC2 instance ID"
  value       = aws_instance.web_server.id
  sensitive   = false
}

State Dosyası

Terraform, oluşturduğu altyapının anlık görüntüsünü terraform.tfstate dosyasında saklıyor. Bu dosya son derece kritik çünkü Terraform bir sonraki çalışmasında mevcut durumu bu dosyayla karşılaştırıyor ve neyin değişmesi gerektiğini hesaplıyor.

Önemli uyarı: State dosyasını asla Git’e push etme! İçinde hassas veriler olabilir ve birden fazla kişi aynı anda çalışırsa state corruption yaşarsın. Bunun çözümü remote backend kullanmak, onu da ilerleyen bölümlerde göreceğiz.

Terraform Kurulumu

Linux üzerinde Terraform kurulumu oldukça basit:

# Ubuntu/Debian icin
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

# Kurulumu dogrula
terraform version

Temel Workflow

Terraform’un çalışma döngüsü dört adımdan oluşuyor:

terraform init: Provider plugin’lerini indirir, backend’i yapılandırır. Bir proje üzerinde ilk kez çalışırken veya provider eklediğinde çalıştırırsın.

terraform plan: Mevcut state ile konfigürasyonu karşılaştırır, yapılacak değişiklikleri gösterir. Hiçbir değişiklik yapmaz, sadece gösterir.

terraform apply: Plan’ı onaylayıp altyapı değişikliklerini uygular.

terraform destroy: Terraform ile oluşturulmuş tüm kaynakları siler. Dikkatli kullan!

# Projeyi baslat
terraform init

# Degisiklikleri incele
terraform plan

# Degisiklikleri uygula
terraform apply

# Otomatik onayla uygula (CI/CD icin)
terraform apply -auto-approve

# Belirli bir degiskeni gecici olarak override et
terraform plan -var="environment=staging"

# Var dosyasiyla calis
terraform plan -var-file="staging.tfvars"

Gerçek Dünya Senaryosu: Web Uygulaması Altyapısı

Şimdi gerçek bir senaryo üzerinden gidelim. Bir web uygulaması için temel AWS altyapısı kuracağız: VPC, subnet, security group ve EC2 instance.

Önce proje yapısını oluşturalım:

terraform-web-altyapi/
├── main.tf          # Ana kaynak tanimlari
├── variables.tf     # Degisken tanimlari
├── outputs.tf       # Output tanimlari
├── providers.tf     # Provider konfigurasyonu
├── terraform.tfvars # Degisken degerleri (gitignore'a ekle)
└── backend.tf       # Remote state konfigurasyonu

Şimdi main.tf dosyamızı yazalım:

# VPC olustur
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.project_name}-vpc"
    Environment = var.environment
  }
}

# Public subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidr
  availability_zone       = "${var.aws_region}a"
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-subnet"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-igw"
  }
}

# Route table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.project_name}-public-rt"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "web" {
  name        = "${var.project_name}-web-sg"
  description = "Web sunucu security group"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS"
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.ssh_allowed_cidrs
    description = "SSH - Sadece guvenli IP'lerden"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-web-sg"
  }
}

# EC2 Instance
resource "aws_instance" "web" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  key_name               = var.key_pair_name

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  user_data = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl enable nginx
    systemctl start nginx
  EOF

  tags = {
    Name        = "${var.project_name}-web"
    Environment = var.environment
  }
}

Remote State ile Ekip Çalışması

Tek kişilik projede local state işe yarar ama ekip olarak çalışınca sorunlar başlar. İki kişi aynı anda terraform apply çalıştırırsa state bozulabilir. Çözüm: Remote backend.

AWS S3 ve DynamoDB kullanarak remote state yapılandırması:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "sirket-terraform-state"
    key            = "web-altyapi/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

DynamoDB tablosunu ve S3 bucket’ı elle oluşturman gerekiyor (ya da ayrı bir bootstrap Terraform projesiyle). DynamoDB tablosu state locking için kullanılıyor, yani iki kişi aynı anda apply çalıştıramıyor.

# DynamoDB tablosunu olustur
aws dynamodb create-table 
  --table-name terraform-state-lock 
  --attribute-definitions AttributeName=LockID,AttributeType=S 
  --key-schema AttributeName=LockID,KeyType=HASH 
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 
  --region eu-west-1

# S3 bucket olustur ve versiyonlamayı etkinlestir
aws s3api create-bucket 
  --bucket sirket-terraform-state 
  --region eu-west-1 
  --create-bucket-configuration LocationConstraint=eu-west-1

aws s3api put-bucket-versioning 
  --bucket sirket-terraform-state 
  --versioning-configuration Status=Enabled

Modüler Yapı

Terraform projeler büyüdükçe her şeyi tek dosyaya yazmak yerine modüller kullanmak şart. Modüller, yeniden kullanılabilir altyapı bileşenleri oluşturmana yarıyor.

modules/
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── ec2/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── rds/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

Modülü kullanmak için:

# Ana main.tf'de modul cagrisi
module "vpc" {
  source = "./modules/vpc"

  project_name        = var.project_name
  environment         = var.environment
  vpc_cidr            = "10.0.0.0/16"
  public_subnet_cidr  = "10.0.1.0/24"
  private_subnet_cidr = "10.0.2.0/24"
}

module "web_server" {
  source = "./modules/ec2"

  project_name      = var.project_name
  environment       = var.environment
  vpc_id            = module.vpc.vpc_id
  subnet_id         = module.vpc.public_subnet_id
  instance_type     = var.instance_type
  ami_id            = var.ami_id
}

Modüller ayrıca Terraform Registry’den de alınabiliyor. HashiCorp’un resmi modülleri oldukça kaliteli:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "production-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = false
}

Ortam Yönetimi: Workspace vs Dizin Yapısı

Staging ve production ortamlarını yönetmenin iki popüler yolu var.

Workspace yaklaşımı:

# Staging workspace olustur
terraform workspace new staging
terraform workspace select staging
terraform apply -var-file="staging.tfvars"

# Production'a gec
terraform workspace select production
terraform apply -var-file="production.tfvars"

# Mevcut workspace'leri listele
terraform workspace list

Dizin yapısı yaklaşımı (genellikle daha tercih edilen):

environments/
├── staging/
│   ├── main.tf
│   ├── terraform.tfvars
│   └── backend.tf
└── production/
    ├── main.tf
    ├── terraform.tfvars
    └── backend.tf

Büyük ekiplerde dizin yapısı yaklaşımı daha güvenli çünkü staging ve production state’leri tamamen birbirinden izole.

CI/CD Entegrasyonu

Terraform’u GitLab CI’ya entegre etmek için basit bir örnek:

# .gitlab-ci.yml
stages:
  - validate
  - plan
  - apply

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/environments/production
  TF_VERSION: "1.6.0"

image:
  name: hashicorp/terraform:${TF_VERSION}
  entrypoint: [""]

before_script:
  - cd ${TF_ROOT}
  - terraform init

validate:
  stage: validate
  script:
    - terraform validate
    - terraform fmt -check

plan:
  stage: plan
  script:
    - terraform plan -out=tfplan
  artifacts:
    paths:
      - ${TF_ROOT}/tfplan
    expire_in: 1 week

apply:
  stage: apply
  script:
    - terraform apply -auto-approve tfplan
  when: manual
  only:
    - main

Sık Yapılan Hatalar ve İpuçları

Terraform fmt kullanmayı unutma. Kod stilini otomatik düzenliyor, PR’larda gereksiz tartışmalardan kurtarıyor:

# Tum dosyalari formatla
terraform fmt -recursive

# Sadece kontrol et, degistirme
terraform fmt -check -recursive

terraform validate ile konfigürasyonu kontrol et. Syntax hatalarını apply’dan önce yakalar:

terraform validate

Hassas verileri outputs’ta dikkatli kullan. Veritabanı şifresi gibi değerleri output’a koyuyorsan sensitive = true ekle:

output "db_password" {
  value     = random_password.db.result
  sensitive = true
}

Kaynakları taşırken dikkatli ol. Bir resource’u rename etmek Terraform’un onu silip yeniden oluşturmasına neden olabilir. terraform state mv komutunu kullan:

# Kaynagi yeni isimle taşi, silme/olusturma olmasin
terraform state mv aws_instance.web aws_instance.web_server

Sonuç

Terraform, modern altyapı yönetiminin vazgeçilmez bir parçası haline geldi. Manuel kurulum ve yapılandırma süreçlerinden kod tabanlı, versiyon kontrollü ve tekrarlanabilir bir altyapı yönetimine geçmek başlangıçta biraz zahmetli görünebilir. Ancak bir kez alıştığında “bunu daha önce neden kullanmadım?” diyeceksin.

Özetle öğrendiklerimize bakalım:

  • Terraform’un temel kavramları: provider, resource, variable, output ve state
  • Temel workflow: init, plan, apply, destroy
  • Remote backend ile ekip çalışmasının nasıl güvenli hale getirileceği
  • Modüler yapıyla büyük projelerin nasıl organize edileceği
  • CI/CD pipeline entegrasyonu
  • Sık yapılan hatalar ve kaçınma yolları

Başlangıç için küçük bir projeyle dene. Mesela mevcut sunucularından birini Terraform ile yeniden kur, state dosyasını S3’e taşı ve basit bir CI pipeline kur. Terraform’un gücünü gerçekten anlamak için üretim ortamında kullanman gerekiyor. Tabii dikkatli ve plan aşamasını atlamamak şartıyla.

Sonraki yazılarda Terraform ile Kubernetes yönetimi, advanced state manipülasyonu ve Terragrunt ile çoklu hesap yönetimini ele alacağız. Sorularınız olursa yorumlarda buluşalım.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir