Linux资源限制详解:ulimit、cgroups、systemd资源控制

发布时间:2026/10/18 14:33:18
Linux资源限制详解:ulimit、cgroups、systemd资源控制
「No space left on device」但磁盘没满「Too many open files」但进程才刚起「Out of memory」但机器内存还有富余——这些诡异的报错十有八九撞上了资源限制。Linux 允许多个进程共享同一台机器为了防止它们互相拖垮内核提供了层层资源隔离机制。这一篇把ulimit、cgroups、systemd三套资源控制手段讲清楚从进程级到容器级一次搞懂这些「限额」从哪来、怎么调。一、三层资源控制的坐标系先建立一张全景图知道每种手段管什么层次手段管什么典型场景进程级ulimit/ rlimit单进程的文件描述符、栈、进程数等调大打开文件数系统级/etc/security/limits.conf登录用户的默认限额全局调整组/容器级cgroups一组进程的 CPU、内存、IO、PID 总量容器、systemd服务级systemd资源指令单个 service 的资源上限生产服务隔离从下往上控制粒度越来越粗、能力越来越强。日常最常打交道的是ulimit和systemd容器运维则绕不开cgroups。二、ulimit进程级的软性枷锁ulimit设置的是进程能使用的资源上限源自内核的 rlimit 机制。它最常用的两个场景是「最大打开文件数」和「最大进程数」。语法格式ulimit [选项] [值]常用参数参数资源常见问题-n最大打开文件描述符数Too many open files-u最大用户进程数fork: retry: Resource temporarily unavailable-s栈大小KB栈溢出-ccore dump 大小生成 core 失败-v虚拟内存大小内存申请失败-l可锁定内存—-a显示所有限制排查第一步查看当前限制ulimit -a预期输出core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 62692 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 -- 关键 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 62692 virtual memory (kbytes, -v) unlimitedopen files默认 1024这对高并发服务Nginx、MySQL、Java远远不够。临时修改当前 shell 有效ulimit -n 65535 ulimit -n65535⚠️ 软限制与硬限制rlimit 分两级级别含义谁能改软限制soft实际生效的当前上限进程自己可以调但不能超过硬限制硬限制hard软限制的天花板只有 root 能提高查看ulimit -Sn # 软限制 ulimit -Hn # 硬限制普通用户可以把软限制往硬限制方向调如ulimit -n 4096但不能超过硬限制也不能提高硬限制。这是为什么有时明明 root 调大了普通用户登录后还是 1024——他的硬限制没变。三、永久修改 ulimit临时改只对当前会话有效要永久生效得改配置。方式一/etc/security/limits.confsudo vim /etc/security/limits.conf添加# 对 * 生效所有用户 * soft nofile 65535 * hard nofile 65535 # 对特定用户 nginx soft nproc 65535 nginx hard nproc 65535四个字段用户名/组、软/硬、资源类型、值。方式二limits.d 目录推荐sudo tee /etc/security/limits.d/99-nofile.conf EOF * soft nofile 65535 * hard nofile 65535 * soft nproc 65535 * hard nproc 65535 EOFlimits.d/下的文件按字典序加载适合分发配置。方式三systemd 服务现代系统重点注意在 systemd 系统Ubuntu 16.04、CentOS 7上通过 SSH 登录时的limits.conf对由 systemd 启动的服务不生效因为服务不是通过登录会话启动的。要改服务的限额得在 unit 文件里设置[Service] LimitNOFILE65535 LimitNPROC65535然后systemctl daemon-reload systemctl restart myservice。⚠️ 排查「明明改了却没生效」的经典坑改的是 limits.conf服务由 systemd 管理→ 要用 unit 的LimitNOFILE。改了没重启服务 / 没重新登录→ limits 在会话/进程启动时读取。nofile受/proc/sys/fs/nr_open上限限制→ 单个进程的 fd 上限不能超过nr_open默认 1048576设太大要同步调它。容器里 limits 被 docker/k8s 覆盖→ 容器进程的 rlimit 由容器运行时如--ulimit决定。user.service影响系统级默认→ 有时要改/etc/systemd/system/user.service.d/里的LimitNOFILE。四、cgroups容器时代的资源隔离ulimit只管单个进程且只覆盖少数几类资源。当你要限制一组进程比如一个容器的所有进程的 CPU、内存、IO 时需要cgroupscontrol groups——把进程分组对组统一限流。cgroups 能控制什么子系统v1控制cpuCPU 时间份额cpuacctCPU 使用统计memory内存上限、OOM 行为blkio块设备 IO 带宽pids进程数上限cpuset绑定 CPU 核与内存节点devices设备访问控制net_cls网络分类标记Docker、Kubernetes 的底层资源隔离全靠 cgroups。你写docker run --cpus1 --memory512m翻译成内核操作就是创建 cgroup 并写上限值。手动体验 cgroups v1# 挂载点多数发行版已自动挂载 ls /sys/fs/cgroup/ # 创建一个组 sudo mkdir /sys/fs/cgroup/memory/mygroup # 限制内存 100MB echo 104857600 | sudo tee /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes # 把某进程加入该组 echo 12345 | sudo tee /sys/fs/cgroup/memory/mygroup/cgroup.procs # 查看该组内存使用 cat /sys/fs/cgroup/memory/mygroup/memory.usage_in_bytes当组内内存超过上限组内进程会被 OOM Killer 干掉在 cgroup 范围内选择。cgroups v1 vs v2特性v1v2层级每个子系统独立挂载统一单层级局限多层级难协调结构清晰主流发行版老系统Ubuntu 22.04、新内核默认v2 的挂载点是统一的/sys/fs/cgroup/文件如memory.max、cpu.max# v2 限制内存 echo 100000000 | sudo tee /sys/fs/cgroup/mygroup/memory.max # v2 限制 CPU100ms 周期内最多用 20ms 0.2 核 echo 20000 100000 | sudo tee /sys/fs/cgroup/mygroup/cpu.max五、systemd 资源控制生产服务首选生产环境里服务基本都托管给systemd。直接在 unit 文件里配资源限制比手写 cgroups 命令干净得多systemd 会自动帮你创建 cgroup。常用资源指令[Service] # CPU最多用 1 个核100%也可写 200% 用两核 CPUQuota100% # CPU 权重相对份额默认 100越大越优先 CPUWeight200 # 内存上限 MemoryMax512M # 内存软限制超了会尽量回收不立刻杀 MemoryHigh400M # 进程数上限 TasksMax100 # 打开文件数 LimitNOFILE65535 # IO 权重0-10000默认 100 IOWeight200完整示例限制一个 Java 服务[Unit] DescriptionMy API Service Afternetwork.target [Service] Typesimple Userapp ExecStart/usr/bin/java -jar /opt/app/api.jar Restarton-failure RestartSec5 # 资源限制 CPUQuota200% # 最多用 2 核 MemoryMax2G # 内存上限 2G MemorySwapMax0 # 禁用 swap TasksMax500 LimitNOFILE65535 [Install] WantedBymulti-user.target应用sudo systemctl daemon-reload sudo systemctl restart my-api验证限制生效# 看服务所属 cgroup systemctl status my-api | grep CGroup # 直接读 cgroup 文件 cat /sys/fs/cgroup/system.slice/my-api.service/memory.max cat /sys/fs/cgroup/system.slice/my-api.service/cpu.max # 看实时内存用量 systemd-cgtopsystemd-cgtop像top但按 cgroup服务聚合资源是排查「哪个服务在吃资源」的利器。六、⚠️ 常见错误只改了ulimit忘了 systemd。这是最常见的坑服务由 systemd 拉起时limits.conf不生效必须配LimitNOFILE。改完没重启服务/重登会话。资源限制在进程启动时读取不重启不生效。nofile超过nr_open报错。ulimit -n 2000000报Operation not permitted先把fs.nr_open调大。容器内调 ulimit 无效。容器进程的限额由运行时决定容器里改不了宿主设置要用docker run --ulimit或 k8s 的securityContext。cgroup v1/v2 混用导致命令失效。新系统是 v2路径和文件名都变了memory.limit_in_bytes→memory.max照抄老教程会失败。MemoryMax设太小导致进程被杀。设了内存上限超了就是 OOM Kill不是慢慢变慢。要留足余量。TasksMax太小导致 fork 失败。TasksMax100的 Java 服务如果线程数超了会Cant fork。线程也算任务数。忽略MemorySwapMax。默认服务可能能用 swap想要「绝不换页」得显式设MemorySwapMax0。七、原理探究从 rlimit 到 cgroup 的演进rlimit进程级的原始限制rlimit是内核最古老的资源限制机制属于进程属性。fork创建子进程时rlimit 会继承所以调整 shell 的 ulimit 能影响它启动的所有子进程。它的局限很明显只管单进程管不了一组。覆盖的资源类型有限fd、栈、进程数、CPU 时间等。是「上限」不是「配额」ulimit -n 65535只表示「最多能开到 65535」不是「分配了 65535」。cgroup面向进程组的配额cgroups 把进程分组group对整组做配额和计量cgroup 一组进程 一组资源控制器 一套层级树它解决了 rlimit 管不了的场景CPU 配额一组进程总共只能用 X% 的 CPU。内存配额一组进程内存总和不超过 Y超了组内 OOM。IO 限速一组进程的磁盘带宽上限。cgroup 的层级hierarchy是一棵树子组继承父组限制父组的额度由子组共享。容器的本质 namespace隔离视图 cgroups限制资源第 110 篇会展开。systemdcgroup 的友好封装systemd 直接以 cgroup 树组织所有服务每个 service、每个 slice 都是一个 cgroup 节点。所以你配MemoryMax、CPUQuotasystemd 自动写对应 cgroup 文件。这也解释了为什么 systemd 服务的限额必须在 unit 里配因为服务的 cgroup 由 systemd 创建登录会话的limits.conf根本管不到它。三者的关系┌──────────────────────────────────────────┐ │ 进程 P │ │ ├─ rlimit自身能开多少 fd、多少进程 │ ← ulimit │ └─ 属于某个 cgroup │ │ ├─ memory.max / cpu.max │ ← cgroup │ └─ 由 systemd 创建和管理 │ ← systemd 指令 └──────────────────────────────────────────┘rlimit 管「进程自己」cgroup 管「进程组配额」systemd 是「配额的自动化管家」。三者不冲突是不同粒度的叠加。八、资源限制排查速查表# 1. 看当前 shell 的限制 ulimit -a # 2. 看某进程的实际限制 cat /proc/1234/limits # 3. 看系统的全局 fd 上限 cat /proc/sys/fs/file-max cat /proc/sys/fs/nr_open # 4. 看当前 fd 使用 cat /proc/sys/fs/file-nr # 5. 看某进程打开的 fd 数 ls /proc/1234/fd | wc -l # 6. 看服务的 cgroup 资源 systemctl status my-api | grep CGroup cat /sys/fs/cgroup/system.slice/my-api.service/memory.max # 7. 按服务聚合看资源 systemd-cgtop找报错根因的对应关系报错大概率原因去哪儿调Too many open filesnofile太小ulimit / systemd LimitNOFILEfork: Resource temporarily unavailablenproc太小ulimit -u / TasksMaxOut of memorycgroup 内存上限MemoryMax / docker --memoryNo space left on devicedf 却有空间inode 满 / fd 满df -i/ ulimit容器进程被杀超 cgroup 限制docker --memory / k8s limits九、收尾资源限制的工程哲学这一篇放在第七篇的最后是因为它是「性能监控」的逆向工程——前面讲的是观察资源用了多少这里讲的是规划资源能用多少。工程上的核心原则是「隔离与配额」多个服务共享机器必须限制各自上限否则一个泄漏的程序能吃光所有内存拖垮全机。容器化时代的最大进步就是把 cgroup 这套资源隔离能力标准化了让「一个容器一个配额」成为默认。生产环境的最佳实践关键服务用 systemd MemoryMax/CPUQuota明确划界容器用limits/requests声明资源把「无限量」变成「有预算」。一句话收尾ulimit管进程、cgroup管容器、systemd管服务。理解了这三层再遇到那些「明明有资源却报错」的怪现象就能顺着/proc/*/limits和/sys/fs/cgroup/一路找到那个卡住你的限制值。