在纯 Docker 环境通过 Traefik 权重实现金丝雀流量切换
解读
面试官想确认三件事:
- 你是否能在不借助 Kubernetes 等编排平台的前提下,用 Docker 原生能力完成灰度发布;
- 你是否理解 Traefik 在 Docker Provider 模式下的动态权重机制;
- 你是否能把“镜像版本—容器标签—Traefik 权重”这三层映射落地,并给出可回滚、可观测、可自动化的闭环方案。
国内场景下,很多中小团队仍用 Docker Compose 裸跑业务,因此“纯 Docker”方案必须兼顾镜像加速源、内网私有 Registry、防火墙策略、云厂商 SLB 健康检查等现实约束。
知识点
- Traefik 3.x 的 Docker Provider:labels 语法、loadbalancer.server.weight、weighted RoundRobin 算法;
- Docker Compose v3.9 的 deploy.labels 与 deploy.replicas:在 Swarm 与单机模式下的差异;
- 健康检查三件套:Dockerfile HEALTHCHECK、Traefik 的 healthcheck path、业务接口的 /readyz;
- 零中断热更新:docker compose up --scale、--no-recreate、--remove-orphans 的组合使用;
- 国内镜像加速:registry.docker-cn.com 已下线,需配置阿里云 ACR 镜像加速器或腾讯云 TCR;
- 最小化攻击面:非 root 用户、distroless 镜像、read-only rootfs、no-new-privileges;
- 可观测:Traefik 暴露 Prometheus metrics,通过阿里云 SLS 或腾讯云 CLS做日志聚类;
- 回滚策略:利用 Compose 的 extends 与 git tag 实现一键回滚,同时把旧版本容器保留至少两个发布窗口。
答案
-
环境准备
- 准备一台国内云主机(CentOS 7.9 或 Ubuntu 22.04),内核 ≥5.4,已开启 ip_forward;
- 安装 Docker 24.x,配置阿里云镜像加速器;
- 新建私有仓库 registry.example.com,使用自签证书 + basic auth,并在 daemon.json 中标记 insecure-registries。
-
镜像与标签约定
- 业务镜像:myapp:1.0.0(稳定版)、myapp:1.1.0-canary(金丝雀);
- 统一使用语义化版本 + build 号作为 tag,禁止 latest。
-
Compose 文件(docker-compose.canary.yml)
version: "3.9" services: stable: image: registry.example.com/myapp:1.0.0 deploy: replicas: 3 labels: - "traefik.enable=true" - "traefik.http.routers.myapp.rule=Host(`api.example.com`)" - "traefik.http.services.myapp-stable.loadbalancer.server.weight=90" - "traefik.http.services.myapp-stable.loadbalancer.server.port=8080" - "traefik.http.routers.myapp.tls.certresolver=letsencrypt" - "traefik.http.middlewares.myapp-headers.headers.customrequestheaders.X-Canary=false" healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8080/readyz"] interval: 5s retries: 3 start_period: 10s user: "65534:65534" read_only: true security_opt: - no-new-privileges:true canary: image: registry.example.com/myapp:1.1.0-canary deploy: replicas: 1 labels: - "traefik.enable=true" - "traefik.http.routers.myapp.rule=Host(`api.example.com`)" - "traefik.http.services.myapp-canary.loadbalancer.server.weight=10" - "traefik.http.services.myapp-canary.loadbalancer.server.port=8080" - "traefik.http.middlewares.myapp-canary-headers.headers.customrequestheaders.X-Canary=true" healthcheck: *ref_stable user: "65534:65534" read_only: true security_opt: - no-new-privileges:true networks: default: name: traefik-net external: true -
Traefik 启动
docker network create traefik-net docker run -d --name traefik \ -p 80:80 -p 443:443 \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v $PWD/acme.json:/acme.json \ --network traefik-net \ traefik:v3.0 \ --providers.docker=true \ --providers.docker.network=traefik-net \ --entrypoints.web.address=:80 \ --entrypoints.websecure.address=:443 \ --certificatesresolvers.letsencrypt.acme.tlschallenge=true \ --certificatesresolvers.letsencrypt.acme.email=ops@example.com \ --certificatesresolvers.letsencrypt.acme.storage=/acme.json \ --metrics.prometheus=true \ --metrics.prometheus.entrypoint=metrics -
灰度与观测
- 上线后,通过阿里云云监控或自建 Grafana 查看 traefik_service_requests_total{service="myapp-canary@docker"} 是否按 10% 比例上涨;
- 若错误率 >1%,立即执行
docker compose -f docker-compose.canary.yml scale canary=0实现秒级回滚; - 若指标正常,逐步调整 weight 到 30→50→90,最终下线 stable 容器,完成全量发布。
-
自动化脚本(CI 侧)
- GitLab CI 中定义
canary:weight变量,通过sed批量替换 Compose 文件; - 使用国内 Runner 缓存(/cache 目录挂载)加速镜像拉取;
- 发布记录写入钉钉群机器人,附带 Traefik Dashboard 链接,方便值班确认。
- GitLab CI 中定义
拓展思考
- 如果业务需要会话保持,Traefik 的 sticky cookie 在 Docker Provider 下仅对同一 service 生效,此时应把 stable 与 canary 合并为一个 service,利用镜像 tag 灰度 + Traefik 的 weighted LoadBalancer,而不是拆 service;
- 当集群规模 >20 节点,建议升级为 Docker Swarm,利用overlay 网络加密与服务发现内置 DNS,避免单机端口冲突;
- 国内监管要求日志留存 6 个月,可把 Traefik AccessLog 通过LogPilot + 阿里云 OSS 归档,并设置生命周期策略降冷;
- 若未来迁移到 Kubernetes,只需把上述权重逻辑改写成TraefikService 的 Weighted RoundRobin,复用同一套镜像与 HEALTHCHECK 路径,实现Docker 到 K8s 的平滑过渡。