Ansible 还是 Terraform:AI 平台基础设施即代码的选型复盘

一、基础设施即代码不是二选一,而是工具维度的精准匹配

AI 平台的基础设施有两个截然不同的管理层面:底层云资源(GPU 节点、网络 VPC、对象存储桶)和上层软件配置(Kubernetes 组件、NVIDIA 驱动、容器运行时的参数)。这两个层面的管理语义完全不同——资源管理是声明式的"我要 30 台 A100 机器",软件配置是过程式的"先装驱动再配内核参数再重启 kubelet"。

Ansible 和 Terraform 分别擅长这两个层面:Terraform 管理基础设施资源的生命周期(创建 → 更新 → 销毁),Ansible 管理操作系统和中间件的配置状态。理论上两者可以互不冲突地协作,但工程实践中的真实问题是:当 GPU 节点被 Terraform 创建出来后,Ansible 如何知道这台节点已经就绪并立即开始配置?当 Terraform 需要销毁一台节点时,如何通知 Ansible 先执行安全下线操作?

这就是选型复盘要回答的核心问题:不是选哪个工具,而是如何设计两者的交接面,使得资源管理和配置管理形成一个无缝编排的闭环。

二、两种工具的协同模式与交接面设计

这种协同模式需要三个关键组件:

动态 Inventory。 传统 Ansible 的主机清单是静态文件,节点 IP 写死在里面。AI 平台的 GPU 节点是动态创建和销毁的,IP 每次不同。解决方案是用 Terraform 的 template_filelocal_file 资源在节点创建后动态生成 Inventory 文件,再通过 Ansible 的 -i 参数传入。更标准的方式是通过 Terraform 的 ansible provisioner 直接调用——但这个 provisioner 的局限是只在 terraform apply 时执行一次,后续节点的配置变更无法通过它触发。

失效安全的顺序编排。 Terraform 和 Ansible 的交接点是最容易出问题的环节。节点创建完成 ≠ 节点可以执行 Ansible——SSH 服务可能需要 30 秒才能完全启动,云平台的 UserData 脚本可能还在运行。如果 Ansible 在这个时间窗口内执行,连接会超时,Playbook 会失败。解决方案是在 Terraform 的 remote-exec provisioner 中嵌入一个健康检查脚本,循环等待 SSH 端口直到连接成功,然后将节点标记为"配置就绪"。

回滚的一致性。 如果 Ansible 配置失败,Terraform 应该如何响应?直接销毁节点重建是最干净的做法,但这要求 Terraform 能感知 Ansible 的执行结果。我们的做法是在节点创建后设置一个 provisioning_status 标签,Ansible 成功后通过 API 将其更新为 ready,Terraform 在后续 Plan 中检查所有节点的状态标签,对 failed 状态的节点自动触发 taint → destroy → recreate 流程。

三、生产代码:Terraform + Ansible 交接面的工程实现

# terraform/gpu_node_pool.tf

# GPU 节点池定义
resource "google_container_node_pool" "gpu_a100" {
  name       = "gpu-a100-pool"
  cluster    = google_container_cluster.ai_platform.name
  node_count = var.gpu_node_count

  node_config {
    machine_type = "a2-highgpu-1g"
    disk_size_gb = 200
    image_type   = "UBUNTU_CONTAINERD"

    guest_accelerator {
      type  = "nvidia-tesla-a100"
      count = 1
    }

    # 节点标签:用于 Ansible 动态分组
    labels = {
      role        = "gpu-worker"
      gpu_type    = "a100"
      provisioned = "pending" # 初始状态,Ansible 完成后改为 ready
    }

    # 操作系统层面的 GPU 驱动依赖
    metadata = {
      install-nvidia-driver = "false" # 由 Ansible 安装指定版本
    }
  }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [node_config[0].labels["provisioned"]]
  }
}

# 健康检查:确保节点 SSH 可达后再交接给 Ansible
resource "null_resource" "health_check" {
  depends_on = [google_container_node_pool.gpu_a100]
  count      = var.gpu_node_count

  connection {
    type        = "ssh"
    host        = element(google_compute_instance.gpu_nodes.*.network_interface.0.access_config.0.nat_ip, count.index)
    user        = "ubuntu"
    private_key = file(var.ssh_private_key_path)
    timeout     = "2m"
  }

  provisioner "remote-exec" {
    inline = [
      # 等待云初始化脚本完成
      "while [ ! -f /var/lib/cloud/instance/boot-finished ]; do sleep 5; done",
      # 验证 NVIDIA GPU 设备被识别
      "lspci | grep -i nvidia || echo 'GPU not detected yet'",
      # 检查容器运行时是否安装(如果镜像已预装)
      "which containerd || apt-get update && apt-get install -y containerd",
    ]
  }
}

# Ansible 配置触发:使用 local-exec 调用 Ansible
resource "null_resource" "ansible_provision" {
  depends_on = [null_resource.health_check]
  count      = var.gpu_node_count

  provisioner "local-exec" {
    command = <<-EOT
      ansible-playbook \
        -i '${element(google_compute_instance.gpu_nodes.*.network_interface.0.access_config.0.nat_ip, count.index)},' \
        -u ubuntu \
        --private-key ${var.ssh_private_key_path} \
        playbooks/gpu-node-setup.yml \
        -e "node_index=${count.index}" \
        -e "node_zone=${var.zone}"
      if [ $? -ne 0 ]; then
        echo "Ansible playbook failed for node ${count.index}" >&2
        # 标记节点为 failed 状态,由下一轮 Terraform Plan 处理
        exit 1
      fi
    EOT
  }
}

这套代码的核心设计原则:Terraform 做它擅长的事——计算期望和实际的差异,Ansible 做它擅长的事——确保操作系统的配置达到声明状态。两者之间通过 provisioned 标签和 health_check 资源作为交接面,不直接耦合。

四、选型复盘:两条路径的边界条件和代价

如果放弃"Terraform + Ansible"的协同架构,走向极端选择单一工具呢?

纯 Terraform 路径。 Terraform 可以通过 remote-exec provisioner 直接执行 shell 命令来安装驱动和配置系统。代价是 Terraform 的 provisioner 不具备幂等性声明——它不知道驱动是否已经安装正确,只能在 apply 时重新执行所有命令。结果就是每次 terraform apply 都会重新跑安装脚本,初次创建 3 分钟的节点配置被拖到 12 分钟。

纯 Ansible 路径。 用 Ansible 的云模块(如 gcp_container_node_pool)创建资源。问题是 Ansible 的云模块不如 Terraform 的 provider 完善,资源间的依赖关系表达不清晰,state 文件的管理也不如 Terraform 成熟。销毁 30 个节点并清理关联的磁盘和 IP,用 Terraform 只需要 terraform destroy,用 Ansible 需要仔细编排 playbook 的顺序和 absent 状态的管理。

协同路径:更繁琐但更稳定的趋势。 维护两套配置(.tf.yml)虽然增加了代码量,但它将基础设施的两个维度解耦到了各自的最佳工具中。协同的关键是两个原则:

  1. Terraform 负责"创建什么",Ansible 负责"配置成什么",这是最清晰的职责划分。
  2. 状态交接只通过标签和 API,不使用 provisioning script 的返回值作为状态判断依据——因为返回值只会反映 exit code,不反应实际系统的运行状态。

五、总结

AI 平台基础设施即代码的选型核心是职责切割而非工具替代:

  1. Terraform 管理基础设施资源:GPU 节点、网络、负载均衡、存储——这些资源的生命周期是创建-更新-销毁,天然适合声明式语言描述。
  2. Ansible 管理操作系统和中间件配置:驱动版本、内核参数、运行时配置——这些是多步骤的过程式任务,需要幂等性和可重复执行。
  3. 交接面需要工程化设计:动态 Inventory、健康检查等待、失败节点的状态回传——这三个组件不比基础设施本身简单,值得投入工程资源打磨。

基础设施不需要漂亮话。Terraform 和 Ansible 不是选哪个更酷的问题,而是你的自动化流水线出了多少次"节点创建成功但配置失败"的告警之后,才被迫去做的交接面设计。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