TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Controlling With Lifecycle and Reverting Drift

Continue in TT Lab

Goal

You control replacement, deletion, and ignoring with the four lifecycle arguments, detect changes that arose outside the code and respond in two directions, and then automate that verdict with a script.

Why it matters

The way infrastructure code goes out of step is always the same — something urgent comes up, someone fixes it directly in the console, and that change does not come back into the code. After a few days in that state, an apply run for an entirely different reason quietly reverts the early-morning emergency action. So the truly hard part of responding to drift is not the commands but the judgment that decides the direction. If the emergency action was wrong, revert to the code; if it was a right judgment, fix the code to match the real thing. Running automatic recovery without this judgment is the most dangerous. The lifecycle block is a device that intervenes in this flow in advance. In particular, ignore_changes removes values that intentionally change outside the code, like an autoscaler's, from detection to reduce false alarms, and prevent_destroy puts a brake on resources that must not be deleted. Finally, the verdict must be made with -detailed-exitcode, not with human eyes, for it to be attached to cron and CI.

Steps

  1. Declare the local_file resource endpoint in /root/tf/lifecycle/main.tf. The file path is /root/tf/lifecycle/out/endpoint.txt, and the contents are a one-line string with no newline. Put create_before_destroy = true in this resource's lifecycle block, run tofu init, and apply.
  2. Add the local_file resource statefile_guard (file /root/tf/lifecycle/out/guard.txt), put prevent_destroy = true in its lifecycle, and apply. Then try to delete only that resource (tofu destroy -target=local_file.statefile_guard) and save the output, including standard error, to /root/tf/lifecycle/out/prevent.txt. The refusal message must remain, and statefile_guard in the state and guard.txt must still be alive.
  3. Create one local_file resource as the file /root/tf/lifecycle/out/managed-note.txt with the contents ignored-value and apply. Then put ignore_changes = [content] in its lifecycle, change only the code's content value to a different string, and save the tofu plan output to /root/tf/lifecycle/out/ignore-plan.txt. The plan must be No changes, and the actual file contents must still be ignored-value.
  4. Make one null_resource hold a version value in triggers, and attach replace_triggered_by = [null_resource.<이름>] (where the placeholder is that resource's name) to the local_file resource checksum (file /root/tf/lifecycle/out/checksum.txt). Leaving checksum's own arguments as they are, change only the version value, save with tofu plan -out=<계획파일> (where the placeholder is the plan file), and then save the result of tofu show -json <계획파일> to /root/tf/lifecycle/out/trigger-plan.json. In resource_changes of the JSON, the actions of local_file.checksum must be two (delete and create). After checking, apply.
  5. Without going through code, edit /root/tf/lifecycle/out/endpoint.txt directly so that its contents include hand-edited. Then run tofu plan -refresh-only -detailed-exitcode -out=<계획파일> (where the placeholder is the plan file), write only the exit code to /root/tf/lifecycle/out/drift-exit.txt (it must be 2), and save the result of tofu show -json <계획파일> to /root/tf/lifecycle/out/drift.json. resource_drift of the JSON must include endpoint. Do not apply this plan.
  6. Revert the real thing to match the code with tofu apply and save the output to /root/tf/lifecycle/out/remediate.txt. The phrase Apply complete must remain, hand-edited must be gone from endpoint.txt, and the file contents must be exactly equal to the content value recorded in the state.
  7. Apply the local_file resource quota (file /root/tf/lifecycle/out/quota.txt) with the content 256. This time, edit the file by hand to 512, do not revert it, but change the value in main.tf to 512 to absorb it into the code, and apply. Then save the tofu plan output to /root/tf/lifecycle/out/accept-plan.txt; it must be No changes, and both quota.txt and main.tf must contain 512.
  8. Create /root/tf/lifecycle/drift-report.sh and give it execute permission. This script must judge drift with a plan command that uses -detailed-exitcode and leave the result in /root/tf/lifecycle/out/drift-report.json with four fields: exit_code, drift, checked_at, and addresses. There must be no drift by the time you finish the lab, so exit_code must be 0 and drift must be false.

Notes

Apply create_before_destroy

Declare the local_file resource endpoint in /root/tf/lifecycle/main.tf. The file path is /root/tf/lifecycle/out/endpoint.txt, and the contents are a one-line string with no newline. Put create_before_destroy = true in this resource's lifecycle block, run tofu init, and apply.

The lifecycle block goes inside the resource. Find the argument that makes the new one get created first when replacement is needed.

Block deletion with prevent_destroy

Add the local_file resource statefile_guard (file /root/tf/lifecycle/out/guard.txt), put prevent_destroy = true in its lifecycle, and apply. Then try to delete only that resource (tofu destroy -target=local_file.statefile_guard) and save the output, including standard error, to /root/tf/lifecycle/out/prevent.txt. The refusal message must remain, and statefile_guard in the state and guard.txt must still be alive.

This argument produces an error at the plan stage. Try to delete with only one target specified and save the whole output.

Ignore a specific attribute with ignore_changes

Create one local_file resource as the file /root/tf/lifecycle/out/managed-note.txt with the contents ignored-value and apply. Then put ignore_changes = [content] in its lifecycle, change only the code's content value to a different string, and save the tofu plan output to /root/tf/lifecycle/out/ignore-plan.txt. The plan must be No changes, and the actual file contents must still be ignored-value.

You see the effect only if you apply first to put the value into the state and then change the code value. The real file's contents must remain the value from the initial apply.

Trigger recreation through another resource's change

Make one null_resource hold a version value in triggers, and attach replace_triggered_by = [null_resource.<이름>] (where the placeholder is that resource's name) to the local_file resource checksum (file /root/tf/lifecycle/out/checksum.txt). Leaving checksum's own arguments as they are, change only the version value, save with tofu plan -out=<계획파일> (where the placeholder is the plan file), and then save the result of tofu show -json <계획파일> to /root/tf/lifecycle/out/trigger-plan.json. In resource_changes of the JSON, the actions of local_file.checksum must be two (delete and create). After checking, apply.

The key is that it is replaced because of someone else's change even though its own arguments are unchanged. Save the plan to a file and then convert it to JSON.

Detect a change made outside the code as drift

Without going through code, edit /root/tf/lifecycle/out/endpoint.txt directly so that its contents include hand-edited. Then run tofu plan -refresh-only -detailed-exitcode -out=<계획파일> (where the placeholder is the plan file), write only the exit code to /root/tf/lifecycle/out/drift-exit.txt (it must be 2), and save the result of tofu show -json <계획파일> to /root/tf/lifecycle/out/drift.json. resource_drift of the JSON must include endpoint. Do not apply this plan.

It is drift only if you fix the real thing directly without going through code. Use a refresh-only plan together with the detailed exit code.

Revert the real thing to match the code

Revert the real thing to match the code with tofu apply and save the output to /root/tf/lifecycle/out/remediate.txt. The phrase Apply complete must remain, hand-edited must be gone from endpoint.txt, and the file contents must be exactly equal to the content value recorded in the state.

This is the first of the response directions. After reverting, the value recorded in the state and the real contents must be the same.

Absorb the drift into the code

Apply the local_file resource quota (file /root/tf/lifecycle/out/quota.txt) with the content 256. This time, edit the file by hand to 512, do not revert it, but change the value in main.tf to 512 to absorb it into the code, and apply. Then save the tofu plan output to /root/tf/lifecycle/out/accept-plan.txt; it must be No changes, and both quota.txt and main.tf must contain 512.

This time it is the opposite direction. If you judge that the value changed by hand is right, fix the code to that value.

Build a drift detection script and report

Create /root/tf/lifecycle/drift-report.sh and give it execute permission. This script must judge drift with a plan command that uses -detailed-exitcode and leave the result in /root/tf/lifecycle/out/drift-report.json with four fields: exit_code, drift, checked_at, and addresses. There must be no drift by the time you finish the lab, so exit_code must be 0 and drift must be false.

Make the verdict by exit code, not by human eyes. Be careful that set -e does not kill the script at exit code 2.