一、上线 = 停服?那是上个世纪的事了
想象一下:
你的 Go 服务正在处理成千上万的请求,
你敲下docker-compose down && docker-compose up -d……
瞬间,所有用户看到:“哎呀,出错了”。
这不是部署,这是拆弹失败现场。
而现代部署的目标是:用户无感,服务不停,老板不慌。
解决方案?滚动更新(Rolling Update) + Docker Compose(配合 Swarm)。
二、为什么 Go 特别适合滚动更新?
- Go 编译出的是单文件静态二进制,镜像小、启动快
- 服务启动后秒级就绪(配合健康检查)
- 无状态设计天然契合横向扩展
只要你的 Go 应用满足:
✅ 无本地状态(Session 存 Redis)
✅ 支持优雅关闭(监听 SIGTERM)
✅ 提供健康检查接口(/healthz)
就能无缝接入滚动更新!
三、实战:一个支持滚动更新的 Go 服务
Step 1:写个健壮的 Go HTTP 服务
// main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from Go! Version: v2\n"))
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 启动服务
go func() {
log.Println("Server starting on :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
// 监听中断信号,实现优雅关闭
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down gracefully...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Graceful shutdown failed: %v", err)
}
log.Println("Server stopped.")
}
✅ 关键点:
/healthz用于健康检查- 监听
SIGTERM实现优雅关闭(Docker stop 会发这个信号)
Step 2:构建轻量 Docker 镜像
# Dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
构建并打标签(模拟新版本):
docker build -t my-go-app:v2 .
Step 3:配置 docker-compose.yml 支持滚动更新
⚠️ 注意:滚动更新需要 Docker Swarm 模式(
docker stack deploy,不是docker-compose up)
# docker-compose.yml
version: '3.8'
services:
web:
image: my-go-app:v2
ports:
- "80:8080"
deploy:
replicas: 3
update_config:
parallelism: 1 # 每次只更新 1 个实例
delay: 10s # 更新间隔 10 秒
order: start-first # 先启新容器,再停旧的(关键!)
failure_action: rollback
restart_policy:
condition: on-failure
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 5s
timeout: 3s
retries: 3
start_period: 10s
networks:
default:
driver: overlay
Step 4:部署到 Swarm 并触发滚动更新
# 初始化 Swarm(如果还没)
docker swarm init
# 首次部署
docker stack deploy -c docker-compose.yml myapp
# 升级时:只需改 image tag(如 v2 → v3),重新 deploy
docker stack deploy -c docker-compose.yml myapp
✅ Docker 会:
- 启动一个新容器(v3)
- 等待其通过健康检查
- 将流量切过去
- 停掉一个旧容器(v2)
- 重复直到全部更新
全程服务不中断!
四、验证:真的零停机吗?
开一个终端持续请求:
while true; do
curl http://localhost/
sleep 0.5
done
输出可能像:
Hello from Go! Version: v2
Hello from Go! Version: v2
Hello from Go! Version: v3 ← 新版本上线!
Hello from Go! Version: v3
✅ 没有 5xx 错误
✅ 请求平滑过渡
五、注意事项 & 最佳实践
| 问题 | 解决方案 |
|---|---|
本地开发用 docker-compose up 不支持滚动更新 | 开发用 Compose,生产用 docker stack deploy(Swarm) |
| 数据库迁移怎么办? | 先部署兼容双版本的 DB schema(向后兼容) |
| 如何回滚? | docker stack deploy 用旧镜像重新部署即可 |
| 非 Swarm 环境? | 考虑用 Nginx + 蓝绿部署(见前文)或 Kubernetes |
💡 阿里云用户提示:你可以在 ECS 上搭建 Swarm 集群,或直接使用 ACK(阿里云 Kubernetes)获得更强大的滚动更新能力。
六、结语:上线不是冒险,是可控的“换轮子”
Go 的简洁 + Docker 的弹性 + 滚动更新的策略,
让你的每一次发布都像给高速行驶的汽车换轮胎——
稳、准、不停歇。
