Terraform Durumu: S3/GCS ile Uzak Arka Uç, Kilitleme ve İçe Aktarma
Terraform'un üretimde en çok kazaya neden olan tek bir yönü varsa o da
ve devlet yönetimi. Ekiplerin bozuk durumları onarmak için saatler harcadığını gördüm.
farklı konfigürasyonlar çünkü bunu iki geliştirici yaptı apply paralel olarak,
Bulutta bulunan ancak eyalette bulunmayan “hayalet” kaynaklar. Bütün bu problemler
ortak bir nedenleri var: kilitlemeden yerel durumu belirtmek.
Bu kılavuz teknik ve pratiktir: AWS için DynamoDB kilitlemeli bir S3 arka ucu yapılandıracağız.
Google Cloud için GCS arka ucu, ortamları ayırmak için çalışma alanlarından bahsedeceğiz
ve nasıl olacağını göreceğiz mevcut kaynakları içe aktar kullanarak kesinti olmadan
blok import Terraform 1.5'te tanıtıldı (nihayet bildirimsel ve güvenli).
Ne Öğreneceksiniz
- Ekiplerde yerel durum neden tehlikelidir ve uzak arka uca nasıl geçilir?
- AWS'de DynamoDB kilitlemeli S3 arka ucunu ayarlayın (üretime hazır)
- Google Cloud Platform'da GCS arka ucunu yapılandırma
- Çalışma Alanı Terraformu: ortamları ayırmak için desen
- Mevcut varlıkları kilitlemeyle içe aktarın
import(Terraform 1.5+) - Acil durum işlemleri: durum manipülasyonu, zorla kilit açma, yedekleme
- Kısmi arka uç yapılandırması: kimlik bilgilerini sabit kodlama olmadan yönetin
Uzak Arka Uç Neden Temeldir?
Dosya terraform.tfstate local'in bir takımda dört temel sorunu vardır:
-
Kilitleme yok: iki geliştirici çalıştırırsa
terraform applyaynı anda hem aynı durumu okur hem de kısmi değişiklikleri yazar, bozuk duruma ve yinelenen kaynaklara yol açar. - Paylaşılmadı: her geliştiricinin kendi yerel kopyası vardır. En son kim var versiyon? En son başvuruyu kim yaptı? Bilmek imkansız.
- Sırları içerir: durum genellikle hassas değerler içerir (RDS şifresi, API anahtarları). Git'e asla girmez.
- Geçmiş yok: Kimin neyi, ne zaman yaptığına dair bir denetim izi yok.
Uzak arka uç tüm bu sorunları çözer: merkezi durum, atomik kilitleme, kullanımda olmayan şifreleme ve geri alma için sürüm oluşturma.
Bootstrap: Terraform ile Arka Uç Kaynakları Oluşturma
Terraform arka ucunun paradoksu, AWS kaynakları (S3 klasörü + DynamoDB tablosu) oluşturmanız gerekmesidir. Önce arka ucu yapılandırabilmek. Çözüm bir mini projeyle önyükleme yapmaktır yerel arka ucu kullanan ayrı.
# bootstrap/main.tf — crea le risorse per il backend remoto
# Questo progetto usa il backend locale (terraform.tfstate nella directory)
# Committalo nel repo come "infra/bootstrap/"
terraform {
required_version = ">= 1.8.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# NOTA: qui non c'e backend block — usa il file locale
}
provider "aws" {
region = "eu-west-1"
}
locals {
name = "acme"
environment = "global"
}
# S3 Bucket per lo state
resource "aws_s3_bucket" "terraform_state" {
bucket = "${local.name}-terraform-state"
# Protezione contro cancellazione accidentale
lifecycle {
prevent_destroy = true
}
tags = {
Name = "${local.name}-terraform-state"
ManagedBy = "Terraform"
Environment = local.environment
}
}
# Versioning: ogni apply crea una nuova versione del file di state
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Encryption at rest: obbligatorio per state con segreti
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true # Riduce i costi KMS del 99%
}
}
# Blocca accesso pubblico: lo state NON deve essere pubblico
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Policy del bucket: nega qualsiasi accesso non-TLS
resource "aws_s3_bucket_policy" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyNonTLS"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.terraform_state.arn,
"${aws_s3_bucket.terraform_state.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}
# DynamoDB table per il locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "${local.name}-terraform-locks"
billing_mode = "PAY_PER_REQUEST" # Nessun costo fisso
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
# Protezione contro cancellazione
lifecycle {
prevent_destroy = true
}
tags = {
Name = "${local.name}-terraform-locks"
ManagedBy = "Terraform"
Environment = local.environment
}
}
# Output: valori da copiare nel backend block dei progetti
output "state_bucket_name" {
value = aws_s3_bucket.terraform_state.id
description = "Nome del bucket S3 per lo state Terraform"
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.terraform_locks.name
description = "Nome della tabella DynamoDB per il locking"
}
output "aws_region" {
value = "eu-west-1"
description = "Region AWS dove sono create le risorse del backend"
}
S3 Arka Uçunu Yapılandırma
Önyükleme kaynaklarınızı oluşturduktan sonra projelerinizde arka ucu ayarlayın.
Blok backend Terraform değişkenlerini (ve bilinen bir sınırlamayı) desteklemez,
öyleyse kullan kısmi arka uç yapılandırması kimlik bilgilerini ayırmak için.
# versions.tf — configurazione backend S3
terraform {
required_version = ">= 1.8.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Backend con partial configuration (valori statici non-sensibili)
backend "s3" {
bucket = "acme-terraform-state"
key = "environments/prod/networking/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "acme-terraform-locks"
encrypt = true
# NON mettere access_key e secret_key qui!
# Usa variabili di ambiente o IAM Role
}
}
# La struttura raccomandata per il "key" (path del file):
# {account}/{environment}/{stack}/terraform.tfstate
#
# Esempi:
# "123456789/dev/networking/terraform.tfstate"
# "123456789/prod/eks-cluster/terraform.tfstate"
# "123456789/shared/monitoring/terraform.tfstate"
# Inizializzazione con partial backend config
# (passa i valori mancanti come -backend-config)
terraform init \
-backend-config="bucket=acme-terraform-state" \
-backend-config="key=environments/dev/networking/terraform.tfstate" \
-backend-config="region=eu-west-1" \
-backend-config="dynamodb_table=acme-terraform-locks"
# Oppure con file di backend config
# backend.hcl (NON committare se contiene credenziali)
# bucket = "acme-terraform-state"
# key = "environments/dev/networking/terraform.tfstate"
# region = "eu-west-1"
# dynamodb_table = "acme-terraform-locks"
terraform init -backend-config=backend.hcl
# Migra da backend locale a remoto
terraform init -migrate-state
# Terraform chiede conferma prima di copiare il state locale su S3
DynamoDB Kilidi
Ne zaman terraform apply ve çalıştırıldığında DynamoDB'de bir satır oluşturulur
ile LockID = {bucket}/{key}. İşlem aniden durdurulursa
(öldürme, çarpma), kilit açılmayabilir. Kilit açmayı zorlamak için:
terraform force-unlock {LOCK_ID}
# Il LOCK_ID e visibile nel messaggio di errore quando Terraform trova il lock
Amerika force-unlock yalnızca eminsen başka kimsenin olmadığını
başvuru sürüyor. Bir başvuru devam ederken engellemenin kaldırılması durumu bozabilir.
Google Cloud Platform için GCS Arka Ucu
# versions.tf — backend GCS
terraform {
required_version = ">= 1.8.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
backend "gcs" {
bucket = "acme-terraform-state-eu"
prefix = "environments/prod/networking"
# prefix = "environments/{environment}/{stack}"
# Il file effettivo sara: {prefix}/default.tfstate
}
}
# Crea il bucket GCS per lo state (bootstrap)
resource "google_storage_bucket" "terraform_state" {
name = "acme-terraform-state-eu"
location = "EU"
force_destroy = false
# Versioning per rollback
versioning {
enabled = true
}
# Encryption con CMEK (opzionale ma consigliato)
# encryption {
# default_kms_key_name = google_kms_crypto_key.terraform_state.id
# }
# Policy di retention: mantieni le versioni per 30 giorni
lifecycle_rule {
condition {
num_newer_versions = 5
with_state = "ARCHIVED"
}
action {
type = "Delete"
}
}
}
# Blocca l'accesso pubblico al bucket
resource "google_storage_bucket_iam_binding" "terraform_state_private" {
bucket = google_storage_bucket.terraform_state.name
role = "roles/storage.admin"
members = [
"serviceAccount:terraform@acme-project.iam.gserviceaccount.com"
]
}
Çalışma Alanı Terraformu: Ortamların Ayrılması
Terraform çalışma alanları, ayrı durum dosyalarını korumanıza olanak tanır.
aynı HCL konfigürasyonu. Her çalışma alanının arka uçta kendi durum dosyası vardır:
env:/{workspace_name}/terraform.tfstate.
# Gestione dei workspace
terraform workspace list
# * default
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace list
# default
# * dev
# staging
# prod
terraform workspace select prod
terraform workspace show # stampa il nome del workspace corrente
# Cancella un workspace (deve essere vuoto)
terraform workspace delete staging
# Uso del workspace nella configurazione HCL
locals {
# Recupera il nome del workspace corrente
workspace = terraform.workspace
# Configurazioni specifiche per workspace
workspace_config = {
dev = {
instance_type = "t3.micro"
min_instances = 1
max_instances = 2
enable_monitoring = false
rds_class = "db.t3.micro"
multi_az = false
}
staging = {
instance_type = "t3.small"
min_instances = 1
max_instances = 3
enable_monitoring = true
rds_class = "db.t3.small"
multi_az = false
}
prod = {
instance_type = "t3.medium"
min_instances = 2
max_instances = 6
enable_monitoring = true
rds_class = "db.t3.medium"
multi_az = true
}
}
# Accede alla configurazione del workspace corrente
# con fallback a "dev" se il workspace non e nella mappa
current_config = lookup(local.workspace_config, local.workspace, local.workspace_config["dev"])
# Naming con workspace nel prefisso
name_prefix = "${local.workspace}-${var.project_name}"
}
# Usa i valori dalla configurazione del workspace
resource "aws_autoscaling_group" "web" {
min_size = local.current_config.min_instances
max_size = local.current_config.max_instances
desired_capacity = local.current_config.min_instances
# ...
}
resource "aws_db_instance" "main" {
instance_class = local.current_config.rds_class
multi_az = local.current_config.multi_az
# ...
}
Çalışma Alanı ve Ayrı Dizinler: Ne Zaman Kullanılmalı?
Çalışma alanları ortamlara uygundur yapısal olarak aynı bu farklı yalnızca ölçeklendirme için (dev/staging/prod). Ortamların farklı mimarileri varsa (örneğin, ürünün bir VPN'si var, geliştiricinin yok), kullanın ayrı dizinler modülleri yeniden kullananlar. Karmaşık altyapılara sahip birçok ekip her zaman ayrı dizinleri tercih eder Maksimum netlik için.
Mevcut Kaynakları İçe Aktarma Bloğuyla İçe Aktarma
Mevcut bir organizasyonda Terraform'u benimserken en sık karşılaşılan durumlardan biri,
Manuel olarak veya diğer araçlarla oluşturulan kaynakları yönetin. Terraform 1.5'ten önce içe aktarma şu şekildeydi:
sadece zorunluluk (terraform import CLI), riskli ve doğrulanamaz. engellemek import (1.5'te GA, 1.7'de geliştirildi
import generate) ve açıklayıcı ve kendinden emin.
# main.tf — import dichiarativo (Terraform 1.5+)
# Step 1: definisci il blocco import
import {
# ID della risorsa nel cloud provider
id = "vpc-0123456789abcdef0"
# Punta alla risorsa HCL che vuoi associare
to = aws_vpc.main
}
# Step 2: scrivi la configurazione HCL della risorsa
# (puoi usare "terraform plan -generate-config-out=generated.tf" per auto-generarla)
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "prod-main-vpc"
Environment = "prod"
ManagedBy = "Terraform" # Aggiunta da noi
}
}
# Step 3: terraform plan verifica che lo state da importare
# corrisponda alla configurazione HCL.
# Se ci sono differenze, Terraform le evidenzia come "~ to change"
# Import multiplo: importa un intero gruppo di risorse
import {
id = "subnet-0abc123"
to = aws_subnet.public[0]
}
import {
id = "subnet-0def456"
to = aws_subnet.public[1]
}
# Import con for_each (Terraform 1.7+)
locals {
existing_subnets = {
"public-1" = "subnet-0abc123"
"public-2" = "subnet-0def456"
}
}
import {
for_each = local.existing_subnets
id = each.value
to = aws_subnet.public[each.key]
}
# Workflow completo di import
# 1. Genera automaticamente la configurazione HCL dalla risorsa esistente
# (Terraform 1.5+ con -generate-config-out)
terraform plan -generate-config-out=generated_imports.tf
# 2. Rivedi il file generato: contiene la configurazione della risorsa
# come Terraform la vede nel cloud provider
cat generated_imports.tf
# 3. Copia e adatta la configurazione nel tuo main.tf
# (il file generated non e perfetto, va revisionato)
# 4. Esegui plan per verificare che l'import sia corretto
terraform plan
# Se il plan mostra "Plan: 0 to add, 0 to change, 0 to destroy"
# con note "Will import", l'import e perfetto
# 5. Apply: esegue l'import effettivo e aggiorna lo state
terraform apply
# 6. Rimuovi i blocchi import dal codice dopo l'apply
# (non sono piu necessari una volta che le risorse sono nello state)
Devlet Manipülasyonu: Acil Durum Operasyonları
Devleti doğrudan manipüle etmenin gerekli olduğu durumlar vardır. Bu işlemler güçlü ve risklidir: bunları her zaman önleyici bir yedeklemeyle gerçekleştirin.
# SEMPRE fare backup prima di operazioni sullo state
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
# Rimuove una risorsa dallo state (la risorsa rimane nel cloud)
# Uso: quando vuoi che Terraform "dimentichi" una risorsa
# senza cancellarla (es. passi a gestirla con un altro tool)
terraform state rm aws_instance.legacy_server
# Sposta una risorsa da un indirizzo a un altro nello state
# Uso: refactoring del codice senza distruggere le risorse
terraform state mv aws_instance.web aws_instance.web_new
terraform state mv 'aws_subnet.public[0]' 'aws_subnet.public["eu-west-1a"]'
# Mostra i dettagli JSON di una risorsa nello state
terraform state show aws_vpc.main
# Pull dello state remoto in locale (utile per ispezione)
terraform state pull > current.tfstate
# Push di uno state locale sul backend remoto
# PERICOLOSO: sovrascrive lo state remoto senza conferma
# Usare solo per recovery da state corrotto
terraform state push recovered.tfstate
# Refresh: aggiorna lo state leggendo lo stato reale del cloud
# (ora deprecato a favore di terraform apply -refresh-only)
terraform apply -refresh-only
Durum Bölümlemesi: Büyük Ekipler İçin Stratejiler
Büyük altyapılara sahip organizasyonlarda tek bir monolitik duruma sahip olmak,
bir sorun: her plan Yüzlerce kaynağı kontrol etmeli, bloğu kilitlemeliyiz
tüm ekip ve bir modüldeki hata tüm dağıtımı engeller. Çözüm bölmek
sen onun içinde kal bağımsız yığınlar.
# Struttura raccomandata per infrastrutture large-scale
# Ogni directory e un progetto Terraform indipendente con il suo state
environments/
├── prod/
│ ├── networking/ # State: prod/networking/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ ├── eks-cluster/ # State: prod/eks-cluster/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ ├── databases/ # State: prod/databases/terraform.tfstate
│ │ ├── main.tf
│ │ └── versions.tf
│ └── monitoring/ # State: prod/monitoring/terraform.tfstate
│ ├── main.tf
│ └── versions.tf
└── dev/
└── ...
# Per passare informazioni tra stack: terraform_remote_state data source
# stack eks-cluster recupera gli output da networking
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "acme-terraform-state"
key = "environments/prod/networking/terraform.tfstate"
region = "eu-west-1"
}
}
resource "aws_eks_cluster" "main" {
name = "prod-cluster"
role_arn = aws_iam_role.eks.arn
vpc_config {
# Usa gli output dallo stack networking
subnet_ids = data.terraform_remote_state.networking.outputs.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = false
}
}
# Alternativa moderna: usare variabili con file .tfvars invece di
# terraform_remote_state. Piu sicuro (evita dipendenze tra state),
# meno conveniente (richiede aggiornamento manuale dei valori)
S3 Arka Uç için IAM Politikası
Üretimde Terraform (geliştiriciler veya CI/CD) tarafından kullanılan IAM rolünün aşağıdaki özelliklere sahip olması gerekir: arka uç için gereken minimum izinler. İşte politikanın tamamı:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateBucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketVersioning",
"s3:GetEncryptionConfiguration"
],
"Resource": [
"arn:aws:s3:::acme-terraform-state",
"arn:aws:s3:::acme-terraform-state/*"
]
},
{
"Sid": "TerraformStateLocking",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable"
],
"Resource": "arn:aws:dynamodb:eu-west-1:*:table/acme-terraform-locks"
},
{
"Sid": "TerraformKMSDecryptState",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:eu-west-1:*:key/*"
}
]
}
Sonuçlar ve Sonraki Adımlar
Durum yönetimi, kurulumunuzda her şeyi üzerine inşa ettiğiniz temeldir Terraform. Uygun kilitleme özelliğine sahip uzak bir arka uç sizi tüm sınıftan korur operasyonel kazalar. Başlangıçtan itibaren doğru şekilde ayarlamak için zaman ayırın: Mevcut ve uygulanabilir ancak zahmetli bir projeyle eyaletin yerel bölgesinden uzak bölgesine geçiş yapın.
Bir sonraki adım Terraform'u profesyonel bir CI/CD hattına entegre etmektir:
sadece idam etme plan e apply otomatik olarak, ancak yönet
Çekme İsteklerinde plan inceleme iş akışı, yetkisiz uygulamaların engellenmesi
ve tespit edilen sapmaların bildirilmesi.
Komple Seri: Terraform ve IaC
- Madde 01 — Sıfırdan Terraform: HCL, Sağlayıcı ve Planla-Uygula-Yok Et
- Madde 02 — Yeniden Kullanılabilir Terraform Modüllerinin Tasarlanması: Yapı, G/Ç ve Kayıt Defteri
- Madde 03 (bu) — Terraform Durumu: S3/GCS ile Uzak Arka Uç, Kilitleme ve İçe Aktarma
- Madde 04 — CI/CD'de Terraform: GitHub Eylemleri, Atlantis ve Çekme İsteği İş Akışı
- Madde 05 — IaC Testi: Terratest, Terraform Yerel Testi ve Sözleşme Testi
- Madde 06 — IaC Güvenliği: Checkov, Trivy ve OPA Kod Olarak Politika
- Madde 07 — Terraform Çoklu Bulut: Paylaşılan Modüllerle AWS + Azure + GCP
- Madde 08 — Terraform için GitOps: Flux TF Denetleyicisi, Spacelift ve Drift Tespiti
- Madde 09 - Terraform, Pulumi ve OpenTofu: Son Karşılaştırma 2026
- Madde 10 - Terraform Kurumsal Kalıpları: Çalışma Alanı, Sentinel ve Ekip Ölçeklendirmesi







