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 资源本身,只建立状态映射;删除资源前务必确认状态已正确纳管。

Terraform on Azure 运行底座 Provider 认证 → 状态映射 → 工作区隔离 → 远程后端协作 → 存量导入 Provider 配置 azurerm ~> 3.100 SP / 托管标识认证 ARM_* 环境变量 features 删除保护 状态文件 资源 ID ↔ 配置映射 依赖关系图 敏感属性存储 丢失 = 重建风险 工作区 dev / stage / prod terraform.workspace 独立状态实例 差异大 → 目录隔离 远程后端 Azure Blob 状态锁(租约) backend.hcl 外置 多人协作一致 Azure 真实资源 资源组 · 虚拟网络 · 子网 · NSG · VM · 存储账户 订阅 ID / 资源 ID 唯一标识 plan / apply 比对 导入现有资源 terraform import import 块 + 生成配置 纳管 标准工作流 init → workspace select → plan → apply → 状态写入远程后端 导入后必须 plan 验证 “No changes”

把五件事串成一条可执行路径

实际项目中,建议按以下顺序落地:第一步,创建用于存放状态的存储账户和容器,启用版本控制与软删除;第二步,在代码中配置 backend "azurerm",执行 terraform init 完成状态迁移;第三步,配置 Provider 认证与版本约束,用 terraform providers lock 生成依赖锁文件;第四步,按环境创建 workspace 或独立目录,分别初始化;第五步,对已有资源逐个编写配置块并 terraform import,每次导入后立即 plan 校验。整个过程的核心判断标准只有一个:terraform plan 输出 “No changes” 时,说明代码、状态与 Azure 真实资源三者一致,IaC 纳管才算真正完成。

状态文件包含敏感信息,务必限制存储账户访问权限,开启 Blob 软删除与版本控制;本地 terraform.tfstate 不应提交到 Git,应在 .gitignore 中排除 *.tfstate 与 .terraform/。
执行 terraform force-unlock 前必须确认没有其他流水线或同事正在 apply,否则可能造成状态损坏。导入资源时不要先 terraform destroy,导入只建立映射,不会删除或修改 Azure 上的真实资源。