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 through a teaching and hands-on approach, 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 state of resource existence (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 running services, 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 can lead to leaks.
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 retries.
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) necessitate inserting 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 planned 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 within the correct boundaries of responsibility and seamlessly integrating with real-world 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
planon Terraform and uses the artifact (plan) as the PR result; after manual approval, applies in a controlled environment. - • 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, automate remediation (e.g., packages); for destructive issues, initiate a ticket.
- • 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: The
web_public_iphere will 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: |
<h1>Deployed by Ansible</h1>
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. Failures should be logged in CI logs and followed up manually (as misconfigurations 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. Reviewers examine the plan for changes; after approval, trigger
terraform apply(executed in a controlled runner to avoid 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 mismatches), and initiate change tickets for destructive changes.
6. Common Pitfalls and Response Manual
Variable Spelling Errors
Some people easily misspell variables when writing playbooks, leading to incorrect logic:
- 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, resulting in MySQL not starting at all. Such small mistakes cannot be helped by automation tools and may amplify errors instead.
State File Conflicts
When multiple people collaborate without configuring remote state, it 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 need to pull the handbrake.
Conclusion
Automation does not completely remove people from the process; rather, it frees their 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 idealism to reality: no longer a “fantasy universal key”, but a reliable productivity tool for the team.
Old Yang’s Time
Here I declare that 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.
My younger colleagues born in the 2000s I call all with Ge. Zhang Ge, Li Ge.
However, this title, when offline at some events, is not very appropriate when the big boss also calls me that.
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 technical circle to say a few words.”
This atmosphere with this title in the internet industry feels a bit mismatched!
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