首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >运维自动化的边界:Ansible、Terraform差异对比分析

运维自动化的边界:Ansible、Terraform差异对比分析

作者头像
IT运维技术圈
发布2025-10-09 12:04:25
发布2025-10-09 12:04:25
2250
举报
文章被收录于专栏:IT运维技术圈IT运维技术圈

0. 写在前面:为什么你需要“神器”而非“常用命令

摘要:基础设施即代码(Terraform)与配置管理/编排(Ansible)把“人为重复劳动”变成了可复现的代码,但当自动化遭遇现实世界——状态漂移、硬件、紧急变更、审批、机敏信息与第三方依赖——就会暴露出“边界”。本文以教学+实操方式讲清这些差距、常见陷阱与落地模式,并给出可直接运行的示例(包括模拟控制台命令与执行结果)。目标是:你读完能把这套方法落地到团队流水线里,既自动化又安全可控。


1. 本质差异:Terraform vs Ansible

  • Terraform:声明式,关注“资源的存在状态”(create/update/destroy),维护全局/远程 state,适合 基础设施生命周期管理(网络、负载均衡、云实例、DNS、托管 DB 等)。
  • Ansible:命令式或幂等(ideally),关注 配置与运行时状态(软件安装、服务配置、文件内容、执行一次性的脚本),适合 机器配置/应用部署/运维脚本

一句话总结:Terraform 管“房子”和“院墙”的存在,Ansible 管“房子里装啥”和“如何打扫”。


2. 现实世界里的常见“边界”与症状

下面列出你会在生产里看到的真实问题,以及为什么纯靠 Ansible 或 Terraform 很难单独解决它们:

状态漂移(drift)

  • • 例如:运维人员手工在运行中的机器上改了配置;Terraform state 仍然认为资源没变。
  • • 结果:terraform apply 无法发现或不愿无脑更改运行态服务导致中断。

敏感信息泄露与 state

  • • Terraform state 与 provider 输出中可能包含明文凭证。团队若未加密/访问控制会泄密。

外部/有状态服务的“不可重建性”

  • • 有些资源(例如:老旧数据库、硬件设备、专有交换机 ACL)不能随便销毁重建;而 Terraform 的惯性是“声明到资源为准”。

长时间运行任务与部分失败

  • • 比如打补丁、迁移大型数据库;一个失败的步骤要求人工检查,不适合全自动 retry。

网络/硬件/物理依赖

  • • On-prem 的交换机、UPS、机柜、光纤等不在云 provider 覆盖面以内,Terraform 提供的能力弱。

审批、合规与人工干预

  • • 合规要求(变更单、审批链)需要在自动化流程外插入人工步骤。

第三方与闭源 API 的不确定性

  • • Provider bug、API 限制、速率限制会导致计划与实际差异。

3. 可落地模式:把差距缩小的实践与架构

下面给出一套实践性很强的模式,旨在把 Terraform 与 Ansible 放在正确的职责边界并和现实流程无缝衔接。

模式 A — 职责划分

  • • Terraform:网络、VPC、子网、负载均衡、云主机(空镜像)、托管数据库实例、DNS、IAM、S3 等基础设施;输出 IP/hostname、ssh-key、userdata 等给上游。
  • • Packer(可选):构建 golden image(包含基础依赖),减少首次配置时间与漂移。
  • • Ansible:配置、应用部署、运行时任务、patching、一次性迁移脚本
  • • CI/CD:对 Terraform 执行 plan 并把 artifact(plan)作为 PR 结果;经过人工审批后在受控环境用 apply
  • • Secrets:HashiCorp Vault / AWS Secrets Manager / SOPS + KMS;Terraform state 存在受控后端(S3 + Dynamo lock / Terraform Cloud)。

模式 B — 安全的连接点

  1. 1. Terraform 输出 JSON -> 动态 inventory 供 Ansible 使用
    • terraform output -json > tf-output.json,然后小脚本把 instance IP 写成 inventory.ini,或者用 Ansible EC2 动态 inventory 插件。
  2. 2. 避免使用 Terraform provisioners(local-exec/remote-exec)来做配置
    • • provisioner 便利但会在失败场景造成不可预料的状态。推荐:使用 userdata/cloud-init 或 Packer 构建镜像,然后用 Ansible 做后续管理。
  3. 3. 蓝绿/滚动替换
    • • 对于有状态服务先做蓝绿或 rolling update;不要把单节点数据库直接替换。
  4. 4. 检测与修复(drift)
    • • 定期运行 terraform plan + ansible --check 报告差异;对非破坏性问题,自动 remediation(例如 packages);对破坏性问题发起工单。
  5. 5. 审批 & 人工干预点
    • • CI 把 plan 输出贴回 PR;Require reviewers -> Merge triggers apply(并带有多层 webhook + 日志)。

4. 实操示例

场景:在 AWS(示例)中用 Terraform 创建一台 EC2(使用软件镜像),然后用 Ansible 安装 Nginx 并放置一个静态页面。示例着力展示“正确的边界”:Terraform 管资源,Ansible 管配置。

4.1 Terraform:简单 main.tf

代码语言:javascript
复制
# 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
}

4.2 Terraform 操作

代码语言:javascript
复制
$ 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(下节)


