零停机部署 Go 服务:用 Docker Compose + 滚动更新,上线如换轮胎不停车

一、上线 = 停服?那是上个世纪的事了

想象一下:

你的 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 会:

  1. 启动一个新容器(v3)
  2. 等待其通过健康检查
  3. 将流量切过去
  4. 停掉一个旧容器(v2)
  5. 重复直到全部更新

全程服务不中断!


四、验证:真的零停机吗?

开一个终端持续请求:

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 的弹性 + 滚动更新的策略,
让你的每一次发布都像给高速行驶的汽车换轮胎——
稳、准、不停歇

0
0
0
0
评论
未登录
暂无评论