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.
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
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.