Terraform首次执行output变量未填充,二次执行正常问题求助
问题现象
首次执行terraform apply时,null_resource的local-exec调用的Python脚本lambda-deploy会触发KeyError: 'api_gateway_id',提示无法获取该输出变量;但第二次执行terraform apply时,脚本可以正常读取到这些输出值并执行成功。执行terraform init、refresh均无法解决此问题。
报错信息
module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): Gathering data from Terraform... module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): Traceback (most recent call last): module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): File "/home/ec2-user/bin/lambda-deploy", line 65, in module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): provide_env('LAMBDA_GATEWAY_ID', 'api_gateway_id') module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): File "/home/ec2-user/bin/lambda-deploy", line 57, in provide_env module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): os.environ[key] = TERRAFORM_OUTPUTS[tf_output] module.serverless_projects["api/admin.zip"].null_resource.serverless_deployment (local-exec): KeyError: 'api_gateway_id'
核心原因
Terraform的执行逻辑是:首次apply时,output值是在所有资源创建完成并写入状态文件后才生成的。而你的null_resource没有明确依赖于API Gateway相关资源,导致local-exec脚本在API Gateway资源尚未创建完成、output还未写入状态时就开始执行,此时terraform output无法返回对应的值,脚本自然抛出KeyError。第二次执行时,所有资源已经存在,状态文件里有了完整的output数据,所以脚本能正常读取。
解决方案
方案1:为null_resource添加明确依赖,确保资源创建完成后再执行脚本
修改null_resource配置,通过depends_on指定依赖API Gateway的核心资源,保证这些资源创建完成后才触发local-exec:
resource "null_resource" "serverless_deployment" { triggers = { source_version = data.aws_s3_bucket_object.package_object.version_id } # 添加依赖,确保API Gateway资源创建完成后再执行脚本 depends_on = [ aws_api_gateway_rest_api.gw, aws_api_gateway_resource.api ] provisioner "local-exec" { command = "lambda-deploy ${var.package_name}" } }
方案2:直接传递资源属性给脚本,避免依赖terraform output
更可靠的方式是跳过读取terraform output的步骤,直接将需要的API Gateway ID等属性作为命令行参数传递给脚本,这样Terraform会自动处理依赖关系,确保值存在时才执行脚本:
- 修改
null_resource的local-exec命令,传入所需参数:
resource "null_resource" "serverless_deployment" { triggers = { source_version = data.aws_s3_bucket_object.package_object.version_id } provisioner "local-exec" { command = "lambda-deploy ${var.package_name} --gateway-id ${aws_api_gateway_rest_api.gw.id} --root-resource-id ${aws_api_gateway_rest_api.gw.root_resource_id}" } }
- 调整
lambda-deploy脚本,从命令行参数接收这些值(而不是读取terraform output),示例如下:
import argparse import os parser = argparse.ArgumentParser() parser.add_argument("package_name") parser.add_argument("--gateway-id", required=True) parser.add_argument("--root-resource-id", required=True) args = parser.parse_args() # 设置环境变量 os.environ['LAMBDA_GATEWAY_ID'] = args.gateway_id # 其他变量同理处理
为什么refresh没用?
terraform refresh只是同步现有资源的状态到本地状态文件,不会改变资源的创建顺序,也无法提前生成output值,所以解决不了执行顺序导致的问题。
内容的提问来源于stack exchange,提问作者Marc

