一、 从“盖房子”与“装修房子”说起

想象一下,你要建造并运营一个现代化的数据中心。这个过程可以拆解成两个主要阶段:

  1. 盖房子(构建基础设施):你需要买地、打地基、砌墙、安装门窗、通水电。这些是基础框架,一旦建好,改动起来成本很高。
  2. 装修与入住(配置与管理):房子盖好了,你需要安装灯具、布置家具、开通网络、设置安防系统。这些是内部的、灵活的配置,可以根据需要随时调整。

在IT世界里,Terraform就像是那位出色的“建筑师”和“结构工程师”,专注于“盖房子”阶段。它用代码定义并创建云服务器、网络、存储、数据库实例等基础设施资源。而Ansible则像是万能的“室内设计师”和“物业管家”,负责“装修与入住”阶段,在创建好的服务器内部安装软件、配置服务、部署应用、进行日常管理。

它们俩一个主外,一个主内,协同工作,就能实现从零到一,再到稳定运行的完整自动化流程,这就是“基础设施即代码”的完美配合。

二、 核心工具简介:Terraform 与 Ansible 各司其职

Terraform 由 HashiCorp 公司开发,它使用一种名为 HCL 的声明式语言。你只需告诉它“我想要什么状态”(比如,我想要两台 Ubuntu 22.04 的云服务器,在一个安全组内),它就会自动计算如何从当前状态达到你描述的目标状态,并调用云厂商的 API 去创建。它的强项在于生命周期管理:创建、修改、销毁基础设施。它有一个状态文件,记录着它管理的所有资源,这是它的“设计图纸”。

Ansible 使用 YAML 语言编写“剧本”,通过 SSH 或 WinRM 协议连接到目标服务器,执行一系列任务。它的核心思想是无代理幂等性。无代理意味着你不需要在目标服务器上预先安装客户端;幂等性意味着同一个剧本运行多次,效果是一样的(比如,安装软件包,如果已经安装了就不会重复安装)。它擅长处理服务器内部的、动态的配置。

简单来说:Terraform 负责“有没有”,Ansible 负责“好不好用”。

三、 如何协同:两种经典工作模式

两者协同的关键在于“信息传递”。Terraform 创建了服务器,它需要把服务器的 IP 地址等信息告诉 Ansible,Ansible 才能知道去配置谁。

模式一:Terraform 置备 + Ansible 配置(最常用) 这是最直观的流程。Terraform 先运行,创建出所有基础设施,并输出一些关键信息(如服务器 IP 地址列表)。然后,我们手动或通过 CI/CD 工具触发 Ansible,将这些信息作为“库存”输入,对服务器进行配置。

模式二:Terraform 调用本地 Ansible(紧密集成) Terraform 本身可以通过 local-execnull_resource 触发器,在资源创建完成后,在本地执行一条命令来调用 Ansible-playbook。这种模式更自动化,但耦合也更紧密,适合简单的场景。

下面,我们通过一个完整的示例来演示第一种模式。

四、 完整示例:部署一个高可用的 Web 应用集群

技术栈声明: 本示例全程使用 AWS 作为云平台。

场景: 我们需要在 AWS 上创建一个高可用的 Web 集群,包含一个负载均衡器、两个位于不同可用区的 Web 服务器,并在服务器上部署 Nginx 和我们的应用代码。

步骤 1:Terraform 构建基础设施 (main.tf)

# 声明使用 AWS 提供商
provider "aws" {
  region = "us-east-1"
}

# 1. 创建 VPC 和子网(我们的“地皮”和“楼层规划”)
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "MainVPC"
  }
}

resource "aws_subnet" "public_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_subnet" "public_b" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1b"
}

