Skip to content

资源限制与日志轮转 ​

两个"跑着跑着就出事"的默认值:容器不限制资源、日志不限制大小。前者让一个容器拖垮整机,后者几个月写满磁盘。

场景 ​

  • 一台机器跑多个服务,要防止互相抢资源。
  • 容器内存涨到把宿主机 OOM。
  • /var/lib/docker 越涨越大,找不出是什么。

一、内存限制 ​

bash
docker run -d --name app \
  --memory 2g \
  --memory-swap 2g \
  --memory-reservation 1g \
  myapp:1.0.0
参数含义
--memory硬上限,超过触发 cgroup OOM
--memory-swap内存 + swap 的总上限。等于 --memory 表示禁用 swap
--memory-reservation软限制,内存紧张时优先回收

--memory-swap 的语义容易搞错:

--memory--memory-swap效果
2g未设可用 2g 内存 + 2g swap
2g2g只能用 2g,无 swap
2g4g2g 内存 + 2g swap
2g-1不限 swap

生产建议 --memory-swap 等于 --memory,禁用 swap。数据库/中间件走 swap 会让延迟从微秒变毫秒。

bash
docker stats --no-stream app
docker inspect -f '{{.State.OOMKilled}} {{.HostConfig.Memory}}' app
text
CONTAINER ID   NAME   CPU %   MEM USAGE / LIMIT   MEM %   NET I/O         BLOCK I/O     PIDS
a3f8c2e91b04   app    8.21%   1.512GiB / 2GiB     75.60%  1.2GB / 892MB   0B / 12.4MB   52

JVM 的额外一层 ​

JVM 默认按物理内存算堆,不是按容器 limit。JDK 10+ 有 UseContainerSupport 默认开启,但堆仍可能吃满 limit,导致堆外内存(元空间、直接内存、线程栈)把容器撑爆。

bash
# 让 JVM 用容器 limit 的 70%
java -XX:MaxRAMPercentage=70 -jar app.jar

经验公式:

text
容器 memory limit ≈ 应用实际需要 × 1.3
JVM MaxRAMPercentage ≈ 70

设 limit 2g + MaxRAMPercentage=70,堆约 1.4g,剩下 600MB 给堆外。反过来 limit 2g + -Xmx2g 必被 OOMKilled。

二、CPU 限制 ​

bash
# 最多用 1.5 核
docker run -d --cpus 1.5 myapp

# 绑定到 CPU 0 和 1
docker run -d --cpuset-cpus "0,1" myapp

# 相对权重(默认 1024,只在争抢时生效)
docker run -d --cpu-shares 512 myapp
参数语义
--cpus绝对配额,硬上限
--cpuset-cpus绑核,物理隔离
--cpu-shares相对权重,空闲时不受限

--cpus 实现的是 CFS 配额:1.5 核 = 每 100ms 周期内最多用 150ms CPU。突发流量会被限流,表现为延迟尖刺。

--cpuset-cpus 会影响 NUMA 本地性,一般只在延迟敏感的场景用。

三、其他限制 ​

bash
docker run -d \
  --pids-limit 512 \
  --ulimit nofile=65535:65535 \
  --ulimit nproc=4096:4096 \
  --blkio-weight 500 \
  --device-read-bps /dev/vda:100mb \
  myapp
参数防什么
--pids-limitfork 炸弹把宿主机进程表打满
--ulimit nofile高并发报 Too many open files
--blkio-weight磁盘 IO 争抢(相对权重 10~1000)
--device-read-bps单容器 IO 限速

--pids-limit 不设时,容器内可以无限 fork,最终把宿主机 kernel.pid_max 耗尽,整机不可用。这是容器逃逸类故障里最常见的一种。

四、日志驱动 ​

bash
docker info | grep -i 'logging driver'
text
 Logging Driver: json-file
