技术博客

Docker 性能调优实战:overlay2 存储驱动、网络优化与内核参数配置

系统讲解 Docker 生产环境的性能调优方法:overlay2 存储驱动的选择与调优(xfs quota/d_type 要求/upperdir 缓存)、容器网络性能优化(bridge vs host 模式选择、禁用 docker-proxy、iptables 规则优化)、内核参数调优(tcp_tw_reuse/somaxconn/conntrack 表容量)、CPU 与内存资源调优,以及 I/O 调度器的选择与性能基准测试方法。

Docker性能调优overlay2网络优化内核参数生产运维

容器运行慢不一定是应用的问题——存储驱动选错、iptables 规则堆积、conntrack 表被打满,任何一个底层问题都能让高性能应用跑出低性能表现。本文从存储、网络、内核三个维度,系统梳理 Docker 的性能调优方法。

性能问题定位思路

先度量,后调优。不要凭感觉调参数。

度量工具:
  容器级别:docker stats(CPU/内存/网络/IO 实时)
  进程级别:docker exec myapp top / htop
  磁盘 IO:docker exec myapp iostat -x 1
  网络延迟:docker exec myapp ping / curl -w "time_total: %{time_total}\n"
  内核级别:perf, bpftrace, nstat, ss

常见瓶颈特征:
  CPU 100%  → 应用计算密集型,考虑资源限制和多副本
  内存 OOM  → 见故障排查篇
  网络延迟高 → 检查 docker-proxy、NAT、连接数
  磁盘 IO 高 → 检查存储驱动、I/O 调度器
  延迟抖动  → 检查 CPU 频率调整、内存大页、GC

一、overlay2 存储驱动优化

overlay2 是 Docker 默认且推荐的存储驱动,但配置不当会带来显著的 I/O 性能问题。

1.1 确认 overlay2 正在使用且配置正确

# 确认存储驱动
docker info | grep -E 'Storage Driver|Backing Filesystem|d_type'
# Storage Driver: overlay2
# Backing Filesystem: xfs
# Supports d_type: true       ← 必须是 true!

# 如果 Supports d_type: false(xfs 格式化时未开启)
# 症状:启动大量容器后性能急剧下降,甚至 Docker daemon 报错
# 解决:重新格式化 xfs(需要迁移数据)
mkfs.xfs -n ftype=1 /dev/sdb    # ftype=1 开启 d_type

1.2 使用 XFS Quota 限制容器存储

# 在 daemon.json 中开启 overlay2 + xfs quota
# /etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true",
    "overlay2.size=20G"          # 每个容器的存储上限
  ]
}

# 验证
docker run -d --name test busybox sleep 3600
docker inspect test --format '{{.GraphDriver.Data.UpperDir}}'
# /var/lib/docker/overlay2/xxx/diff ← 容器的写层

# 查看容器磁盘使用
docker system df -v

1.3 分离 /var/lib/docker 到独立磁盘

# 生产环境强烈建议将 Docker 数据目录挂载到独立 SSD

# 1. 停止 Docker
systemctl stop docker

# 2. 移动数据(或新磁盘格式化后挂载)
rsync -aP /var/lib/docker/ /data/docker/

# 3. 修改 daemon.json
{
  "data-root": "/data/docker"
}

# 4. 或者用 bind mount(更常用)
mount /dev/sdb1 /var/lib/docker

# 永久挂载(/etc/fstab)
/dev/sdb1 /var/lib/docker xfs defaults,noatime 0 0
# noatime:不更新访问时间,减少 IO

# 5. 启动 Docker
systemctl start docker
docker info | grep "Docker Root Dir"

1.4 镜像层缓存优化

# 构建顺序影响缓存命中率
# 错误顺序(每次代码变更都要重新安装依赖):
COPY . .
RUN pip install -r requirements.txt   # 代码变了,缓存失效

# 正确顺序(依赖单独一层,代码变更不影响依赖层):
COPY requirements.txt .
RUN pip install -r requirements.txt   # 依赖不变,命中缓存
COPY . .                              # 只有这层重新构建

# 清理构建缓存(定期执行)
docker buildx prune --keep-storage=10gb -f

二、网络性能优化

2.1 选对网络模式

# bridge 模式(默认):有 NAT 开销
# 每个出站包经过 NAT 转换,入站包经过 docker-proxy 或 iptables DNAT

# host 模式:无 NAT,性能最好
# 适合:高性能网络服务(如 Nginx、Redis、消息队列)
docker run -d --network host nginx

