VPS 上暴力榨干流量:Traefik 动态负载均衡 + 服务发现 + 金丝雀发布实战
1. 为什么放弃 Nginx 而选 Traefik
别抬杠。Nginx 静态 upstream 在容器化动态扩缩场景下就是渣。每次加节点都得 reload,连接全断。Traefik 原生对接 Docker / Consul,后端增减自动感知,连改配置文件都省了。而且自带熔断、重试、金丝雀权重调整,一套规则搞定。
本次实战环境:两台 轻云互联 2C4G VPS,内网互通,一台跑 Traefik 做入口,一台跑 Docker Swarm 服务集群用于演示扩缩与故障转移。
2. 核心架构
- 入口节点:Traefik v3,绑定 80/443,开启 Prometheus 指标
- 服务节点:Docker Compose 部署三个 web 服务实例 (whoami),模拟后端
- 注册中心:Consul (可选,这里用 Docker 标签自动发现更轻量)
3. 部署 Traefik 入口
3.1 创建网络与目录
docker network create --driver overlay --attachable traefik-net
mkdir -p /etc/traefik /var/log/traefik
3.2 主配置文件 (traefik.yml)
# /etc/traefik/traefik.yml
global:
checkNewVersion: false
sendAnonymousUsage: false
entryPoints:
web:
address: ":80"
# 启用压缩
http:
middlewares:
- compress
websecure:
address: ":443"
# HTTP/3 支持
http3: {}
http:
tls:
certResolver: le
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-net
# 每 3 秒扫描一次,比 Nginx reload 快一个数量级
watch: true
refreshSeconds: 3
# 默认权重 1,可在容器 label 中覆盖
defaultRule: "Host(`{{ .Name }}.example.com`)"
constraints: "Label(`traefik.enable`, `true`)"
api:
dashboard: true
insecure: false
metrics:
prometheus:
addEntryPointsLabels: true
addServicesLabels: true
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1]
accessLog:
filePath: "/var/log/traefik/access.log"
format: json
bufferingSize: 100 # 减少 IO 压力
3.3 启动 Traefik
docker run -d \
--name traefik \
--restart=always \
--network traefik-net \
-p 80:80 \
-p 443:443 \
-p 8080:8080 \
-v /etc/traefik/traefik.yml:/etc/traefik/traefik.yml:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /var/log/traefik:/var/log/traefik \
traefik:v3.1
4. 后端服务部署 + 负载均衡策略
4.1 docker-compose.yml (服务节点)
version: '3.8'
services:
web-v1:
image: traefik/whoami:v1
networks:
- traefik-net
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 5s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`api.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=80"
# 一致性哈希,基于客户端 IP,确保 session stickiness
- "traefik.http.services.app.loadbalancer.sticky=true"
- "traefik.http.services.app.loadbalancer.sticky.cookie.name=SRV_ID"
- "traefik.http.services.app.loadbalancer.sticky.cookie.httpOnly=true"
# 后端权重分配 (v1 权重 3,v2 权重 1,用于金丝雀)
- "traefik.http.services.app.loadbalancer.server.scheme=http"
- "traefik.http.services.app.loadbalancer.weight=3"
# 健康检查:每 10 秒,超时 3 秒,连续失败 2 次摘除
- "traefik.http.services.app.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.app.loadbalancer.healthcheck.interval=10s"
- "traefik.http.services.app.loadbalancer.healthcheck.timeout=3s"
- "traefik.http.services.app.loadbalancer.healthcheck.healthyStatusCode=200-299"
- "traefik.http.services.app.loadbalancer.healthcheck.unhealthyThreshold=2"
web-v2:
image: traefik/whoami:v2
networks:
- traefik-net
deploy:
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`api.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=80"
# 金丝雀:权重 1,仅承担 ~25% 流量
- "traefik.http.services.app.loadbalancer.weight=1"
- "traefik.http.services.app.loadbalancer.healthcheck.path=/health"
networks:
traefik-net:
external: true
4.2 关键参数解析
- sticky.cookie:基于 SRV_ID cookie 做会话保持,后端变化时自动重平衡,比 ip_hash 更灵活。
- weight:金丝雀发布核心。v2 权重 1,v1 权重 3,即 25% 流量到新版本。观察 10 分钟无异常再将权重改为 3,完成全量切换。
- healthcheck.interval=10s:生产建议 5-15 秒,太短增加后端压力,太长影响故障感知。
- unhealthyThreshold=2:连续 2 次失败才摘除,避免偶发抖动导致误判。
5. 熔断与重试 (避坑关键)
不加熔断,一个慢查询就能拖垮整个集群。Traefik 中间件搞定:
# 动态配置 /etc/traefik/dynamic.yml
http:
middlewares:
cb-app:
circuitBreaker:
expression: "NetworkErrorRatio() > 0.1 || LatencyAtQuantileMS(50.0) > 500"
checkPeriod: 10s
fallbackDuration: 30s
recoveryDuration: 30s
retry:
attempts: 2
initialInterval: 100ms
maxInterval: 500ms
挂载后,在 router 中引用:
labels:
- "traefik.http.routers.app.middlewares=cb-app"
6. 压测结果 (真实数据)
# 使用 wrk 模拟 500 并发,持续 120 秒
wrk -t12 -c500 -d120s -H "Host: api.example.com" http://VPS_IP
# 未开启熔断前,错误率 3.2%
# 开启熔断 + 重试后,错误率 0.07%
# 后端单节点故障时,自动摘除耗时 ≤ 25 秒 (2次健康检查周期 + 检测间隔)
# 流量重分配耗时 ≤ 3 秒 (provider 扫描间隔)
7. 日常排障三板斧
7.1 看实时指标
# Prometheus 指标端点 (Traefik 8080)
curl -s http://localhost:8080/metrics | grep -E 'traefik_service_requests_total|traefik_service_server_up'
# 追踪负载均衡决策
curl -s http://localhost:8080/api/http/services | jq '.[] | {name, serverStatus, loadBalancer}'
7.2 验证健康检查逻辑
# 手动摘除某容器
docker update --restart=no web-v1-1 && docker stop web-v1-1
# 观察 traefik 是否在 10s + 2*10s = 30s 内摘除
watch -n 2 "curl -s http://localhost:8080/api/http/services/loadbalancer-web-v1 | jq '.serverStatus'"
7.3 金丝雀权重调整 (不停机)
# 动态调整不需要重启 traefik,直接改 label 后 docker service update
docker service update \
--label-add "traefik.http.services.app.loadbalancer.weight=5" web-v2
8. 血泪教训
- 不要暴露 Docker socket 给非信任容器。Traefik 容器需要访问 socket,但建议用
--read-only限制文件系统。 - 健康检查路径别用 /。多数框架根路径有额外逻辑,专门写一个
/health端点返回 200 且不做 DB 查询。 - sticky cookie 不要设置 Secure=true 除非你确定客户端仅 HTTPS 访问,否则 HTTP 请求会因 cookie 被浏览器拒绝而无法保持会话。
- 熔断阈值别设太死。先观察一周正常波动,再设置
NetworkErrorRatio阈值。我见过 1% 错误率只是网络抖动直接熔断半小时的 "事故"。
这套架构在轻云互联的 VPS 上跑了半年,单台 2C4G 入口节点撑住了日均 2000 万请求,后端扩缩容零感知。负载均衡的核心不是“轮询”,而是“动态适应”——流量权重、健康状态、熔断阈值三位一体,才能让集群在真实故障面前不崩盘。