# 2. 创建安全组(防火墙规则,允许 HTTP 和 SSH)
resource "aws_security_group" "web_sg" {
  name        = "web_sg"
  description = "Allow HTTP and SSH"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # 生产环境应限制为特定IP
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# 3. 创建启动模板(定义服务器“蓝图”:镜像、类型、密钥对)
resource "aws_launch_template" "web_lt" {
  name_prefix   = "web-template"
  image_id      = "ami-0c55b159cbfafe1f0" # Ubuntu 22.04 LTS
  instance_type = "t2.micro"
  key_name      = "my-key-pair" # 你需要提前在 AWS 创建密钥对

  network_interfaces {
    associate_public_ip_address = true
    security_groups             = [aws_security_group.web_sg.id]
  }

  tag_specifications {
    resource_type = "instance"
    tags = {
      Name = "WebServer"
      Role = "Web"
    }
  }
}

# 4. 创建自动伸缩组(自动创建并管理两个 Web 服务器)
resource "aws_autoscaling_group" "web_asg" {
  name_prefix      = "web-asg-"
  desired_capacity = 2
  max_size         = 4
  min_size         = 2

  vpc_zone_identifier = [aws_subnet.public_a.id, aws_subnet.public_b.id]
  launch_template {
    id      = aws_launch_template.web_lt.id
    version = "$Latest"
  }

  tag {
    key                 = "Name"
    value               = "WebServer"
    propagate_at_launch = true
  }
}

# 5. 创建应用负载均衡器
resource "aws_lb" "web_alb" {
  name               = "web-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.web_sg.id]
  subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
}

resource "aws_lb_target_group" "web_tg" {
  name     = "web-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
}

resource "aws_lb_listener" "front_end" {
  load_balancer_arn = aws_lb.web_alb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.web_tg.arn
  }
}

# 6. 将自动伸缩组关联到目标组
resource "aws_autoscaling_attachment" "asg_attachment" {
  autoscaling_group_name = aws_autoscaling_group.web_asg.name
  alb_target_group_arn   = aws_lb_target_group.web_tg.arn
}

# 7. 关键输出:输出负载均衡器的访问地址,以及我们需要的服务器信息。
#    这里我们通过 AWS 实例数据源来获取已创建的实例的私有 IP。
output "alb_dns_name" {
  value       = aws_lb.web_alb.dns_name
  description = "负载均衡器的 DNS 名称,用于访问网站"
}

# 获取自动伸缩组内所有实例的私有 IP,用于 Ansible 库存
data "aws_instances" "web_instances" {
  instance_tags = {
    "aws:autoscaling:groupName" = aws_autoscaling_group.web_asg.name
  }
  depends_on = [aws_autoscaling_group.web_asg] # 确保实例已创建
}

output "web_instance_private_ips" {
  value       = data.aws_instances.web_instances.private_ips
  description = "Web 服务器的私有 IP 地址列表,供 Ansible 使用"
  sensitive   = false
}

运行 terraform apply 后,基础设施就创建好了。你会得到 alb_dns_name (如 web-alb-123456.us-east-1.elb.amazonaws.com) 和 web_instance_private_ips (如 ["10.0.1.10", "10.0.2.20"])。

步骤 2:Ansible 配置服务器 (inventory.inisite.yml)

我们将 Terraform 输出的 IP 列表写入 Ansible 的库存文件。这个过程可以手动,也可以通过脚本自动完成(例如,用 terraform output -json 解析)。

库存文件 inventory.ini

[web_servers]
10.0.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=/path/to/my-key-pair.pem
10.0.2.20 ansible_user=ubuntu ansible_ssh_private_key_file=/path/to/my-key-pair.pem

[web_servers:vars]
# 可以在这里定义组变量
app_version = "1.0.0"

Ansible 剧本 site.yml

