OnCallReady

Terraform: Language & Workflow: interview questions

The question you are most likely to get for each topic, a model answer, and what else comes up. From chapter 12 of the course.

Walk me through what terraform init, plan and apply each do. Junior

Terraform is declarative: you describe the end state, and because it is idempotent, applying the same code twice changes nothing the second time.

Also asked: What is the difference between declarative and imperative infrastructure tools? · What is Terraform state, and why does it exist? · What is the difference between a resource and a data source?

What is Infrastructure as Code, and why is Terraform called declarative? Junior

Infrastructure as Code means the infrastructure - networks, machines, databases, firewall rules - is described in text files kept in git. So it is reproducible (the same code builds the same environment), reviewable (a change is a diff someone reads before it happens) and versioned (history, blame, revert).

Declarative: a resource block does not say "create"; it says "this must exist":

resource "azurerm_resource_group" "orders" {
  name     = "rg-orders-dev"
  location = "westeurope"
}

Terraform compares that with what exists and does whatever is needed - nothing, if it is already there. That makes it idempotent, safe to run on every commit. An imperative tool, like a bash script, is a list of steps: run it twice and you get two of everything or an error.

Terraform provisions (creates the infrastructure); configuration management like Ansible sets up what runs inside a machine. They complement each other.

Also asked: What is the difference between Terraform and Ansible? · What is a Terraform provider? · What does "multi-cloud" mean with Terraform, and what does it not mean?

Learn it: 12.1 What Terraform is for, and the blocks it is made of

What is .terraform.lock.hcl, and should it be committed? Junior

It is the dependency lock file, like package-lock.json: for every provider it records the exact version that terraform init chose, the constraints the code allowed, and hashes (checksums) of the download, so a tampered or different binary is refused.

Commit it. Without it, two engineers resolving ~> 4.14 a week apart can get different provider versions and see different plans from identical code.

Things to know:

The version constraint itself lives in required_providers: ~> 4.14 allows 4.x from 4.14 up, but not 5.0.

Also asked: What does the ~> version constraint mean? · How do you use two configurations of the same provider, for two regions? · Where should provider credentials come from?

Learn it: 12.3 Providers, versions and the lock file

How do you pass different values to the same Terraform code for dev and prod? Junior

Declare input variables and give them values from outside:

variable "env" {
  type = string
  validation {
    condition     = contains(["dev", "test", "prod"], var.env)
    error_message = "env must be dev, test or prod."
  }
}

Then one .tfvars file per environment: terraform plan -var-file=prod.tfvars. Other ways: -var 'env=prod', TF_VAR_env, terraform.tfvars and *.auto.tfvars (read automatically), and the default.

Precedence, lowest to highest: default, TF_VAR_ environment variables, terraform.tfvars, *.auto.tfvars, then -var / -var-file in command-line order. When a value is not what you expect, walk that list from the top.

Inside the code use var.env; build derived values once in locals; expose results with output. A secret variable gets sensitive = true - which hides it in output but not in state.

Also asked: A variable is marked sensitive. Is its value safe? · What is the difference between a variable and a local? · What happens when a required variable has no value in a CI job?

Learn it: 12.5 Variables, locals and outputs

How do you make a Terraform configuration reject bad input before it creates anything? Mid

Three layers, earliest first:

A check block is different: it warns but never fails the run - for monitoring things that can drift, like a certificate's expiry.

Also asked: What is the difference between a list, a set and a map in Terraform? · What does nullable = false do on a variable? · When would you use a check block instead of a precondition?

Learn it: 12.8 Types, validation and custom conditions

Explain for expressions and splat expressions, and when you would use each. Junior

A for expression transforms one collection into another, like JavaScript's array.map(); square brackets make a list, braces make a map, and an if filters:

[for s in var.subnets : s.name if s.public]
{ for name, cidr in var.subnets : name => cidrhost(cidr, 4) }

