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

AWS CDK多栈部署HttpApi自定义域名与Lambda别名报错排查

问题分析与解决方案

核心问题

部署dev和prod两个栈时出现域名冲突,原因是同一个自定义域名(api.example.com)不能被多个CDK栈重复创建。当前代码中每个MyStack实例都会生成独立的DomainName资源,而AWS规定同一区域的自定义域名只能归属于一个CloudFormation资源,因此第二个栈部署时必然报错。

正确实现方案

必须将共享资源(自定义域名、HttpApi)拆分到独立栈,dev和prod的Lambda栈作为依赖栈,导入共享栈的HttpApi资源,再分别添加各自的Lambda集成、路由和阶段映射。这样既保证共享资源唯一,又能实现多阶段的独立部署。

步骤1:创建共享资源栈(SharedStack)

负责创建全局唯一的DomainName和HttpApi基础框架,不绑定具体业务Lambda:

# shared_stack.py
from aws_cdk import Stack, RemovalPolicy
from aws_cdk import aws_apigateway as apigw
from aws_cdk import aws_apigatewayv2_alpha as apigwv2
from aws_cdk import aws_certificatemanager as acm
from constructs import Construct

class SharedStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # 导入已有的证书
        domain_cert = acm.Certificate.from_certificate_arn(
            self,
            "DomainCert",
            "arn:aws:acm:region:123456:certificate/353434343434343",
        )

        # 创建全局唯一的自定义域名
        self.domain_name = apigwv2.DomainName(
            self,
            "ApiDomain",
            domain_name="api.example.com",
            certificate=domain_cert,
            endpoint_type=apigw.EndpointType.REGIONAL,
            security_policy=apigw.SecurityPolicy.TLS_1_2,
        )
        self.domain_name.apply_removal_policy(RemovalPolicy.RETAIN)

        # 创建共享的HttpApi
        self.http_api = apigwv2.HttpApi(
            self,
            "SharedHttpApi",
            api_name="shared-api-gateway",
        )

步骤2:修改Lambda业务栈(LambdaStack)

从共享栈导入HttpApi和DomainName,创建各自环境的Lambda、别名,并绑定到共享HttpApi:

# lambda_stack.py
from aws_cdk import Duration, RemovalPolicy, Stack
from aws_cdk import aws_ec2 as ec2
from aws_cdk import aws_apigatewayv2_alpha as apigwv2
from aws_cdk import aws_lambda as _lambda
from aws_cdk.aws_apigatewayv2_integrations_alpha import HttpLambdaIntegration
from constructs import Construct

class LambdaStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, stg: str, http_api: apigwv2.HttpApi, domain_name: apigwv2.DomainName, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        self.stg = stg
        self.func_envs = kwargs.get("func_envs", {})

        # 创建基础层(如需共享可移至SharedStack)
        function_base_layer = _lambda.LayerVersion(
            self,
            f"BaseLayer-{stg}",
            removal_policy=RemovalPolicy.RETAIN,
            code=_lambda.Code.from_asset("layer.zip"),
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_9],
            compatible_architectures=[_lambda.Architecture.X86_64],
            layer_version_name=f"Base_Layer-{stg}",
        )

        # 导入VPC
        function_vpc = ec2.Vpc.from_lookup(self, f"lambda-vpc-{stg}", vpc_id="vpc-123456")

        # 创建当前环境的Lambda函数(名称加stg后缀避免冲突)
        lambda_func = _lambda.Function(
            self,
            f"lambda-func-{stg}",
            function_name=f"lambda_func_{stg}",
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset("lambda.zip"),
            handler="app.handler",
            layers=[function_base_layer],
            environment=self.func_envs,
            memory_size=512,
            timeout=Duration.seconds(30),
            architecture=_lambda.Architecture.X86_64,
            current_version_options=_lambda.VersionOptions(
                removal_policy=RemovalPolicy.RETAIN,
                description=f"{self.stg}-123456",
            ),
            vpc=function_vpc,
        )

        # 创建Lambda别名
        lambda_func_version = lambda_func.current_version
        lambda_func_alias = _lambda.Alias(
            self,
            f"LambdaAlias-{stg}",
            alias_name=f"{stg}-{lambda_func_version.version}",
            version=lambda_func_version,
        )

        # 创建Lambda集成
        lambda_integration = HttpLambdaIntegration(
            f"LambdaIntegration-{stg}",
            handler=lambda_func_alias,
        )

        # 为共享HttpApi添加当前环境的路由
        http_api.add_routes(
            path="/{proxy+}",
            methods=[apigwv2.HttpMethod.POST],
            integration=lambda_integration,
            stage=stg
        )

        # 添加当前环境的阶段,并配置域名映射(用mapping_key区分dev/prod)
        http_api_stage = http_api.add_stage(
            id=f"http-api-{stg}-stage",
            stage_name=stg,
            auto_deploy=True,
            domain_mapping=apigwv2.DomainMappingOptions(
                domain_name=domain_name,
                mapping_key=stg  # 访问路径:dev.api.example.com 或 prod.api.example.com
            ),
        )
        http_api_stage.apply_removal_policy(RemovalPolicy.DESTROY)

步骤3:修改CDK入口文件(app.py)

先部署共享栈,再将共享栈的HttpApi和DomainName传递给dev/prod的Lambda栈:

#!/usr/bin/env python3
import os
import aws_cdk as cdk
from shared_stack import SharedStack
from lambda_stack import LambdaStack

env_info: dict = {
    "region": os.environ.get("AWS_REGION", "us-east-1"),
    "account": os.environ.get("AWS_ACCOUNT_ID", "123456789"),
}

app = cdk.App()

# 先创建共享资源栈
shared_stack = SharedStack(app, "shared-api-stack", env=env_info)

# 创建dev环境栈,传入共享资源
LambdaStack(
    app, 
    "test-lambda-dev", 
    stg="dev", 
    http_api=shared_stack.http_api,
    domain_name=shared_stack.domain_name,
    env=env_info,
    func_envs={"ENV": "dev", "DB_TABLE": "dev-table"}
)

# 创建prod环境栈,传入共享资源
LambdaStack(
    app, 
    "test-lambda-prod", 
    stg="prod", 
    http_api=shared_stack.http_api,
    domain_name=shared_stack.domain_name,
    env=env_info,
    func_envs={"ENV": "prod", "DB_TABLE": "prod-table"}
)

app.synth()

关键修改说明

  1. 共享资源隔离:自定义域名和HttpApi只在SharedStack中创建一次,避免重复冲突。
  2. 资源命名唯一:Lambda函数、别名、集成等资源名称添加stg后缀,确保dev/prod资源不冲突。
  3. 阶段映射区分:通过mapping_key实现子域名访问(dev.api.example.com对应dev阶段,prod.api.example.com对应prod阶段),也可调整为路径前缀模式。
  4. 依赖传递:Lambda栈通过构造函数参数接收共享栈的资源引用,保证CDK能正确处理栈间依赖关系。

内容的提问来源于stack exchange,提问作者m.will325

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 14:55:20