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()
关键修改说明
- 共享资源隔离:自定义域名和HttpApi只在SharedStack中创建一次,避免重复冲突。
- 资源命名唯一:Lambda函数、别名、集成等资源名称添加
stg后缀,确保dev/prod资源不冲突。 - 阶段映射区分:通过
mapping_key实现子域名访问(dev.api.example.com对应dev阶段,prod.api.example.com对应prod阶段),也可调整为路径前缀模式。 - 依赖传递:Lambda栈通过构造函数参数接收共享栈的资源引用,保证CDK能正确处理栈间依赖关系。
内容的提问来源于stack exchange,提问作者m.will325
相关产品推荐
相关产品推荐

