Locking #
State locking adalah mekanisme yang mencegah dua proses Terraform berjalan secara bersamaan pada state yang sama. Tanpa locking, dua orang yang menjalankan terraform apply secara bersamaan bisa menghasilkan state yang corrupt — keduanya membaca state lama, keduanya membuat perubahan, dan salah satu akan menimpa pekerjaan yang lain. Ini bukan edge case yang jarang terjadi; di tim yang aktif, race condition ini sangat mungkin terjadi.
flowchart LR
A["Developer A\nterraform apply"] --> B["Baca state\nserial: 5"]
B --> C["Tulis state\nserial: 6"]
D["Developer B\nterraform apply"] --> E["Baca state\nserial: 5 (lama!)"]
E --> F["Tulis state\nserial: 6"]
F --> G["State A\nOVERWRITE ❌"]
style A fill:#e3f2fd,stroke:#1565c0
style D fill:#e3f2fd,stroke:#1565c0
style G fill:#ffebee,stroke:#c62828Mengapa Locking Krusial #
SKENARIO TANPA LOCKING:
Developer A (menjalankan terraform apply):
t=0s: Baca state (serial: 5)
t=2s: Buat aws_vpc.main → state serial: 6
t=4s: Buat aws_subnet.public → state serial: 7
Developer B (menjalankan terraform apply di waktu yang sama):
t=1s: Baca state (serial: 5) ← baca state LAMA sebelum A selesai
t=3s: Buat aws_vpc.main → GAGAL (sudah ada, dibuat A)
t=5s: Tulis state (serial: 6) ← OVERWRITE state A yang serial: 7!
Hasil:
- aws_vpc.main ada di cloud, tapi state B tidak mencatatnya
- aws_subnet.public ada di cloud (dibuat A), tapi tidak ada di state B
- State sekarang tidak mencerminkan realita → drift permanen
Cara Locking Bekerja #
Ketika terraform apply atau terraform plan dimulai, Terraform mencoba mengambil lock sebelum melakukan apapun.
PROSES LOCKING:
terraform apply dimulai
│
▼
Coba acquire lock
│
┌───┴───┐
│ │
Berhasil Gagal (lock sudah dipegang)
│ │
▼ ▼
Lanjut Error: "Error acquiring the state lock"
eksekusi Tampilkan info siapa yang memegang lock
│
▼
Eksekusi selesai
│
▼
Release lock
flowchart TD
A["terraform apply\ndimulai"] --> B["Coba acquire lock"]
B --> C{"Lock\nberhasil?"}
C -->|"Ya"| D["Eksekusi plan"]
C -->|"Tidak"| E["Error: lock\nsudah dipegang"]
D --> F["Release lock"]
F --> G["State tersimpan"]
style A fill:#e3f2fd,stroke:#1565c0
style D fill:#e8f5e9,stroke:#2e7d32
style E fill:#ffebee,stroke:#c62828
style G fill:#e8f5e9,stroke:#2e7d32Locking di Berbagai Backend #
# S3 Backend — butuh DynamoDB untuk locking
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-state-lock" # Wajib untuk locking
encrypt = true
}
}
# Lock entry di DynamoDB saat apply sedang berjalan:
# LockID: "my-terraform-state/production/terraform.tfstate"
# Info: {"ID":"abc123", "Operation":"OperationTypeApply",
# "Who":"user@hostname", "Created":"2024-01-15T10:30:00Z"}
# GCS Backend — locking built-in, tidak perlu resource tambahan
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "production"
# Locking menggunakan GCS object versioning — otomatis
}
}
# Terraform Cloud — locking built-in, dikelola platform
terraform {
cloud {
organization = "my-org"
workspaces {
name = "production"
}
}
}
flowchart TD
A["Backend\nConfiguration"] --> B{"Jenis Backend?"}
B -->|"S3"| C["DynamoDB Table\n(hash_key: LockID)"]
B -->|"GCS"| D["GCS Object\nVersioning (built-in)"]
B -->|"Terraform Cloud"| E["Platform Lock\n(built-in)"]
C --> F["Lock entry\ndi DynamoDB"]
D --> G["Lock di\nGCS metadata"]
E --> H["Lock di\nTFC API"]
style A fill:#e3f2fd,stroke:#1565c0
style C fill:#fff3e0,stroke:#e65100
style D fill:#e8f5e9,stroke:#2e7d32
style E fill:#e8f5e9,stroke:#2e7d32Pesan Error Lock dan Artinya #
# Error yang muncul saat lock sedang dipegang:
$ terraform apply
╷
│ Error: Error acquiring the state lock
│
│ Error message: ConditionalCheckFailedException: ...
│ Lock Info:
│ ID: abc123def456
│ Path: my-terraform-state/production/terraform.tfstate
│ Operation: OperationTypeApply
│ Who: [email protected]
│ Version: 1.6.3
│ Created: 2024-01-15 10:30:00.123456789 +0000 UTC
│ Info:
╵
# Informasi penting dari error ini:
# - ID: digunakan untuk force-unlock jika diperlukan
# - Who: siapa yang memegang lock saat ini
# - Created: kapan lock diambil (jika sudah lama, mungkin stuck)
Menangani Stuck Lock #
Terkadang lock tidak terlepas otomatis — misalnya jika proses Terraform terpotong paksa (Ctrl+C, crash, network timeout di CI/CD).
# Cek apakah ada lock aktif
terraform plan
# Jika ada lock, error message akan menampilkan Lock ID
# Force unlock — gunakan Lock ID dari error message
terraform force-unlock abc123def456
# PENTING: Pastikan tidak ada proses Terraform lain yang sedang berjalan
# sebelum melakukan force-unlock. Force unlock pada apply yang masih
# berjalan bisa menyebabkan state corrupt.
SEBELUM FORCE UNLOCK, VERIFIKASI:
□ Tidak ada proses terraform yang berjalan di mesin lain
□ Tidak ada CI/CD job yang masih aktif
□ Lock memang stuck (created time sudah lama, misalnya > 30 menit)
□ Kamu tahu siapa yang memegang lock dan sudah konfirmasi mereka
Setelah force unlock:
□ Jalankan terraform plan untuk memverifikasi state konsisten
□ Baru lanjutkan dengan terraform apply jika plan bersih
Locking di CI/CD Pipeline #
Di CI/CD pipeline, lock bisa menjadi masalah jika beberapa pipeline dijalankan bersamaan. Ada beberapa strategi untuk mengelolanya.
# Strategi 1: Serialized pipeline
# Pastikan hanya satu pipeline apply yang bisa berjalan bersamaan
# (konfigurasi di CI/CD tool — GitHub Actions concurrency, GitLab resource groups)
# GitHub Actions: concurrency group
name: Terraform Apply
concurrency:
group: terraform-production # Hanya satu workflow dengan group ini boleh jalan
cancel-in-progress: false # Jangan cancel yang sedang jalan, antrikan
jobs:
apply:
runs-on: ubuntu-latest
steps:
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
# Strategi 2: Timeout yang masuk akal
# Jika pipeline stuck dan lock tidak terlepas setelah timeout,
# pipeline bisa dianggap failed dan alert dikirim
terraform apply -lock-timeout=5m # Tunggu maksimal 5 menit untuk acquire lock
# Jika tidak berhasil dalam 5 menit, error dan gagal
Disable Locking (Hanya untuk Keadaan Darurat) #
# ANTI-PATTERN: Disable locking secara permanen
terraform apply -lock=false # ✗ Sangat berbahaya di lingkungan tim
# BENAR: Disable locking hanya jika benar-benar terpaksa
# dan kamu yakin tidak ada proses lain yang berjalan
terraform plan -lock=false # Misalnya: hanya untuk inspeksi darurat
Distributed Locking Internals #
Setiap backend memiliki implementasi locking yang berbeda, tapi prinsip dasarnya sama: atomic compare-and-set operation yang memastikan hanya satu proses yang bisa memegang lock.
sequenceDiagram
participant A as Process A
participant Lock as Lock Service
participant B as Process B
A->>Lock: Acquire lock (ID: abc123)
Lock-->>A: Lock acquired ✓
B->>Lock: Acquire lock (ID: def456)
Lock-->>B: Denied — lock held by abc123
A->>Lock: Release lock (ID: abc123)
Lock-->>A: Lock released ✓
B->>Lock: Acquire lock (ID: def456)
Lock-->>B: Lock acquired ✓
style A fill:#3b82f6,stroke:#1e40af,color:#fff
style B fill:#8b5cf6,stroke:#6d28d9,color:#fff
style Lock fill:#f59e0b,stroke:#d97706,color:#fff# S3 + DynamoDB locking internals:
# 1. Terraform membuat item di DynamoDB dengan LockID
# 2. Menggunakan DynamoDB conditional write:
# PutItem dengan ConditionExpression = "attribute_not_exists(LockID)"
# 3. Jika LockID sudah ada → ConditionalCheckFailedException
# 4. Hanya satu proses yang berhasil menulis → atomic
# DynamoDB lock item structure:
{
"LockID": "my-bucket/production/terraform.tfstate",
"Info": {
"ID": "a3b4c5d6-e7f8-9012-abcd-ef1234567890",
"Operation": "OperationTypeApply",
"Who": "[email protected]",
"Version": "1.6.3",
"Created": "2024-01-15T10:30:00Z",
"Path": "my-bucket/production/terraform.tfstate"
}
}
# GCS locking internals:
# 1. Terraform membuat file .tflock di bucket
# 2. Menggunakan GCS preconditions:
# ifGenerationMatch: 0 (hanya berhasil jika file belum ada)
# 3. Hapus file .tflock saat release
# 4. GCS menjamin atomicity melalui preconditions
Lock Timeout Scenarios #
Terraform menunggu lock terlepas dalam durasi yang bisa dikonfigurasi. Memahami timeout behavior sangat penting untuk pipeline CI/CD.
# Default lock timeout adalah 0s — langsung error jika lock dipegang
terraform apply
# Error: Error acquiring the state lock (langsung gagal)
# Atur timeout untuk menunggu lock terlepas
terraform apply -lock-timeout=5m # Tunggu maksimal 5 menit
terraform apply -lock-timeout=30s # Tunggu maksimal 30 detik
terraform apply -lock-timeout=1h # Tunggu maksimal 1 jam
PERILAKU LOCK TIMEOUT:
lock-timeout=0s (default):
Coba acquire → gagal → error langsung
Cocok untuk: developer lokal yang bisa retry manual
lock-timeout=5m:
Coba acquire → gagal → retry setiap beberapa detik → 5 menit → error
Cocok untuk: CI/CD pipeline yang menunggu apply lain selesai
lock-timeout=30m:
Tunggu lebih lama untuk apply besar yang waktunya tidak pasti
Cocok untuk: apply yang mengelola ratusan resource
PERINGATAN: Jika apply A berjalan 45 menit dan lock-timeout B = 30 menit,
pipeline B akan timeout dan gagal. Hitung estimasi waktu apply
terpanjang untuk menentukan timeout yang tepat.
Troubleshooting Stuck Lock #
# SITUASI: Lock stuck, tidak tahu siapa yang memegang
$ terraform plan
Error: Error acquiring the state lock
Lock Info:
ID: a3b4c5d6-e7f8-9012-abcd-ef1234567890
Operation: OperationTypeApply
Who: deploy-agent@ci-runner-03
Created: 2024-01-15 10:30:00 +0000 UTC
Info:
# LANGKAH 1: Cek apakah proses masih berjalan
# Di CI/CD — cek apakah runner/pod masih aktif
kubectl get pods -l app=terraform-runner
# Jika pod sudah tidak ada → lock stuck, aman untuk force-unlock
# LANGKAH 2: Cek umur lock
# Lock yang sudah lebih dari 30 menit tanpa progress biasanya stuck
LOCK_CREATED="2024-01-15T10:30:00Z"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "Lock age: $(($(date -d "$NOW" +%s) - $(date -d "$LOCK_CREATED" +%s))) seconds"
# LANGKAH 3: Cek DynamoDB langsung (untuk S3 backend)
aws dynamodb get-item \
--table-name terraform-state-lock \
--key '{"LockID":{"S":"my-bucket/production/terraform.tfstate"}}'
# LANGKAH 4: Force unlock jika yakin tidak ada proses aktif
terraform force-unlock a3b4c5d6-e7f8-9012-abcd-ef1234567890
# LANGKAH 5: Verifikasi state konsisten setelah unlock
terraform plan
# Harusnya "No changes" — jika ada perubahan tak terduga,
# kemungkinan apply sebelumnya terpotong di tengah jalan
Lock Table Design Patterns #
Untuk organisasi besar yang mengelola banyak state, DynamoDB lock table perlu dirancang dengan baik.
# Lock table yang bisa digunakan untuk banyak state (shared)
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST" # Tidak perlu provisioned capacity
hash_key = "LockID"
attribute {
name = "LockID"
type = "S" # String
}
# TTL untuk auto-cleanup lock yang stuck
# (perlu TTL attribute bernama "ExpiresAt" di item)
ttl {
attribute_name = "ExpiresAt"
enabled = true
}
point_in_time_recovery {
enabled = true # Backup lock table
}
}
# PATTERNS:
# 1. Shared lock table untuk semua state
# Satu DynamoDB table, banyak LockID berbeda
# Cocok untuk: organisasi kecil-sedang (< 50 state files)
# LockID: "bucket-name/path/to/terraform.tfstate"
# 2. Per-environment lock table
# DynamoDB table terpisah per environment
# terraform-lock-production, terraform-lock-staging
# Cocok untuk: isolasi ketat antar environment
# Memisahkan IAM permission per table
# 3. Per-team lock table
# Setiap tim punya lock table sendiri
# Tim infrastruktur: terraform-lock-infra
# Tim aplikasi: terraform-lock-apps
# Cocok untuk: organisasi besar dengan banyak tim
# REKOMENDASI: Mulai dengan pattern 1 (shared), pisahkan
# hanya jika ada kebutuhan IAM isolation yang spesifik
Ringkasan #
- Locking mencegah state corrupt akibat dua proses apply yang berjalan bersamaan — ini bukan fitur opsional tapi kebutuhan di lingkungan tim.
- S3 butuh DynamoDB untuk locking — konfigurasi
dynamodb_tabledi backend S3 adalah wajib, bukan opsional.- GCS dan Terraform Cloud sudah built-in locking tanpa konfigurasi tambahan.
- Lock error berisi Lock ID — simpan ini jika perlu force-unlock, tapi verifikasi dulu tidak ada proses aktif sebelum melakukannya.
- Force unlock hanya saat lock benar-benar stuck — force unlock pada apply yang masih berjalan bisa menyebabkan state corrupt.
- Serialized CI/CD pipeline adalah cara terbaik mencegah lock conflict — gunakan concurrency groups atau resource locks di CI/CD tool.
- Distributed locking menggunakan atomic compare-and-set — S3+DynamoDB memakai conditional write, GCS memakai generation preconditions.
- Lock timeout default 0 detik (langsung error) — naikkan ke 5 menit untuk CI/CD yang perlu menunggu apply lain selesai.
- Troubleshooting stuck lock: cek apakah proses masih berjalan, hitung umur lock, cek DynamoDB langsung, baru force-unlock jika yakin aman.
- Lock table design: mulai dengan shared table untuk semua state, pisahkan hanya jika ada kebutuhan IAM isolation.