Terraform IaC state drift DynamoDB

Terraform - wykrywanie state drift i bezpieczny force-unlock zablokowanego stanu

Diagnoza i naprawa Terraform state drift: uzgodnienie ręcznych zmian, terraform import, refresh-only plan. Bezpieczny force-unlock stanu zablokowanego w DynamoDB.

Jerzy Kopaczewski ·
terraform plan pokazuje niespodziewane zmiany: zasoby do usunięcia lub modyfikacji, których nikt nie planował. Alternatywnie: terraform plan lub apply zwraca błąd Error acquiring the state lock - stan jest zablokowany przez przerwany proces CI/CD lub crashed terminal. Oba problemy blokują dalsze operacje IaC na infrastrukturze.

Ten runbook opisuje diagnozę state drift i bezpieczne odblokowywanie stanu Terraform. Porównanie narzędzi IaC (Terraform vs Pulumi) znajdziesz w artykule Terraform vs Pulumi - które narzędzie IaC wybrać. Podstawowy runbook do prostego odblokowania stanu to Terraform State Lock Stuck. W kwestii zarządzania infrastrukturą jako kodem - umów konsultacje.

Objaw

Scenariusz A: State Drift

terraform plan pokazuje zmiany, które nie odpowiadają żadnej modyfikacji w kodzie:

# Typowe objawy drift:
# 1. Plan chce usunąć zasoby, które istnieją w chmurze
#    "Plan: 0 to add, 0 to change, 3 to destroy."
#    (ale nikt nie usuwał tych zasobów z kodu!)

# 2. Plan chce zmienić atrybuty, mimo braku zmian w .tf
#    ~ resource "aws_security_group" "main" {
#        ~ ingress = [...] -> [...]  # "forces replacement"
#      }

# 3. Plan pokazuje "resource has been changed outside of Terraform"
#    Note: Objects have changed outside of Terraform

# Sprawdź co plan proponuje
terraform plan -detailed-exitcode
# Exit code 0 = no changes (OK)
# Exit code 1 = error
# Exit code 2 = changes present (DRIFT!)

# Sprawdź konkretne różnice
terraform plan -json | jq '.resource_changes[] | select(.change.actions != ["no-op"]) | {address, actions: .change.actions}'

# Porównaj state z rzeczywistością
terraform show -json | jq '.values.root_module.resources[] | {type, name, values: (.values | keys)}'

Scenariusz B: State Lock Stuck

# Typowy komunikat błędu:
# Error: Error acquiring the state lock
#
# Error message: ConditionalCheckFailedException: The conditional request failed
# Lock Info:
#   ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
#   Path:      my-project/terraform.tfstate
#   Operation: OperationTypePlan
#   Who:       user@hostname
#   Version:   1.9.0
#   Created:   2026-08-07 14:23:45.123456 +0000 UTC

# Sprawdź kto trzyma lock w DynamoDB
aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID": {"S": "my-bucket/my-project/terraform.tfstate"}}' \
  --query 'Item.Info.S' --output text | jq .
# Pokaże: Who, Operation, Created timestamp

# Sprawdź czy proces CI/CD nadal działa
# Jeśli Created > 30 min temu i pipeline nie działa → lock jest orphaned

Przyczyna

State Drift - dlaczego state nie zgadza się z infrastrukturą:

1. Ręczne zmiany w konsoli/CLI (ClickOps):

Ktoś zmienił security group, dodał tag, zmodyfikował parametr przez konsolę AWS/Azure/GCP. Terraform state nie wie o tych zmianach - pokazuje „stary” obraz infrastruktury.

2. Inny proces IaC zarządza tymi samymi zasobami:

CloudFormation stack, Ansible playbook, Pulumi program lub inny Terraform workspace modyfikuje zasoby, które są też w tym state. Konflikty współdzielonych zasobów.

3. Auto-scaling / AWS default behaviors:

AWS automatycznie dodaje tagi, modyfikuje security groups (np. VPC default SG), zmienia atrybuty managed resources. Terraform widzi te zmiany jako drift.

