使用Terraform Cloud时,能否让null_resource在本地执行脚本?
问题背景
我通过Terraform Cloud结合Terraform开展基础设施即代码(IaC)工作,目标是创建Azure Container App。原本计划用带有local-exec provisioner的null_resource执行脚本,完成Docker镜像构建并推送到Azure Container Registry(ACR),但由于Terraform Cloud运行在远程环境,脚本无法按预期执行。
现有Terraform配置
provider "azurerm" { features {} } resource "azurerm_resource_group" "example" { name = "example-resources" location = "East US" } resource "azurerm_container_registry" "acr" { name = "myUniqueACRName" resource_group_name = azurerm_resource_group.example.name location = azurerm_resource_group.example.location sku = "Basic" admin_enabled = true } resource "null_resource" "deploy_image_acr" { triggers = { deploy_time = timestamp() } provisioner "local-exec" { command = "./deploy_to_acr.sh ${var.client_id} ${var.client_secret} ${var.tenant_id} ${var.subscription_id} ${azurerm_container_registry.acr.name} ${var.image_name}" } depends_on = [azurerm_container_registry.acr] }
配套Bash脚本
#!/bin/bash # Login to Azure with the Service Principal az login --service-principal -u "$1" -p="$2" --tenant "$3" # Set the desired subscription az account set --subscription "$4" # Remove all existing images for repo in $(az acr repository list --name "$5" --output tsv); do az acr repository delete --name "$5" --repository "$repo" --yes done # Build and push the new image docker build -t <image_name> . az acr login --name $5 docker tag <image_name> $5.azurecr.io/<image_name> docker push $5.azurecr.io/<image_name>
核心问题
在Terraform Cloud中执行该配置时,local-exec provisioner无法正常工作,因为它的设计初衷是在Terraform运行的本地机器上执行,而Terraform Cloud默认使用远程执行环境。
我有两个问题:
- 使用Terraform Cloud时,是否可以让
null_resource或其他资源中定义的脚本在我的本地机器上执行? - 如果无法本地执行,我可以采用哪些替代方法在部署流程中运行Docker命令或脚本?
解决方案
问题1:能否让脚本在本地机器执行?
可以,有两种可行方式:
- 切换到Terraform Cloud本地执行模式:在工作区设置中,将执行模式从默认的"远程执行"改为"本地执行"。这种模式下,Terraform Cloud仅负责存储状态文件、提供变量管理和触发执行,实际的
terraform apply会在你的本地机器上运行,local-exec自然会在本地执行脚本。 - 本地运行Terraform并连接Terraform Cloud状态后端:保持工作区为远程执行模式,但在本地机器上运行
terraform init(配置Terraform Cloud为状态后端)和terraform apply。此时local-exec会在你的本地环境执行,状态文件仍会同步到Terraform Cloud。
问题2:无法本地执行时的替代方案
如果必须使用Terraform Cloud的远程执行环境,推荐以下几种替代方法:
1. 使用Azure Container Registry任务(ACR Tasks)
直接利用ACR的内置能力构建并推送镜像,无需依赖本地或Terraform Cloud环境的Docker。通过Terraform的azurerm_container_registry_task资源定义构建任务,ACR会在云端完成镜像构建:
resource "azurerm_container_registry_task" "image_build" { name = "image-build-task" container_registry_name = azurerm_container_registry.acr.name resource_group_name = azurerm_resource_group.example.name location = azurerm_resource_group.example.location platform { os = "Linux" architecture = "amd64" } source { type = "Local" # 如果代码在Git仓库,可以用Git类型并指定仓库地址 context = filebase64("path/to/your/docker/context") } docker_step { dockerfile_path = "Dockerfile" image_names = ["${azurerm_container_registry.acr.name}.azurecr.io/${var.image_name}:latest"] push_enabled = true } trigger { base_image_trigger { name = "base-image-update" type = "Runtime" } } }
创建任务后,可以通过azurerm_container_registry_task_run资源手动触发构建,或者设置自动触发(比如代码提交、基础镜像更新)。
2. 整合CI/CD流水线
将镜像构建推送环节从Terraform流程中分离,交给专门的CI/CD工具(如GitHub Actions、Azure DevOps Pipelines)处理:
- 第一步:CI/CD流水线拉取代码,构建Docker镜像并推送到ACR;
- 第二步:触发Terraform Cloud执行基础设施部署,直接引用ACR中已存在的镜像部署Azure Container App。
这种方式职责分离,更符合IaC最佳实践,也避免了Terraform环境依赖Docker的问题。
3. 使用自定义Terraform Cloud Runner
Terraform Cloud允许使用自定义Runner,你可以创建包含Docker daemon和Azure CLI的自定义镜像,将Runner部署在自己的环境或云端。这样远程执行环境就具备了运行Docker命令的能力,原有的local-exec脚本就能正常执行。
4. 使用Terraform Docker Provider
通过Terraform的docker provider直接管理镜像构建和推送,但需要确保Terraform Cloud环境中Docker daemon可用(通常需要自定义Runner):
provider "docker" { host = "unix:///var/run/docker.sock" # 如果需要通过ACR认证,可以配置registry_auth registry_auth { address = "${azurerm_container_registry.acr.name}.azurecr.io" username = azurerm_container_registry.acr.admin_username password = azurerm_container_registry.acr.admin_password } } resource "docker_image" "app" { name = "${azurerm_container_registry.acr.name}.azurecr.io/${var.image_name}:latest" build { context = "./path/to/docker/context" dockerfile = "Dockerfile" } } resource "docker_registry_image" "app" { name = docker_image.app.name image_id = docker_image.app.id }
内容的提问来源于stack exchange,提问作者Hiten Samalia

