lesson ── terraform-beginner ── ~15 мин ── 5 шагов
Terraform is quiet by default. An apply fails with a short message. A plan looks odd, and you are left guessing. That is normal for a CLI, but behind it there are full diagnostic logs and tools that read them.
In this lesson you will work through three scenarios: a broken dependency (cycle), a confusing error from the provider (you need TF_LOG), and an unpredictable plan (you need graph). By the end you will know what to do when "something is not working and I do not understand what."
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
A cycle is when A depends on B and B depends on A. The Terraform graph is acyclic by contract, so this code fails at plan.
Create a file main.tf in ~/tf-debug:
resource "aws_s3_bucket" "first" { bucket = "linuxlab-cycle-first-${random_id.suffix.hex}" tags = {PairedWith = aws_s3_bucket.second.bucket
}
}
resource "aws_s3_bucket" "second" { bucket = "linuxlab-cycle-second-${random_id.suffix.hex}" tags = {PairedWith = aws_s3_bucket.first.bucket
}
}
resource "random_id" "suffix" {byte_length = 4
}
first refers to second.bucket, and second refers to
first.bucket. Which one do you create first? Nobody knows.
Run plan:
cd /home/student/tf-debug
terraform init -input=false
terraform plan
You get:
Error: Cycle: aws_s3_bucket.first, aws_s3_bucket.second
This is protection, not a bug. Terraform does not guess, it refuses.
If you do not get Cycle: check that both references are in place (grep PairedWith main.tf).
✓ Cycle caught. Now let us visualize the graph to see the problem.
Terraform can print the graph in Graphviz dot format:
terraform graph
That is text. To see the picture you need graphviz (the dot command).
It is installed in the sandbox:
terraform graph | dot -Tpng > /tmp/graph.png
The file /tmp/graph.png holds the visualization. In our case you will
see two arrows between aws_s3_bucket.first and aws_s3_bucket.second,
one each way. That is the cycle.
With TF 1.4+ there is cycle highlighting right in the command:
terraform graph -draw-cycles
Nodes in cycles are marked red in the dot output.
More detail in tf-graph.
If you get `dot: command not found`: graphviz is not installed. It should be there in the sandbox, but if it fails, skip this step and move on.
✓ Graph printed. Now let us fix the cycle.
The cleanest way to break a cycle is to move the dependent value into a third resource or into a local. Replace main.tf with:
resource "random_id" "suffix" {byte_length = 4
}
locals { first_name = "linuxlab-cycle-first-${random_id.suffix.hex}" second_name = "linuxlab-cycle-second-${random_id.suffix.hex}"}
resource "aws_s3_bucket" "first" {bucket = local.first_name
tags = {PairedWith = local.second_name
}
}
resource "aws_s3_bucket" "second" {bucket = local.second_name
tags = {PairedWith = local.first_name
}
}
Now both buckets depend on random_id, not on each other. The paired
bucket names in the tags come from locals, which are just strings,
not resource attributes.
terraform plan
terraform apply -auto-approve
Plan shows 2 to add (random_id is already created, or will be), and
apply goes through.
Do not forget to remove the original cross references `aws_s3_bucket.X.bucket`: they were the source of the cycle.
✓ Cycle broken, both buckets created. This is the usual pattern: move the shared name into a local.
Throw in a deliberate error: try to create a bucket with an invalid name. S3 does not allow uppercase letters or special characters.
Create a file bad.tf alongside the others:
resource "aws_s3_bucket" "broken" { bucket = "LinuxLab-INVALID-${random_id.suffix.hex}"# ^^^^^^^^^^^^^^^^^^^^^^^^ uppercase letters are not allowed in S3
}
Run apply with the normal output:
terraform apply -auto-approve
You get a short error message from the provider, something like "InvalidBucketName" or "BucketAlreadyExists" (LocalStack may return something slightly different). What exactly is wrong is not obvious.
Now the same thing, but with TF_LOG=DEBUG and a saved copy:
TF_LOG=DEBUG terraform apply -auto-approve 2>&1 | tee /tmp/tf.log
The file /tmp/tf.log holds the entire diagnostic output. Look for the
HTTP response from the cloud:
grep -A 5 'HTTP/1.1 4' /tmp/tf.log | head -30
You will see the original message from the S3 API, usually with an XML
block <Error><Code>...</Code><Message>...</Message></Error>. That
Message is clearer than what Terraform showed in the normal output.
See tf-log-debug for log levels and filtering.
If grep finds nothing, TF_LOG did not take effect. Check that the variable is set: `echo $TF_LOG`. It should be `DEBUG`.
✓ TF_LOG turned on, the debug output is saved. In real work this is the first step when errors look strange.
OpenTofu keeps its CLI and state compatible with Terraform for the
commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. But on the first switch, back up
your state and run on a feature branch, because the differences
concentrate in newer features (variables in the backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
Delete bad.tf:
rm /home/student/tf-debug/bad.tf
Apply. Terraform sees that broken is gone from the HCL and removes it
from state (if it ever got there; on a failed create it usually does
not):
terraform apply -auto-approve
terraform plan -detailed-exitcode
echo "exit: $?"
It should say exit: 0. This is the main invariant: after apply, a
repeat plan is clean.
To lock it in: every time apply fails or a plan is confusing, reach for
TF_LOG=DEBUG, the graph, and a search for 4xx in the logs. This is
routine, not heroics.
If the plan is not clean, look at what changes it shows (`terraform plan` with no flags). Most likely something is left over from broken.
✓ State and HCL match. The beginner track is done. You can come back any time, or wait for intermediate.
When something is not working and the reason is unclear:
terraform validate, is it the syntax? A typo?terraform fmt, maybe the style is getting in the way (rare, but it happens).terraform plan -detailed-exitcode, are there any changes at
all? Maybe you have already applied everything.terraform graph -draw-cycles, maybe a cycle?TF_LOG=DEBUG terraform apply 2>&1 | tee /tmp/tf.log,
the full debug log.grep -A 5 'HTTP/1.1 4' /tmp/tf.log, what does the cloud say?terraform console, what are the real values of the variables
and expressions?This order goes from cheap to expensive. Do not jump straight into TF_LOG if the error is a simple typo that validate would have found in a second.
Three debugging tools. TF_LOG=DEBUG for confusing errors from the provider. terraform graph for working out dependencies and cycles. Reading the HTTP requests in the logs, for cases where the Terraform message hides the point while the cloud gives you the original error.
команды
TF_LOG=DEBUG terraform apply 2>&1 | tee tf.loglog plus a saved copy to dig throughterraform graph | dot -Tpng > graph.pngvisualize the dependency graphterraform graph -draw-cyclesTF 1.4+: highlight cyclesgrep -A 3 'HTTP/1.1 4' tf.log4xx responses from the cloud: usually clearerконцепции