0. Introduction: Why You Need “Tools” Instead of “Common Commands”
Abstract: Infrastructure as Code (Terraform) and Configuration Management/Orchestration (Ansible) transform “human repetitive labor” into reproducible code. However, when automation encounters the real world—state drift, hardware, emergency changes, approvals, sensitive information, and third-party dependencies—”boundaries” are exposed. This article explains these gaps, common pitfalls, and practical models in a teaching + hands-on manner, providing directly runnable examples (including simulated console commands and execution results). The goal is for you to implement this method in your team pipeline, achieving both automation and safety.
1. Essential Differences: Terraform vs Ansible
- • Terraform: Declarative, focuses on the existence state of resources (create/update/destroy), maintains global/remote state, suitable for infrastructure lifecycle management (networks, load balancers, cloud instances, DNS, managed DBs, etc.).
- • Ansible: Imperative or idempotent (ideally), focuses on configuration and runtime state (software installation, service configuration, file content, executing one-time scripts), suitable for machine configuration/application deployment/operations scripts.
In summary: Terraform manages the existence of the “house” and “fence”, while Ansible manages “what’s inside the house” and “how to clean it”.
2. Common “Boundaries” and Symptoms in the Real World
Below are real issues you will encounter in production, and why relying solely on Ansible or Terraform makes it difficult to solve them:
State Drift
- • For example: Operations personnel manually changed configurations on running machines; Terraform state still believes the resource has not changed.
- • Result:
terraform applycannot detect or unwillingly changes the running state service, causing interruptions.
Sensitive Information Leakage and State
- • Terraform state and provider outputs may contain plaintext credentials. If the team does not encrypt/access control, it will leak.
Non-reconstructability of External/Stateful Services
- • Some resources (e.g., legacy databases, hardware devices, proprietary switch ACLs) cannot be casually destroyed and rebuilt; Terraform’s inertia is to “declare based on resources”.
Long-running Tasks and Partial Failures
- • For example, patching or migrating large databases; a failed step requires manual inspection, unsuitable for full automation retry.
Network/Hardware/Physical Dependencies
- • On-premises switches, UPS, cabinets, fiber optics, etc., are not covered by cloud providers, and Terraform’s capabilities are weak.
Approvals, Compliance, and Manual Interventions
- • Compliance requirements (change orders, approval chains) need to insert manual steps outside the automation process.
Uncertainty of Third-party and Closed-source APIs
- • Provider bugs, API limitations, and rate limits can lead to discrepancies between plans and actual outcomes.
3. Practical Models: Bridging the Gaps with Practices and Architectures
Below is a highly practical model aimed at placing Terraform and Ansible in the correct responsibility boundaries and seamlessly integrating with real processes.
Model A — Responsibility Division
- • Terraform: Networks, VPCs, subnets, load balancers, cloud hosts (empty images), managed database instances, DNS, IAM, S3, etc.; outputs IP/hostname, ssh-key, userdata, etc., to upstream.
- • Packer (optional): Builds golden images (including basic dependencies), reducing initial configuration time and drift.
- • Ansible: Configuration, application deployment, runtime tasks, patching, one-time migration scripts.
- • CI/CD: Executes
terraform planand uses the artifact (plan) as PR results; after manual approval, applies in a controlled environment usingapply. - • Secrets: HashiCorp Vault / AWS Secrets Manager / SOPS + KMS; Terraform state exists in a controlled backend (S3 + Dynamo lock / Terraform Cloud).
Model B — Secure Connection Points
- 1. Terraform outputs JSON -> dynamic inventory for Ansible use
- •
terraform output -json > tf-output.json, then a small script writes the instance IP to inventory.ini, or use Ansible EC2 dynamic inventory plugin.
- • Provisioners are convenient but can cause unpredictable states in failure scenarios. Recommended: Use userdata/cloud-init or Packer to build images, then manage with Ansible.
- • For stateful services, first perform blue-green or rolling updates; do not directly replace single-node databases.
- • Regularly run
terraform plan+ansible --checkto report differences; for non-destructive issues, automatic remediation (e.g., packages); for destructive issues, initiate a work order.
- • CI posts plan output back to PR; Require reviewers -> Merge triggers
apply(with multiple webhooks + logs).
4. Practical Example
Scenario: Create an EC2 instance in AWS (example) using Terraform, then use Ansible to install Nginx and place a static page. The example focuses on demonstrating the “correct boundaries”: Terraform manages resources, Ansible manages configurations.
4.1 Terraform: Simple main.tf
# main.tf (example)
provider "aws" {
region = "us-east-1"
}
resource "aws_key_pair" "deploy" {
key_name = "demo-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "aws_security_group" "ssh_http" {
name = "sg-ssh-http-demo"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-0c94855ba95c71c99" # Example: Ubuntu 18.04 (please adjust according to real account)
instance_type = "t3.micro"
key_name = aws_key_pair.deploy.key_name
vpc_security_group_ids = [aws_security_group.ssh_http.id]
tags = { Name = "demo-web" }
# cloud-init for initial boot, only prepares for ssh availability
user_data = <<-EOF
#cloud-config
ssh_authorized_keys:
- ${file("~/.ssh/id_rsa.pub")}
EOF
}
output "web_public_ip" {
value = aws_instance.web.public_ip
}
4.2 Terraform Operations
$ terraform init
Initializing the backend...
Initializing provider plugins...
Terraform has been successfully initialized!
$ terraform plan -out=tfplan.binary
Refreshing provider state...
Plan: 1 to add, 0 to change, 0 to destroy.
$ terraform apply "tfplan.binary"
aws_key_pair.deploy: Creating...
aws_security_group.ssh_http: Creating...
aws_instance.web: Creating...
... (omitted)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
web_public_ip = "3.125.78.99"
Note: Here,
web_public_ipwill be used for Ansible inventory (next section).
4.3 Passing Terraform Output to Ansible (Generating Simple Inventory)
Save tf-output.json:
$ terraform output -json > tf-output.json
$ cat tf-output.json
{
"web_public_ip": {
"sensitive": false,
"type": "string",
"value": "3.125.78.99"
}
}
Generate inventory.ini:
$ python3 - <<'PY'
import json
j=json.load(open('tf-output.json'))
ip = j['web_public_ip']['value']
with open('inventory.ini','w') as f:
f.write('[web]\n')
f.write(f'{ip} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa\n')
print('inventory.ini written')
PY
$ cat inventory.ini
[web]
3.125.78.99 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
4.4 Ansible: site.yml
# site.yml
- hosts: web
gather_facts: yes
become: yes
tasks:
- name: Ensure apt cache is up to date
apt:
update_cache: yes
tags: packages
- name: Install nginx
apt:
name: nginx
state: latest
notify: restart nginx
tags: packages
- name: Deploy index.html
copy:
dest: /var/www/html/index.html
content: |
<html><body><h1>Deployed by Ansible</h1></body></html>
tags: deploy
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
4.5 Ansible Execution
$ ansible-playbook -i inventory.ini site.yml
PLAY [web] ******************************************************************
TASK [Gathering Facts] ********************************************************
ok: [3.125.78.99]
TASK [Ensure apt cache is up to date] *****************************************
changed: [3.125.78.99]
TASK [Install nginx] **********************************************************
changed: [3.125.78.99]
TASK [Deploy index.html] ******************************************************
changed: [3.125.78.99]
RUNNING HANDLER [restart nginx] **********************************************
changed: [3.125.78.99]
PLAY RECAP ********************************************************************
3.125.78.99 : ok=5 changed=4 unreachable=0 failed=0
Note: If Ansible reports an error (e.g., unreachable/failed), we do not automatically retry destroying or modifying instances at the Terraform level. We should log the failure in CI logs and create a work order/manual follow-up (as misconfiguration may lead to data loss).
5. Testing, Rollback, and Exception Handling Models
5.1 Terraform’s “Plan-Approve-Apply” Workflow
- 1. Execute
terraform planin the PR, and use the plan output as an artifact (or useterraform show -json plan). - 2. Reviewer reviews the plan against changes; after approval, trigger
terraform apply(executed in a controlled runner, avoiding local direct apply). - 3. Use a state backend (S3 + Dynamo/consul or Terraform Cloud) to ensure locking and audit logging.
GitHub Actions:
# .github/workflows/terraform.yml (snippet)
on: [pull_request]
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: terraform init
run: terraform init
- name: terraform plan
run: terraform plan -out=tfplan
- name: upload plan
uses: actions/upload-artifact@v4
with:
name: tfplan
path: tfplan
The apply step should only be executed in a protected branch or triggered manually.
5.2 Rollback Strategies
- • Non-destructive changes: Try to make replacement changes as rolling updates or blue-green, avoiding
destroyfollowed bycreate. - • Database/Stateful: Always establish backups -> verify backups -> then execute destructive changes. Use migration scripts (idempotent) in conjunction with Ansible.
- • Short-term rollback: Use AMI rollback or DNS quick switch to the old cluster in case of failure.
5.3 Drift Detection and Automatic Repair
- • Periodically run:
terraform plan -detailed-exitcode(0: no changes, 2: changes) and alert. - • For non-destructive differences, automatically repair with Ansible (e.g., package version mismatch), and initiate change orders for destructive changes.
6. Common Pitfalls and Response Manual
Variable Spelling Errors
Some people easily misspell variable results logic when writing playbooks:
- hosts: dbservers
vars:
mysql_port: 3306
tasks:
- name: Start MySQL
service:
name: mysqld
state: started
port: "{{ mysql_prt }}"
Execution:
fatal: [db01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'mysql_prt' is undefined"}
Missing an o resulted in MySQL not starting at all. Such small mistakes cannot be helped by automation tools and can amplify errors instead.
State File Conflicts
When multiple people collaborate without configuring remote state, the results can be dangerous:
$ terraform apply
Error acquiring the state lock
Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed
The state file was modified simultaneously by two engineers, ultimately requiring manual intervention to unlock.
$ terraform force-unlock LOCK_ID
Terraform state has been successfully unlocked!
This situation indicates that Terraform is not always “autopilot”; sometimes you still need to pull the handbrake.
Conclusion
Automation does not completely remove people from the process; rather, it frees people’s energy from repetitive labor to focus on judgment, design, and auditing. Terraform and Ansible each have their roles, and when you place them in the right positions, supplemented by CI, auditing, and drills, automation transitions from an ideal to reality: no longer a “fantasy universal key”, but a reliable productivity tool for the team.
Old Yang’s Time
Let me clarify, in daily life, everyone calls me Bo Ge, which has nothing to do with seniority, mainly because I am older. It’s just a nickname.
I call my younger colleagues born in the 2000s with “Ge” as well, like Zhang Ge, Li Ge.
However, this title feels a bit inappropriate when the “golden master” calls it during offline events.
For example, last time a planning director from a certain group said at a company meeting: “Today we are happy! Please welcome Bo Ge from the IT operations and maintenance technology circle to say a few words.”
This atmosphere and this title feel a bit mismatched in the internet industry!
Every time I encounter this situation, I want to respond: “Meeting you all is fate, thank you for your kindness, no need to say anything, it’s all in the wine. I drink, you all feel free!”
So from now on, let’s call me Old Yang, which is both down-to-earth and low-key. I think it’s quite nice.
Operations X Files series articles:
From Alarm to CTO: An 11-hour Life-and-Death Race of a P0 Incident
Enterprise-level Kubernetes Cluster Security Hardening Complete Guide (with one-click check script)
Don’t leave after reading. Cultivation lies in likes, shares, and views. Accumulate merits in this life to earn blessings in the next.
Click to read the original text or open the address to collect and analyze global VPS projects in real-time.
vps.top365app.com
Old Yang AI’s account: 98dev