在容器化环境中,某个服务突然响应变慢、CPU 使用率触顶,却又没有明显的死循环,往往与 CPU 限额或调度节流有关。Docker 通过 Linux cgroups 控制容器可使用的处理器资源;如果配置不合理,即使宿主机仍有空闲核心,容器内进程也可能因配额耗尽而被暂停。本文从限额配置、指标观察到异常定位,给出一套可直接执行的排查方法。🔍
一、先理解三类 CPU 控制方式
--cpus 是最直观的硬性限额。例如设置 --cpus=1.5,表示容器最多获得相当于 1.5 个逻辑 CPU 的计算时间,它不代表容器只能运行在某两个固定核心上。
--cpu-period 与 --cpu-quota 用于精细控制配额。两者的关系可以理解为“可用 CPU 核数约等于 quota ÷ period”。例如周期为 100000 微秒、配额为 50000 微秒,就相当于限制为 0.5 个 CPU。通常优先使用更易读的 --cpus,只有在需要精确设置时再直接调整 period 和 quota。
--cpu-shares 只是竞争时的相对权重,并非硬上限。宿主机 CPU 空闲时,低权重容器仍可能使用较多处理器资源;只有多个容器同时争抢 CPU 时,权重差异才会影响时间分配。因此,不能用 shares 代替明确的 CPU 限额。参数含义可参考 Docker 资源约束官方文档。
二、正确配置容器 CPU 限额
限制新容器最多使用一个半核,可执行:
docker run -d --name app --cpus=1.5 your-image
需要将容器固定到指定逻辑核心时,可使用:
docker run -d --name app --cpuset-cpus="0,2" your-image
--cpuset-cpus 控制的是允许运行的位置,而 --cpus 控制的是总计算时间。两者可以同时使用,但应确保绑定的核心数量足以承载配置的配额。例如只绑定一个核心时,即使设置 --cpus=2,容器也无法真正并行使用两个核心。⚙️
对于正在运行的容器,可以动态调整限额:
docker update --cpus=2 app
修改后使用以下命令核对实际配置,避免只看启动脚本而忽略运行状态:
docker inspect app --format '{{json .HostConfig}}'
三、识别“CPU 很忙”还是“被节流”
第一步运行 docker stats app,观察 CPU 百分比是否长时间接近配置上限。需要注意,多核环境中的百分比可能超过 100%,因此不要简单地将 100% 等同于整台宿主机满载,还要结合宿主机逻辑 CPU 数和容器限额判断。
第二步进入容器检查具体进程:
docker exec app ps -eo pid,ppid,comm,%cpu --sort=-%cpu
如果单个进程持续占用配额,应继续检查业务线程、批处理任务、垃圾回收、压缩加密操作或异常重试。若容器内进程 CPU 占用看似不高,但请求延迟周期性升高,则更要关注 cgroup 节流计数。
cgroup v2 的检查方法
先通过 docker inspect 获取容器进程 PID,再定位它所属的 cgroup:
PID=$(docker inspect -f '{{.State.Pid}}' app)
cat /proc/$PID/cgroup
进入对应 cgroup 目录后查看 cpu.stat。其中常见字段包括 nr_periods、nr_throttled 和 throttled_usec。如果 nr_throttled 与 throttled_usec 在业务高峰期间快速增长,说明容器频繁耗尽 CPU 配额,被内核暂停到后续周期。cgroup v2 接口说明可查阅 来源链接 内核文档。
cgroup v1 的检查方法
旧环境通常读取 cpu.stat,并结合 cpu.cfs_quota_us 与 cpu.cfs_period_us 判断限额。如果 quota 为负值,通常表示未设置 CFS 硬配额;如果 quota 明显偏小,则可能是部署参数、编排配置或运行时更新造成的限制。
四、异常节流的常见原因
- 限额低于实际需求:平均 CPU 看似正常,但短时并发会迅速耗尽配额,形成排队和尾延迟。
- 线程数设置过大:程序按照宿主机核心数创建大量工作线程,却只能使用少量 CPU 配额,导致频繁竞争和上下文切换。
- 核心绑定不合理:多个高负载容器被固定到相同核心,而其他核心处于空闲状态。
- 宿主机整体争用:即使容器没有触发硬配额,也可能受到其他容器或宿主机进程影响。
- 编排配置覆盖:镜像启动参数、Compose 文件、部署平台和后续的 docker update 可能产生不一致。
五、推荐的排查顺序
- 使用 docker inspect 确认 cpus、quota、period、shares 和 cpuset 的实际值。
- 通过 docker stats 对照容器 CPU 使用率与配置上限。
- 检查宿主机的整体负载、每核利用率和运行队列,排除全局资源争抢。
- 读取 cgroup 的 cpu.stat,连续采样并比较节流计数的增量。
- 定位容器内高 CPU 进程及线程,结合应用日志判断是否存在重试、死循环或突发任务。
- 在压测或监控依据充分时逐步提高限额,不要直接取消所有限制。
总结
Docker CPU 问题不能只看一个百分比下结论。排查时应同时确认“配置了多少配额”“进程实际消耗多少 CPU”“cgroup 是否发生节流”以及“宿主机是否存在争用”。✅ 生产环境中,建议使用 --cpus 设置清晰上限,以 shares 调整竞争优先级,并持续监控节流次数、节流时长和业务延迟。只有把容器指标、内核计数与应用行为结合起来,才能区分合理限速、配置过紧和真正的进程异常。