4. State uszkodzony lub niezsynchronizowany:

Przerwany terraform apply zapisał częściowy state. Zasoby zostały utworzone, ale state nie został zaktualizowany. Albo S3 versioning przywrócił starszą wersję stanu.

State Lock - dlaczego lock jest „stuck”:

1. CI/CD pipeline przerwany w trakcie operacji:

GitHub Actions job cancelled, Jenkins node crashed, terminal zamknięty podczas terraform apply. Proces nie zdążył zwolnić lock w DynamoDB.

2. Network timeout do DynamoDB:

Operacja Terraform zakończyła się, ale unlock request do DynamoDB nie dotarł (timeout sieciowy, IAM temporary credentials expired).

3. Długi apply bez timeout:

terraform apply na dużej infrastrukturze (>100 zasobów) trwa godzinami. Lock jest aktywny, ale inny developer próbuje plan w tym samym czasie.

Rozwiązanie

Drift - uzgodnienie state z rzeczywistością:

A) Refresh state (zaktualizuj state bez zmian w infrastrukturze):

# 1. Najpierw zrób backup state!
terraform state pull > terraform.tfstate.backup.$(date +%Y%m%d_%H%M%S)

# 2. Refresh - synchronizuj state z aktualną infrastrukturą
terraform plan -refresh-only
# Sprawdź co się zmieni w state (nie w infrastrukturze!)
# Pokaże: "Terraform will update the state to reflect changes made outside"

# 3. Jeśli zmiany wyglądają OK - zastosuj refresh
terraform apply -refresh-only -auto-approve

# 4. Teraz normalny plan powinien być czysty
terraform plan
# Expected: "No changes. Your infrastructure matches the configuration."

B) Import zasobów, które istnieją ale nie są w state:

# Scenariusz: zasób istnieje w chmurze, ale Terraform go nie zna
# Plan pokazuje: "resource will be created" - mimo że już istnieje

# 1. Sprawdź czy zasób istnieje
aws ec2 describe-security-groups --group-ids sg-0123456789abcdef

# 2. Import do state (Terraform 1.5+ - import block w kodzie)
# W pliku .tf dodaj:
# imports.tf - deklaratywny import (Terraform 1.5+)
import {
  to = aws_security_group.main
  id = "sg-0123456789abcdef"
}

# Lub dla zasobów z composite ID:
import {
  to = aws_route_table_association.private
  id = "subnet-abc123/rtbassoc-xyz789"
}
# 3. Uruchom plan - Terraform porówna config z importowanym zasobem
terraform plan -generate-config-out=generated.tf
# generated.tf zawiera konfigurację odpowiadającą importowanemu zasobowi

# 4. Przenieś wygenerowaną konfigurację do właściwego pliku .tf
# Dostosuj do swojego stylu/konwencji

# 5. Uruchom apply (import + alignment)
terraform apply

# 6. Usuń bloki import {} po pomyślnym imporcie

C) State rm - usuń zasób ze state (nie z infrastruktury):

# Scenariusz: Terraform chce usunąć zasób, który powinien istnieć
# ale nie powinien być zarządzany przez TEN workspace

# 1. Sprawdź co jest w state
terraform state list | grep "zasób_do_usunięcia"

# 2. Usuń ze state (zasób zostaje w infrastrukturze!)
terraform state rm 'aws_security_group.legacy'
# "Successfully removed 1 resource instance(s)."

# 3. Zweryfikuj - plan nie powinien próbować usuwać
terraform plan

D) Moved blocks - refactoring bez drift:

# Gdy przeniosłeś/przenazywaś zasoby w kodzie i plan chce destroy+create:
moved {
  from = aws_instance.web_server
  to   = aws_instance.app_server
}

moved {
  from = module.old_name
  to   = module.new_name
}

State Lock - bezpieczne odblokowywanie:

E) Force-unlock (po weryfikacji!):

# WAŻNE: NIGDY nie rób force-unlock jeśli inny proces nadal działa!
# Zawsze sprawdź najpierw:

