Docker容器本身不会自动帮你控制资源占用,如果不对CPU、内存、磁盘IO做显式约束,单个容器可能拖垮整台宿主机。很多人把容量规划理解成上线前设置一下内存和CPU限制,其实忽略了运行中的数据变化、镜像层膨胀以及副本调度带来的额外开销。真正可落地的容量规划需要把监控、限制策略和扩缩容动作串起来,而不是等到监控告警才临时加资源。

一、Docker资源占用的底层模型
Docker主要依靠Linux cgroup和namespace来实现资源限制与隔离。cgroup负责限制和统计资源使用,namespace负责隔离进程视图。CPU限制涉及--cpus只是更友好的封装,底层会换算成周期和配额。内存限制对应memory.limit_in_bytes,但容易忽略的是page cache也会计入容器内存使用。如果程序频繁读写文件,即使实际堆内存不高,容器也可能因为page cache膨胀而触发OOM。
磁盘占用需要分成镜像层和容器可写层来看。基础镜像和构建层是只读的,多个容器可以共享;可写层每个容器独立,日志文件、临时文件、包安装内容都写在这里。如果不限制日志大小,Docker默认的JSON日志文件可能把宿主机磁盘写满。网络方面Docker原生没有提供严格的带宽限制,通常需要借助tc或第三方CNI插件。理解这些底层机制,才能知道哪些资源可以动态调整,哪些需要重建容器。
docker run -d --name web --cpus=1.5 --memory=1g --memory-swap=2g --pids-limit=200 nginx:1.25
上面命令中--memory-swap=2g表示1g物理内存加1g swap,如果设置成-1则不限制swap,可能让容器无节制使用swap造成性能骤降。实际生产环境建议显式设置swap额度,或者将--memory-swap设置成与--memory相同以禁用swap。
二、用监控数据反推容量基线
容量规划不能靠拍脑袋,需要采集至少一周的基线数据,覆盖工作日和周末。docker stats可以实时查看CPU、内存、网络和磁盘IO,但它属于采样值,不适合长期趋势分析。cAdvisor和Prometheus能够存储时序数据,适合做容量趋势和峰值分析。日常排查中可以用docker stats --no-stream快速观察单机容器状态。
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"输出中的MemUsage格式是“已用/限制”,如果限制为0说明容器没有设置内存上限。还可以用docker inspect获取更精确的配置与状态,例如查看某个容器的内存上限:
docker inspect -f '{{.HostConfig.Memory}}' web规划单实例配额时,建议按峰值用量乘以1.3到1.5的系数预留缓冲。宿主机容量则需要把系统预留部分考虑进去,通常预留总内存的10%左右给内核和基础服务。CPU属于可压缩资源,短暂超卖不会立刻导致进程被杀;内存是不可压缩资源,OOM会直接终止进程,所以内存规划要更加保守。
三、单机环境下的垂直扩缩容
垂直扩缩容是调整单个容器的CPU或内存上限。docker update可以在容器运行中修改部分资源限制,比如--cpus、--memory、--memory-swap。但不是所有参数都能在线修改,设备访问、重启策略等仍然需要重建容器。值得特别注意的是,降低内存限制可能导致容器立刻触发OOM kill,操作前应确认当前内存占用不会贴近新的限制值。
docker update --memory 1g --cpus 2 web
docker inspect -f '{{.HostConfig.Memory}}' web磁盘扩容会更麻烦,因为镜像层和容器可写层在创建时已经确定,不能直接在线增加。如果业务会持续落盘数据,应该提前挂载volume或tmpfs,把数据与容器生命周期解耦。无状态服务做垂直扩缩容通常比较安全;有状态服务如数据库,更推荐使用宿主目录挂载,避免容器删除时数据一并丢失。调整之后需要再用docker stats观察一段时间,确认没有频繁OOM或CPU节流。
另外还要注意宿主机内核版本和cgroup版本。老内核下的cgroup v1存在memory.kmem统计不准确的问题,可能导致内存使用显示异常。新环境建议直接使用cgroup v2,资源统计和限制会更加稳定,也有利于后续做容器级别的内存回收。
四、Swarm集群的水平扩缩容
当单台宿主机资源接近瓶颈时,水平扩展是更合理的选择。Docker Swarm提供docker service scale命令,可以直接修改服务的副本数。集群模式会通过调度器把新副本分散到不同节点,但前提是各节点的资源标签和可用容量要保持一致,否则可能出现调度不均,部分节点过载而其他节点空闲。
docker service create --name web --replicas 2 --publish 80:80 nginx:1.25 docker service scale web=5 docker service update --replicas 3 web
扩缩容过程中,Swarm默认采用滚动更新,逐个启动新副本,旧的副本再停止。如果服务是无状态类型,可以快速扩展;如果服务有初始化任务或健康检查,需要通过--update-parallelism和--update-delay控制节奏。缩容时Swarm会先停止多余副本,但不会自动迁移数据,所以有状态副本必须使用共享存储卷。
水平扩容还会带来负载均衡和会话保持的隐藏成本。Swarm的ingress网络会把请求分发到各副本,如果应用依赖本地会话,需要改造为Redis等外部存储。扩容之后数据库连接池、文件句柄数等中间环节也要同步调整,否则新副本可能因为连接数耗尽反而拖慢整体响应。
五、扩缩容避坑与策略建议
先明确瓶颈类型很重要:CPU高通常可以水平扩展,内存高则需要判断是否存在内存泄漏。如果内存随请求增长但不释放,优先排查代码,而不是无脑加内存。OOMKilled状态可以通过docker inspect -f '{{.State.OOMKilled}}'检查,必要时配合HEALTHCHECK实现自动重启。
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f https://www.xiadaxia.com.cn/ || exit 1
建议采用“垂直优先,水平兜底”的策略:在业务早期,垂直调整资源配额更快;当单节点资源利用率超过70%或无法满足峰值时,再启动水平扩展。同时要定期做容量评审,每次发布前用压测数据更新容量基线。以下几个点需要特别留意:
- 为每个容器设置内存和CPU硬限制,避免出现无限制容器
- 限制日志大小,使用json-file日志驱动并配置max-size
- 容器化数据库时预留更大缓冲,并密切监控磁盘IO
- 定期清理dangling镜像和已退出的容器,释放磁盘空间
容量规划不是一次性动作,而是与CI/CD、监控告警联动的持续过程。把资源配额写进Compose或Swarm编排文件,既方便版本管理,也便于审计和回滚。只有让容器在资源可控的前提下稳定运行,后续的自动扩缩容才有可靠基础。

