Azure DevOps中Terraform init与其他命令认证上下文不一致问题
上下文
流水线中执行Terraform init的任务片段:
- task: AzureCLI@2 displayName: 'Terraform init' inputs: azureSubscription: $(SERVICE_CONNECTION) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | terraform init \ -backend-config="resource_group_name=$(REMOTE_STATE_RG)" \ -backend-config="storage_account_name=$(REMOTE_STATE_SA)" \ -backend-config="container_name=$(REMOTE_STATE_CONTAINER)" \ -backend-config="key=terraform.tfstate"
使用版本:Terraform 1.5.5,azurerm provider 3.68.0,后端配置:
backend "azurerm" { }
问题
执行Terraform init时返回认证错误:
Initializing the backend... ╷ │ Error: Error building ARM Config:
Authenticating using the Azure CLI is only supported as a User (not a
Service Principal). │ │ To authenticate to Azure using a Service
Principal, you can use the separate 'Authenticate using a Service Principal' │ auth method...
│ Alternatively you can authenticate using the Azure CLI by using a User Account.
但执行Terraform apply的任务却一切正常:
- task: AzureCLI@2 displayName: 'Terraform apply' inputs: azureSubscription: $(SERVICE_CONNECTION) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: 'terraform apply --auto-approve terraform.tfplan'
疑问与解答
1. 为何Terraform init与其他命令的认证行为存在差异?
Terraform的后端初始化(init阶段)和资源操作(apply等阶段)使用的认证逻辑相互独立:
- init阶段对接azurerm后端存储(用于保存tfstate),此时Terraform优先尝试用本地Azure CLI上下文认证,但Azure DevOps中AzureCLI@2任务用服务主体登录后的CLI上下文,不被azurerm后端的认证逻辑支持。
- apply阶段调用azurerm provider操作Azure资源,azurerm provider可以识别AzureCLI@2任务自动注入的服务主体环境变量(如
AZURE_CLIENT_ID、AZURE_TENANT_ID等),因此能正常完成认证。
2. 如何让Terraform init像其他命令一样使用Azure服务主体认证?
两种方案均无需手动获取服务主体密钥,仅依赖现有SERVICE_CONNECTION:
方案1:在AzureCLI@2任务中手动映射环境变量
AzureCLI@2任务登录后会自动设置AZURE_*开头的服务主体环境变量,将其映射为Terraform azurerm后端识别的ARM_*前缀变量即可:
- task: AzureCLI@2 displayName: 'Terraform init' inputs: azureSubscription: $(SERVICE_CONNECTION) scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | # 映射AzureCLI注入的SP变量为Terraform后端识别格式 export ARM_CLIENT_ID=$AZURE_CLIENT_ID export ARM_CLIENT_SECRET=$AZURE_CLIENT_SECRET export ARM_TENANT_ID=$AZURE_TENANT_ID export ARM_SUBSCRIPTION_ID=$AZURE_SUBSCRIPTION_ID # 执行init命令 terraform init \ -backend-config="resource_group_name=$(REMOTE_STATE_RG)" \ -backend-config="storage_account_name=$(REMOTE_STATE_SA)" \ -backend-config="container_name=$(REMOTE_STATE_CONTAINER)" \ -backend-config="key=terraform.tfstate"
方案2:使用Azure DevOps原生Terraform任务
改用官方Terraform任务,自动适配服务连接,无需手动处理环境变量:
# 安装指定版本Terraform - task: TerraformInstaller@0 displayName: 'Install Terraform 1.5.5' inputs: terraformVersion: '1.5.5' # 执行Terraform init,自动用服务连接认证后端 - task: TerraformTaskV4@4 displayName: 'Terraform init' inputs: provider: 'azurerm' command: 'init' backendServiceArm: $(SERVICE_CONNECTION) backendAzureRmResourceGroupName: $(REMOTE_STATE_RG) backendAzureRmStorageAccountName: $(REMOTE_STATE_SA) backendAzureRmContainerName: $(REMOTE_STATE_CONTAINER) backendAzureRmKey: 'terraform.tfstate'
内容的提问来源于stack exchange,提问作者Frédéric De Lène Mirouze

