Terraform Workspace ile Çoklu Ortam Yönetimi

Çok ortamlı altyapı yönetimi, her sysadmin’in er ya da geç karşılaştığı bir kabus haline gelir. Development, staging, production ortamları için ayrı ayrı Terraform kodları mı yazacaksın? Her birini ayrı dizinlerde mi tutacaksın? Yoksa tek bir kod tabanından tüm ortamları mı yöneteceksin? İşte tam bu noktada Terraform Workspace’ler devreye giriyor ve hayatını ciddi ölçüde kolaylaştırıyor.

Terraform Workspace Nedir?

Terraform’da workspace, aynı konfigürasyon dosyalarını kullanarak birden fazla bağımsız state dosyası yönetmenizi sağlayan bir mekanizmadır. Basitçe söylemek gerekirse, tek bir .tf dosyası seti yazıyorsun ama bu kodu dev, staging, prod gibi farklı ortamlar için ayrı ayrı çalıştırabiliyorsun. Her workspace’in kendi state dosyası var, birbirlerinden tamamen izole çalışıyorlar.

Terraform’u ilk kurduğunda otomatik olarak default adında bir workspace ile başlarsın. Bu workspace’i fark etmeden sürekli kullanıyorsundur. Ama işler büyüdükçe, “dev’de deneyeyim, sonra prod’a alayım” diye düşündüğünde workspace’lerin gerçek gücünü anlıyorsun.

State Dosyalarının İzolasyonu

Her workspace için Terraform ayrı bir state dosyası tutar. Local backend kullanıyorsan terraform.tfstate.d/ dizini altında her workspace için ayrı bir klasör oluşturulur:

terraform.tfstate.d/
├── dev/
│   └── terraform.tfstate
├── staging/
│   └── terraform.tfstate
└── prod/
    └── terraform.tfstate

Remote backend kullandığında (S3, Terraform Cloud vb.) her workspace için ayrı bir state path oluşturulur. Bu izolasyon sayesinde dev ortamında yaptığın değişiklik, prod’un state’ini hiçbir şekilde etkilemez.

Temel Workspace Komutları

Workspace yönetimi için kullanacağın komutlar oldukça basit. Hepsini ezberlemenize gerek yok ama sık kullandıklarınızı mutlaka bilmelisiniz:

# Mevcut workspace listesini gör
terraform workspace list

# Yeni workspace oluştur
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# Workspace'ler arası geçiş yap
terraform workspace select dev

# Aktif workspace'i göster
terraform workspace show

# Workspace sil (önce kaynakları destroy etmen gerekebilir)
terraform workspace delete staging

terraform workspace list komutunu çalıştırdığında aktif workspace’in başında * işareti görürsün:

$ terraform workspace list
  default
* dev
  staging
  prod

Bu kadar basit. Komutların açıklamaları:

  • list: Tüm workspace’leri listeler, aktif olanı * ile gösterir
  • new: Yeni bir workspace oluşturur ve otomatik olarak o workspace’e geçer
  • select: Belirtilen workspace’e geçiş yapar
  • show: Sadece aktif workspace adını basar
  • delete: Workspace’i siler, içinde kaynak varsa önce destroy etmen gerekir

Gerçek Dünya Senaryosu: AWS’de Multi-Ortam Altyapısı

Diyelim ki bir e-ticaret şirketinde çalışıyorsun ve AWS üzerinde üç ortam yönetmen gerekiyor: dev, staging, prod. Her ortamın farklı instance boyutları, farklı replica sayıları ve farklı domain isimleri olacak. Bunu workspace ile nasıl yaparsın?

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

mkdir -p terraform-multienv/{modules/ec2,environments}
cd terraform-multienv
touch main.tf variables.tf outputs.tf terraform.tfvars

Variables ve Locals ile Ortam Bazlı Konfigürasyon

Workspace’lerin gerçek gücü, terraform.workspace değişkenini kullanarak dinamik konfigürasyon yapabilmenden geliyor. İşte bu yüzden workspace’leri seviyorum:

# variables.tf
variable "region" {
  description = "AWS region"
  type        = string
  default     = "eu-west-1"
}

variable "project_name" {
  description = "Proje adi"
  type        = string
  default     = "ecommerce"
}

