一次低流量 ECS 服务的真实降本复盘:拆解 ALB、Fargate 与公网 IPv4 的固定成本,并用 Cloudflare Tunnel、单副本和分阶段切流把月成本从约 83 美元降到约 30 美元。
一套运行正常的 AWS 架构,也可能不适合它当前的访问量。
这次改造开始于一次成本审计。服务部署在 AWS 上,访问量不高,ECS 的 CPU 平均使用率约为 1.25%,内存平均使用率约为 20.8%,但月成本预测已经接近 82.58 美元。
问题不在某一条异常请求,也不在突然增加的流量。大部分费用来自一直开着的基础设施:负载均衡器、 两个常驻容器、多个公网 IPv4、数据库和监控。即使一天只有几个人访问,这些资源也会按小时计费。
改造完成后,服务仍然运行在 AWS ECS Fargate 上,数据库也仍然使用私有 RDS。我们把公网入口从 AWS Application Load Balancer 改为 Cloudflare Tunnel,并把两个常驻 Fargate task 收敛为一个 较小的 ARM64 task。最终固定成本估算为 30.24 美元/月,比原先下降约 63%。
本文会从术语开始解释,重点说明四件事:账单为什么高、架构怎么改、迁移如何避免中断,以及这套方案 牺牲了什么。文中的域名、账号、资源名和密钥路径都已经去除,成本与利用率数据来自一次真实改造。
一、先认识原来的架构
改造前,请求会经过下面这条链路:
用户发起 HTTPS 请求 → Cloudflare DNS 找到服务地址 → AWS ALB 接收并分配请求 → 两个 Fargate Web 容器中的一个处理请求 → 容器访问私有 RDS 数据库。
对第一次接触 AWS 的读者,可以先把这些名词理解成下面几样东西。
ECS 和 Fargate 是什么
容器可以理解为一个打包好的应用运行环境。应用代码、运行时和依赖都装在里面,部署时不必重新手工配置 服务器。
Amazon ECS 是 AWS 的容器调度服务,负责启动、停止和替换容器。Fargate 是 ECS 的一种运行方式。 使用 Fargate 后,开发者不需要维护底层虚拟机,只需要声明容器需要多少 CPU 和内存。
ECS 中一个正在运行的工作单元叫作 task。原架构常驻两个 task,意味着应用始终有两个副本。一个副本 出现故障时,负载均衡器可以把请求发给另一个副本。
ALB 是什么
ALB 全称 Application Load Balancer,即应用负载均衡器。它站在应用前面,接收公网 HTTP/HTTPS 请求,再把请求分发给后端容器。
ALB 能提供 TLS 终止、健康检查、七层路由和多副本流量分发。问题在于它有固定小时费,并按 LCU 计算使用费。LCU 是 Load Balancer Capacity Unit,即负载均衡器容量单位。AWS 会根据连接数、处理字节数 和路由规则计算实际使用量。即使访问量很低,只要 ALB 一直存在,小时费就一直存在。AWS 的官方定价说明 也明确列出了 ALB 小时费和 LCU 两部分费用。
RDS 是什么
RDS 是 AWS 托管的关系型数据库。AWS 负责底层机器、备份和部分维护工作,应用通过数据库连接访问它。
这次审计中,数据库 CPU 使用率不高,但可用内存已经不宽裕。继续降低数据库规格可能导致内存压力、 连接失败或频繁重启。因此,这次没有动数据库,降本重点放在 Web 入口和容器副本上。
公网 IPv4 为什么也会收费
AWS 会对使用中的公网 IPv4 地址收费。按 2026 年 7 月公开价格,每个地址每小时为 0.005 美元。一个地址连续运行 730 小时,月成本约为 3.65 美元,具体规则可查看 Amazon VPC 定价。
原架构中,ALB 跨两个可用区使用公网地址,两个 Fargate task 也各有公网地址。单看每个地址并不贵, 四个地址加起来就成了稳定的成本项。
二、为什么低访问量仍然会产生高账单
云服务常被概括为“按使用付费”,但这里的“使用”不只指用户请求量。只要某个资源被创建并保持运行, 它就可能持续计费。
原架构的主要固定成本包括:
| 资源 | 为什么持续收费 |
|---|---|
| ALB | 按运行小时和 LCU 计费 |
| 两个 Fargate task | 按预留的 vCPU、内存和运行时间计费 |
| 四个公网 IPv4 | 每个地址按小时计费 |
| RDS | 数据库实例和存储持续计费 |
| Container Insights | 产生额外监控指标和日志费用 |
| Secrets、ECR、CloudWatch | 分别按密钥、镜像存储和告警数量计费 |
AWS Fargate 会根据 task 分配的 vCPU、内存、操作系统、CPU 架构和运行时间计费,实际 CPU 是否空闲 不会改变已经分配的规格。计算方式可以参考 AWS Fargate 定价。
因此,CPU 平均使用率只有 1.25%,并不意味着 Fargate 账单会自动降到原来的 1.25%。降低费用需要调整 task 数量、task 规格或运行时长。
三、目标架构:让应用主动连接 Cloudflare
新架构移除了 ALB。公网请求先到 Cloudflare,再通过 Cloudflare Tunnel 到达与应用放在同一个 task
里的 cloudflared 容器。
公网用户 → Cloudflare Edge(DNS、HTTPS、CDN)→ 已建立的 Tunnel →
cloudflared辅助容器 → 同一个 task 内的 Web 应用(localhost:3000)。Web 应用继续访问私有 RDS 和对象存储;Secrets Manager 在启动时注入密钥; task 的日志、指标和告警继续发送到 CloudWatch。
Cloudflare Tunnel 是什么
传统公网服务通常会在源站开放一个入站端口,例如 443。用户请求从公网进入这个端口,再到达应用。
Cloudflare Tunnel 的连接方向不同。源站上的 cloudflared 进程先主动连接 Cloudflare,并保持一组长连接。
用户请求到达 Cloudflare 后,再沿已经建立的 Tunnel 传给源站。Cloudflare 将其称为 outbound-only
connection model,详细机制见 Cloudflare Tunnel 官方文档。
这里的“只出站”容易让初学者误解。它并不表示应用只能发送请求,不能接收用户访问。它表示建立网络连接 的动作由源站发起,后续请求和响应都可以在这条已建立的双向通道中传输。
sidecar 是什么
sidecar 指和主应用一起运行、为主应用提供辅助能力的容器。本方案中,Web 应用是主容器,
cloudflared 是 sidecar。它们被放进同一个 ECS task,共享网络和生命周期。
Fargate 使用 ECS 的 awsvpc 网络模式。同一 task 内的容器可以通过 localhost 通信,这是
AWS ECS 官方文档
明确支持的行为。因此 cloudflared 可以把请求转发到 http://localhost:3000,不需要把应用的
3000 端口开放给公网。
零入站不等于零公网 IP
最终的应用安全组没有任何入站规则,公网无法直接访问 task 的 3000 端口,也无法访问
cloudflared 的监控端口。
不过,这个 task 仍保留了一个公网 IPv4,用于访问 ECR、Secrets Manager、CloudWatch 和外部对象存储。 保留它是一个成本取舍:一个公网 IPv4 约 3.65 美元/月,通常仍低于 NAT Gateway 的固定成本,也省去了 为多个 AWS 服务配置 VPC Endpoint 的复杂度。
所以,更准确的描述是:源站没有公网入站入口,但 task 仍借助一个公网 IPv4 访问外部服务。
几个后面会反复出现的术语
| 术语 | 可以先这样理解 |
|---|---|
| DNS | 把域名转换成实际访问目标的“地址簿” |
| TLS | HTTPS 使用的加密协议,负责加密连接和校验证书 |
| 安全组 | AWS 资源旁边的一组虚拟防火墙规则,决定哪些流量可以进出 |
| Listener | ALB 监听某个端口并处理请求的规则,例如监听 443 端口 |
| Target Group | ALB 可以把请求转发到的一组后端实例或容器 |
| IaC | Infrastructure as Code,用代码声明和更新云资源 |
| canary | 先让少量测试流量走新链路,确认正常后再切正式流量 |
| origin | Tunnel 最终把请求转发到的后端地址,例如 http://localhost:3000 |
| OAuth / session | OAuth 负责第三方登录授权,session 保存用户已经登录的状态 |
四、成本如何降到 30.24 美元
新架构的固定成本按 2026 年 7 月美国东部区域公开单价和约 730 小时/月估算。价格会变化,实际使用前应重新查询 AWS Pricing 页面或 Price List API。
| 资源 | 月成本估算(美元) | 说明 |
|---|---|---|
| 单个 ARM64 Fargate task | 8.51 | 0.25 vCPU、1 GiB,常驻运行 |
| RDS 计算 | 11.68 | 保留原数据库规格 |
| RDS gp3 存储 | 2.30 | 20 GiB |
| 一个公网 IPv4 | 3.65 | 只用于 task 出站 |
| 7 个 Secrets Manager secret | 2.80 | 数据库、认证、Tunnel 等运行时密钥 |
| 7 个 CloudWatch standard alarm | 0.70 | ECS、RDS 和定时任务告警 |
| ECR 镜像存储 | 不高于 0.60 | 按当时约 6 GiB 估算,并限制保留数量 |
| 合计 | 30.24 | 不含低流量日志、API 与传输浮动费用 |
原成本预测约为 82.58 美元/月,新的固定基线约为 30.24 美元/月,每月减少约 52.34 美元,降幅约 63%。主要变化来自以下几项:
- 删除 ALB 以及它使用的公网 IPv4。
- 删除第二个常驻 Fargate task。
- 把剩余 task 缩小为 0.25 vCPU、1 GiB,并改用 ARM64。
- 关闭 Container Insights,保留必要的标准指标和告警。
- 把 ECR 生命周期策略收紧,只保留少量近期镜像。
这份估算没有把账户抵扣金、试用额度或一次性优惠算成长期节省,也没有把已有 Cloudflare 套餐费用归到 AWS 改造收益里。如果为了这个方案额外购买 Cloudflare 付费功能,需要单独计入成本。
表格里的 Secrets Manager 用来加密保存数据库密码和 token;ECR 是存放容器镜像的仓库;CloudWatch 负责收集 AWS 资源的日志、指标和告警。Container Insights 是 CloudWatch 提供的增强容器观测能力, 会收集更细的 task 和容器指标。低流量服务可以先确认标准 ECS 指标是否已经够用,AWS 文档说明 Fargate 服务会自动发送 CPU 和内存利用率等标准指标,Container Insights 则会产生额外费用。参见 Amazon ECS CloudWatch 指标说明。
五、为什么没有直接删除 ALB
写完 IaC 只完成了准备工作,主要风险集中在流量真正切换的时刻。这里的 IaC 由 AWS CDK 生成 CloudFormation 模板,用代码管理网络、数据库、容器和告警。DNS、TLS、Tunnel origin、 登录回调和应用健康检查只要有一项配置错误,用户就可能看到 502 或无法登录。
实际迁移采用了下面的顺序:
- 审计现有资源和流量,部署
cloudflared辅助容器,但暂时保留 ALB。 - 等待新的 ECS task 健康后,创建一个临时 canary 域名,让它先走 Tunnel。
- 如果 canary 验证失败,就修正配置并重新验证;此时正式流量仍然走 ALB,不受影响。
- canary 全链路通过后,把正式域名切到 Tunnel,并验证登录、写操作、存储和定时任务。
- 确认稳定后,再依次解除删除保护、移除 ECS 与 Target Group 的关联,并由 IaC 删除 ALB 相关资源。
- 最后重新清点线上资源,并复核新的成本基线。
第一步:先建立可回滚的双入口阶段
新 task 同时包含 Web 和 cloudflared,但原 ALB 暂时保留。正式域名仍然走 ALB,新建一个临时 canary
域名走 Tunnel。
这样即使 Tunnel 配置错误,也只会影响 canary,不会直接影响正式用户。
第二步:canary 不只检查 /health
健康接口返回 200 只能说明进程还活着,不能证明完整业务链路正常。实际验收还应覆盖:
- 公开首页、搜索和详情页。
- 未登录 API 是否仍然返回正确的 401。
- OAuth 登录回调地址是否保持不变。
- 已有登录 session 是否继续有效。
- 一次没有可见数据变化的登录后写操作。
- 对象存储的临时写入、读取和删除。
- 定时任务的鉴权与执行。
第三步:正式切流后再观察
canary 通过后,才把正式域名指向 Tunnel。切换后继续检查公网响应、ECS task 状态、Tunnel connector 状态和 CloudWatch 告警。
确认正式流量稳定后,才开始删除 ALB。此时如果出现问题,仍可以先把 DNS 指回 ALB。
第四步:分阶段删除 ALB
ALB 可能开启 deletion protection,CloudFormation 栈之间也可能存在引用。一次性删除所有资源容易因 依赖关系失败,因此退役过程分为几步:
- 先关闭 ALB deletion protection。
- 显式解除 ECS Service 与旧 Target Group 的关联。
- 删除监控栈中对 ALB 指标的引用。
- 删除 ALB、Listener 和 Target Group。
- 最后删除 ALB 安全组以及应用安全组中的旧入站规则。
六、没有 ALB 后,谁来判断服务是否健康
ALB 被删除后,原来的 target health 检查也随之消失。新方案把健康判断放进 ECS task definition。
Web 容器的健康检查同时访问两个地址:
http://127.0.0.1:3000/api/health # Web 应用是否正常
http://127.0.0.1:2000/ready # cloudflared 是否准备好转发流量
只有两个检查都成功,Web 容器才被标记为健康。任一检查连续失败,task 会进入不健康状态,ECS Service 会启动替代 task。
AWS 的规则是:只有 task definition 中配置了健康检查的 essential container,才参与 task 健康状态的
计算。健康检查在容器内部执行,可以访问 localhost。具体判定规则见
Amazon ECS 容器健康检查。
把两个检查放进同一个健康命令还有一个好处:即使 Web 进程正常,但 Tunnel 已经断开,ECS 也不会继续 把这个 task 当作可用实例。
七、没有 ALB 后,监控怎么调整
删除 ALB 后,以下指标也会消失:
- ALB 5xx 数量。
- Target Group unhealthy host 数量。
- ALB access log。
- ALB 层的连接数和延迟指标。
替代方案需要覆盖计算、数据库、入口和“服务彻底消失”四类问题:
| 关注点 | 替代观测方式 |
|---|---|
| ECS 计算资源 | CPU、内存使用率告警 |
| ECS 是否还有活任务 | CPU 指标连续缺失告警 |
| 数据库 | RDS CPU、剩余存储、连接数告警 |
| Tunnel | /ready 联合健康检查、Tunnel connector 状态与日志 |
| 公网入口 | Cloudflare Analytics 和真实 HTTPS 探测 |
| 定时任务 | Lambda error alarm 与执行日志 |
cloudflared 会提供 Prometheus 格式的 metrics endpoint,可用于读取连接数、吞吐和运行状态。
端点配置方式见 Cloudflare Tunnel 监控文档。
Cloudflare 的部署示例也说明,/ready 只有在 connector 与 Cloudflare 网络之间存在活动连接时才返回
200,参见 Cloudflare Kubernetes 部署示例。
这里有个小细节:ECS 自动扩缩容会创建自己的高、低负载告警。服务已经处于最小副本数时,低负载告警
可能长期显示为 ALARM,它表示“满足缩容条件但已经不能再缩”,不等同于业务不可用。运维面板应把
自动扩缩容内部告警和业务故障告警分开看。
八、迁移中遇到的三个实际问题
1. Tunnel origin 协议写错,导致 502
Cloudflare 面向用户提供 HTTPS,不代表 Tunnel 到源站也必须使用 HTTPS。
本次应用容器只在 3000 端口提供 HTTP。如果 Tunnel origin 配成 https://localhost:3000,
cloudflared 会尝试和一个只会说 HTTP 的端口进行 TLS 握手,结果是 502。
修正后的 origin 是:
http://localhost:3000
排查这类问题时,先确认 Cloudflare 到用户、Cloudflare 到 origin 是两段独立连接,不要只看浏览器地址栏 里有没有 HTTPS。
2. CloudFormation 模板已删除 ALB,ECS 仍保留旧关联
最终 CloudFormation 模板已经不再包含 LoadBalancers 属性,但线上 ECS Service 查询结果仍然保留了
已经删除的 Target Group ARN。ARN 是 AWS 资源的唯一标识。CloudFormation 的 drift 差异检查也没有
提示异常。
最后通过 ECS API 显式传入空列表,触发一次受控滚动部署:
aws ecs update-service \
--cluster <cluster-name> \
--service <service-name> \
--load-balancers '[]'
AWS CLI 文档说明,可以通过空列表移除已有的 loadBalancers,并且这个操作会触发新的 Service
deployment。参见 aws ecs update-service。
迁移脚本随后增加了一个硬门禁:部署完成后,loadBalancers 长度必须为 0,否则流水线失败。IaC 模板
正确和线上运行状态正确是两项不同的检查,不能用其中一项代替另一项。
3. Cost Explorer 不会立刻显示新成本
资源删除后,Cost Explorer 里的本月预测可能仍然维持在旧水平。AWS 官方说明 Cost Explorer 至少每 24 小时刷新一次,上游账单数据有时还会更晚,参见 Cost Explorer 文档。
因此,切换当天可以用资源数量和公开单价计算新的固定基线,但不能把旧预测当作改造失败。更稳妥的做法 是记录切换时间,一周后按 UsageType 检查 ALB、LCU 和多余公网 IPv4 是否已经停止产生费用。
九、安全上做了哪些收口
移除 ALB 不应换来一个直接暴露的应用端口。最终架构采用了以下约束:
- 应用安全组没有任何入站规则。
- RDS 保持在隔离子网,不分配公网地址。
- RDS 的 5432 端口只接受来自应用安全组的连接。
- Tunnel token 只存放在 Cloudflare 和 Secrets Manager,由 ECS 在运行时注入。
- token 不写进镜像、代码仓库、task command 或 CI 日志。
cloudflared镜像固定到明确版本和 ARM64 digest,避免同一个标签在未来指向不同内容。- 正式域名和 OAuth callback 不变,减少认证系统跟随迁移的范围。
Cloudflare Tunnel 能减少源站入站暴露,但它不能替代应用鉴权、数据库访问控制、密钥轮换和依赖更新。 这些仍然需要独立维护。
十、这套方案牺牲了什么
30 美元的架构和 80 美元的架构提供的能力并不完全相同。节省来自明确的取舍。
单副本会有短暂不可用窗口
常驻 task 从两个变成一个后,应用不再具备 task 级无缝容错。ECS 会自动替换不健康 task,但新 task 拉取镜像、启动应用和通过健康检查需要时间。在这段时间里,用户可能暂时无法访问。
自动扩缩容的范围设置为 1 到 2,可以在 CPU 升高时增加副本,但它不等于始终有两个健康副本。
Tunnel 成为新的运行依赖
入口现在依赖 Cloudflare 和 cloudflared。Tunnel 配置错误、token 失效或 connector 无法连接时,
公网访问会失败。镜像固定、联合健康检查、canary 和 token 轮换流程只能降低风险,不能消除依赖。
入口观测从 AWS 转移到 Cloudflare
ALB access log 和 ALB 指标不再存在。应用日志仍在 CloudWatch,公网入口分析则迁移到 Cloudflare。 如果团队必须在 AWS 内完成所有入口审计,这个方案可能不合适。
回滚时间变长
ALB 被彻底删除后,回滚需要用 IaC 重新创建 ALB、Listener、Target Group 和安全组,再切回 DNS。
保留独立证书和显式的 legacyAlbEnabled 开关可以缩短恢复步骤,但仍比直接切换到一个闲置 ALB 慢。
十一、什么情况下适合使用这套方案
比较适合:
- 访问量较低且变化不大。
- 只有一个主要 Web 服务,没有复杂的 ALB 路由规则。
- 可以接受 task 替换时的短暂不可用。
- 域名已经由 Cloudflare 管理。
- 团队愿意同时维护 AWS 与 Cloudflare 两个控制面。
- 当前目标是降低固定成本,不追求多可用区入口级 SLA。
需要谨慎或不适合:
- 业务要求持续双副本或更高可用性。
- 入口依赖大量 ALB listener rule、WAF 或 AWS 原生审计。
- 有稳定高流量,ALB 的托管扩展和负载均衡能力更重要。
- 组织不允许第三方 Tunnel 进入生产链路。
- 团队无法接受 Cloudflare Free Plan 缺少付费 SLA 的风险。Cloudflare 的 Zero Trust 套餐说明 列出了免费版与带 SLA 付费方案的差异。
如果业务增长,可以重新把最小副本数调到 2,或由同一份 IaC 恢复 ALB。降本架构应当允许升级,不能把 当前低流量假设永久写死。
十二、可直接复用的迁移检查表
改造前
- [ ] 用 Cost Explorer 和资源清单确认真实成本,不只看抵扣后的账单。
- [ ] 查看至少 7 到 14 天的 ECS CPU、内存和请求量。
- [ ] 确认数据库是否真的有降配空间。
- [ ] 计算 ALB、task、公网 IPv4、日志和监控的固定成本。
- [ ] 明确业务能否接受单副本替换窗口。
- [ ] 准备可恢复旧入口的 IaC 开关。
canary 阶段
- [ ] Tunnel token 通过 Secrets Manager 注入,命令和日志不回显。
- [ ]
cloudflared与应用通过 localhost 通信。 - [ ] 应用安全组没有新增公网入站规则。
- [ ] 临时域名的健康接口和公开页面返回正常。
- [ ] 登录、OAuth、写操作、对象存储和定时任务通过。
- [ ] 原 ALB 仍然可用,可以快速切回。
正式切流后
- [ ] 正式域名响应来自 Cloudflare。
- [ ] ECS service desired/running/pending 符合预期。
- [ ] task 与 Web 容器健康,
cloudflaredconnector 正常。 - [ ] CloudWatch 业务告警没有新增异常。
- [ ] 先关闭 deletion protection,再删除 ALB。
- [ ] ECS Service 的
loadBalancers长度最终为 0。 - [ ] App 安全组入站规则数为 0。
- [ ] RDS 仍然是私网,安全组只信任 App 安全组。
- [ ] 更新预算阈值,并在一周后复核 Cost Explorer。
结语
这次改造没有迁移数据库,也没有把服务搬离 AWS。主要变化只是重新审视了哪些能力当前真的需要常驻。
原架构用 ALB 和两个 task 换取更好的入口托管与副本容错。低访问量阶段,这些能力形成了约 83 美元的 成本底座。新架构接受单副本和 Tunnel 依赖,把固定基线压到约 30 美元,同时保留私有托管数据库、容器 部署、自动替换和扩容能力。
这个结果不能直接套用到所有 ECS 服务。更通用的做法是先把账单拆成固定成本与随流量变化的成本,再逐项 询问:这个资源解决的风险现在是否存在,删除后用什么门禁和回滚方案补上。答案明确后,降本才不会变成 一次没有退路的删资源操作。
