
核心前提是镜像极小、启动秒级、进程可被容器运行时正确管理;需用CGO_ENABLED=0编译,选用scratch或alpine基础镜像,设essential:true主容器,避免shell包装entryPoint。
Go 编译出的静态二进制文件天然适合容器化,但直接扔进 ECS 容器镜像里跑不等于“高效”。关键前提是:镜像必须极小、启动必须秒级、进程必须可被容器运行时正确管理。否则会触发 ECS 任务超时失败、健康检查反复重启、资源浪费等问题。
CGO_ENABLED=0 go build 编译,避免动态链接依赖(如 libc),否则 Alpine 镜像会报 no such file or directory
scratch 或 alpine:latest 作为基础镜像,而非 golang:alpine —— 后者自带 Go 工具链,镜像体积多出 300MB+essential: true 的主容器,且其 entryPoint 或 command 不能是 shell 包装脚本(如 sh -c "./app"),否则 SIGTERM 无法透传,导致优雅退出失效Fargate 不允许你调优内核参数或限制 cgroup 子系统,所以 Go 程序的资源行为必须完全
可控。常见错误是按本地开发习惯设 2GB 内存 + 1vCPU,结果在 Fargate 上频繁 OOMKilled 或 GC 停顿飙升。
memory.limit_in_bytes,务必通过 GOMEMLIMIT 显式限制,例如 GOMEMLIMIT=1.2G(留出 200MB 给 runtime 和 OS)1024 = 1 vCPU),但 Go 的 GOMAXPROCS 默认读取的是逻辑 CPU 数——Fargate 实际只分配部分时间片,建议显式设为 GOMAXPROCS=2 避免线程调度争抢http.Server.ReadTimeout 和 WriteTimeout,Fargate 背后的 NLB 默认空闲超时是 350 秒,若 Go 服务无响应控制,连接会被静默断开Go 进程默认忽略 SIGTERM,而 ECS 在任务停止前会发送该信号(非 SIGKILL)。若你的服务没监听它,就会硬终止,正在处理的 HTTP 请求、数据库事务、消息消费都会中断。
package main
import (
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// 启动 HTTP server
server := &http.Server{Addr: ":8080", Handler: handler}
// 监听 SIGTERM
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-sigChan
// 给正在处理的请求最多 10 秒宽限期
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
server.ListenAndServe()
}
log.Fatal() 或 os.Exit() 在主 goroutine 外提前退出,这会跳过 Shutdown()
Run() 是否支持上下文取消;否则应改用 http.Serve() 手动控制生命周期Shutdown() 的超时必须小于该值ECS 本身不解析结构化日志,也不执行 HTTP 健康检查——它依赖你把日志输出到 stdout/stderr,并由 CloudWatch Logs 或 FireLens 收集;健康检查则由 ALB/NLB 或你自己在容器内暴露端点完成。
os.OpenFile("app.log")),全部走 fmt.Println 或 log.SetOutput(os.Stdout),否则日志丢失且无法被 CloudWatch 抓取/healthz)必须返回 HTTP 200,且响应体为空或极小,避免触发 ALB 的 1KB 响应体截断或超时firelensConfiguration,将 stdout 自动转发到 OpenSearch 或 Kinesis,无需修改 Go 代码最易被忽略的一点:ECS 任务的 healthCheck 字段(Docker Healthcheck)和 ALB 的健康检查是两套机制,别混淆。前者只影响 ECS 控制台显示状态,后者才决定流量是否转发。Fargate 任务里,优先以 ALB 配置为准。