DevOps Peach

Advanced Terraform · Environments, Import and Drift · Lesson 4 of 4

Detecting and Handling Drift

Drift is any difference between real infrastructure and state; find it with refresh-only plans and scheduled -detailed-exitcode runs, decide whether to accept or revert it, and add check blocks for ongoing health.

Practice interactively →

Drift happens when someone changes infrastructure outside Terraform: a hot-fix in the console, a script, another tool. Left alone, the next unrelated apply silently reverts the hot-fix, or fails.

flowchart LR
  SCH["Nightly CI: plan -detailed-exitcode"] --> EX{"exit code"}
  EX -->|"0"| OK["No drift"]
  EX -->|"2"| ALERT["Drift: open an issue"]
  ALERT --> DEC{"Keep the change?"}
  DEC -->|"yes"| CODE["Update code, or apply -refresh-only"]
  DEC -->|"no"| REV["terraform apply: revert"]
  %% step SCH: A scheduled pipeline runs plan against every environment, for example every night.
  %% step EX: With -detailed-exitcode, plan exits 0 for no changes, 1 for errors and 2 for changes.
  %% step ALERT: Exit code 2 means code and reality differ, so the job alerts the owners.
  %% step DEC: A person decides whether the outside change was right.
  %% step CODE: If it was right, put it in the code (or accept it into state) so Terraform stops reverting it.
  %% step REV: If not, a normal apply puts the infrastructure back to the code.

Seeing drift

Command Shows
terraform plan -refresh-only Only differences between state and reality
terraform plan -detailed-exitcode Exit 0 = no changes, 1 = error, 2 = changes present (for scripts)
terraform apply -refresh-only Accept reality into state without changing infrastructure
terraform plan -detailed-exitcode -input=false -lock=false
case $? in
  0) echo "clean" ;;
  2) echo "drift or pending changes" ; exit 2 ;;
  *) echo "plan failed" ; exit 1 ;;
esac

Deciding what to do

Situation Action
Hot-fix that should stay Change the code to match, then plan shows nothing
Change by a tool that owns that field lifecycle { ignore_changes = [...] }
Unwanted manual change terraform apply reverts it
Object is broken but config is fine terraform apply -replace='docker_container.app["web"]' recreates it (replaces the old taint)

check blocks: continuous assertions

check "shop_api_health" {
  data "http" "health" {
    url = "http://localhost:8081/healthz"
  }
  assert {
    condition     = data.http.health.status_code == 200
    error_message = "shop-api health check returned ${data.http.health.status_code}."
  }
}

A check runs on every plan and apply. A failing assertion is a warning, not an error, so it never blocks a deployment; it is ideal for "is the service still answering?" checks. The data source inside it is scoped to the check. HCP Terraform can run these checks on a schedule (health assessments).

Prevent drift in the first place

Traps: -replace only plans the replacement of the given address; dependent resources may also change, so read the plan; and ignore_changes = all hides every future change to the resource, including the ones you want.

Practice interactively →