# main.tf - Ortam bazli konfigürasyonlar
locals {
  env = terraform.workspace

  # Her ortam icin farkli instance boyutlari
  instance_sizes = {
    dev     = "t3.micro"
    staging = "t3.medium"
    prod    = "t3.large"
  }

  # Her ortam icin farkli instance adetleri
  instance_counts = {
    dev     = 1
    staging = 2
    prod    = 4
  }

  # RDS instance boyutlari
  db_instance_classes = {
    dev     = "db.t3.micro"
    staging = "db.t3.medium"
    prod    = "db.r5.large"
  }

  # Ortama göre tag'ler
  common_tags = {
    Environment = local.env
    Project     = var.project_name
    ManagedBy   = "Terraform"
    Workspace   = terraform.workspace
  }

  # Seçili degerleri locals'a ata
  current_instance_size  = local.instance_sizes[local.env]
  current_instance_count = local.instance_counts[local.env]
  current_db_class       = local.db_instance_classes[local.env]
}

Bu yapıyla dev workspace’indeyken t3.micro instance ayağa kalkacak, prod‘a geçince t3.large kullanılacak. Aynı kod, farklı sonuçlar.

EC2 ve RDS Kaynakları Tanımlama

# ec2.tf
resource "aws_instance" "web" {
  count         = local.current_instance_count
  ami           = data.aws_ami.amazon_linux.id
  instance_type = local.current_instance_size

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-web-${local.env}-${count.index + 1}"
    Role = "WebServer"
  })

  lifecycle {
    # Prod ortaminda kazara silme engeli
    prevent_destroy = local.env == "prod" ? true : false
  }
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# rds.tf
resource "aws_db_instance" "main" {
  identifier        = "${var.project_name}-db-${local.env}"
  engine            = "postgres"
  engine_version    = "14.9"
  instance_class    = local.current_db_class
  allocated_storage = local.env == "prod" ? 100 : 20
  db_name           = "${var.project_name}_${local.env}"
  username          = "dbadmin"
  password          = var.db_password

  # Sadece prod'da multi-az
  multi_az = local.env == "prod" ? true : false

  # Sadece prod'da silme koruması
  deletion_protection = local.env == "prod" ? true : false

  # Dev'de backup tutma, prod'da 7 gun
  backup_retention_period = local.env == "prod" ? 7 : 0

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-db-${local.env}"
  })
}

S3 Backend ile Remote State Yönetimi

Production ortamında local state kullanmak intihar sayılır. S3 backend ile remote state kuruyoruz:

