【免费下载链接】aws-cdk-examples
Example projects using the AWS CDK
本文基于 aws-cdk-examples 仓库中的 python/route53-failover 示例,讲解如何使用 AWS CDK 构建一套 Route 53 故障转移 DNS 方案:通过 Route 53 健康检查持续探测部署在不同 Region 的两个端点,当主端点失效时自动把流量切换到备用端点,并在发生故障转移时通过 SNS 邮件通知管理员。读完本文,你将掌握 Failover DNS 记录(Failover属性 +Identifier)的 CDK 建模方式、AWS/Route53的HealthCheckStatus指标如何联动 CloudWatch 告警,以及跨 Region 部署 Stack 时cross_region_references的正确用法。
一、方案架构:七个核心组件
该示例要解决的问题是:一个站点/服务部署在两个地域(如us-east-1和us-west-2),希望 DNS 层能在主地域不可用时自动切流。整体架构由以下组件构成(见 README):
- Primary Endpoint(主端点):正常情况下承载全部流量的端点。在本示例中是一个 Fargate 服务 + 网络负载均衡器(NLB);
- Secondary Endpoint(备用端点):主端点故障时接管流量的备用端点,同样是一个 Fargate 服务 + NLB;
- Route 53 Hosted Zone:创建 Failover DNS 记录所在的公网托管区;
- Route 53 Health Checks:分别探测主、备端点可用性的健康检查(HTTP 类型,80 端口);
- Route 53 Failover DNS Record:默认指向主端点、主端点健康检查失败时自动指向备端点的 DNS 记录;
- SNS Topic:发生故障转移/健康检查失败事件时发布通知的主题;
- SNS Subscription:订阅该主题并投递邮件(或其他通道)的订阅,确保管理员及时感知。
从源码结构看,上述组件被拆分为 5 个 Stack,由入口 app.py 统一组装:
| 文件 | 职责 |
|---|---|
| app.py | 读取cdk.json上下文,装配全部 Stack |
| fargate_app_stack.py | 构造一个“VPC + Fargate 服务 + NLB”的示例端点,被实例化两次(主/备) |
| hosted_zone_stack.py | 创建(或复用)Route 53 公网托管区 |
| healthcheck_alarm_stack.py | 独立 Health Check + Failover 记录 + CloudWatch 告警 + SNS 邮件 |
| alias_healthcheck_record_stack.py | 另一种方案:Alias 记录 +EvaluateTargetHealth内置健康评估 |
依赖版本要求见 requirements.txt:aws-cdk-lib>=2.262.0,<3.0.0、constructs>=10.5.0,<11.0.0。
二、上下文参数配置(cdk.json)
部署前必须提供 4 个上下文值(README “Getting Started” 第 5 步强调的重点)。cdk.json 中context段给出的默认示例值为:
"context": { "domain": "aws-cdk-samples.com", "email": "user@aws-cdk-samples.com", "primaryRegion": "us-east-1", "secondaryRegion": "us-west-2" }domain:Failover 记录所在的 Hosted Zone 域名(本示例同时用它在 CDK 中创建一个新 Hosted Zone);email:接收故障转移/健康检查失败告警的邮箱地址;primaryRegion/secondaryRegion:主/备示例应用分别部署到的 Region。
app.py 通过app.node.try_get_context(...)读取这四个值,并额外从环境变量CDK_DEFAULT_ACCOUNT、CDK_DEFAULT_REGION获取当前账号与默认 Region:
domain = app.node.try_get_context('domain') email = app.node.try_get_context('email') primaryRegion = app.node.try_get_context('primaryRegion') secondaryRegion = app.node.try_get_context('secondaryRegion') account = os.getenv('CDK_DEFAULT_ACCOUNT') region = os.getenv('CDK_DEFAULT_REGION')三、示例端点:FargateAppStack(主/备各一份)
FargateAppStack 负责搭建一个可被 Route 53 探测的 HTTP 端点:
- VPC:
ec2.Vpc(..., max_azs=2),源码注释明确说明“Limit AZs to avoid reaching resource quotas”(限制可用区数量以避免触及配额上限); - ECS 集群 + Fargate 服务:使用 L2 构图
ecs_patterns.NetworkLoadBalancedFargateService,容器镜像为 AWS 官方示例镜像amazon/amazon-ecs-sample,该镜像内置一个监听 80 端口、返回 200 的 HTTP 应用,天然适合作为健康检查目标; - 安全组规则:为服务安全组显式放通来自 VPC CIDR 的 TCP 80 入站(
add_ingress_rule,description="Allow http inbound from VPC")。这一点很关键——NLB 位于 VPC 内,若安全组不放行 VPC 网段,健康检查将无法从 NLB 探测到容器; - 输出:
CfnOutput("LoadBalancerDNS")输出 NLB 的 DNS 名称,方便部署后验证端点可访问性。
app.py 将该 Stack 实例化两次:
app1 = FargateAppStack(app, "PrimaryFargateApp", env=cdk.Environment( account=account, region=primaryRegion)) app2 = FargateAppStack(app, "SecondaryFargateApp", env=cdk.Environment( account=account, region=secondaryRegion))即主应用部署在primaryRegion、备应用部署在secondaryRegion,实现跨地域双端点。
四、托管区:HostedZoneStack
HostedZoneStack 只有一处关键逻辑——直接创建一个公网托管区:
self.zone = route53.PublicHostedZone(self, "HostedZone", zone_name=domain)源码中同时保留了被注释掉的第二种用法:若你的账号里已经存在目标托管区,应改用查找导入而非重复创建,否则会因托管区名冲突导致部署失败:
# self.hostzone = route53.HostedZone.from_lookup(self, "HostedZone", domain_name=domain)README “Getting Started” 第 4 步“Select a Route 53 HostedZone that can be used for this project”即对应这一选择:新建(默认代码路径)或复用(改用HostedZone.from_lookup)。
五、方案 A:独立 Health Check + Failover 记录 + SNS 告警
这是 README 主打的完整方案,实现在 HealthcheckAlarmStack 中,按顺序做了四件事:
1. 为两个端点各建一个 Route 53 健康检查
使用 L1 资源CfnHealthCheck,检查类型为HTTP、端口 80,探测目标分别是主/备 NLB 的 DNS 名:
primaryHealthCheck = route53.CfnHealthCheck(self, "DNSPrimaryHealthCheck", health_check_config=route53.CfnHealthCheck.HealthCheckConfigProperty( fully_qualified_domain_name=primaryLoadBalancer.load_balancer_dns_name, type="HTTP", port=80))(备端点 同样 创建一个HTTP/80 端口的检查。)
2. 创建成对的 Failover A 记录
两条ARecord使用相同的记录名failover(最终域名为failover.<domain>),通过 L1 属性failover和set_identifier构成主备对——这是 Route 53 Failover 路由策略的硬性要求:PRIMARY 与 SECONDARY 记录必须共享同一个Identifier,且标识符不能以数字开头。
primary = route53.ARecord(self, "PrimaryRecordSet", zone=zone, record_name="failover", target=route53.RecordTarget.from_alias(route53_targets.LoadBalancerTarget(primaryLoadBalancer))) primaryRecordSet = primary.node.default_child primaryRecordSet.failover = "PRIMARY" primaryRecordSet.health_check_id = primaryHealthCheck.attr_health_check_id primaryRecordSet.set_identifier = "Primary"备记录 对应 设置failover = "SECONDARY"、set_identifier = "Secondary",并绑定第二个健康检查。这里同时演示了 L2 与 L1 的混用技巧:route53.ARecord(L2)创建后,通过primary.node.default_child拿到其底层 L1 资源,再设置 L2 未暴露的Failover/Identifier/HealthCheckId属性。记录目标用route53.RecordTarget.from_alias(route53_targets.LoadBalancerTarget(...)),即指向 NLB 的 Alias 记录(免额外收费、无需在 LB 变更时手工更新 A 记录值)。
3. 基于 HealthCheckStatus 指标的 CloudWatch 告警
健康检查本身的状态会以AWS/Route53命名空间下的HealthCheckStatus指标上报(0=失败,1=成功,2=灰区)。源码 L56-L70 据此构造告警:
healthCheckMetric = cloudwatch.Metric( metric_name="HealthCheckStatus", namespace="AWS/Route53", statistic="Minimum", period=Duration.minutes(1), region="us-east-1", dimensions_map={"HealthCheckId": primaryHealthCheck.attr_health_check_id}) healthCheckAlarm = healthCheckMetric.create_alarm(self, 'HealthCheckFailureAlarm', evaluation_periods=1, threshold=1, comparison_operator=cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD)含义:按主健康检查 ID 维度过滤,1 分钟周期取最小值,只要有一个评估周期低于阈值 1(即健康检查变红)就触发告警。
4. 告警动作:SNS 主题 + 邮件订阅
snsTopic = sns.Topic(self, "AlarmNotificationTopic") snsTopic.add_subscription(EmailSubscription(email_address=email)) ... healthCheckAlarm.add_alarm_action(SnsAction(snsTopic))email参数来自cdk.json的上下文(在 app.py L38-L49 传入)。至此形成闭环:端点挂掉 → Route 53 健康检查失败 → DNS 自动切到 Secondary →HealthCheckStatus指标跌到 0 → CloudWatch 告警 → SNS 邮件通知管理员。
六、方案 B:Alias 记录 + EvaluateTargetHealth(无独立 Health Check 资源)
AliasHealthcheckRecordStack 演示了实现同一目标的另一种路线:不创建CfnHealthCheck资源,而是让 Alias 记录自身对目标做健康评估:
primary = route53.ARecord(self, "PrimaryRecordSet", zone=zone, record_name="alias", target=route53.RecordTarget.from_alias(route53_targets.LoadBalancerTarget(primaryLoadBalancer))) primaryRecordSet = primary.node.default_child primaryRecordSet.failover = "PRIMARY" primaryRecordSet.add_property_override('AliasTarget.EvaluateTargetHealth', True) primaryRecordSet.set_identifier = "Primary"要点:
- 记录名改为
alias(alias.<domain>),与方案 A 的failover.<domain>共存,便于两种方案在同一个托管区中对照验证; EvaluateTargetHealth在 CDK 中无 L2/L1 直接暴露,故用add_property_override('AliasTarget.EvaluateTargetHealth', True)注入(L21、L32);开启后 Route 53 会按默认间隔探测目标本身,而非引用独立 Health Check 资源;- 源码中 CloudWatch 告警与 SNS 部分被 注释掉——因为该方案没有独立的
HealthCheckId可用作指标维度,若需告警需另行处理。
两种方案对比:方案 A 的健康检查可独立配置探测间隔、请求路径、失败判定(FailureIsolated相关参数在 L1 中均可设),且HealthCheckStatus指标可按HealthCheckId维度精确建告警,可观测性更好;方案 B 少一个 Health Check 资源(每个 Health Check 是独立计费项),配置更简洁,适合“记录目标本身即探测目标”的场景。
七、跨 Region 装配的关键细节
在 app.py 中,两个承载健康检查与 DNS 记录的 Stack 均显式部署到us-east-1,并设置了cross_region_references=True:
HealthcheckAlarmStack( app, "HealthCheckAlarm", zone=hostedZoneStack.zone, primaryLoadBalancer=app1.fargate_service.load_balancer, secondaryLoadBalancer=app2.fargate_service.load_balancer, email=email, env=cdk.Environment(account=account, region="us-east-1"), cross_region_references=True)这里有两层原因:
- Route 53 托管区与 Health Check 的控制平面位于 us-east-1:Hosted Zone 是全局资源,在 us-east-1 管理最自然,源码中
HealthCheckAlarmStack、AliasHealthcheckRecordStack的Metric也都写死region="us-east-1"; - 跨 Region 引用必须开启
cross_region_references:app1/app2的 NLB 分别位于primaryRegion/secondaryRegion,而 DNS Stack 在 us-east-1。CDK 默认不允许一个 Stack 的属性引用其他 Region Stack 中的资源属性,cross_region_references=True告知 CDK 允许这类跨 Region 属性引用(对 NLB 而言其 DNS 名是跨 Region 可达的全局标识,因此可安全引用)。同理 hosted_zone_stack 的 zone 也被传入另外两个 Stack。
八、部署步骤(Getting Started)
完整继承 README 的 6 步流程:
- 克隆仓库;
- 安装依赖。README 写的是
npm install(CDK CLI 场景);Python 项目还需按 requirements.txt 安装 CDK 库,例如pip install -r requirements.txt; - 配置好 AWS 凭证与环境变量(
CDK_DEFAULT_ACCOUNT/CDK_DEFAULT_REGION由cdk deploy上下文提供); - 选择一个可使用的 Route 53 Hosted Zone(沿用默认代码则是 CDK 新建
domain指定的托管区;复用已有托管区需按 HostedZoneStack 注释改为HostedZone.from_lookup); - 在
cdk.json中提供必需的上下文值:domain(托管区)、email(告警邮箱)、primaryRegion与secondaryRegion(示例应用部署地域)——cdk.json L69-L72 已给出示例默认值,请替换为自己的值; - 执行
cdk deploy部署全部 Stack。
部署后可用两个验证点确认生效:cdk deploy输出的LoadBalancerDNS输出值(fargate_app_stack.py L41-L44)应可 curl 访问;对failover.<domain>执行 DNS 查询应返回主 NLB 的地址,停止主 Region 的 Fargate 服务使健康检查失败后,应观察到解析切换到备 NLB,并收到 SNS 邮件。
九、适用前提与注意事项
- 域名所有权:CDK 创建的托管区(
route53.PublicHostedZone)要求你拥有该域名的注册权限才能完成域名解析对接;若使用已有托管区,请改用from_lookup路径; - 计费:每个独立 Health Check、每个 Alias 记录之外的标准 DNS 记录变更都会计费,示例共创建 2 个 Health Check 与 4 条 Failover 记录,评估成本时可参照;
- Region 硬编码:DNS 与告警链路固定在 us-east-1(托管区与
HealthCheckStatus指标的所在地),若主/备应用恰好部署在 us-east-1 之外,跨 Region 引用即生效,这也是必须设置cross_region_references的原因; - 探测端口:示例探测 80 端口,若你的端点使用 HTTPS,需把
CfnHealthCheck的type/port与 Fargate 服务的安全组入站规则同步调整(fargate_app_stack.py L35-L39 目前仅放行 TCP 80)。
至此,本示例完整覆盖了“CDK 声明 Route 53 Failover DNS → 独立健康检查驱动切流 → CloudWatch 指标 + SNS 邮件闭环告警”这条链路,并额外提供了基于EvaluateTargetHealth的轻量化替代写法,可作为跨地域 DNS 容灾的标准参考实现。
【免费下载链接】aws-cdk-examples
Example projects using the AWS CDK
相关推荐
aws-cdk-examples Python 示例:用 CDK 为 AWS Lambda 自动构建 CloudWatch Dashboard
aws cdk examples Python 示例:用 CDK 为 AWS Lambda 自动构建 CloudWatch Dashboard 本示例来自 aw
Unkey服务健康检查:实现自动恢复与故障转移
Unkey服务健康检查:实现自动恢复与故障转移 还在为API服务中断而烦恼?Unkey的开源健康检查机制帮你实现服务自愈与故障自动转移,让API认证服务永不停机
后端API网关认证鉴权aws-cdk-examples 之 ec2-cloudwatch:用 Python CDK 实现 EC2 定时停止与 AWS Backup 自动化管理
aws cdk examples 之 ec2 cloudwatch:用 Python CDK 实现 EC2 定时停止与 AWS Backup 自动化管理 在 a
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考