先问一句你是不是也遇到过这种场景——线上服务毫无征兆地卡死登录服务器一看 Load Average 飙到几十docker stats里某个容器 CPU 占用拉满宿主机 CPU 全部被打爆服务一个接一个超时我前几年刚上手 Docker 时就被这么坑过一次一个内部用的开源 BI 工具容器没加任何资源限制跑了两周直接把这台 8 核 16G 的机器吃到 OOM内核开始杀进程最后整个 Docker 守护进程都僵住了所有容器一起陪葬。这篇文章就围绕「Docker 开源软件应急处理」这件事把资源限制和性能瓶颈这块掰开揉碎讲清楚。内容包括为什么容器一定要做资源限制、CPU/内存/磁盘 IO 的配置参数怎么选、线上出故障时怎么快速定位和止血、以及我一直用的排查命令组合。内容偏实操结论都来自我踩过的坑和跑过的压测适合正在用 Docker 跑开源组件MySQL、Redis、Nginx、各类 Java 应用的运维和开发同学纯新手也能按步骤直接抄作业。1. 先搞清楚容器为什么会被“拖垮”资源限制的原理解读1.1 容器不是虚拟机共享内核才是资源问题的根源很多人对容器有个误解以为 Docker 容器和虚拟机一样资源是隔离好的。实际上完全不是一回事。虚拟机里跑的是完整操作系统通过 Hypervisor 做硬件级隔离你给 VM 分配 4 核 8G它就真的只有 4 核 8G里面跑什么都不会影响到宿主机上的其他 VM。但容器不同。容器本质上是宿主机上的普通进程多个容器共享同一个宿主机内核。像docker run启动的 Nginx 容器它在宿主机上就是一堆 nginx 进程CPU、内存、磁盘 IO、网络带宽全都直接使用宿主机的资源。如果没有额外限制一个失控的容器完全可以吃光整台机器的所有资源导致其他容器和宿主机自身都卡死。我见过最典型的一幕有人部署了一个开源爬虫框架的 Docker 镜像没加内存限制结果爬虫线程数失控内存占用一路涨到十几个 G宿主机开始疯狂使用 swap磁盘 IO 被打满MySQL 容器查询延迟从 5ms 飙到 3 秒最后整个服务雪崩。这就是典型的“一个容器拖垮全家”。1.2 cgroup 和 namespace限制资源、隔离视图的两根支柱要理解 Docker 怎么限制资源必须知道两个 Linux 内核机制namespace 和 cgroup。namespace 负责“看不着”。它给容器创造了一个隔离的视图让容器里的进程以为自己独占了一个系统。比如 PID namespace 让容器里的进程 PID 从 1 开始mount namespace 让容器只能看到自己的文件系统挂载点。这就是为什么容器里执行ps -ef只能看到容器自己的进程。cgroup 负责“用不多”。它是 Linux 内核的资源控制功能可以对一组进程的 CPU、内存、磁盘 IO、网络带宽做配额限制。Docker 的资源限制参数--cpus、--memory等最终都是通过 cgroup 实现的。打个比方namespace 是给每个房间装了单向玻璃房间里的人以为外面只有自己一家cgroup 是给每个房间装了独立水表和电表每月限额供应。这两者配合容器才能在共享内核的前提下实现资源隔离。这里有一个新手特别容易踩的坑容器里执行top或free看到的 CPU 核数和内存总量是宿主机的不是容器自己的限制值。因为/proc文件系统默认是宿主机的视图除非设置--cgroupns或使用专门适配的镜像比如带lxcfs的否则容器里看到的内存总量永远是你物理机的总量。这意味着你无法单纯靠容器里的free命令判断容器的内存限制是多少得用 cgroup 内部的接口文件来看# 查看容器被限制的内存上限bytes cat /sys/fs/cgroup/memory.max # 如果内核版本较老可能是 memory/memory.limit_in_bytes cat /sys/fs/cgroup/memory/memory.limit_in_bytes1.3 默认不限制的坑为什么一定要显式配置资源配额Docker 默认情况下不对容器做 CPU 和内存限制。这意味着什么意味着只要容器里的进程能申请到资源内核就会给它。这在开发和测试环境问题不大但一旦上了生产尤其是多容器共存的场景风险极大。我总结过几个高频事故场景内存泄漏的 Java 应用容器堆内存一直在涨直到把宿主机内存吃满触发内核 OOM Killer随机杀进程往往杀掉的是别的无辜容器。某个容器的定时任务到了执行时间突然并发创建大量线程CPU 使用率瞬间飙满宿主机上的所有容器一起响应变慢。日志组件异常短时间内写出几十 GB 日志直接打满磁盘所有依赖磁盘写入的服务包括数据库全部异常。容器内进程频繁读写磁盘把 IO 带宽占满虽然 CPU 和内存看起来没超但实际整个存储子系统已经不可用了。所以结论很明确凡是跑在 Docker 里的开源软件尤其是对外提供服务的组件必须做资源限制。这不只是防别人更是保护你自己——万一容器出问题限制能让你有时间和空间去处理而不是直接被拖入故障漩涡。后面我会给出我常用的资源配额参考值。2. 给容器装上“刹车”CPU 与内存限制的实操配置2.1 CPU 限制的核心参数与选值逻辑CPU 限制的参数有四个分别是--cpus容器可使用的 CPU 核心数支持小数比如--cpus1.5表示最多使用 1.5 个核。这是最推荐的方式简单直观。--cpu-shares相对权重默认 1024。它不限制绝对用量只在 CPU 竞争时按权重分配。比如两个容器权重分别是 1024 和 512CPU 资源紧张时前者分到的 CPU 是后者的两倍。--cpuset-cpus绑定物理 CPU 核比如--cpuset-cpus0,1表示只用宿主机第 0 和第 1 个 CPU 核。适合对延迟敏感、需要避免 CPU 切换开销的场景。--cpu-period和--cpu-quota这是 CFS 调度器的底层参数--cpus其实就是这两个参数的封装。一般不需要手动设置。我从实际使用经验出发推荐大家的选值逻辑是先看这个服务的性质再定配额。对于数据库类容器MySQL、PostgreSQL、RedisCPU 配额不能拍脑袋。我遇到过有人把 MySQL 容器限制成--cpus1结果业务高峰期一个复杂查询直接把 CPU 打到 100%大量慢查询堆积。数据库这类组件建议先压测确定基线再设一个稍高于峰值的配额。比如压测发现峰值是 3.5 核那就设--cpus4留一点点余量但不至于失控。对于业务应用类容器Java/Go/Python 服务可以直接限制 1~2 核因为这类应用本身是水平扩展的一个实例挂了还有其他实例顶着。限制得紧一些反而能把问题提前暴露出来。对于离线任务型容器日志采集、定时任务、批处理建议限制较低比如--cpus0.5或--cpus1避免定时任务集中执行时把宿主机打满。2.2 内存限制与 OOM 行为控制内存限制的参数是--memory或-m比如--memory2g表示容器最多使用 2G 内存。配合--memory-swap可以控制内存和 swap 的总量。这里有一个非常关键的细节很多人搞错--memory-swap并不等于 swap 分区的大小而是「内存 swap 的总上限」。比如你设置--memory2g --memory-swap3g那容器可以用的内存是 2Gswap 最多 1G加起来 3G。如果只设置--memory2g不设置--memory-swapDocker 默认--memory-swap等于 2 倍内存大小也就是容器最多用 2G 物理内存 2G swap。我踩过一个坑Java 应用容器设置了--memory4g没设置--memory-swap结果容器实际可以用到 8G4G 内存 4G swap。应用堆内存设置得比较大跑了一周后物理内存被占满开始疯狂写 swap性能直线下降。后面我统一用--memory4g --memory-swap4g这样「内存和 swap 总量相等」的配置强制容器不能用 swap内存一超就直接 OOM好过吃 swap 导致性能雪崩。另一个参数是--oom-kill-disable。这个参数要非常谨慎只有同时设置了--memory时才生效它的作用是当容器内存超过限制时不杀容器内进程而是让进程阻塞在内存申请上。这听着好像不错实际上绝大多数情况是灾难——进程申请不到内存又不会被杀会一直 hang 住容器变成「假死」状态比被杀掉更难受。我基本不用这个参数让小容器直接被杀、重启都比假死好排查得多。如果你用 Kubernetes内存限制还会涉及 Pod 的 QoS 等级。设置了内存 limit 的 Pod 属于 Burstable 或 Guaranteed 等级没设置 limit 的 Pod 一旦节点内存紧张会最先被驱逐。这和 Docker 纯容器场景不完全一样但思路相通明确限制是你的「保护伞」不是「枷锁」。2.3 磁盘与 IO 限制容易被忽略的隐藏瓶颈磁盘 IO 限制是资源限制里最容易被忽略的部分但实际上生产环境的性能瓶颈磁盘 IO 出现的概率比 CPU 和内存都要高。原因很简单CPU 和内存可以通过加配置提高磁盘 IO 的物理上限很难突破而且多个容器共享同一块磁盘一个容器疯狂写数据所有容器都跟着遭殃。Docker 的 IO 限制参数主要针对块设备# 限制容器读磁盘的带宽为 50MB/s写磁盘的带宽为 30MB/s docker run -d --device-read-bps /dev/sda:50mb --device-write-bps /dev/sda:30mb nginx # 限制容器读写磁盘的 IOPS docker run -d --device-read-iops /dev/sda:1000 --device-write-iops /dev/sda:1000 nginx不过说实话这些参数在 overlay2 存储驱动下的表现并不稳定而且配置相对繁琐。我更推荐以下三种更实用的方式来控制磁盘问题第一种顶层目录隔离。给每个容器的数据卷挂载到独立的宿主机目录并使用quota或project quota做目录级容量限制防止单个容器数据无限增长打爆磁盘。第二种日志限制。这可能是最简单但最有效的磁盘保护方式。在 Docker 配置文件/etc/docker/daemon.json中设置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这个配置的意思是每个容器最多保留 3 个日志文件每个最大 10MB超出后自动轮转删除。我在生产环境统一用了这个配置后日志打爆磁盘的事故基本绝迹。注意这个配置只对新建容器生效老容器需要重建才生效。第三种监控和告警。部署节点级的磁盘空间监控超过 80% 就告警。这个是我的底线配置因为哪怕你做了再完善的限制宿主机总磁盘还是会被 Docker 镜像、容器层、数据卷之外的文件占满。2.4 docker-compose 场景下如何统一配置资源限制现在的开源应用大概率会提供docker-compose.yml来编排资源限制在 compose 文件里配置同样简单services: mysql: image: mysql:8.0 deploy: resources: limits: cpus: 2.0 memory: 2g reservations: cpus: 0.5 memory: 512m注意deploy.resources这种写法在 Docker Compose V2 中是支持的但在 Docker Swarm 中才完整生效。如果你只是docker-compose up启动部分资源限制参数是会生效的。我实测下来docker compose命令行下的 CPU 和内存限制是可以正常写入 cgroup 的只是reservations预留在非 Swarm 模式下不生效。如果你用的是旧版docker-composeV1也可以用mem_limit、cpus这种顶层配置services: redis: image: redis:7 cpus: 1 mem_limit: 512m这两种写法在 V2 中已经统一推荐用deploy.resources但我见过很多老项目的 compose 文件还是旧写法能识别就尽量兼容。这里再补充一个我在实战中总结的关键点资源限制数值不要写在代码仓库的默认 compose 文件里而是写在一个docker-compose.override.yml中。原因是不同环境的机器配置不同开发机 8 核 16G生产机 32 核 64G如果在基础 compose 里写死了 2 核 2G开发环境会施展不开生产环境又可能不足。用 override 文件覆盖就能按环境调整docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d3. 定位性能瓶颈我压箱底的排查工具与命令组合3.1 从容器视角到内核视角先看 stats再挖根因当线上出现性能问题我第一步永远是执行docker stats它能以 1 秒间隔实时显示所有容器的 CPU、内存、网络和磁盘 IO 用量docker stats --no-stream输出类似CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O a1b2c3d4e5f6 mysql-demo 128.50% 1.2GiB / 2GiB 60.00% 125MB / 312MB 45.2MB / 12.1MB f6e5d4c3b2a1 app-server 356.20% 3.8GiB / 4GiB 95.00% 1.2GB / 2.2GB 12.3MB / 4.5MB看这个输出的两个技巧CPU 如果超过 100%说明容器使用了超过 1 个 CPU 核多核并行对--cpus4的容器来说 356% 意味着还有余量内存接近 LIMIT 就要警惕尤其 MEM% 长期高于 85% 时OOM 风险非常大。docker stats能快速定位「哪个容器出了问题」但它只能告诉你现象不能告诉你原因。我见过太多人卡在这一步发现某个容器 CPU 高却不知道进程在干什么。下一步进入容器用top -Hp查看具体线程或者直接pidstat看到更多信息# 在宿主机上找到容器主进程 PID docker inspect --format {{.State.Pid}} container_name # 查看该进程的资源占用 top -p PID # 查看该进程下所有线程的 CPU 占用 top -Hp PID要知道容器内的进程在宿主机上就是普通进程直接用宿主机工具分析完全可行。这也是容器排障的重要思路容器只是隔离视图但进程还是同一个进程。3.2 strace 与 perf找到进程到底在“忙”什么如果我们确定某个容器 CPU 高但容器内无法直接看到原因这时候就要祭出两个利器strace和perf。strace跟踪系统调用能看到进程在读哪些文件、连接哪些 socket、执行了哪些系统调用。我之前排查过一个高 CPU 的 Nginx 容器用strace发现进程在反复epoll_wait和accept实际上是有大量的短连接请求涌入根本不是死循环是流量问题。# 跟踪某个 PID 的系统调用只看和网络相关的 strace -p PID -f -e tracenetwork # 统计系统调用耗时分布 strace -p PID -c -fperf是性能分析神器可以采样 CPU 执行的热点函数# 对某个 PID 采样 10 秒 perf top -p PID我之前靠perf抓到一个 Java 应用的死循环采样结果显示热点集中在java.util.HashMap.putMapEntries配合线程 dump 定位到是并发场景下的哈希碰撞死循环JDK 8 的老坑。这个用docker stats是绝对看不出来的。不过这两个工具都需要一定的系统编程基础新手用起来可能会觉得输出很陌生。我的建议是不需要一开始就精通但至少要能在「容器的 CPU 莫名其妙高了」的时候想起来可以用它们。多试几次就有感觉了。3.3 常见瓶颈形态速查现象、定位与对策为了让你快速对号入座我整理了一份性能瓶颈速查表。这不是教科书内容是我实际排障中反复遇到的真实形态表现常见原因定位手段常见对策容器 CPU 持续 100%业务代码死循环、GC 频繁、流量突增docker statstop -HpjstatJava加资源限制、升级配置、排查代码内存缓慢增长直至 OOM内存泄漏、缓存无上限docker stats观察趋势 jmapJava限制内存、配置 OOM 重启策略、修复代码磁盘 IO 持续高位日志过多、数据量增长、索引重建iostat、docker stats的 BLOCK I/O 列日志轮转、扩容磁盘、优化查询网络延迟高/连接数打满连接池配置过大、DDoSss -s、netstat优化连接池、加防火墙策略、水平扩容容器间互相影响资源竞争、noisy neighbortop看整体负载给每个容器做资源限制、拆分宿主机注意最后一行「noisy neighbor」。这个说法一开始来自云计算领域指同一个物理机上某个租户容器占用了过多资源导致邻居租户性能下降。在 Docker 部署中这也是最常见的隐患你排查了半天自己的应用发现没问题结果是被隔壁容器的日志采集任务拖累了。这也是我一直强调「所有容器必须加限制」的原因。4. 应急处理流程从故障到恢复的标准动作4.1 快速止血保命优先别急着查根因线上故障处理有一个原则先恢复再排查。当你发现一个容器把宿主机打满时第一反应不是 SSH 上去慢慢分析日志而是先让系统稳定下来。我的标准止血操作如下第一步临时限制异常容器的资源。用docker update可以在运行中的容器上直接修改资源限制不需要重启# 立即限制异常容器最多使用 1 核、1G 内存 docker update --cpus1 --memory1g --memory-swap1g container_name这个操作很神奇它不需要容器重启cgroup 限制会即时生效。我靠这一招不知道救了多少次已经卡死的服务。有人说docker update只能改部分参数实测 CPU 和内存限制是最稳定生效的。第二步如果限制资源后容器依然无法响应果断重启docker restart container_name如果重启也没用说明容器本身已经病入膏肓直接从编排中摘除docker stop container_name docker rm container_name如果是 docker-compose 管理的服务用docker-compose stop service_name第三步记录异常容器的当前状态便于后续复盘。我习惯在止血后立刻截取docker inspect和docker logs的关键信息docker inspect container_name /tmp/container_inspect_$(date %s).json docker logs --tail 200 container_name /tmp/container_logs_$(date %s).txt docker stats --no-stream /tmp/stats_$(date %s).txt这几个文件保存下来等故障恢复后再慢慢分析。没有现场证据的应急处理等于白处理。4.2 收集现场证据为什么我说“先拍照后处理”上面提到的「先拍照后处理」是我应急流程里最重要的一条经验。很多新手在故障发生时只顾着修修完之后想复盘发现连当时的日志都没保存只能靠记忆猜测效率极低。除了容器本身的现场宿主机层面的数据同样关键。我建议在止血前后各采集一组宿主机指标# 采集内存、CPU、IO 的瞬时快照 free -h uptime iostat -x 1 3 top -b -n 1 dmesg -T | tail 50 # 查看 OOM 等内核事件dmesg特别有用。当宿主机发生 OOM 时内核会记录哪个进程被杀了、当时内存占用情况。有时候你压根不知道容器为什么挂了结果dmesg里赫然写着Out of memory: Killed process 12345 (java)真相一下就清楚了。我还遇到过一次「容器神秘重启」的案例容器配置了restart: always每次 OOM 被杀后 Docker 自动拉起来但业务数据丢失。客户一脸懵不知道怎么回事。后来就是靠dmesg看到内核 OOM 记录然后发现是隔壁的批处理容器把内存吃光了牵连到 MySQL 容器被内核杀掉。4.3 根因定位与修复从现象到代码/配置层面的解决止血之后才是真正体现功力的环节找到根因彻底修复。我给的排查路径是查看容器日志docker logs --tail 500 container_name先找异常堆栈或明显错误。如果日志无异常说明问题可能不在应用本身而是资源竞争或内核层面。此时结合宿主机的top、free、iostat分析整体负载。如果宿主机负载正常但容器响应慢检查网络。用docker network inspect看端口映射再用curl -w加时间统计测应用接口耗时。如果应用接口耗时高直接上 APM 工具如 SkyWalking、Arthas定位到具体方法。修复层面我总结过几类高频问题的解法Java 应用频繁 Full GC 导致 CPU 高、停顿长调整堆内存参数确保-Xmx略小于容器内存限制排查代码里的内存泄漏用jmap导堆内存分析。MySQL 慢查询拖垮 CPU开启慢查询日志配合EXPLAIN分析执行计划该加索引的加索引该改 SQL 的改 SQL。Nginx 出现大量连接堆积调整worker_processes、worker_connections排查 upstream 是否有超时配置。Redis 内存暴涨排查是否有大 Key、未设置过期时间的缓存考虑开启maxmemory和淘汰策略。这些问题的共性是资源限制只能「兜底」让你在问题发生时不至于整个服务雪崩但真正的性能瓶颈必须从代码和架构层面解决。Docker 的资源限制是安全网不是免死金牌。4.4 预防机制与自愈方案用一次故障换长期稳定一个成熟的运维体系不能只靠人肉应急。我的建议是给 Docker 环境配齐三件套资源限制、健康检查、自动重启。首先是统一限制模板。我内部对所有容器执行一个「底线资源策略」所有业务容器必须设置--cpus和--memory日志必须走日志轮转配置数据卷必须做容量限额。这条规则写进部署规范新容器上线前由 CI 检查。检查工具很简单脚本读一下docker inspect的输出看看有没有NanoCpus和Memory字段即可docker inspect --format {{.HostConfig.NanoCpus}} {{.HostConfig.Memory}} container_name如果返回 0说明没有设置限制直接打回。其次是健康检查。业务容器在 Dockerfile 或 compose 中配置 HEALTHCHECKservices: app: image: myapp:latest healthcheck: test: [CMD, curl, -f, http://localhost:8080/health] interval: 30s timeout: 3s retries: 3 start_period: 30s健康检查的意义在于它能持续监控容器状态而不只是进程是否存活。进程活着不代表服务可用这个坑我踩过无数次。第三是自动重启策略。容器配置restart: unless-stopped能在进程崩溃时让 Docker 自动拉起容器。配合--restart on-failure:5可以限制重启次数防止无限重启导致资源反复耗尽。最后有条件的话上监控告警。docker stats数据可以通过 Prometheus cAdvisor 采集配置 CPU 使用率超过 85% 持续 5 分钟、内存使用率超过 90%、磁盘剩余低于 20% 等规则告警。告警的意义不是让你第一时间处理而是让你能在故障影响扩大之前收到通知。有了这套机制后面我再遇到容器资源问题大部分时候是「告警通知 → 远程查看 → docker update 限流 → 稳定后再处理」而不是半夜爬起来处理雪崩。5. 常见问题排查速查表从安装到日常运行的避坑心得5.1 Docker 服务起不来或 Docker Desktop 无法启动怎么办虽然文章主题是资源限制和性能瓶颈但「Docker 本身都跑不起来」的问题也经常蹦出来而且它往往是一切后续故障的起点。我把它放在速查表里因为遇到一次就够让人崩溃。先说 Linux 上最常见的docker命令报权限错Failed to connect to the Docker daemon或Got permission denied while trying to connect to the Docker daemon socket原因基本都是当前用户不在 docker 组里。# 将当前用户加入 docker 组 sudo usermod -aG docker $USER # 重新登录或执行以下命令让组权限生效 newgrp docker然后是docker服务本身启动失败。先看状态和日志sudo systemctl status docker sudo journalctl -u docker --since 10 minutes ago常见原因包括daemon.json配置语法错误、容器数量过多导致启动超时、磁盘满了、以及内核模块未加载老版本的内核需要overlay模块。再来看 Windows 上高频出现的 Docker Desktop 启动报错。最经典的是这个Docker Desktop failed to start because virtualisation support wasnt detected这通常是 Windows 的虚拟化功能没开启。检查顺序是任务管理器 — 性能 — CPU 页签看「虚拟化」是否显示「已启用」。如果没启用去 BIOS/UEFI 里打开 Intel VT-x 或 AMD-V/SVM。另外Docker Desktop 依赖 WSL2 或 Hyper-V需要你在「启用或关闭 Windows 功能」里勾选「适用于 Linux 的 Windows 子系统」和「虚拟机平台」然后重启电脑# 管理员权限 PowerShell 中执行启用 WSL2 相关功能 wsl --install5.2 镜像下载慢别硬等换源或者分层优化镜像下载慢是每个 Docker 用户都逃不过的问题。开源项目的官方镜像动辄几百 MB网络不好时一个docker pull能卡半天。我的处理方式有三板斧第一板斧配置镜像加速器。在 Linux 上编辑/etc/docker/daemon.json在 Windows 上通过 Docker Desktop 的 Settings — Docker Engine 里配置{ registry-mirrors: [https://docker.m.daocloud.io, https://dockerproxy.com] }配置后执行systemctl restart dockerWindows 下自动生效。实测下来下载速度能有数量级的提升。第二板斧合理精简镜像。不要无脑用最新的 latest 标签选择体积更小的 slim 或 alpine 版本# 避免 docker pull nginx # 改用 docker pull nginx:stable-alpine官方的nginx镜像接近 200MB但nginx:stable-alpine只有 50MB 左右。对下载速度和磁盘占用都是质的改善。第三板斧对于公司内部频繁使用的镜像搭一个私有镜像仓库Harbor 或 Registry把公共镜像先拉到内网然后所有机器从内网拉取。这个方案一劳永逸适合机器数量较多的团队。5.3 容器日志暴涨与磁盘被写满的紧急处理说一个我记忆深刻的翻车案例有一次一个日志采集容器因为权限配置异常不停打印错误日志JSON 格式的日志文件在半小时内从几 MB 涨到 20 多 GB直接把磁盘写满。等到我发现的时候docker logs已经卡住了MySQL 容器也进入只读状态整个系统几乎瘫痪。紧急处理的办法很粗暴但有效# 1. 找到占用空间最大的容器日志文件 du -h $(docker inspect --format {{.LogPath}} $(docker ps -aq)) 2/dev/null | sort -h # 2. 先阻止日志继续增长——停掉容器或限制日志大小 docker update --log-opt max-size10m --log-opt max-file3 container_name注意docker update对日志参数不一定能即时生效需要重启容器才能让--log-opt生效。如果磁盘已经满了第一时间docker stop那个疯狂写日志的容器然后清空日志文件# 注意不能用 rm会有句柄残留要用 truncate 清空 truncate -s 0 $(docker inspect --format {{.LogPath}} container_name)清空后按前文说的在daemon.json中配置默认日志轮转然后重建容器。这件事之后我把所有环境的 Docker 日志轮转都统一配置了再没遇到过「日志涨到打爆磁盘」的情况强烈建议你提前做。5.4 端口映射、网络性能与容器间通信的常见坑网络层面的性能瓶颈往往比较隐蔽因为 CPU 和内存看起来都正常但服务就是慢。我遇到过的最常见的三种情况第一种端口映射到 0.0.0.0 导致的外部连接问题。docker run -p 8080:80默认把宿主机所有网卡上的 8080 都映射到容器的 80如果有安全要求最好指定内网 IP-p 192.168.1.100:8080:80。第二种Docker 默认的 bridge 网络吞吐量有瓶颈。bridge 网络的数据包要经过 Docker 的 iptables NAT 规则在高并发场景比如压测超过 10 万 QPS下会有明显的转发损耗。性能敏感的服务建议直接使用host网络模式docker run --network host nginxhost 模式直接共享宿主机网络栈省掉了 NAT 和端口映射的开销延迟更低、吞吐更大。代价是容器无法做端口隔离多个用同一端口的容器会冲突。我一般只在性能要求极高的场景才会用。第三种容器内看到的外部连接都是假象。很多开源软件输出连接信息时显示的是 Docker 网桥的 IP比如 172.17.0.2而不是客户端真实 IP。在做访问控制、限流、审计时一定要意识到这一点需要真实 IP 的话把容器换成 host 网络或配置网络层代理。5.5 容器迁移、数据卷和文件挂载的权限陷阱资源限制和性能瓶颈解决完之后数据持久化和文件挂载是另一个高频翻车点。最经典的问题是容器内进程以 root 运行它写出来文件在宿主机上属于 root导致宿主机其他用户无法访问。比如挂载数据卷docker run -v /host/data:/container/data mysql:8.0MySQL 容器启动时可能因为/container/data的权限不对而拒绝启动。这通常不是 Docker 的问题而是挂载目录的所有者 UID 和容器内进程的 UID 不匹配。排查方法很直接# 查看容器内进程以什么 UID 运行 docker exec container_name id mysql # 在宿主机上把挂载目录的所有者改成对应的 UID chown -R 999:999 /host/data再一个低级但常见的坑在docker cp拷贝容器和宿主机之间的文件时文件所有者仍然是容器里的 UID如果在宿主机上直接用会发现文件所有者是个不存在的 UID 数字。这时候用chown改成自己就好。这些坑虽然小但往往会让人卡上几个小时。如果对权限问题没有把握最简单的做法是把挂载目录的所有者直接设置成容器内进程的 UID或者修改容器内进程的启动用户为宿主机当前用户 UID。6. 一个完整的应急处理实战案例从发现到复盘说了这么多理论我最后用一个我实际处理的故障把整条流程串起来。这个案例非常有代表性几乎覆盖了资源限制和性能瓶颈的所有要素。那是一个跑在 8 核 16G 机器上的电商业务共部署了 6 个容器2 个 Java 应用、1 个 MySQL、1 个 Redis、1 个 Nginx、1 个日志采集器。某天下午 3 点监控告警突然响起宿主机 Load Average 从 2 飙到 15多个接口超时率超过 30%。我第一时间docker stats --no-stream发现 MySQL 容器的 CPU 占用 680%内存使用率 98%——远超我对它的限制预期这里就是没有限制导致的后果。再top查看宿主机MySQL 进程排在第一位CPU 占用 700% 左右。按照流程我立刻用docker update给 MySQL 加上限制docker update --cpus4 --memory8g --memory-swap8g mysql-container然后观察到 CPU 一直在 400% 附近徘徊说明确实是从疯涨状态控下来了。接着查看 MySQL 慢查询日志发现从下午 2:50 开始出现大量同一个 SQL 的慢查询执行时间从 200ms 涨到 8 秒。到这里真相已经很明显不是 MySQL 的问题是业务 SQL 的性能问题。查看这个 SQL发现有个大表关联查询没有走索引执行计划显示全表扫描。此时正好有活动流量高峰并发一上来MySQL CPU 直接被打满查询全部堆积最终拖垮整个系统。修复动作是两个第一DBA 补上索引第二调整应用连接池上限防止流量高峰时建立过多连接。补完索引后慢查询从 8 秒降到 50msMySQL CPU 降到 150% 上下系统恢复正常。事后复盘时我总结了几条经验MySQL 这类数据库容器必须设置资源限制否则一个慢查询就能打爆整台机器。监控告警要覆盖到「宿主机 Load Average」和「MySQL 慢查询数」不能只监控容器的存活状态。补索引这种操作在高峰期也可以执行MySQL 8.0 支持在线 DDL不要等到晚上故障不等人。docker update是应急神器一定要熟练掌握它让你在不停机的情况下先给问题容器戴上「紧箍咒」。这个案例最值钱的一点是如果不是资源限制我连从容排障的机会都没有。故障发生时机器可能在几分钟内就完全不可登录了那时候别说看慢查询连docker stats都执行不了。7. 最后分享几个我从实战中摸出来的 Docker 资源优化细节到文章末尾我不打算写什么总结陈词那没什么用。我把这些年实操中沉淀下来的、最容易被人忽略的几个细节直接列出来你能用上一条就是赚到。第一个细节docker stats显示的 CPU 百分比是相对于宿主机的全部 CPU 核数计算的不是你给容器限制的核数。比如你给容器--cpus4宿主机是 8 核docker stats里显示 400% 时表示容器已用满 4 核。但如果它显示 200%并不代表容器还有大量余量——因为如果容器内应用是单线程的它最多也就用 100%。判断性能瓶颈不要只盯百分比要结合容器内线程数、请求量一起看。第二个细节合理利用--init参数。容器内没有 init 系统时如果一个 Java 应用的子进程变成僵尸进程kill时可能无法正常回收。加--init后 Docker 会注入一个 tini 作为 PID 1 进程负责信号转发和子进程回收。这虽然不是资源限制的直接手段但对容器稳定性影响很大间接减少了容器 CPU 高但是找不到原因的怪问题。第三个细节不是所有 CPU 限流都能通过--cpus解决。在 Kubernetes 中CPU 配额用的是 CFS 的cpu.cfs_period_us和cpu.cfs_quota_us如果你在容器里看到 CPU 被限制的迹象strace 里大量sched_yield或/sys/fs/cgroup/cpu.stat中nr_throttled数值飙升说明容器正在被 CPU 限流。这种 被限流 和CPU 跑满是两回事前者是配额不够用后者是应用失控。看 cgroup 统计是最准确的方式cat /sys/fs/cgroup/cpu.stat如果nr_throttled一直增加说明配额确实不够了这时候不是优化代码而是考虑扩容。第四个细节Docker 默认的存储驱动 overlay2 在磁盘 IO 密集型场景下性能会比直接宿主机挂载方式差一些。如果你对数据库类容器的 IO 性能很敏感建议把数据目录用-v挂载到宿主机而不是放在容器可写层里。容器可写层的写入走 copy-on-write性能开销远高于直接挂载的卷。最后如果你现在还有线上容器完全没有资源限制答应我看完这篇就去加。加完你会发现凌晨三点被告警电话叫醒的概率会低很多。