# backend.tf
terraform {
  required_version = ">= 1.5.0"

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

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

S3 backend kullanırken workspace’ler otomatik olarak şu yolları kullanır:

  • default workspace: ecommerce/terraform.tfstate
  • dev workspace: env:/dev/ecommerce/terraform.tfstate
  • prod workspace: env:/prod/ecommerce/terraform.tfstate

Bu yapıyı oluşturduktan sonra workspace başlatma süreci:

# Backend'i initialize et
terraform init

# Workspace'leri oluştur
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# Dev ortamina gecis yap ve plan gör
terraform workspace select dev
terraform plan

# Onaylarsan uygula
terraform apply

CI/CD Pipeline’a Entegrasyon

GitLab CI veya GitHub Actions ile workspace’leri otomatize etmek, DevOps süreçlerinin tam merkezine workspace’leri oturtur. İşte basit bir GitLab CI örneği:

# .gitlab-ci.yml
variables:
  TF_VERSION: "1.6.0"
  AWS_DEFAULT_REGION: "eu-west-1"

stages:
  - validate
  - plan
  - apply

.terraform_base:
  image: hashicorp/terraform:${TF_VERSION}
  before_script:
    - terraform init -input=false
    - terraform workspace select ${TF_WORKSPACE} || terraform workspace new ${TF_WORKSPACE}

terraform:validate:
  extends: .terraform_base
  stage: validate
  variables:
    TF_WORKSPACE: "dev"
  script:
    - terraform validate
    - terraform fmt -check
  only:
    - merge_requests

terraform:plan:dev:
  extends: .terraform_base
  stage: plan
  variables:
    TF_WORKSPACE: "dev"
  script:
    - terraform plan -out=tfplan-dev -input=false
  artifacts:
    paths:
      - tfplan-dev
    expire_in: 1 hour
  only:
    - develop

terraform:apply:dev:
  extends: .terraform_base
  stage: apply
  variables:
    TF_WORKSPACE: "dev"
  script:
    - terraform apply -auto-approve -input=false tfplan-dev
  dependencies:
    - terraform:plan:dev
  only:
    - develop
  when: manual

terraform:plan:prod:
  extends: .terraform_base
  stage: plan
  variables:
    TF_WORKSPACE: "prod"
  script:
    - terraform plan -out=tfplan-prod -input=false
  artifacts:
    paths:
      - tfplan-prod
    expire_in: 1 hour
  only:
    - main

terraform:apply:prod:
  extends: .terraform_base
  stage: apply
  variables:
    TF_WORKSPACE: "prod"
  script:
    - terraform apply -auto-approve -input=false tfplan-prod
  dependencies:
    - terraform:plan:prod
  only:
    - main
  when: manual

Bu pipeline şöyle çalışır:

  • develop branch’ine push geldiğinde dev ortamı için plan çıkar, manuel onay sonrası apply eder
  • main branch’ine push geldiğinde prod için plan çıkar, manuel onay bekler
  • Merge request’lerde sadece validate ve format check yapar

Workspace ile Ortak Tuzaklar ve Çözümleri

Yanlış Workspace’de Apply Yapmak

En sık yapılan hata bu. Dev’de test edeceksin, prod workspace’indesin farkında değilsin ve apply basıyorsun. Bunu önlemek için prompt’una workspace bilgisini ekleyebilirsin:

# ~/.bashrc veya ~/.zshrc dosyasına ekle
tf_workspace_info() {
  if [ -d .terraform ]; then
    workspace=$(terraform workspace show 2>/dev/null)
    if [ -n "$workspace" ]; then
      echo "[TF: $workspace]"
    fi
  fi
}

# PS1'e ekle (bash)
PS1='u@h:w $(tf_workspace_info)$ '

Artık terminal prompt’unda hangi workspace’de olduğunu göreceksin.

Bir diğer güvenlik önlemi olarak, prod workspace’inde çalışırken ekstra onay isteyen bir wrapper script yazabilirsin:

#!/bin/bash
# /usr/local/bin/tf-safe adinda kaydet ve chmod +x yap

CURRENT_WORKSPACE=$(terraform workspace show)

if [ "$CURRENT_WORKSPACE" = "prod" ]; then
  echo "DIKKAT: Su anda PROD workspace'indesiniz!"
  echo "Devam etmek istediginizi yazmak icin 'prod-onayla' girin:"
  read confirmation
  if [ "$confirmation" != "prod-onayla" ]; then
    echo "Iptal edildi."
    exit 1
  fi
fi

terraform "$@"

Workspace’e Göre Provider Konfigürasyonu

Farklı ortamlar için farklı AWS hesapları veya region kullanmak isteyebilirsin. Bu durumda alias kullanman gerekir:

# providers.tf
locals {
  account_ids = {
    dev     = "111111111111"
    staging = "222222222222"
    prod    = "333333333333"
  }

  assume_role_arns = {
    dev     = "arn:aws:iam::111111111111:role/TerraformRole"
    staging = "arn:aws:iam::222222222222:role/TerraformRole"
    prod    = "arn:aws:iam::333333333333:role/TerraformRole"
  }
}

provider "aws" {
  region = var.region

  assume_role {
    role_arn = local.assume_role_arns[terraform.workspace]
  }

  default_tags {
    tags = local.common_tags
  }
}

Bu yaklaşımla her workspace farklı bir AWS hesabına deploy eder. Çok hesaplı ortam yönetimi için ideal.

Workspace Limitleri ve Alternatifler

Workspace her derde deva değil. Bazı durumlarda workspace yerine farklı bir yaklaşım tercih edilebilir.

Workspace kullanmayı düşün:

  • Ortamlar yapısal olarak aynı ama konfigürasyon değerleri farklıysa
  • Küçük-orta ölçekli projelerde
  • Hızlı prototipler için
  • CI/CD pipeline’larında branch bazlı deployment için

Workspace yerine ayrı dizin veya repo düşün:

  • Ortamlar yapısal olarak ciddi ölçüde farklıysa (prod’da var olmayan servisler dev’de var gibi)
  • Farklı ekiplerin farklı ortamlara erişmesi gerekiyorsa
  • Compliance gereklilikleri nedeniyle tam izolasyon gerekiyorsa
  • Çok büyük ve karmaşık altyapılarda

Terragrunt kullanıyorsan workspace yerine Terragrunt’ın kendi ortam yönetim yapısını tercih edebilirsin. Ama bu ayrı bir yazı konusu.

Outputs ve Workspace Farkındalığı

Output’larınızı da workspace’e göre düzenlemenizi öneririm. Özellikle başka Terraform modülleri veya scriptler bu output’ları kullanıyorsa ortamı bilmek önemlidir:

# outputs.tf
output "environment_info" {
  description = "Ortam bilgileri"
  value = {
    workspace      = terraform.workspace
    instance_count = local.current_instance_count
    instance_type  = local.current_instance_size
    db_class       = local.current_db_class
  }
}

output "web_instance_ips" {
  description = "Web sunucu IP'leri"
  value       = aws_instance.web[*].private_ip
}

output "db_endpoint" {
  description = "Veritabani endpoint'i"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

output "deployment_summary" {
  description = "Deployment ozeti"
  value       = <<-EOT
    Ortam: ${terraform.workspace}
    Instance Sayisi: ${local.current_instance_count}
    Instance Tipi: ${local.current_instance_size}
    DB Sinifi: ${local.current_db_class}
    Multi-AZ: ${local.env == "prod" ? "Evet" : "Hayir"}
  EOT
}

Tüm workspace’lerin durumunu tek bir scriptle kontrol etmek istiyorsan:

#!/bin/bash
# check-all-workspaces.sh

WORKSPACES=("dev" "staging" "prod")
ORIGINAL_WS=$(terraform workspace show)

echo "=== Tum Workspace Durumu ==="
for ws in "${WORKSPACES[@]}"; do
  echo ""
  echo "--- $ws ---"
  terraform workspace select "$ws" > /dev/null 2>&1
  terraform output environment_info 2>/dev/null || echo "Output alinamadi"
done

# Orijinal workspace'e don
terraform workspace select "$ORIGINAL_WS" > /dev/null 2>&1
echo ""
echo "=== $ORIGINAL_WS workspace'ine donuldu ==="

State Yönetimi ve Bakım

Workspace’ler büyüdükçe state yönetimi kritik hale gelir. Düzenli bakım alışkanlıkları edinmek şart:

# Belirli bir workspace'in state'ini listele
terraform workspace select dev
terraform state list

# State'i yedekle (prod için önemli)
terraform workspace select prod
terraform state pull > prod-state-backup-$(date +%Y%m%d).json

# Drift detection - altyapı gerçekten plana uygun mu?
terraform workspace select prod
terraform plan -detailed-exitcode
# Exit code 0: Degisiklik yok
# Exit code 1: Hata
# Exit code 2: Degisiklik var (drift var demek)

# Kaynak import - manuel yaratilan kaynagi state'e al
terraform workspace select dev
terraform import aws_instance.web[0] i-0123456789abcdef0

State lock sorunlarıyla karşılaşırsan:

# DynamoDB'deki lock'u zorla kaldir (dikkatli kullan!)
terraform force-unlock LOCK-ID

# Lock ID'yi hata mesajindan alabilirsin
# Ornek: Error acquiring the state lock: Lock Info:
#   ID: 12345678-1234-1234-1234-123456789012

Sonuç

Terraform workspace’ler, altyapı kodunu DRY (Don’t Repeat Yourself) prensibine uygun tutmanın en pratik yollarından biri. Tek bir kod tabanıyla dev, staging, prod ortamlarını yönetebilmek hem bakım maliyetini düşürür hem de ortamlar arası tutarsızlıkları ortadan kaldırır. “Dev’de çalışıyor ama prod’da çalışmıyor” problemlerinin önemli bir kısmı farklı ortamlarda farklı kodların çalışmasından kaynaklanır. Workspace bunu köklü şekilde çözer.

Dikkat edilmesi gereken en kritik nokta, hangi workspace’de olduğunu her zaman bilmek. Bu yazıda gösterdiğim prompt değişikliği ve tf-safe wrapper script gibi güvenlik önlemlerini ciddiye alın. Prod’da yanlış apply kabusu herkesin başına bir kez gelir, ikinci kez gelmemesi için önlem almak lazım.

CI/CD pipeline entegrasyonuyla birlikte workspace’ler gerçek gücünü gösterir. Branch bazlı deployment, otomatik ortam seçimi ve manuel onay adımlarıyla hem hız hem de güvenlik sağlayabilirsiniz. Altyapınız büyüdükçe workspace’lerin sınırlarına da dikkat edin ve gerektiğinde Terragrunt veya ayrı dizin/repo yaklaşımına geçmekten çekinmeyin. Doğru araç, doğru problem için.

Bir yanıt yazın

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