驱动行为用在哪
json-file(默认)写 /data/docker/containers/*/*-json.log小规模、docker logs 方便
local二进制格式,自带轮转推荐,更省空间
journald交给 systemd-journald已统一用 journald 的机器
syslog发到 syslog有集中日志系统
fluentd / gelf / awslogs直接发到日志平台云环境
none不记录高频无价值日志

全局配置(/etc/docker/daemon.json):

json
{
  "log-driver": "local",
  "log-opts": { "max-size": "50m", "max-file": "5" }
}

local 驱动的好处:默认就带轮转(每个文件 20MB,最多 5 个)。json-file 必须显式配。

单容器覆盖:

bash
docker run -d --log-driver json-file \
  --log-opt max-size=100m --log-opt max-file=3 \
  myapp

改配置只对新容器生效

改完 daemon.json 重启 Docker,已有容器仍用旧配置。要让已有的生效只能重建容器。所以日志配置一定要在第一批容器启动前就定好。

五、日志占了多少 ​

bash
du -sh /data/docker/containers/*/*-json.log | sort -h | tail -5
text
48M	/data/docker/containers/a3f8c2e91b04.../a3f8c2e91b04-json.log
2.1G	/data/docker/containers/7d3c1e9a2b44.../7d3c1e9a2b44-json.log

2.1G 那个就是没配轮转的。当场清空:

bash
truncate -s 0 /data/docker/containers/7d3c1e9a2b44.../7d3c1e9a2b44-json.log

必须 truncate,不能 rm——Docker 持有文件句柄,rm 后空间不释放。

六、磁盘都去哪了 ​

bash
docker system df -v
text
Images space usage:

REPOSITORY   TAG      IMAGE ID       CREATED        SIZE      SHARED    UNIQUE    CONTAINERS
myapp        1.0.1    7d3c1e9a2b44   2 hours ago    312MB     210MB     102MB     1
myapp        1.0.0    c1a2b3d4e5f6   3 days ago     310MB     210MB     100MB     0
<none>       <none>   4e5f6a7b8c9d   5 days ago     298MB     208MB     90MB      0

Containers space usage:
CONTAINER ID   IMAGE         COMMAND   LOCAL VOLUMES   SIZE      CREATED        STATUS
a3f8c2e91b04   myapp:1.0.1   "..."     1               12.4MB    2 hours ago    Up 2 hours

Local Volumes space usage:
VOLUME NAME   LINKS   SIZE
mysql-data    1       1.84GB

Build cache usage: 6.3GB

UNIQUE 才是这个镜像独占的空间,SHARED 是和别的镜像共用的层。

清理顺序(从安全到危险):

bash
docker builder prune                      # 构建缓存,安全
docker image prune                        # 悬空镜像,安全
docker container prune                    # 已停止容器,较安全
docker system prune                       # 上面全做 + 未用网络
docker image prune -a                     # 所有未使用镜像,慎用
docker system prune -a --volumes          # 含数据卷,危险

查看 overlay2 占用:

bash
du -sh /data/docker/overlay2
du -sh /data/docker/volumes
du -sh /data/docker/containers

三块通常占 90%:镜像层(overlay2)、卷、日志。

磁盘 inode 也会用尽

df -h 显示还有空间,但 df -i 显示 inode 100%,新文件创建失败。大量小文件(日志、构建缓存)会造成这个问题。

bash
df -i /data
find /data/docker -xdev -type f | wc -l

七、监控建议 ​

bash
# 一次性快照
docker stats --no-stream

# 持续刷新
docker stats

# 只保留指定字段
docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.PIDs}}'
text
NAME      CPU %    MEM %    PIDS
app       8.21%    75.60%   52
mysql     2.14%    61.20%   38
nginx     0.12%    1.40%    9

告警阈值建议:

指标警告严重
容器内存 / limit> 80%> 92%
宿主机磁盘> 75%> 88%
宿主机内存可用< 15%< 8%
单容器 CPU持续打满持续打满且延迟上升
OOMKilled 次数≥ 1反复出现

生产上用 cAdvisor + Prometheus + Grafana 采集,但至少要有磁盘和内存两条告警。

八、踩过的坑 ​

现象根因处置
容器反复重启,退出码 137OOMKilled调大 limit 或降 MaxRAMPercentage
宿主机整体卡死某容器无限 fork配 --pids-limit
日志文件几个 G没配轮转truncate + 配 log-opts
rm 了日志但磁盘没降文件句柄还在用 truncate
docker stats 内存远低于 limit 仍被 OOM堆外内存/页缓存也算limit 留 30% 余量
CPU 有尖刺--cpus 的 CFS 配额限制突发提高配额或去掉限制
Too many open files没设 nofile--ulimit nofile
df -h 有空间但写不进去inode 用尽df -i,清小文件
改 daemon.json 后旧容器日志还在涨旧容器不继承新配置重建容器