Infrastructure as Code and Configuration Management
This domain tests how you declare, review, and reconcile infrastructure: the Terraform/OpenTofu plan-apply lifecycle and state model, and idempotent configuration management with Ansible, Chef, Puppet or Salt.
Core rules
- Two models coexist. Provisioning tools (Terraform, CloudFormation, Pulumi) manage resource lifecycles declaratively, tracking real IDs in state. Configuration management tools converge existing hosts to a desired state. Both must be idempotent: a second run changes nothing.
- Terraform state maps declarations to real resources. Keep it in a remote backend with locking (S3 + DynamoDB, GCS, Terraform Cloud) so concurrent CI runs cannot corrupt it. State stores attribute values in plaintext, so the bucket is a secret-bearing asset.
- Workflow:
fmt→validate→plansaved as an artifact → human review →applythe saved plan. Avoid blind applies and avoid-targetas routine practice; never hand-edit state withoutstate mvorimport. - Pin provider and module versions and commit the dependency lock file. Prefer
for_eachwith stable keys overcountfor any collection that can be reordered. - Ansible: use modules over
shell/command; dry-run with--check --diff; organise roles and inventories; usechanged_whenandfailed_whenso handlers and CI exit codes are truthful. - Policy as code (OPA/Conftest, Sentinel, Checkov) gates the plan before apply.
Worked example
A module creates subnets with count = length(var.subnets) for three entries, [a, b, c]. Someone deletes b from the list. Reasoning: count indexes by position, so index 1 (currently b) is updated to c and index 2 (the old c) is destroyed - one subnet is re-created with a new CIDR and one disappears, which in a VPC means re-IP-ing workloads. With for_each = { for s in var.subnets : s.name => s }, keys are subnet names: deleting b destroys exactly one resource and leaves a and c untouched.
Traps
- Committing
terraform.tfstateor secret-bearing*.tfvars; secrets passed as resource arguments land in state and in plan output. - Assuming a plan reviewed in a pull request equals what CI applies later - the plan goes stale as main moves.
- Calling a playbook idempotent because it exits 0, while its
shelltasks reportchangedevery run. - Using workspaces instead of separate state and credentials when environments need real blast-radius isolation.