1. 当带宽限制失效时,咱们在对抗什么?
某个周五下午,运维老张发现他精心配置的Docker Compose网络带宽限制突然失效了:明明在docker-compose.yml里写好了network段的driver_opts配置,但容器间传输速度还是飙到了千兆网卡上限。这种场景常见于:
- 微服务密集部署:多个容器争夺同一物理网卡资源
 - CI/CD流水线:构建时大量镜像层传输导致网络拥堵
 - 多租户环境:需要保证不同业务组的网络公平性
 
以老张的案例为例,他用的是以下配置(技术栈:Docker 20.10 + Compose v2.15):
version: '3.8'
services:
  data_processor:
    image: alpine:3.18
    command: tail -f /dev/null
    networks:
      app_net:
        driver_opts:
          com.docker.network.tc.bandwidth: "10mbit"
  web_server:
    image: nginx:1.23
    networks:
      - app_net
networks:
  app_net:
    driver: bridge
然而通过iperf3测试容器间传输速率时,实际测得带宽仍高达940Mbps。接下来咱们一起看看问题出在哪。
2. 排查三板斧:从表象到根源
2.1 第一斧:确认TC规则是否生效
进入容器查看流量控制规则:
# 进入data_processor容器
docker exec -it data_processor sh
# 查看网卡队列规则(需安装tc命令)
apk add iproute2
tc qdisc show dev eth0
预期应该看到类似htb 1: root的层级结构,但实际输出是:
qdisc noqueue 0: dev eth0 root refcnt 2 
这说明流量控制规则根本没生效
2.2 第二斧:检查Docker网络驱动
查看网络详情:
docker network inspect app_net
关键字段输出:
"Options": {
    "com.docker.network.tc.bandwidth": "10mbit"
},
"Driver": "bridge"
问题浮现:bridge驱动本身不支持TC带宽限制,该参数仅适用于macvlan或自定义驱动
2.3 第三斧:物理网卡关联验证
查看宿主机的虚拟网卡:
# 查找与容器关联的veth设备
ip link | grep veth
# 检查宿主侧的TC规则
tc -s qdisc show dev vetha1b2c3d
发现宿主机侧也没有生成任何队列规则,印证了驱动不兼容的结论
3. 修复方案:让流量控制真正落地
3.1 正确使用macvlan驱动
修改docker-compose.yml:
networks:
  app_net:
    driver: macvlan
    driver_opts:
      parent: eth0  # 指定物理网卡
      com.docker.network.tc.bandwidth: "10mbit"
但此时启动会报错:
Error response from daemon: macvlan driver does not support tc bandwidth options
因为官方macvlan驱动也不支持带宽限制,需要改用第三方方案
3.2 使用自定义网络插件
安装带宽控制插件:
docker plugin install --grant-all-permissions gorilla/tc
调整配置:
networks:
  app_net:
    driver: gorilla/tc
    options:
      parent: eth0
      tc.ingress.rate: "10mbit"  # 入方向限制
      tc.egress.rate: "10mbit"   # 出方向限制
此时在容器内执行tc qdisc show,可以看到:
qdisc htb 1: dev eth0 root refcnt 2 r2q 10 default 1 direct_packets_stat 0 direct_qlen 1000
通过iperf3测试,实际带宽稳定在9.8Mbps左右
4. 关键原理:TC如何在Docker中工作
4.1 Linux流量控制体系
- HTB(Hierarchical Token Bucket):分层令牌桶算法
 - Classful队列:支持多级带宽分配
 - Filter规则:基于IP/端口等条件分流
 
典型TC命令结构:
tc qdisc add dev eth0 root handle 1: htb default 12
tc class add dev eth0 parent 1: classid 1:1 htb rate 10mbit ceil 10mbit
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 172.20.0.3 flowid 1:1
4.2 Docker网络驱动差异
| 驱动类型 | TC支持 | 适用场景 | 性能损耗 | 
|---|---|---|---|
| bridge | ❌ | 单机容器通信 | 低 | 
| macvlan | ❌ | 直连物理网络 | 最低 | 
| overlay | ❌ | 跨主机容器网络 | 中 | 
| gorilla/tc | ✅ | 需要QoS控制的场景 | 较高 | 
5. 避坑指南:那些年我们踩过的雷
5.1 内核模块缺失
确保加载所需模块:
modprobe ifb  # 中间功能块设备
modprobe act_mirred  # 流量重定向
5.2 网络模式选择
避免使用host网络模式:
# 错误配置示例
services:
  bad_service:
    network_mode: host  # 会绕过所有TC规则
5.3 速率单位陷阱
正确使用单位后缀:
# 有效写法
tc.ingress.rate: "10mbit"  # 10兆比特/秒
tc.egress.rate: "1gbit"    # 1千兆比特/秒
# 无效写法
tc.ingress.rate: "10MBps"  # 插件可能无法解析大写单位
6. 扩展应用:当带宽限制遇上K8s
虽然本文聚焦Docker Compose,但Kubernetes的带宽限制方案可做对比:
apiVersion: v1
kind: Pod
metadata:
  name: limited-pod
spec:
  containers:
  - name: app
    image: nginx
    resources:
      limits:
        kubernetes.io/egress-bandwidth: "10M"
但K8s方案存在局限性:
- 仅支持egress方向控制
 - 依赖CNI插件实现
 - 无法精细到端口级别
 
7. 总结:从现象到本质的修炼之路
通过这次故障排查,我们掌握了:
- Docker网络驱动的特性差异
 - TC规则的实际生效条件
 - 第三方插件的选型方法
 
最终的配置模板:
version: '3.8'
services:
  service_a:
    networks:
      qos_net:
        ipv4_address: 172.22.0.10
networks:
  qos_net:
    driver: gorilla/tc
    options:
      parent: eth0
      tc.ingress.rate: "20mbit"
      tc.egress.rate: "50mbit"
      tc.ingress.burst: "2mbit"  # 突发带宽设置
记住:当配置不生效时,沿着声明配置 → 驱动支持 → 内核实现的路径逐步验证,终能找到突破口。下次遇到类似问题,愿你也能像老张一样从容破解!
评论