Terraform Cloud ile Çoklu Azure Subscription
Selamlar, bu yazımda kurumsal Azure ortamlarında neredeyse herkesin başına gelen bir konuya bakacağız: birden fazla subscription'ı Terraform Cloud workspace'leri ile nasıl düzgün yönetiriz? Tipik bir senaryoyu hayal edin; production için bir subscription, staging için bir tane, networking ortak servislerinin oturduğu ayrı bir tane, belki güvenlik ekibinin kendi alanı. Hepsini lokal makinedeki state dosyalarıyla ya da sıradan bir CI/CD pipeline'ı ile yönetmeye kalkınca işler hızlıca dağılıyor. Hadi bunu nasıl topluyoruz konuşalım.
Workspace ile izolasyon neden ihtiyaç?
Çoklu subscription'ın temel sıkıntısı şu: her subscription kendi kimliğini, kendi state'ini ve çoğu zaman kendi onay akışını ister. Terraform Cloud'un workspace modeli tam buraya oturuyor. Her workspace kendi değişkenleriyle, kendi state'iyle ve kendi izinleriyle yaşıyor. Yani siz compute-prod workspace'ine Contributor yetkili bir service principal verirken, networking-staging workspace'i tamamen ayrı bir kimlikle koşabiliyor.
Bence buradaki en kritik kazanç state izolasyonu. Aynı state dosyasında 3 subscription'ın kaynaklarını tutmak, bir gün bir refactor sırasında yanlış kaynağı yok etmenize sebep oluyor. Workspace ayırınca o riski büyük oranda kapatıyoruz.
Adlandırma ve klasör düzeni
Workspace adlarını {bilesen}-{ortam} deseninde tutmayı tercih ediyorum: networking-prod, compute-prod, database-staging gibi. Hem gözle taranabiliyor, hem de Terraform tarafında for_each ile yönetmek kolaylaşıyor.
Repo tarafında da her bileşene ayrı bir working directory açıp workspace'leri trigger_prefixes ile o klasöre bağlıyorum:
infrastructure/
networking/
compute/
database/
modules/
networking/
compute/
Böylece networking/ altındaki bir değişiklik sadece networking workspace'lerini tetikliyor, compute boşuna plan koşmuyor.
Workspace'leri Terraform ile yonetmek
Burada işin biraz meta tarafı var: Terraform Cloud workspace'lerini yine Terraform ile tanımlıyorum. tfe provider'ı tam bunun için. Aşağıdaki gibi bir local map ile başlıyoruz:
locals {
workspaces = {
"networking-prod" = {
subscription = "production"
working_dir = "infrastructure/networking"
auto_apply = false
trigger_prefixes = ["infrastructure/networking/", "modules/networking/"]
}
"compute-prod" = {
subscription = "production"
working_dir = "infrastructure/compute"
auto_apply = false
trigger_prefixes = ["infrastructure/compute/", "modules/compute/"]
}
"networking-staging" = {
subscription = "staging"
working_dir = "infrastructure/networking"
auto_apply = true
trigger_prefixes = ["infrastructure/networking/", "modules/networking/"]
}
}
}
resource "tfe_workspace" "this" {
for_each = local.workspaces
name = each.key
organization = var.tfc_organization
working_directory = each.value.working_dir
auto_apply = each.value.auto_apply
trigger_prefixes = each.value.trigger_prefixes
}
Production için auto_apply = false koyduğumuza dikkat edin. Açıkçası ben bu kuralı bir kez kırdım, sonra production'da yanlışlıkla geçen bir plan yüzünden gece gece düzeltme yapmak zorunda kaldım. O günden beri prod workspace'lerinde otomatik apply asla açmıyorum.
OIDC ile federated kimlik
Service principal secret'larını workspace değişkeni olarak tutmak çalışıyor ama şahsi kanaatim, 2026'da artık OIDC üzerinden federated identity tercih edilmeli. Terraform Cloud, Azure AD'ye karşı kısa ömürlü token üretiyor ve ARM_CLIENT_SECRET derdi tamamen ortadan kalkıyor:
resource "tfe_variable" "use_oidc" {
for_each = tfe_workspace.this
key = "TFC_AZURE_PROVIDER_AUTH"
value = "true"
category = "env"
workspace_id = each.value.id
}
resource "tfe_variable" "run_phase_audience" {
for_each = tfe_workspace.this
key = "TFC_AZURE_RUN_CLIENT_ID"
value = var.azure_subscriptions[local.workspaces[each.key].subscription].client_id
category = "env"
workspace_id = each.value.id
}
Azure tarafında her subscription için bir App Registration açıp federated credential olarak Terraform Cloud workspace'ini ekliyorsunuz. Sırlar repo'da ya da TFC'de durmuyor, dönmüyor da; her plan kendi token'ını alıyor.
Run trigger ile workspace zincirleme
Workspace'ler birbirine bağımlı olunca, mesela compute networking'in çıktısına ihtiyaç duyunca, run trigger devreye giriyor:
resource "tfe_run_trigger" "compute_after_networking_prod" {
workspace_id = tfe_workspace.this["compute-prod"].id
sourceable_id = tfe_workspace.this["networking-prod"].id
}
Aşağı akıştaki workspace, yukarıdakinin çıktısını tfe_outputs ile okuyor:
data "tfe_outputs" "networking" {
organization = "my-company"
workspace = "networking-${var.environment}"
}
locals {
subnet_ids = data.tfe_outputs.networking.values.subnet_ids
}
Sik karsilasilan tuzaklar
- Tek workspace'te birden fazla subscription:
ARM_SUBSCRIPTION_IDruntime'da değiştirilebilir gibi görünür ama state karışır, drift detection saçmalar. Her subscription kendi workspace'inde dursun. - Variable set'leri dağıtmak: Ortak tag'leri ya da
TF_VAR_companygibi şirket geneli değerleri her workspace'e tek tek girmek bakım kabusudur. Bunun için variable set var, kullanın. - trigger_prefixes vermemek: Tek bir repo'da çoklu workspace varsa her commit her workspace'i tetikler. CI dakikalarınız uçar, gereksiz plan'lar Slack'i doldurur.
- OIDC'siz uzun ömürlü secret: Service principal secret'ı 2 yıllık ömürle workspace'te unutmak hâlâ sık görülen bir hata. Rotasyon planınız yoksa OIDC'ye geçin.
Kapanis
Terraform Cloud workspace'leri çoklu Azure subscription dünyasında işi gerçekten toparlıyor; her workspace kendi state'i, kendi kimliği ve kendi izinleriyle ayağa kalkıyor. Bana sorarsanız doğru başlangıç şu: bileşen başına ortam başına workspace açın, OIDC ile kimliği federe edin, bağımlılıkları run trigger ile yönetin. İhtiyaç büyüdükçe granülariteyi ayarlarsınız. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