# 性能对比(同宿主机 iperf3 测试):
# host 模式:~9.5 Gbps
# bridge 模式:~6-8 Gbps(受 VETH pair 和 NAT 影响)
# overlay 模式:~4-6 Gbps(额外 VXLAN 封装)

2.2 禁用 docker-proxy,使用 iptables hairpin

# docker-proxy 是每个端口映射对应一个用户态进程
# 高并发时成为瓶颈(context switch 开销大)

# 查看当前 docker-proxy 进程
ps aux | grep docker-proxy
# /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip ...
# 每个 -p 端口映射都有一个进程!

# 禁用(使用 iptables 直接 DNAT,更高效)
# /etc/docker/daemon.json
{
  "userland-proxy": false
}

systemctl restart docker

# 验证:重启后不再有 docker-proxy 进程
ps aux | grep docker-proxy

2.3 容器 DNS 性能优化

# Docker 内置 DNS(127.0.0.11)性能说明
# 每次容器 DNS 查询都经过容器内的 resolv.conf → 127.0.0.11 → Docker DNS

# 优化1:减少 DNS 超时等待(默认等待较长)
# 在容器内或 compose 中配置
# compose.yml
services:
  api:
    dns:
      - 127.0.0.11     # Docker 内置 DNS(容器名解析)
      - 8.8.8.8        # 外部 DNS(外网域名)
    dns_search: .      # 不追加搜索域(减少无效查询)
    dns_opt:
      - "ndots:1"      # 减少 FQDN 查找次数(默认 ndots:5 很慢)
      - "timeout:2"
      - "attempts:2"

# 优化2:应用层 DNS 缓存
# Go 应用默认有 DNS 缓存,Java 默认 TTL=30s
# Python 无缓存(频繁 DNS 查询时影响显著)
# 解决:应用内部使用 aiodns 或设置 networkaddress.cache.ttl

2.4 连接跟踪表(conntrack)优化

# 症状:高并发时出现 "nf_conntrack: table full, dropping packet"
# 这是 Linux 内核的连接跟踪表满了,不是 Docker 的问题

# 查看当前状态
cat /proc/sys/net/netfilter/nf_conntrack_count    # 当前连接数
cat /proc/sys/net/netfilter/nf_conntrack_max      # 最大值
# 如果 count 接近 max,就会丢包

# 临时调大(立即生效)
echo 1048576 > /proc/sys/net/netfilter/nf_conntrack_max

# 永久生效(/etc/sysctl.d/99-docker.conf)
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 300    # 默认 432000(5 天)太长
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 15       # 默认 120 秒

# 应用参数
sysctl -p /etc/sysctl.d/99-docker.conf

# 非 Docker 场景(内部微服务通信),考虑关闭 conntrack
# 只对 Docker 内网通信的 iptables 规则关闭 conntrack
iptables -t raw -I PREROUTING -i docker0 -j NOTRACK
iptables -t raw -I OUTPUT -o docker0 -j NOTRACK

三、内核参数调优

3.1 TCP 参数

# /etc/sysctl.d/99-docker-perf.conf

# TCP 连接复用(TIME_WAIT 状态的连接可以被新连接重用)
net.ipv4.tcp_tw_reuse = 1

# 减少 TIME_WAIT 连接的占用时间
net.ipv4.tcp_fin_timeout = 15

# 增大监听队列(解决高并发下 accept 队列溢出)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 增大本地端口范围(短连接多时容易耗尽)
net.ipv4.ip_local_port_range = 1024 65535

# TCP 快速打开(减少握手延迟)
net.ipv4.tcp_fastopen = 3

# 增大接收/发送缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 应用
sysctl -p /etc/sysctl.d/99-docker-perf.conf

3.2 文件描述符限制

# 容器内进程继承宿主机的 ulimit 设置

# 查看当前限制
ulimit -n    # 文件描述符数量
# 1024 ← 默认值对高并发服务来说太低

# 系统级调整(/etc/security/limits.conf)
*    soft    nofile    65535
*    hard    nofile    65535
root soft    nofile    65535
root hard    nofile    65535

# systemd 级别(/etc/systemd/system/docker.service.d/ulimit.conf)
[Service]
LimitNOFILE=1048576

# daemon.json 级别(对所有容器生效)
{
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 65535,
      "Soft": 65535
    }
  }
}

# 单个容器级别(覆盖默认)
docker run --ulimit nofile=65535:65535 myapp

3.3 内存大页(HugePage)

# 适合内存密集型应用(如 Redis、数据库)
# 大页减少 TLB miss,降低内存访问延迟

