在纯 Docker 环境通过 Traefik 权重实现金丝雀流量切换

解读

面试官想确认三件事:

  1. 你是否能在不借助 Kubernetes 等编排平台的前提下,用 Docker 原生能力完成灰度发布;
  2. 你是否理解 Traefik 在 Docker Provider 模式下的动态权重机制
  3. 你是否能把“镜像版本—容器标签—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 实现一键回滚,同时把旧版本容器保留至少两个发布窗口

答案

  1. 环境准备

    • 准备一台国内云主机(CentOS 7.9 或 Ubuntu 22.04),内核 ≥5.4,已开启 ip_forward;
    • 安装 Docker 24.x,配置阿里云镜像加速器
    • 新建私有仓库 registry.example.com,使用自签证书 + basic auth,并在 daemon.json 中标记 insecure-registries。
  2. 镜像与标签约定

    • 业务镜像:myapp:1.0.0(稳定版)、myapp:1.1.0-canary(金丝雀);
    • 统一使用语义化版本 + build 号作为 tag,禁止 latest。
  3. 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
    
  4. 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
    
  5. 灰度与观测

    • 上线后,通过阿里云云监控或自建 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 容器,完成全量发布。
  6. 自动化脚本(CI 侧)

    • GitLab CI 中定义 canary:weight 变量,通过 sed 批量替换 Compose 文件;
    • 使用国内 Runner 缓存(/cache 目录挂载)加速镜像拉取;
    • 发布记录写入钉钉群机器人,附带 Traefik Dashboard 链接,方便值班确认。

拓展思考

  • 如果业务需要会话保持,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 的平滑过渡