You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform升级后AWS API Gateway循环依赖错误求助

Terraform升级后API Gateway与Lambda循环依赖问题解决建议

问题背景

将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 06:10:18