# 查看当前大页配置
cat /proc/meminfo | grep -i huge
# HugePages_Total:    0
# HugePages_Free:     0
# Hugepagesize:       2048 kB

# 分配大页(2MB 页面,共 1024 个 = 2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# 永久生效
echo "vm.nr_hugepages = 1024" >> /etc/sysctl.d/99-hugepages.conf

# 容器使用大页(需要挂载 /dev/hugepages)
docker run -d \
    --mount type=tmpfs,destination=/dev/hugepages,tmpfs-mode=1770 \
    --privileged \
    redis:7-alpine redis-server --save "" --appendonly no

# 注意:透明大页(THP)对延迟敏感应用反而有害(Redis 建议关闭)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

四、I/O 调度器优化

# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber none

# 对于 SSD:使用 none(绕过调度器,最低延迟)
echo none > /sys/block/sda/queue/scheduler

# 对于 HDD:使用 mq-deadline(防止 I/O 饥饿)
echo mq-deadline > /sys/block/sda/queue/scheduler

# 永久生效(udev 规则)
cat > /etc/udev/rules.d/60-ioscheduler.rules << 'EOF'
# SSD 使用 none 调度器
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
# HDD 使用 mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
EOF

# 队列深度(NVMe SSD 可以调大)
cat /sys/block/nvme0n1/queue/nr_requests   # 查看当前值
echo 1024 > /sys/block/nvme0n1/queue/nr_requests

五、性能基准测试

容器存储 IO 基准

# 测试容器内写速度(顺序写)
docker run --rm busybox sh -c \
    "dd if=/dev/zero of=/tmp/test bs=1M count=512 conv=fdatasync 2>&1"
# 512+0 records in
# 512+0 records out
# 536870912 bytes (512.0MB) copied, 1.234 s, 415 MB/s

# 测试 overlay2 层的写放大
# (容器写 vs 裸盘写的速度比较)
dd if=/dev/zero of=/tmp/host-test bs=1M count=512 conv=fdatasync

网络基准测试

# 安装 iperf3 测试容器间网络带宽
docker run -d --name iperf-server --network app-net \
    networkstatic/iperf3 -s

docker run --rm --network app-net \
    networkstatic/iperf3 -c iperf-server -t 10
# [  5] 0.00-10.00  sec  9.31 GBytes  8.00 Gbits/sec  sender
# [  5] 0.00-10.00  sec  9.31 GBytes  8.00 Gbits/sec  receiver

# 测试容器到外网延迟
docker run --rm busybox ping -c 10 8.8.8.8 | tail -2

CPU 调优

# 绑定容器到特定 CPU(NUMA 感知,减少 cache miss)
docker run -d --cpuset-cpus="0,1" myapp   # 绑定到 CPU 0 和 1

# 查看 NUMA 拓扑
numactl --hardware
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 ...
# node 1 cpus: 12 13 14 15 ...

# 同一 NUMA node 的 CPU + 内存
docker run -d \
    --cpuset-cpus="0-11" \
    --cpuset-mems="0" \       # 使用 node 0 的内存
    myapp

性能调优检查清单

存储层
  □ 存储驱动确认为 overlay2(xfs with d_type=true)
  □ /var/lib/docker 在独立 SSD 上
  □ 使用 noatime 挂载选项
  □ 磁盘 I/O 调度器已针对 SSD/HDD 优化

网络层
  □ userland-proxy 已禁用("userland-proxy": false)
  □ conntrack 表容量足够(nf_conntrack_max ≥ 512K)
  □ conntrack TCP 超时已缩短(established: 300s)
  □ 高性能服务考虑 host 网络模式

内核层
  □ tcp_tw_reuse = 1
  □ somaxconn = 65535
  □ nofile ulimit ≥ 65535
  □ 透明大页(THP)已关闭(延迟敏感服务)

运行时
  □ 已设置合理的 --cpus 和 --memory(防止单容器抢占)
  □ 关键服务 --memory-swappiness=0(禁止 swap)
  □ CPU 密集型服务考虑 cpuset 绑核

小结

Docker 性能调优的优先级:存储 > 网络 > 内核参数 > CPU/内存资源分配

存储是最常见的瓶颈,尤其是 overlay2 跑在带机械硬盘的系统上。把 /var/lib/docker 挂载到独立 SSD、确认 xfs d_type 开启,这两步的效果往往比其他所有调优加起来还显著。

网络层最有效的优化是禁用 userland-proxy(减少进程 context switch)和解决 conntrack 表溢出问题(高并发下的隐蔽杀手)。内核参数调整主要是防止在高并发时达到系统默认上限,需要在压测环境中验证后再上生产。