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:#c62828

Mengapa 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:#2e7d32

Locking 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:#2e7d32

Pesan 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_table di 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.

← Sebelumnya: Remote   Berikutnya: Migration →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact