一、为什么要把Terraform和Ansible 放在一起看
很多人会把 Terraform 和 Ansible 分开学:
- Terraform 用来创建云资源
- Ansible 用来配置服务器
单独看都没问题,但真正落地时,问题往往出在两者怎么接起来。
最常见的低效做法是:
- 先用 Terraform 创建 Azure 虚拟机
- 再手动整理 IP
- 再手写 Ansible inventory
- 机器一变更,inventory 又得改
这套流程最大的问题就是:基础设施已经自动化了,主机清单还是半手工维护。
Ansible 官方提供的 azure.azcollection.azure_rm 动态清单插件,就是为了解决这个问题:
它可以直接从 Azure Resource Manager 查询虚拟机信息,不再依赖手工维护静态 hosts 文件,在主机经常变化的云环境中,减少维护静态 inventory 的负担。
所以这篇文章是一个思路:
Terraform 负责创建 Azure 资源并打标签,Ansible 通过 Azure 动态清单按标签自动发现并分组,最后执行配置。
二、整体架构思路
整个流程可以概括成下面四步:
- Terraform 创建 Azure VM、网卡、公网 IP、NSG 等资源
- Terraform 在 VM 上打标签,例如:
env=devrole=webapp=demo
- Ansible 使用
azure_rm动态清单插件,从 Azure 拉取虚拟机信息 - Ansible Playbook 按标签生成的主机组执行配置任务
这种方式的好处是:
- 不需要手工维护 inventory
- 扩容新主机后,Ansible 下次执行自动发现
- 可以按环境、角色、应用自动分组
- Terraform 和 Ansible 的职责边界更清晰
三、为什么推荐"动态清单 + 标签分组",而不是 Terraform 直接生成 inventory 文件
Terraform 确实可以通过 local_file 在本地生成 inventory 文件。
这种方式适合简单 demo,但不适合作为主方案。云主机 IP、数量、角色都可能变化。 如果 inventory 是 Terraform 渲染出来的静态文件,那它仍然是一份"产物文件",而不是实时视图。
如果换一台机器执行 Terraform,而本地文件不存在,Terraform 会把它识别为需要重建的资源,这容易带来额外 diff 噪音。
我知道有provisioner 这个东西,但是Terraform 官方也明确建议: provisioner 应该是最后手段,因为 Terraform 的核心职责是管理资源生命周期,而不是承担复杂的后置配置逻辑。
因此,在 Azure 场景下,更推荐的方式是:
- Terraform:创建 VM 并打好标签
- Ansible:通过 Azure 动态清单实时读取 Azure 里的VM
- Playbook:按标签生成的分组执行配置
四、准备工作
Azure 动态清单依赖 Ansible 的 Azure Collection。
azure.azcollection.azure_rm 是官方支持的 Azure Resource Manager inventory 插件,需要在执行 Ansible 的控制节点上安装相应 collection 和依赖。该插件要求 inventory 配置文件名以 azure_rm.yml 或 azure_rm.yaml 结尾。
1. 安装Ansible与Azure Collection
bash
sudo apt update
sudo apt install -y pipx
pipx ensurepath
pipx install ansible
ansible-galaxy collection install azure.azcollection
pipx runpip ansible install -r ~/.local/share/pipx/venvs/ansible/lib/python*/site-packages/ansible_collections/azure/azcollection/requirements.txt
实际
requirements.txt路径可能因安装方式不同而略有差异,可先用ansible-galaxy collection list和python -c等方式确认安装目录。
2. 准备Azure 凭证
azure_rm 支持多种认证方式,包括 auto、env、credential_file、cli、msi。
本地测试时可以直接使用 Azure CLI 登录;
bash
az login
az account set --subscription "<your-subscription-id>"
``
CI/CD 或自动化环境更推荐服务主体等非交互式方式。
例如使用环境变量:
bash
export AZURE_SUBSCRIPTION_ID="your_subscription_id"
export AZURE_CLIENT_ID="your_client_id"
export AZURE_SECRET="your_client_secret"
export AZURE_TENANT="your_tenant_id"
五、Terraform:创建Azure VM并打标签
下面示例不是完整生产模板,只演示关键思想:
Terraform 创建虚拟机时,把后续 Ansible 需要的分组信息提前写进标签。
json
resource "azurerm_linux_virtual_machine" "web" {
name = "vm-web-01"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
size = "Standard_B2s"
admin_username = "azureuser"
network_interface_ids = [
azurerm_network_interface.web_nic.id
]
admin_ssh_key {
username = "azureuser"
public_key = file("~/.ssh/id_rsa.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
tags = {
env = "dev"
role = "web"
app = "demo"
}
}
这里最重要的不是 VM 本身,而是 tags。
因为后面 Ansible 动态清单就可以基于这些标签自动分组。
六、使用Azure动态清单自动发现主机
inventory目录下创建一个文件,名称必须以 azure_rm.yml 结尾,例如:
yaml
plugin: azure.azcollection.azure_rm #指定 Azure 动态清单插件
auth_source: auto
include_vm_resource_groups: #限制查询范围,避免全订阅扫描,效率更高
- rg-demo-dev
plain_host_names: yes #避免默认 inventory 主机名过长
#按标签自动生成组
keyed_groups:
- prefix: tag
key: tags
- prefix: azure_loc
key: location
#显式生成web、dev 这样的逻辑组
conditional_groups:
web: tags.role is defined and tags.role == 'web'
dev: tags.env is defined and tags.env == 'dev'
#控制 ansible_host 取公网IP 或私网IP
hostvar_expressions:
ansible_host: (public_ipv4_addresses + private_ipv4_addresses) | first
验证一下:
bash
ansible-inventory -i inventory/myazure_rm.yml --graph
如果 Terraform 创建的 VM 已经存在且认证没问题,你就应该能看到按标签或条件生成的主机组。
七、让Terraform和Ansible 联动起来
terraform apply 后,Azure 里已经有了带标签的 VM;
Ansible 运行时直接去 Azure 读取当前状态。
也就是说,两者之间的"桥梁"不是一个中间文件,而是 Azure 资源本身的标签元数据。
这样做有几个好处:
- Terraform 扩容一台新
role=web的 VM 后,Ansible 下次执行会自动把它归入web组 - 如果某台主机删除了,它也会从动态清单里自然消失
- 不需要写额外的 inventory 渲染逻辑
- 不需要担心静态 hosts 文件和真实云资源状态不一致
八、示例 playbook
例如你要给所有 web 主机安装 Nginx,在playbooks目录创建web.yml:
yaml
- name: Configure web servers
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
执行方式:
bash
ansible-playbook -i inventory/myazure_rm.yml playbooks/web.yml \
-u azureuser --private-key ~/.ssh/id_rsa
如果 Terraform 后续又新增了两台 role=web 的虚拟机,只要标签一致,Ansible 不需要改 inventory,就能自动把它们纳入执行范围。