0. 写在前面:为什么你需要“神器”而非“常用命令
摘要:基础设施即代码(Terraform)与配置管理/编排(Ansible)把“人为重复劳动”变成了可复现的代码,但当自动化遭遇现实世界——状态漂移、硬件、紧急变更、审批、机敏信息与第三方依赖——就会暴露出“边界”。本文以教学+实操方式讲清这些差距、常见陷阱与落地模式,并给出可直接运行的示例(包括模拟控制台命令与执行结果)。目标是:你读完能把这套方法落地到团队流水线里,既自动化又安全可控。
一句话总结:Terraform 管“房子”和“院墙”的存在,Ansible 管“房子里装啥”和“如何打扫”。
下面列出你会在生产里看到的真实问题,以及为什么纯靠 Ansible 或 Terraform 很难单独解决它们:
状态漂移(drift)
terraform apply 无法发现或不愿无脑更改运行态服务导致中断。敏感信息泄露与 state
外部/有状态服务的“不可重建性”
长时间运行任务与部分失败
网络/硬件/物理依赖
审批、合规与人工干预
第三方与闭源 API 的不确定性
下面给出一套实践性很强的模式,旨在把 Terraform 与 Ansible 放在正确的职责边界并和现实流程无缝衔接。
plan 并把 artifact(plan)作为 PR 结果;经过人工审批后在受控环境用 apply。terraform output -json > tf-output.json,然后小脚本把 instance IP 写成 inventory.ini,或者用 Ansible EC2 动态 inventory 插件。terraform plan + ansible --check 报告差异;对非破坏性问题,自动 remediation(例如 packages);对破坏性问题发起工单。apply(并带有多层 webhook + 日志)。场景:在 AWS(示例)中用 Terraform 创建一台 EC2(使用软件镜像),然后用 Ansible 安装 Nginx 并放置一个静态页面。示例着力展示“正确的边界”:Terraform 管资源,Ansible 管配置。
main.tf# main.tf (示例)
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" # 示例:Ubuntu 18.04(请按真实账户调整)
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 用于首次引导,仅做 ssh 可用性准备
user_data = <<-EOF
#cloud-config
ssh_authorized_keys:
- ${file("~/.ssh/id_rsa.pub")}
EOF
}
output "web_public_ip" {
value = aws_instance.web.public_ip
}$ 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...
... (略)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
web_public_ip = "3.125.78.99"说明:此处
web_public_ip将被用于 Ansible inventory(下节)
保存 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"
}
}生成 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_rsasite.yml# site.yml
-hosts:web
gather_facts:yes
become:yes
tasks:
-name:Ensureaptcacheisuptodate
apt:
update_cache:yes
tags:packages
-name:Installnginx
apt:
name:nginx
state:latest
notify:restartnginx
tags:packages
-name:Deployindex.html
copy:
dest:/var/www/html/index.html
content:|
<html><body><h1>Deployed by Ansible</h1></body></html>
tags:deploy
handlers:
-name:restartnginx
service:
name:nginx
state: restarted$ 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注意点:如果 Ansible 报错(例如 unreachable/failed),我们不在 Terraform 层自动重试销毁或修改实例。应把失败记录进 CI 日志并创建工单 / 手动跟进(因为配错可能导致数据丢失)。
terraform plan,并将 plan 输出作为 artifact(或用 terraform show -json plan)。terraform apply(在受控 runner 中执行,避免本地直接 apply)。GitHub Actions:
# .github/workflows/terraform.yml (片段)
on: [pull_request]
jobs:
plan:
runs-on:ubuntu-latest
steps:
-uses:actions/checkout@v4
-name:terraforminit
run:terraforminit
-name:terraformplan
run:terraformplan-out=tfplan
-name:uploadplan
uses:actions/upload-artifact@v4
with:
name:tfplan
path: tfplanapply 步骤应该只在 protected branch 或有人工触发时执行。
destroy 再 create。terraform plan -detailed-exitcode(0: no changes, 2: changes)并告警。有些人写 playbook 时容易拼错变量结果逻辑直接跑偏:
- hosts:dbservers
vars:
mysql_port:3306
tasks:
-name:启动MySQL
service:
name:mysqld
state:started
port: "{{ mysql_prt }}"执行:
fatal: [db01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'mysql_prt' is undefined"}少写了一个 o,结果 MySQL 根本起不来。这类小失误,自动化工具帮不了,反而容易放大错误。
多人协作时若没配置远端 state结果就很危险:
$ terraform apply
Error acquiring the state lock
Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed状态文件被两个工程师同时修改,最终只能人工介入解锁。
$ terraform force-unlock LOCK_ID
Terraform state has been successfully unlocked!这种情况说明 Terraform 并不总是“自动驾驶”,有时还得拉手刹。
自动化不是把人从流程中完全移除,而是把人的精力从可重复劳动中解放出来,投入到判断、设计与审计上。Terraform 和 Ansible 各司其职,当你把它们摆在正确的位置,再辅以 CI、审计与演练,自动化就从理想走入现实:不再是“幻想中的万能钥匙”,而是团队可靠的生产力工具。
这里我先声明一下,日常生活中大家都叫我波哥,跟辈分没关系,主要是岁数大了.就一个代称而已. 我的00后小同事我喊都是带哥的.张哥,李哥的. 但是这个称呼呀,在线下参加一些活动时.金主爸爸也这么叫就显的不太合适. 比如上次某集团策划总监,公司开大会来一句:“今个咱高兴!有请IT运维技术圈的波哥讲两句“ 这个氛围配这个称呼在互联网这行来讲就有点对不齐! 每次遇到这个情况我就想这么接话: “遇到各位是缘分,承蒙厚爱,啥也别说了,都在酒里了.我干了,你们随意!” 所以以后咱们改叫老杨,即市井又低调.还挺亲切,我觉得挺好.
运维X档案系列文章:
企业级 Kubernetes 集群安全加固全攻略( 附带一键检查脚本)
看完别走.修行在于点赞、转发、在看.攒今世之功德,修来世之福报
点击阅读原文或打开地址实时收集分析全球vps的项目 vps.top365app.com
老杨AI的号: 98dev