4. Terraform on Azure实战
在 Azure 上使用 Terraform,真正决定工程能否长期稳定运行的,往往不是资源块写得多漂亮,而是 Provider 认证、状态文件、工作区隔离、远程后端和存量资源导入这五件事是否处理得当。它们共同构成 Terraform 的“运行底座”:Provider 决定 Terraform 以什么身份、对哪个订阅说话;状态文件记录真实资源与配置的映射;工作区提供环境隔离;远程后端保证多人协作时状态一致;导入则让已有资源纳入 IaC 管理。下面按实际落地顺序展开。
Provider 配置:认证与版本锁定
Azure Provider 的认证方式有多种,生产环境推荐使用服务主体(Service Principal)或托管标识,避免依赖个人账号。最基础的写法是显式声明 azurerm Provider 并锁定版本范围,同时通过 features 块控制删除行为:
terraform {
required_version = ">= 1.6.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.100"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
}
}
认证信息不要写进代码。推荐通过环境变量注入:ARM_CLIENT_ID、ARM_CLIENT_SECRET、ARM_TENANT_ID、ARM_SUBSCRIPTION_ID。在 CI/CD 中,这些值来自密钥库或流水线变量组。若使用托管标识,则只需设置 ARM_USE_MSI=true 并赋予相应 RBAC 角色。版本锁定同样关键:~> 3.100 表示允许 3.x 内升级但不跨大版本,避免 Provider 升级导致资源属性变更。
状态管理:本地状态的局限
Terraform 状态文件 terraform.tfstate 记录了每个资源的 ID、属性和依赖关系。默认本地状态下,状态文件与代码同目录,存在三个问题:无法多人共享、容易误删、包含敏感信息(如连接字符串)。因此任何超过单人实验的项目都应尽早切换到远程后端。状态文件不是“缓存”,而是 Terraform 判断“资源是否存在、是否需要变更”的唯一依据,一旦丢失,Terraform 会认为资源不存在并尝试重建。
远程后端:Azure Storage 作为状态仓库
Azure 上最常用的远程后端是 Azure Storage Account 中的 Blob 容器。它天然支持状态锁(通过 Blob 租约),能防止两人同时 apply 造成状态冲突。配置方式如下:
terraform {
backend "azurerm" {
resource_group_name = "rg-tfstate-prod"
storage_account_name = "sttfstateprod001"
container_name = "tfstate"
key = "network/prod.terraform.tfstate"
}
}
注意后端块中不能使用变量,所有值必须字面量或通过 -backend-config 传入。初始化时执行 terraform init -backend-config=backend.hcl,把环境相关参数外置。状态锁在 apply 期间自动获取,若上一次异常中断导致锁未释放,可用 terraform force-unlock <LOCK_ID> 解除,但必须先确认没有其他进程正在运行。
工作区:环境隔离的两种思路
Terraform 工作区(workspace)允许同一套代码管理多套状态。执行 terraform workspace new dev 会创建独立状态,terraform workspace select prod 切换环境。在代码中可用 terraform.workspace 动态命名资源:
resource "azurerm_resource_group" "app" {
name = "rg-app-${terraform.workspace}"
location = "eastasia"
}
但工作区并非万能。当环境之间差异较大(如 prod 需要额外防火墙、诊断设置)时,工作区会让代码充满条件判断。更清晰的做法是“目录隔离 + 独立后端 key”:envs/dev 与 envs/prod 各自持有后端配置,共享 modules/。工作区适合差异小的场景,目录隔离适合差异大的场景。
导入现有资源:从“已存在”到“受管理”
导入是把 Azure 上已有资源纳入 Terraform 状态的过程。以已存在的资源组为例,先编写与真实资源一致的配置块,再执行导入:
terraform import azurerm_resource_group.app \
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app-prod
导入后必须执行 terraform plan,理想结果是 “No changes”。若出现差异,说明配置块与真实属性不一致,需逐项修正。Terraform 1.5 之后支持 import 块,可把导入意图写进代码并配合 terraform plan -generate-config-out=generated.tf 自动生成配置草稿,再人工精简。导入不会修改 Azure 资源本身,只建立状态映射;删除资源前务必确认状态已正确纳管。
把五件事串成一条可执行路径
实际项目中,建议按以下顺序落地:第一步,创建用于存放状态的存储账户和容器,启用版本控制与软删除;第二步,在代码中配置 backend "azurerm",执行 terraform init 完成状态迁移;第三步,配置 Provider 认证与版本约束,用 terraform providers lock 生成依赖锁文件;第四步,按环境创建 workspace 或独立目录,分别初始化;第五步,对已有资源逐个编写配置块并 terraform import,每次导入后立即 plan 校验。整个过程的核心判断标准只有一个:terraform plan 输出 “No changes” 时,说明代码、状态与 Azure 真实资源三者一致,IaC 纳管才算真正完成。
terraform.tfstate 不应提交到 Git,应在 .gitignore 中排除 *.tfstate 与 .terraform/。terraform force-unlock 前必须确认没有其他流水线或同事正在 apply,否则可能造成状态损坏。导入资源时不要先 terraform destroy,导入只建立映射,不会删除或修改 Azure 上的真实资源。