---
- name: 配置高可用 Web 集群
  hosts: web_servers # 对应库存文件中的组
  become: yes # 使用 sudo 权限
  tasks:
    - name: 更新 apt 软件包缓存
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: 安装 Nginx
      apt:
        name: nginx
        state: present

    - name: 创建应用代码目录
      file:
        path: /var/www/myapp
        state: directory
        owner: www-data
        group: www-data

    - name: 部署应用静态文件
      copy:
        src: ./myapp_files/index.html # 假设你的应用文件在本地这个目录
        dest: /var/www/myapp/
        owner: www-data
        group: www-data
      notify:
        - restart nginx # 触发处理器,如果文件有变化则重启nginx

    - name: 配置 Nginx 虚拟主机
      template:
        src: ./templates/nginx.conf.j2 # Jinja2 模板文件
        dest: /etc/nginx/sites-available/myapp
        owner: root
        group: root
      notify:
        - restart nginx

    - name: 启用 Nginx 站点配置
      file:
        src: /etc/nginx/sites-available/myapp
        dest: /etc/nginx/sites-enabled/myapp
        state: link
      notify:
        - restart nginx

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

Jinja2 模板 templates/nginx.conf.j2

server {
    listen 80 default_server;
    root /var/www/myapp;
    index index.html;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }
}

现在,运行 ansible-playbook -i inventory.ini site.yml,Ansible 就会连接到那两台服务器,完成所有软件安装和配置。最后,访问 Terraform 输出的 alb_dns_name,你就能看到通过负载均衡器访问到的、由两台服务器承载的网站了。

五、 应用场景、优缺点与注意事项

应用场景:

  • 混合云/多云部署:Terraform 统一管理不同云资源,Ansible 提供一致的配置。
  • 完整的 CI/CD 流水线:代码提交后,CI 工具先调用 Terraform 创建/更新环境,再调用 Ansible 部署新版本应用。
  • 灾难恢复:用代码定义的环境可以快速在另一个区域重建。
  • 开发与生产环境一致性:使用相同的 Terraform 和 Ansible 代码,确保环境无差异。

技术优缺点:

  • 优点
    • 职责分离:架构清晰,Terraform 管资源,Ansible 管配置,符合单一职责原则。
    • 工具优势最大化:利用了 Terraform 强大的资源管理和状态跟踪,以及 Ansible 灵活的配置管理和丰富的模块。
    • 灵活性高:可以独立更新基础设施或应用配置。
  • 缺点/挑战
    • 学习两种工具:团队需要掌握两种不同的语言和范式。
    • 状态与信息传递:需要妥善管理 Terraform 的状态文件(.tfstate)并建立可靠的方式将输出传递给 Ansible。
    • 潜在的不一致:如果绕过工具手动修改了资源或配置,会导致状态不一致。

注意事项:

  1. 状态文件是命根子:Terraform 的 .tfstate 文件必须安全存储(如使用 S3 后端 + DynamoDB 锁),并严格进行版本控制备份。
  2. 密钥管理:在代码中避免硬编码密码、密钥。使用 Terraform 的变量文件(.tfvars)或云厂商的密钥管理服务(如 AWS Secrets Manager),Ansible 则可以使用 Vault。
  3. 库存动态管理:对于自动伸缩组,服务器 IP 可能会变。建议使用 Ansible 的动态库存插件(如 aws_ec2 插件),直接从 AWS 获取实时服务器列表,而不是静态文件。
  4. 执行顺序与依赖:确保 Ansible 剧本在 Terraform 完全创建资源并处于稳定状态后再运行。可以在 CI/CD 管道中设置明确的阶段和依赖。

六、 总结

将 Ansible 与 Terraform 结合,构建了一套从基础设施到应用配置的端到端自动化体系。Terraform 像一位精准的蓝图执行者,负责搭建稳定、可复现的底层环境;Ansible 像一位细致的配置艺术家,负责让环境变得生动、可用。这种组合不仅提升了效率,更重要的是它带来了可预测性、可审计性和极强的可恢复能力,是现代化运维和 DevOps 实践中不可或缺的利器。掌握它们的协同,意味着你能够用代码真正驾驭从云资源到业务服务的全生命周期。