Terraform Module Design, Remote State, and Ansible Fundamentals
This post covers two topics: Terraform module design with GCS remote state, and Ansible’s core structure with idempotency verification. The two are complementary — Terraform provisions infrastructure, Ansible configures servers internally.
Factoring Reusable Infrastructure Units
Avoiding Copy-Paste Across Environments
Without modules, the same VPC logic is written separately for dev and prod in Terraform. Change one, forget the other, and problems arise. Modules work like functions: define once, use with different parameters.
Target structure:
|
|
modules/ defines reusable infra units. environments/ handles actual deployment, passing in different variable values.
Input Declarations
|
|
Resource Definitions
|
|
Exported Values
|
|
Non-Production Workspace Instantiation
|
|
Concrete Variable Assignments
|
|
The prod environment calls the same module, just passing different vpc_name and subnet_cidr. The module itself remains unchanged.
Distributed Snapshot Backend and Write-Lock Guard
On-Disk Snapshots Cannot Serve Multiple Contributors
Terraform’s state file records “what currently exists on GCP.” Without remote state, the state only exists locally:
- Local machine lost = all infrastructure information gone
- Multi-person collaboration = each person has their own state copy, conflicting with each other
GCS backend stores state centrally in a GCS bucket, shared by everyone.
Preventing Simultaneous Apply Races
If two people run terraform apply simultaneously, both read the same state, each calculates a diff, and each applies changes — unpredictable results.
State locking means the first apply locks the state after starting, and the second person must wait for the lock to be released. This corresponds to the Consistency in CAP Theorem: in a distributed system, two operations cannot simultaneously modify the same thing.
|
|
GCS backend natively supports locking, implemented via GCS object’s generation mechanism. No additional configuration needed.
Initialization:
|
|
The reason plan and apply should be separate: plan shows “what will happen,” apply actually executes. In production environments, plan results are typically reviewed before applying.
Querying and Reordering Snapshot Entries
|
|
state rm does not delete the resource on GCP — it only makes Terraform stop tracking it. On the next apply, Terraform thinks the resource doesn’t exist and may attempt to recreate it. So state rm is typically paired with terraform import: first remove, then re-import to a new address.
Host Configuration via Agent Playbooks
Provisioning Tool Versus Configuration Tool
| Terraform | Ansible | |
|---|---|---|
| Purpose | Provision infrastructure | Configure servers internally |
| Language | HCL (declarative) | YAML playbook |
| State | State file | No state, re-checks each time |
| Connection method | API call | SSH (agentless) |
| Idempotency | Naturally idempotent | Requires correct playbook writing |
Ansible agentless means: target machines don’t need any Ansible agent installed, only SSH and Python. Ansible runs on the control machine (your laptop or CI runner), connecting to target machines via SSH to execute operations.
In modern K8s environments, Ansible’s role has diminished because K8s YAML replaces much of the configuration work. But VM and on-premise environments are still very common.
Inventory, Module, Task, Playbook, Role
Inventory: Which machines you manage, grouped for organization.
|
|
Module: Ansible’s built-in operation units, e.g., file, copy, apt, service. Modules are the foundation of idempotency — each module checks target state before executing. If state already matches, it does nothing.
Task: One step, calling one module.
Playbook: A combination of one or more tasks, defining what to execute on which hosts.
Role: A collection of multiple tasks, reusable, similar to Terraform modules. Suitable for encapsulating reusable logic like nginx installation or prometheus configuration.
Minimal YAML Example
|
|
|
|
Confirming No-Op on Re-Run
First run:
|
|
Second run (without changing anything):
|
|
file and copy modules are idempotent. The second run showing ok means no changes were made. command module is not idempotent — it always executes. This is a design point requiring attention. Idempotent command operations need creates or when condition controls.