Docker 性能调优实战:overlay2 存储驱动、网络优化与内核参数配置
系统讲解 Docker 生产环境的性能调优方法:overlay2 存储驱动的选择与调优(xfs quota/d_type 要求/upperdir 缓存)、容器网络性能优化(bridge vs host 模式选择、禁用 docker-proxy、iptables 规则优化)、内核参数调优(tcp_tw_reuse/somaxconn/conntrack 表容量)、CPU 与内存资源调优,以及 I/O 调度器的选择与性能基准测试方法。
容器运行慢不一定是应用的问题——存储驱动选错、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 表溢出问题(高并发下的隐蔽杀手)。内核参数调整主要是防止在高并发时达到系统默认上限,需要在压测环境中验证后再上生产。
