Terraform升级后AWS API Gateway循环依赖错误求助
问题背景
将Terraform从0.13.5升级到1.3.3后,因使用ARM芯片替换了template_file资源为templatefile函数。修改aws_api_gateway_rest_api资源的body引入templatefile后,出现循环依赖错误,按AWS Provider官方示例重构代码(拆分Stage资源、移除依赖关系)后问题仍存在。
相关代码
resource "aws_api_gateway_rest_api" "apigw" { name = "standard-${var.environment_name}-${var.tenant_name}-isu-adapter" body = templatefile("${path.module}/swagger.yaml", { apiTitle = "standard-${var.environment_name}-${var.tenant_name}-isu-adapter" # should correspond to aws_api_gateway_rest_api.apigw.name! readLambdaUri = values(module.isu-adapter-read-lambda.lambda)[0].invoke_arn } ) policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "execute-api:Invoke" ], "Principal": { "AWS": "arn:aws:iam::${var.central_account_id}:root" }, "Effect": "Allow", "Resource": "*" } ] } EOF } resource "aws_api_gateway_deployment" "isu-adapter" { rest_api_id = aws_api_gateway_rest_api.apigw.id triggers = { redeployment = sha1(jsonencode(aws_api_gateway_rest_api.apigw.body)) } lifecycle { create_before_destroy = true } } resource "aws_api_gateway_stage" "default_stage" { deployment_id = aws_api_gateway_deployment.isu-adapter.id rest_api_id = aws_api_gateway_rest_api.apigw.id stage_name = "default" } resource "aws_api_gateway_stage" "isu_stage" { deployment_id = aws_api_gateway_deployment.isu-adapter.id rest_api_id = aws_api_gateway_rest_api.apigw.id stage_name = "${var.environment_name}-${var.tenant_name}" description = format("%s/%s", md5(file("${path.module}/apigw.tf")), md5(file("${path.module}/swagger.yaml"))) } resource "aws_lambda_permission" "read-lambda" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = values(module.isu-adapter-read-lambda.lambda)[0].function_name principal = "apigateway.amazonaws.com" source_arn = "${aws_api_gateway_rest_api.apigw.execution_arn}/*/*" } module "isu-adapter-read-lambda" { source = "../../../modules/lambda" environment_name = var.environment_name lambda_name = "isu-adapter-read" handler_name = "read.handler" souce_path = "adapters/lambda/" source_bucket = var.source_bucket build_number = var.build_number app_version = var.app_version custom_role = substr("standard-${var.environment_name}-isu-adapter-read-${var.tenant_name}-lambda", 0, 64) vpc_subnet_ids = var.adapter_vpc_id == "" ? [] : var.adapter_subnet_ids vpc_security_group_ids = aws_security_group.isu-lambda-group.*.id timeout = "180" tenants = [ { name = var.tenant_name theme = var.tenant_name domain = "" } ] environment_variables = [ { STANDARD_BREAKER_TABLE_NAME = aws_dynamodb_table.circuit_breaker_state_table.name STANDARD_BREAKER_TABLE_KEY = var.tenant_name, STANDARD_CACHING_TABLE_NAME = aws_dynamodb_table.isu_adapter_caching_table.name STANDARD_ADAPTER_TYPE = var.adapter_type STANDARD_ADAPTER_PRIVATE_BUCKET_NAME = var.adapter_private_bucket_enabled ? aws_s3_bucket.isu_adapter_private_bucket[0].bucket : "" STANDARD_ADAPTER_PRIVATE_BUCKET_ENABLED = var.adapter_private_bucket_enabled STANDARD_TENANT_NAME = var.tenant_name STANDARD_ADAPTER_AUTH = var.use_adapter_auth_secret ? data.aws_secretsmanager_secret_version.auth-secret[0].secret_string : var.adapter_auth BASE_URL = var.adapter_base_url STANDARD_ADAPTER_POSTBOX_READ_HISTORY_TABLE_NAME = aws_dynamodb_table.isu_adapter_postbox_read_history.name STANDARD_ADAPTER_POSTBOX_READ_HISTORY_QUEUE = data.aws_sqs_queue.isu_adapter_postbox_read_history_queue.url }] } module "isu-adapter-write-lambda" { source = "../../../modules/lambda" environment_name = var.environment_name lambda_name = "isu-adapter-write" handler_name = "write.handler" souce_path = "adapters/lambda/" source_bucket = var.source_bucket build_number = var.build_number app_version = var.app_version custom_role = substr("standard-${var.environment_name}-isu-adapter-write-${var.tenant_name}-lambda", 0, 64) vpc_subnet_ids = var.adapter_vpc_id == "" ? [] : var.adapter_subnet_ids vpc_security_group_ids = aws_security_group.isu-lambda-group.*.id tenants = [ { name = var.tenant_name theme = var.tenant_name domain = "" } ] environment_variables = [ { STANDARD_DEDUPLICATION_TABLE = aws_dynamodb_table.write_deduplication_table.name STANDARD_BREAKER_TABLE_NAME = aws_dynamodb_table.circuit_breaker_state_table.name STANDARD_BREAKER_TABLE_KEY = var.tenant_name STANDARD_ADAPTER_TYPE = var.adapter_type STANDARD_ADAPTER_AUTH = var.use_adapter_auth_secret ? data.aws_secretsmanager_secret_version.auth-secret[0].secret_string : var.adapter_auth BASE_URL = var.adapter_base_url STANDARD_CACHING_TABLE_NAME = aws_dynamodb_table.isu_adapter_caching_table.name STANDARD_ADAPTER_WRITE_FEEDBACK_QUEUE = data.aws_sqs_queue.write_feedback_queue.url } ] }
错误信息
Error: Cycle: aws_api_gateway_stage.default_stage, module.isu-adapter-read-lambda.null_resource.download[0], module.isu-adapter-read-lambda.aws_s3_bucket_object.lambda_source, aws_api_gateway_deployment.isu-adapter (destroy deposed d32dac59), module.isu-adapter-read-lambda.aws_s3_object.lambda_source (destroy), module.isu-adapter-read-lambda.aws_lambda_function.lambda["giessen"], module.isu-adapter-read-lambda.output.lambda (expand), aws_api_gateway_rest_api.apigw, aws_api_gateway_deployment.isu-adapter
已执行步骤
- 用
templatefile引用swagger.yaml更新aws_api_gateway_rest_api的body - 移除
aws_api_gateway_deployment对aws_api_gateway_rest_api的depends_on - 将stage名称移至独立的
aws_api_gateway_stage资源 - 执行
terraform init和terraform apply
解决建议
循环依赖的核心原因是:API Gateway的body依赖Lambda的invoke_arn,而Lambda的权限又依赖API Gateway的execution_arn,同时Deployment触发逻辑绑定了API Gateway的body完整内容,形成闭环。以下是针对性解决步骤:
1. 拆分Swagger模板与Lambda集成,避免硬编码ARN
修改swagger.yaml,移除其中直接引用Lambda ARN的x-amazon-apigateway-integration配置,改用Terraform的aws_api_gateway_integration资源来关联Lambda。这样API Gateway的body不再依赖Lambda的输出,切断循环的关键链路。
修改后的aws_api_gateway_rest_api:
resource "aws_api_gateway_rest_api" "apigw" { name = "standard-${var.environment_name}-${var.tenant_name}-isu-adapter" body = templatefile("${path.module}/swagger.yaml", { apiTitle = "standard-${var.environment_name}-${var.tenant_name}-isu-adapter" # 移除readLambdaUri参数,不再在Swagger中硬编码Lambda ARN } ) policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "execute-api:Invoke" ], "Principal": { "AWS": "arn:aws:iam::${var.central_account_id}:root" }, "Effect": "Allow", "Resource": "*" } ] } EOF }
添加API Gateway集成资源示例(假设Swagger中定义了/read路径的GET方法):
# 假设Swagger中已定义/read资源,这里获取其ID data "aws_api_gateway_resource" "read_resource" { rest_api_id = aws_api_gateway_rest_api.apigw.id parent_id = aws_api_gateway_rest_api.apigw.root_resource_id path_part = "read" } # 绑定GET方法到Lambda resource "aws_api_gateway_method" "read_method" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = data.aws_api_gateway_resource.read_resource.id http_method = "GET" authorization = "NONE" } # 集成Lambda函数 resource "aws_api_gateway_integration" "read_lambda_integration" { rest_api_id = aws_api_gateway_rest_api.apigw.id resource_id = data.aws_api_gateway_resource.read_resource.id http_method = aws_api_gateway_method.read_method.http_method integration_http_method = "POST" # Lambda集成固定用POST type = "AWS_PROXY" uri = values(module.isu-adapter-read-lambda.lambda)[0].invoke_arn }
2. 调整Deployment触发逻辑,减少依赖
原来的Deployment触发使用sha1(jsonencode(aws_api_gateway_rest_api.apigw.body)),会导致Deployment完全依赖API Gateway的body内容,进而牵连到Lambda的输出。改为直接基于swagger文件和关键变量的哈希值触发:
resource "aws_api_gateway_deployment" "isu-adapter" { rest_api_id = aws_api_gateway_rest_api.apigw.id triggers = { # 用swagger文件本身的哈希+环境/租户变量作为触发条件 redeployment = sha1(join("", [ file("${path.module}/swagger.yaml"), var.environment_name, var.tenant_name ])) } lifecycle { create_before_destroy = true } }
3. 优化Lambda权限的Source ARN(可选)
如果仍存在依赖问题,可以调整aws_lambda_permission的source_arn构造方式,避免直接引用API Gateway的execution_arn(该属性依赖API Gateway资源完全创建):
resource "aws_lambda_permission" "read-lambda" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = values(module.isu-adapter-read-lambda.lambda)[0].function_name principal = "apigateway.amazonaws.com" # 用API Gateway ID构造ARN,无需等待execution_arn生成 source_arn = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.apigw.id}/*/*" } # 新增获取当前region和account的data资源 data "aws_region" "current" {} data "aws_caller_identity" "current" {}
4. 显式控制依赖顺序(谨慎使用)
如果以上步骤仍有问题,可以给Lambda权限添加depends_on,确保API Gateway Deployment先完成:
resource "aws_lambda_permission" "read-lambda" { # ... 其他配置 ... depends_on = [aws_api_gateway_deployment.isu-adapter] }
内容的提问来源于stack exchange,提问作者Monsy

