Docker 生产规范:资源限制、日志驱动与重启策略配置详解
系统讲解 Docker 生产环境必须配置的三大规范:CPU/内存/IO 资源限制(cgroup v2 参数详解、OOM 处理)、日志驱动选型与轮转策略(json-file/journald/fluentd 对比)、重启策略的适用场景与陷阱,以及 daemon.json 全局配置模板与容器运行时安全加固清单。
把 Docker 用于生产环境,不能只靠 docker run -d。不限制资源,一个容器故障就能把整台机器拖垮;不配置日志轮转,日志文件会把磁盘撑满;不设置重启策略,服务重启后容器不自动恢复。本文是 Docker 生产环境的必配规范清单。
一、资源限制
为什么必须设置资源限制
不限制资源的风险:
场景:一台 32 核 64GB 的服务器运行 10 个容器
容器A 发生内存泄漏 → 占用 50GB 内存
→ 宿主机 OOM Killer 随机杀进程
→ 可能杀掉其他容器的进程(包括数据库!)
→ 级联故障
正确做法:
给每个容器设置合理的资源上限
OOM 时只杀泄漏的那个容器
其他容器正常运行
CPU 限制
# --cpus:限制最多使用多少 CPU 核数(推荐)
docker run -d --cpus=1.5 nginx # 最多 1.5 核
docker run -d --cpus=0.5 redis # 最多 0.5 核
# --cpu-shares:相对权重(默认 1024,只在争用时生效)
docker run -d --cpu-shares=512 worker # 正常时不限,争用时获得 50% 份额
docker run -d --cpu-shares=2048 api # 争用时获得 200% 份额(优先保证 api)
# --cpuset-cpus:绑定到特定 CPU 核
docker run -d --cpuset-cpus="0,1" nginx # 只用第 0、1 号核
docker run -d --cpuset-cpus="2-5" worker # 只用第 2-5 号核
# 组合使用(推荐生产配置)
docker run -d \
--cpus=2 \
--cpu-shares=1024 \
--name api-prod \
myapp:v1
# 验证
docker stats api-prod --no-stream
# CPU %:实时 CPU 占用(超过 cpus×100% 时触发限制)
CPU 限制原理:
# Docker 通过 cgroup 实现 CPU 限制
# --cpus=1.5 等价于:
# cpu.cfs_period_us = 100000 (100ms 一个调度周期)
# cpu.cfs_quota_us = 150000 (每个周期允许运行 150ms)
# 查看容器的 cgroup 配置
CONTAINER_ID=$(docker inspect -f '{{.Id}}' api-prod)
cat /sys/fs/cgroup/cpu/docker/${CONTAINER_ID}/cpu.cfs_quota_us
# 150000
# cgroup v2(Ubuntu 22.04+ 默认)
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/cpu.max
# 150000 100000
内存限制
# --memory:内存上限(超出被 OOM Kill)
docker run -d --memory=512m myapp # 512MB 内存上限
# --memory-swap:内存 + swap 总上限
docker run -d \
--memory=512m \
--memory-swap=512m \ # swap=memory 时:禁止使用 swap
myapp
# --memory-swap=-1 → 无限 swap(不推荐,会导致 swap 撑满)
# 内存软限制(压力大时内核会尝试回收)
docker run -d \
--memory=1g \
--memory-reservation=512m \ # 保证至少 512MB(软限制)
myapp
# 禁止 OOM Kill(容器使用过多内存时挂起而不被杀死)
docker run -d \
--memory=1g \
--oom-kill-disable \ # 谨慎使用!可能导致整机 OOM
mysql
# 调整 OOM 优先级(-1000 最不容易被杀,1000 最容易)
docker run -d --oom-score-adj=500 myapp
处理 OOM Kill:
# 查看容器是否被 OOM Kill
docker inspect myapp --format '{{.State.OOMKilled}}'
# true ← 被 OOM Kill 过
# 查看系统 OOM 日志
dmesg | grep -i "oom\|killed"
journalctl -k | grep -i "oom"
# 容器 OOM 后自动重启(配合 --restart 使用)
docker run -d \
--memory=512m \
--restart=unless-stopped \ # OOM Kill 后自动重启
myapp
磁盘 IO 限制
# 限制块设备读写速率
docker run -d \
--device-read-bps /dev/sda:100mb \ # 读不超过 100MB/s
--device-write-bps /dev/sda:50mb \ # 写不超过 50MB/s
myapp
# 限制 IO 操作次数(IOPS)
docker run -d \
--device-read-iops /dev/sda:1000 \ # 最多 1000 次读/秒
--device-write-iops /dev/sda:500 \ # 最多 500 次写/秒
myapp
# 查看设备名称
lsblk
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# sda 8:0 0 100G 0 disk
# └─sda1 8:1 0 100G 0 part /
二、日志驱动配置
日志驱动对比
驱动名称 特点 适用场景
─────────────────────────────────────────────────────────
json-file 默认,日志存本地 JSON 文件 大多数场景
journald 写入 systemd journal CentOS/RHEL 系统
syslog 发送到 syslog 服务 已有 syslog 基础设施
fluentd 发送到 Fluentd 聚合器 EFK 日志栈
gelf 发送到 Graylog 已有 Graylog
awslogs 发送到 AWS CloudWatch AWS 环境
splunk 发送到 Splunk 企业 Splunk 部署
none 丢弃所有日志 批处理任务
json-file 配置(必须设置轮转)
# 全局配置(所有容器默认使用)
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m", ← 单个日志文件最大 100MB
"max-file": "3", ← 保留最近 3 个文件(共 300MB)
"compress": "true" ← 旧日志文件 gzip 压缩
}
}
# 不设置轮转的后果:
# 1个运行 6 个月的容器日志可能 → 100GB+
# 磁盘满了整个 Docker 无法运行
# 单容器覆盖(优先级高于 daemon.json 全局配置)
docker run -d \
--log-driver=json-file \
--log-opt max-size=50m \
--log-opt max-file=5 \
--log-opt compress=true \
--name mysql-prod \
mysql:8.0
# 查看容器使用的日志驱动
docker inspect mysql-prod --format '{{json .HostConfig.LogConfig}}'
# {"Type":"json-file","Config":{"max-file":"5","max-size":"50m"}}
# 日志文件位置
ls /var/lib/docker/containers/<container_id>/
# <container_id>-json.log ← 当前日志
# <container_id>-json.log.1 ← 轮转后的旧日志
# <container_id>-json.log.1.gz ← 压缩后的旧日志
fluentd 日志驱动(集中收集)
# 先部署 Fluentd(或 Fluent Bit)收集器
docker run -d \
--name fluentd \
-p 24224:24224 \
-v /data/fluentd/conf:/fluentd/etc \
fluent/fluentd:v1.16
# 容器日志发送到 Fluentd
docker run -d \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag="docker.{{.Name}}" \
--log-opt fluentd-async=true \ # 异步发送(不阻塞容器)
myapp
# Fluentd 配置示例(/data/fluentd/conf/fluent.conf)
# <source>
# @type forward
# port 24224
# </source>
# <match docker.**>
# @type elasticsearch
# host elasticsearch
# port 9200
# index_name docker-logs
# </match>
三、重启策略
# 4 种重启策略
docker run -d --restart=no nginx # 不自动重启(默认)
docker run -d --restart=always nginx # 总是重启(系统重启后也启动)
docker run -d --restart=unless-stopped nginx # 手动 stop 后不重启
docker run -d --restart=on-failure:5 nginx # 失败时重启,最多 5 次
# 策略对比:
# no:脚本或一次性任务
# always:核心服务(但 docker stop 后也会在系统重启时再次启动)
# unless-stopped:生产服务推荐(手动 stop 后不再自动启动)
# on-failure:N:不稳定的服务,防止无限重启
# 修改运行中容器的重启策略(无需重建)
docker update --restart=unless-stopped mysql-prod
docker update --restart=unless-stopped nginx-prod redis-prod # 批量
# 查看重启策略
docker inspect myapp --format '{{.HostConfig.RestartPolicy}}'
# {unless-stopped 0}
重启次数与退避时间:
# Docker 的重启退避(指数退避,防止重启风暴)
# 第1次重启:立即
# 第2次重启:等待 100ms
# 第3次重启:等待 200ms
# 第4次重启:等待 400ms
# ...最大等待 1 分钟
# 查看重启次数
docker inspect myapp --format '{{.RestartCount}}'
# 查看最近一次退出状态
docker inspect myapp --format '{{json .State}}'
四、daemon.json 生产配置模板
{
"data-root": "/data/docker",
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3",
"compress": "true"
},
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://hub-mirror.c.163.com"
],
"insecure-registries": [],
"live-restore": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
},
"userland-proxy": false,
"ip-forward": true,
"iptables": true,
"dns": ["114.114.114.114", "8.8.8.8"],
"features": {
"buildkit": true
}
}
各字段说明:
# data-root:Docker 数据存储路径
# 默认 /var/lib/docker,生产建议放到大磁盘上
mkdir -p /data/docker
# 修改后需要迁移旧数据或重新 pull 镜像
# live-restore:Docker daemon 重启时容器继续运行
# 升级 Docker 时容器不停机!必须开启
# userland-proxy:false 使用 iptables DNAT(性能更好)
# true 使用用户态代理(兼容性好但性能差)
# nofile ulimit:容器内可打开的文件描述符数量
# 默认很低(1024),高并发服务需要调高
五、生产容器配置模板
# 生产环境完整 docker run 命令模板
docker run -d \
# 命名
--name myapp-prod \
\
# 网络
--network app-net \
-p 8080:8080 \
\
# 资源限制
--cpus=2 \
--memory=1g \
--memory-swap=1g \ # 禁止 swap
--memory-reservation=512m \ # 软限制
\
# 重启
--restart=unless-stopped \
\
# 日志
--log-driver=json-file \
--log-opt max-size=100m \
--log-opt max-file=3 \
\
# 安全
--read-only \ # 根文件系统只读
--tmpfs /tmp:rw,noexec \ # /tmp 用内存
--security-opt no-new-privileges \
--cap-drop ALL \ # 去除所有 capabilities
--cap-add NET_BIND_SERVICE \ # 只加回需要的
\
# 健康检查
--health-cmd="curl -f http://localhost:8080/health || exit 1" \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
--health-start-period=15s \
\
# 环境变量(非敏感)
-e APP_ENV=production \
-e LOG_LEVEL=info \
\
# 数据
-v app_data:/data \
\
myapp:v1.2.3 # 固定版本,不用 latest
六、生产运维检查脚本
#!/bin/bash
# check-containers.sh — 容器健康巡检
echo "=== 容器状态 ==="
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.RunningFor}}"
echo ""
echo "=== 不健康容器 ==="
docker ps --filter "health=unhealthy" --format "{{.Names}}: {{.Status}}"
echo ""
echo "=== 重启次数异常(>3次)==="
for container in $(docker ps -q); do
NAME=$(docker inspect -f '{{.Name}}' $container | tr -d '/')
RESTARTS=$(docker inspect -f '{{.RestartCount}}' $container)
if [ "$RESTARTS" -gt 3 ]; then
echo "⚠️ $NAME: 已重启 $RESTARTS 次"
fi
done
echo ""
echo "=== 磁盘使用 ==="
docker system df
echo ""
echo "=== 资源使用 Top 5 ==="
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" \
| sort -k2 -rn | head -6
小结
Docker 生产环境三个必须:
资源限制:--cpus + --memory + --memory-swap 组合,防止单容器拖垮整机;内存超限触发 OOM Kill 后配合 --restart=unless-stopped 自动恢复。
日志轮转:daemon.json 全局设置 max-size=100m max-file=3,防止日志撑满磁盘,这是运维最常忽视的问题。
重启策略:核心服务统一用 unless-stopped,手动维护时 docker stop 不会在重启后自动拉起,防止误操作。