A splat [*] is shorthand for "this attribute of every element": azurerm_subnet.app[*].id is the same as [for s in azurerm_subnet.app : s.id].

Use a splat for the simple case on a list (a count resource). Use a for expression when you filter, reshape, build a map, or work on a for_each resource (which is a map: values(azurerm_subnet.app)[*].id or a for).

Try either in terraform console before writing it down. And for JavaScript habits: + is arithmetic only - build strings with interpolation ("rg-${var.env}") - and == never converts types.

Also asked: How do you build strings in Terraform? · What does a dynamic block do? · What is the difference between templatefile and interpolation?

Learn it: 12.11 Expressions: references, operators, for, splat and templates

Which Terraform functions do you use most, and for what? Junior

The everyday ones, all testable in terraform console:

There are no user-defined functions; locals and modules are how you reuse logic.

Also asked: What is the difference between try and coalesce? · How would you split a /16 into subnets of different sizes in Terraform? · How do you safely reference a resource created with count = var.enabled ? 1 : 0?

Learn it: 12.14 Functions: the ones you use every day

How does Terraform decide the order in which it creates resources? Junior

From references, not from file order. When azurerm_virtual_network.main uses azurerm_resource_group.main.name, it implicitly depends on the group. Terraform builds a dependency graph from every reference, then walks it - independent resources in parallel (10 at a time by default), dependents after what they need. Destroy walks the same graph backwards. terraform graph prints it.

The classic bug: a resource names its resource group with a literal string instead of a reference, so it has no edge in the graph and fails on a fresh environment with ResourceGroupNotFound. The fix is the reference, not depends_on.

depends_on is only for hidden dependencies that no attribute shows - for example a secret that can only be written once a role assignment exists. Overusing it slows plans and, on a data source, defers the read to apply.

A data source is read-only: it looks up something that exists, Terraform never creates or deletes it.

Also asked: What is the difference between a data source and terraform import? · When would you use depends_on? · What are meta-arguments in Terraform?

Learn it: 12.18 Data sources, references and the dependency graph

What is the difference between count and for_each, and why does it matter? Junior

Both create several instances of a resource; the difference is how each instance is addressed, and the address is the identity Terraform matches to state:

Remove the second item of a list under count and every element after it shifts down an index; anything whose name cannot change in place is replaced. One removed subnet becomes four destroyed. With for_each, removing "app" is exactly one destroy.

Rule: for_each for anything with an identity, keyed by something unique, stable and known at plan time (a name, not an ID that exists only after apply). count for identical anonymous copies and the on/off switch (count = var.enabled ? 1 : 0, read with one(...[*])).

Already on count? Rename the state entries with moved blocks, and check the plan shows no replacements.

Also asked: Why does for_each not accept a list? · What does "known at plan time" mean for for_each keys? · How do you migrate a resource from count to for_each without recreating it?

Learn it: 12.20 count vs for_each - the one that causes outages

How do you read a Terraform plan before approving it? Junior

  1. Read the summary line last, not first. "1 to add, 1 to change, 1 to destroy" can hide a replacement - that is the add and the destroy together.
  2. Search for must be replaced and # forces replacement. -/+ means destroy, then create; the marked attribute is why. A replaced storage account or database loses its data.
  3. Check every - destroy is intended.
  4. (known after apply) is normal on creates, suspicious on something that should be stable.
  5. "Objects have changed outside of Terraform" above the plan means someone changed things by hand, and this plan may undo it.

Then apply exactly what was reviewed: terraform plan -out=tfplan, review, terraform apply tfplan. A plain apply re-plans and can differ. In automation, terraform show -json tfplan | jq can list every delete and stop the job.

Also asked: What does terraform validate check, and what does it not? · Why should you not use -target routinely? · What does terraform plan -detailed-exitcode return, and what is it used for?

Learn it: 12.24 The workflow, read like a reviewer

Practise these answers with flashcards and labs Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.