Linux系统资源动态调度容器防止资源抢占
多容器部署在同一台云服务器上,资源互相抢占往往导致关键业务响应变慢甚至卡死。
解决思路不是停掉容器,而是给每个容器设定资源上限和权重,再根据负载动态调整调度参数。
本文以 Docker 和 systemd 环境为例,介绍一套零基础可执行的动态调度配置方案,涉及 cgroup v2、CPU 权重、内存锁定和动态调整命令,完成后可显著改善资源抢占问题。
先确定你的环境是否支持动态调度
动态调度依赖两项底层能力:一是内核 cgroup 版本,Docker 20.10 以上默认开启 cgroup v2,可通过 cat /sys/fs/cgroup/cgroup.controllers 查看,如果输出中有 cpu、memory,说明支持;
二是 systemd 版本在 245 以上,可通过 systemd --version 确认。
检查完这两项后,建议先把 Docker 的 cgroup 驱动切换为 systemd,否则 systemd 和 Docker 各自管理 cgroup 会互相覆盖配置。
操作方式是在 /etc/docker/daemon.json 中写入以下内容:
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
保存后执行 systemctl restart docker。
给容器设置资源配额并动态调整
第一步,设置初始限制。 创建容器时,用 --cpus 限定容器最多使用的 CPU 核数,用 --memory 限定内存上限,用 --memory-swap 限定 swap 总量,避免容器无限占用宿主机内存。
例如:
docker run -d --name my-app \
--cpus="1.5" \
--memory="1g" \
--memory-swap="1g" \
--cpu-shares=2048 \
nginx:latest
--cpu-shares 是相对权重,默认值是 1024,设为 2048 表示该容器在 CPU 繁忙时获得两倍于默认容器的调度机会。
第二步,动态调整运行中的容器。 当业务高峰来临时,希望临时增加某个容器的 CPU 核数,不需要重启容器,直接执行:
docker update --cpus="2.0" --cpu-shares=3072 my-app
如果发现某容器内存占用异常,也可以直接收紧:
docker update --memory="512m" --memory-swap="512m" my-app
需要注意,内存限制只能调小后才能调大,实际变更以容器重启后的最终配置为准。
第三步,使用 systemd 对容器服务做动态调度。 如果你的容器是通过 systemd 管理,可以编辑服务单元文件,在 [Service] 段加入 CPUWeight 项:
[Service]
CPUWeight=50
MemoryMax=1G
CPUWeight 是 cgroup v2 的权重参数,取值范围为 1-10000,默认 100,数值越高,获得 CPU 时间片越多。
修改后执行 systemctl daemon-reload && systemctl restart 容器服务名 即可生效。
自动检测负载并动态调整调度参数
手动调整适合低频运维,若要实现真正的动态调度,用脚本定期检查 CPU 占用并自动调整更实用。
以下脚本每 60 秒读取容器 CPU 使用率,超过 80% 就提高其 CPU 份额,低于 20% 就降低:
#!/bin/bash
CONTAINER="my-app"
MAX_SHARES=4096
MIN_SHARES=512
while true; do
USAGE=$(docker stats --no-stream --format "{{.CPUPerc}}" $CONTAINER | sed 's/%//' | cut -d. -f1)
CURRENT=$(docker inspect --format '{{.HostConfig.CpuShares}}' $CONTAINER)
if [ -n "$USAGE" ] && [ "$USAGE" -gt 80 ]; then
NEW=$((CURRENT * 2))
if [ $NEW -le $MAX_SHARES ]; then
docker update --cpu-shares=$NEW $CONTAINER
echo "$(date): 负载过高,cpu-shares 调整至 $NEW"
fi
elif [ -n "$USAGE" ] && [ "$USAGE" -lt 20 ]; then
NEW=$((CURRENT / 2))
if [ $NEW -ge $MIN_SHARES ]; then
docker update --cpu-shares=$NEW $CONTAINER
echo "$(date): 负载较低,cpu-shares 调整至 $NEW"
fi
fi
sleep 60
done
注意,这个脚本只对长期运行的生产容器适用,且不建议在数据库容器上自动调低权重,避免数据库响应质量下降。
避坑说明:这四个问题最容易误导人
第一,--cpus 和 --cpu-shares 不是同一层面的限制。 --cpus 是硬限制,超出后容器被 throttle;--cpu-shares 只是相对权重,只在多容器争抢时发挥作用。
只设置 --cpu-shares 而不设置 --cpus,无法防止 CPU 密集型容器独占整机。
第二,
内存限制不要随意搭配 swap。 --memory-swap 必须大于等于 --memory,
如果设置为相等数值,
相当于容器无法使用 swap,
能有效避免容器 swap 写入拖垮磁盘 IO,
但对内存需求突增的容器容易触发 OOM。
第三,cgroup v1 和 v2 的权重参数不通用。 在 cgroup v1 中,Docker 的 --cpu-shares 映射到 cpu.shares;
而在 cgroup v2 中,systemd 的 CPUWeight 是独立于 Docker 之外的另一套配置。
混用时要确认你操作的是不是同一个 cgroup 路径。
第四,
修改资源限制后要通过 docker inspect 验证。 执行 docker update 后立即通过 docker stats 观察不一定能看到变化,
因为 CPU 占用数据本身是不断波动的,
正确方式是查看配置项是否真的写入成功。
效果验证怎么做才准确
配置完成后,建议用压力测试工具验证效果。
先在宿主机安装 stress-ng:
yum install -y stress-ng # CentOS/RHEL
apt install -y stress-ng # Debian/Ubuntu
然后对一个测试容器施加高压力:
docker run -d --name stress-test --cpus="1" --cpu-shares=512 stress-ng --cpu 4 --timeout 120s
同时运行另一个普通容器,再用 systemd-cgtop 观察两个容器的 CPU 分配比例:
systemd-cgtop
正常情况下,
权重较高的容器会获得更多 CPU 时间片,
而 --cpus="1" 容器即使压力很大,
CPU 占用率也会被限制在 100% 左右,
不会拖垮宿主机其他进程。
常见问题解答
Q:设置了 CPU 权重后,业务容器仍然被其他容器抢占资源,是怎么回事?
先检查宿主机 CPU 是否存在超线程争抢,然后确认两个容器是否都在同一个 cgroup 层级。
建议先给核心业务容器设置 --cpus 硬限制,再用 --cpu-shares 调整权重,两者配合才能达到防抢占效果。
Q:docker update 动态调整后,容器重启配置会丢失吗?
不会丢失。docker update 修改的是容器的 HostConfig,重启容器后配置仍保留。
但如果你是用 docker run 重新创建容器而不是重启,需要重新指定资源参数。
Q:Web 应用容器内存经常接近上限,怎么判断是正常占用还是内存泄漏?
可以结合 docker stats --no-stream 连续观察,如果内存占用持续上升且不回落,且容器内没有缓存类进程,基本可以判断为内存泄漏。
建议先调小内存限制让容器 OOM 重启,再结合日志定位。
Q:有没有更简单的工具能直观看到容器资源抢占情况?
Docker 自带的 docker stats 最直接,可以实时看到每个容器的 CPU、内存、网络 IO。
需要看历史趋势可以用 cAdvisor,更详细的内核级信息可以查看 /sys/fs/cgroup/<容器ID>/cpu.stat 中的 throttled_time 字段,该值持续增长说明容器确实被系统限制过 CPU。
如果你正在处理 Linux 系统资源动态调度容器防止资源抢占,建议先按本文步骤完整执行,再根据自己的环境做微调;
遇到异常时优先回看避坑和高频问题部分。
选云服务器时尽量确认宿主机内核开启了 cgroup v2,目前主流云厂商的新一代云服务器均已默认支持,相关配置建议以你实际购买的服务商控制台和官方文档为准。