4.3 把 Terraform 输出给 Ansible(生成简单的 inventory)

保存 tf-output.json

代码语言:javascript
复制
$ terraform output -json > tf-output.json
$ cat tf-output.json
{
  "web_public_ip": {
    "sensitive": false,
    "type": "string",
    "value": "3.125.78.99"
  }
}

生成 inventory.ini

代码语言:javascript
复制
$ 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

代码语言:javascript
复制
# 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

4.5 Ansible 执行

代码语言:javascript
复制
$ 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 日志并创建工单 / 手动跟进(因为配错可能导致数据丢失)。


5. 测试、回滚与异常处理模式

5.1 Terraform 的“预测-审批-应用”工作流

  1. 1. 在 PR 里执行 terraform plan,并将 plan 输出作为 artifact(或用 terraform show -json plan)。
  2. 2. Reviewer 审查 plan 对比变更;通过后触发 terraform apply(在受控 runner 中执行,避免本地直接 apply)。
  3. 3. 使用 state 后端(S3 + Dynamo/consul 或 Terraform Cloud)保证锁定并记录审计。

GitHub Actions:

代码语言:javascript
复制
# .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: tfplan

apply 步骤应该只在 protected branch 或有人工触发时执行。

5.2 回滚策略

  • 不可破坏变更:尽量把替换类变更做成滚动替换或蓝绿,避免 destroycreate
  • 数据库/有状态:永远制定备份 -> 验证备份 -> 再执行破坏性变更。并用迁移脚本(idempotent)配合 Ansible 执行。
  • 短期回滚:在失败时使用 AMI 回滚或 DNS 快速切换到旧集群。

5.3 Drift 检测与自动修复

  • • 周期性运行:terraform plan -detailed-exitcode(0: no changes, 2: changes)并告警。
  • • 对于非破坏性差异,自动用 Ansible 修复(例如 package 版本不符),对破坏性变化发起变更工单。

6. 常见坑与应对手册

变量拼写错误

有些人写 playbook 时容易拼错变量结果逻辑直接跑偏:

代码语言:javascript
复制
- hosts:dbservers
vars:
    mysql_port:3306
tasks:
    -name:启动MySQL
      service:
        name:mysqld
        state:started
        port: "{{ mysql_prt }}"

执行:

代码语言:javascript
复制
fatal: [db01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'mysql_prt' is undefined"}

少写了一个 o,结果 MySQL 根本起不来。这类小失误,自动化工具帮不了,反而容易放大错误。

状态文件冲突

多人协作时若没配置远端 state结果就很危险:

代码语言:javascript
复制
$ terraform apply
Error acquiring the state lock
Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed

状态文件被两个工程师同时修改,最终只能人工介入解锁。

代码语言:javascript
复制
$ terraform force-unlock LOCK_ID
Terraform state has been successfully unlocked!

这种情况说明 Terraform 并不总是“自动驾驶”,有时还得拉手刹。

总结

自动化不是把人从流程中完全移除,而是把人的精力从可重复劳动中解放出来,投入到判断、设计与审计上。Terraform 和 Ansible 各司其职,当你把它们摆在正确的位置,再辅以 CI、审计与演练,自动化就从理想走入现实:不再是“幻想中的万能钥匙”,而是团队可靠的生产力工具。

老杨时间

这里我先声明一下,日常生活中大家都叫我波哥,跟辈分没关系,主要是岁数大了.就一个代称而已. 我的00后小同事我喊都是带哥的.张哥,李哥的. 但是这个称呼呀,在线下参加一些活动时.金主爸爸也这么叫就显的不太合适. 比如上次某集团策划总监,公司开大会来一句:“今个咱高兴!有请IT运维技术圈的波哥讲两句“ 这个氛围配这个称呼在互联网这行来讲就有点对不齐! 每次遇到这个情况我就想这么接话: “遇到各位是缘分,承蒙厚爱,啥也别说了,都在酒里了.我干了,你们随意!” 所以以后咱们改叫老杨,即市井又低调.还挺亲切,我觉得挺好.

运维X档案系列文章:

从告警到CTO:一个P0故障的11小时生死时速

企业级 Kubernetes 集群安全加固全攻略( 附带一键检查脚本)

看完别走.修行在于点赞、转发、在看.攒今世之功德,修来世之福报

点击阅读原文或打开地址实时收集分析全球vps的项目 vps.top365app.com

老杨AI的号: 98dev

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT运维技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 本质差异:Terraform vs Ansible
  • 2. 现实世界里的常见“边界”与症状
  • 3. 可落地模式:把差距缩小的实践与架构
    • 模式 A — 职责划分
    • 模式 B — 安全的连接点
  • 4. 实操示例
    • 4.1 Terraform:简单 main.tf
    • 4.2 Terraform 操作
    • 4.3 把 Terraform 输出给 Ansible(生成简单的 inventory)
    • 4.4 Ansible:site.yml
    • 4.5 Ansible 执行
  • 5. 测试、回滚与异常处理模式
    • 5.1 Terraform 的“预测-审批-应用”工作流
    • 5.2 回滚策略
    • 5.3 Drift 检测与自动修复
  • 6. 常见坑与应对手册
    • 变量拼写错误
    • 状态文件冲突
  • 总结
  • 老杨时间
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档