# 1. Sprawdź kto trzyma lock
aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID": {"S": "my-bucket/my-project/terraform.tfstate"}}' \
  --query 'Item.Info.S' --output text | jq .

# 2. Zweryfikuj czy proces nadal żyje
# - Sprawdź CI/CD pipeline (czy job jest aktywny?)
# - Sprawdź Who pole - czy ta osoba nadal ma terminal otwarty?
# - Sprawdź Created timestamp - jeśli > 1h temu, prawdopodobnie orphaned

# 3. Jeśli lock jest orphaned - odblokuj
LOCK_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"  # z komunikatu błędu
terraform force-unlock $LOCK_ID

# 4. Jeśli force-unlock nie działa (state corrupted) - usuń ręcznie z DynamoDB
aws dynamodb delete-item \
  --table-name terraform-locks \
  --key '{"LockID": {"S": "my-bucket/my-project/terraform.tfstate"}}'

# 5. NATYCHMIAST po unlock - uruchom plan aby zweryfikować state
terraform plan

F) Zapobieganie przyszłym stuck locks w CI/CD:

# GitHub Actions - timeout + cleanup
name: Terraform Apply
on: [push]
jobs:
  apply:
    runs-on: ubuntu-latest
    timeout-minutes: 60  # Twardy limit na cały job
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - name: Terraform Apply
        id: apply
        timeout-minutes: 45  # Timeout na apply (zostawi czas na unlock)
        run: terraform apply -auto-approve
        continue-on-error: true

      # Cleanup lock jeśli apply failed/timeout
      - name: Force Unlock on Failure
        if: failure() || cancelled()
        run: |
          LOCK_ID=$(terraform force-unlock -force 2>&1 | grep -oP '[a-f0-9-]{36}' | head -1)
          if [ -n "$LOCK_ID" ]; then
            terraform force-unlock -force "$LOCK_ID"
          fi
# backend.tf - konfiguracja z lock timeout
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "my-project/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true

    # Timeout na lock acquisition (default: 0 = wait forever)
    # Ustaw na czas krótszy niż CI/CD job timeout
    skip_metadata_api_check = false
  }
}

Walidacja

# 1. Po naprawie drift - plan musi być czysty
terraform plan -detailed-exitcode
# Expected: exit code 0 (no changes)

# 2. Po force-unlock - plan musi się wykonać bez błędów lock
terraform plan
# Expected: brak "Error acquiring the state lock"

# 3. Zweryfikuj że state jest spójny
terraform state list | wc -l
# Porównaj z oczekiwaną liczbą zasobów

# 4. Sprawdź że nie ma zasobów "tainted"
terraform show -json | jq '[.values.root_module.resources[] | select(.tainted == true)] | length'
# Expected: 0

# 5. Drift detection - skonfiguruj automatyczną detekcję
terraform plan -refresh-only -detailed-exitcode
# W CI/CD: uruchamiaj scheduled job co 24h
# Exit code 2 = drift detected → alert do zespołu

# 6. Sprawdź DynamoDB - lock powinien być wolny
aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID": {"S": "my-bucket/my-project/terraform.tfstate"}}' \
  --query 'Item'
# Expected: brak Item (lock zwolniony) lub null
State drift to najczęstsza przyczyna „niespodzianek" w Terraform. Najlepsza prewencja: (1) zakaz ClickOps - wszystkie zmiany tylko przez IaC, (2) scheduled drift detection (terraform plan -refresh-only w CI/CD co 24h), (3) Terraform Cloud/Spacelift z automatycznym drift alertingiem. Force-unlock jest operacją bezpieczną pod warunkiem, że ŻADEN inny proces nie wykonuje w tym momencie apply. Nigdy nie rób force-unlock na ślepo - zawsze sprawdź kto i kiedy zablokował stan.

 

Jerzy Kopaczewski

Terraform state sprawia problemy?

Umów bezpłatną 30-minutową rozmowę. Pomożemy naprawić drift, skonfigurować automatyczną detekcję i zabezpieczyć pipeline CI/CD przed stuck locks.