Go 服务与 ECS/Fargate 容器部署
# Go 服务与 ECS/Fargate 容器部署
ECR 保存镜像,Task Definition 描述怎样运行,ECS Service 维持实例数量,Fargate 提供运行容器的算力,ALB 接入请求。把这些名称放在一条链上,比单独背服务定义更容易理解。
已有概念介绍复用 Amazon ECS 和 Docker。本篇整理原项目引入 Go 服务的实际步骤。
# 为什么保留 Node,再增加 Go
原项目采用逐步拆分:Node 保留账号和已有 API,Go 接管个人介绍相关能力。这是一种按业务边界迁移的方法,不是为了换语言重写整个项目。选择 Fargate 也不是因为 Go 不支持 Lambda,而是要运行容器服务并建立可复用的发布、健康检查和服务发现能力。
实际生产选型仍应比较常驻成本、负载、依赖和团队维护能力。一个小服务未必需要 Kubernetes;Fargate 减少主机维护,但不会代你设计业务高可用。
# 两条访问路径不能混在一起
公开查询:浏览器 → API Gateway → VPC Link → Internal ALB → Go Task
内部调用:Node Lambda → Cloud Map 私有服务名 → Go Task
└→ RDS
2
3
Cloud Map 是服务发现:Task IP 变化后,调用方通过注册服务名寻找当前实例。ALB 是负载均衡:根据规则与健康状态转发请求。原项目让两者服务不同入口,不是必须同时使用的固定架构。
内部 URL 不应公开映射,但私网可达不等于调用者已经被业务授权。敏感内部写接口仍要根据威胁模型配置服务身份验证,并限制安全组来源。
# ECR 怎样管理发布镜像
ECR(Elastic Container Registry) (opens new window) 是 AWS 托管的容器镜像仓库,负责保存镜像、访问授权、扫描与生命周期管理。ECR 保存镜像,ECS 负责运行;ECR 本身不构建或部署应用。
CI 构建并推送镜像,ECS 的 Task Definition 引用镜像 digest(内容摘要),启动任务时通过 Execution Role 获取镜像。这样不必给任务额外配置外部仓库的长期密码,仍要保证网络和最小权限正确。
- 发布标签设置为不可变,部署锁定 digest,避免同一个
latest指向不同代码。 - 生命周期策略清理无用镜像,但保留当前版本与可回滚版本。
- 镜像扫描提供漏洞线索,不会自动修复;仍要评估可利用性、更新基础镜像与依赖。
# 从代码到健康实例
- 应用先具备可运行契约。从配置读取端口与数据库地址,日志写 stdout/stderr,处理退出信号,提供健康检查。
- 构建镜像。固定 builder 与运行环境,选择正确 CPU 架构,使用非 root 用户;构建时不复制
.env、私钥或本机凭据。 - 准备共享资源。ECR、VPC、日志、权限与 Secret 先存在,再创建引用它们的运行服务。
- 执行数据库迁移。使用一次性任务,不让多个 API 副本启动时争抢 schema 变更。
- 启动服务。Task Definition 指向不可变镜像,Service 启动期望数量的 Task,ALB 健康后才接收流量。
- 回读验证。检查实际镜像 digest、Task revision、目标健康、公开读路径和内部调用,不仅检查 running 数量。
首次 ECR 和 Service 在同一 Stack 创建时,镜像仓库还没存在就不可能提前推镜像。可先创建基础资源、令 Service 容量为 0,推送镜像后再经过审查启动服务。日常运行容量是否为 0,要由响应时间与成本目标决定。
# Task Role 与 Execution Role
Execution Role 供 ECS 启动任务时拉镜像、发送日志、注入 Secret 等使用;Task Role 供运行中的应用调用 AWS API。普通 Go API 如果只使用已注入的数据库连接,不应因此获得部署 Stack 或删除队列的权限。
Secret 注入环境变量后,应用仍能读取其值,因此必须防日志泄漏。Secret 更新也不必然刷新已运行 Task,需要根据注入方式安排重启或应用轮换。
# 健康检查与优雅退出
/healthz 表示进程活着;/readyz 表示当前能否接业务,例如必要依赖可用。不能把所有下游偶发慢请求都等同于进程损坏,否则可能引发重启风暴。
收到退出信号后先停止接受新工作,再在时间预算内完成当前请求,释放连接并退出。队列 Worker 不应在事务尚未提交时先删消息。Go 代码实践复用 生产 HTTP 服务。
# 一次滚动发布为什么需要新旧实例重叠
以两个常驻实例为例,先启动新版实例,等待其真正可接请求,再逐步移出旧实例;如果先关旧版再等新版下载镜像,就会制造服务空窗。需要预留发布期容量,核对 Service 的最小健康比例和最大运行比例,而不是只按平时两个实例估算资源。
旧实例退出时还有两种时间预算:负载均衡器给已有请求留出的排空时间,以及容器收到停止信号后到被强制结束的时间。应用应停止接新工作,在预算内完成或取消已有工作。只写了 Go 的 Shutdown,但容器停止预算更短,仍可能被强制中断。
队列任务也一样:收到退出信号后不再领取新消息;当前任务能完成就提交后确认,来不及完成就保留可恢复状态,让后续消费通过幂等机制接手,而不是把消息先删掉。
# 健康检查不能给出它没有测过的保证
原项目 ALB Target Group 使用 /healthz,它不查数据库;应用另有 /readyz 进行短超时数据库检查。因此 ALB healthy 不能被描述成 “数据库持续可用” 。需要用业务探针和依赖指标补上这层验证。
在新设计中,可以根据服务是否依赖数据库接流量决定 readiness 策略,但要控制检查频率和超时,避免依赖短暂抖动导致所有实例一起退出。尤其不要假定所有目标都不健康时 ALB 一定停止转发:当目标组只剩不健康的已注册目标时,它可能继续向这些目标转发,这就是 fail-open。因此健康检查不能代替应用自身的限流和依赖故障处理。
# 怎么查启动失败
| 表现 | 优先检查 |
|---|---|
| 拉不到镜像 | ECR 地址、tag/digest、CPU 架构、Execution Role 与网络出口 |
| 取不到 Secret | Secret ARN、KMS 权限、网络与角色归属 |
| 容器立刻退出 | stopped reason、容器退出码、启动日志、缺失配置 |
| Task running 但 ALB unhealthy | 监听地址是否 0.0.0.0、端口、安全组、路径、启动宽限期 |
| 公网正常、内部调用失败 | Cloud Map 注册、VPC DNS、TTL、内部端口和服务鉴权 |
# 面试时可以这样回答
服务显示 running,为什么还不能算部署成功?参考答案
running 只说明容器进程存在,不保证请求能到达或数据库可用。我会继续检查 Task 使用的镜像版本、ALB 健康状态、公开接口与内部调用,再验证一个真实业务读写链路。发布成功需要端到端证据,不能只看资源状态。
怎么让容器更新时尽量不中断请求?参考答案
先给发布保留额外容量,新实例就绪后再移出旧实例。旧实例停止领取新工作,并在排空和容器停止预算内处理完当前请求。后台任务则提交成功才确认消息,没完成的可以幂等接手。这样才同时覆盖流量切换与进程退出,不能只靠多副本或一个健康接口。
为什么不把部署权限直接给 Task Role?参考答案
Task Role 是运行中的业务代码可以使用的权限。如果它能部署或改 IAM,应用一旦被入侵,影响会扩散到整个基础设施。部署身份、启动任务用的 Execution Role 和业务 Task Role 分开,应用只拿自己调用 AWS 服务必需的权限,能缩小泄漏后的影响范围。
# 复用来源与参考
整理自原项目 Go 服务的应用容器、共享资源、Production ECS 与 VPC Link/Cloud Map 章节。