外观
资源限制与日志轮转
两个"跑着跑着就出事"的默认值:容器不限制资源、日志不限制大小。前者让一个容器拖垮整机,后者几个月写满磁盘。
场景
- 一台机器跑多个服务,要防止互相抢资源。
- 容器内存涨到把宿主机 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 |
| 2g | 2g | 只能用 2g,无 swap |
| 2g | 4g | 2g 内存 + 2g swap |
| 2g | -1 | 不限 swap |
生产建议 --memory-swap 等于 --memory,禁用 swap。数据库/中间件走 swap 会让延迟从微秒变毫秒。
bash
docker stats --no-stream app
docker inspect -f '{{.State.OOMKilled}} {{.HostConfig.Memory}}' apptext
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 52JVM 的额外一层
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-limit | fork 炸弹把宿主机进程表打满 |
--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 -5text
48M /data/docker/containers/a3f8c2e91b04.../a3f8c2e91b04-json.log
2.1G /data/docker/containers/7d3c1e9a2b44.../7d3c1e9a2b44-json.log2.1G 那个就是没配轮转的。当场清空:
bash
truncate -s 0 /data/docker/containers/7d3c1e9a2b44.../7d3c1e9a2b44-json.log必须 truncate,不能 rm——Docker 持有文件句柄,rm 后空间不释放。
六、磁盘都去哪了
bash
docker system df -vtext
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.3GBUNIQUE 才是这个镜像独占的空间,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 采集,但至少要有磁盘和内存两条告警。
八、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 容器反复重启,退出码 137 | OOMKilled | 调大 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 后旧容器日志还在涨 | 旧容器不继承新配置 | 重建容器 |