Agent通常以常驻进程的形式部署在服务器或容器里,用来采集指标、执行任务或接收控制面下发的指令。当多个Agent共享同一个宿主机内核时,某一个Agent出现内存泄漏、CPU空转或文件系统误操作,都可能把整台节点拖垮。Linux内核提供的Cgroups与Namespace恰好覆盖资源隔离的两个核心诉求:Cgroups给进程设置CPU、内存、块设备IO等硬边界,Namespace则把进程装进独立的进程树、挂载空间和网络栈。两者结合之后,Agent既不能超额占用宿主资源,也难以看到或干扰同节点的其他进程。

一、先厘清Cgroups与Namespace的分工
很多人会把Cgroups和Namespace混在一起,但其实它们解决的是两类不同问题。Cgroups是资源控制器,负责回答进程最多能用多少CPU、多少内存、多少IO;Namespace是视图隔离机制,负责回答进程能看见哪些PID、哪些挂载点、哪块网卡。举个例子,一个Agent进程可能被限制只能使用256MB内存,这是Cgroups在起作用;它同时只能看到自己命名空间内的进程号,宿主机上的其他进程完全不显示,这是PID Namespace在起作用。
可以用一条命令查看当前进程所属的cgroup路径和命名空间。在cgroup v2系统中,/proc/self/cgroup会给出层级信息,/proc/self/ns目录则列出各类命名空间的inode编号。这些编号相同,说明两个进程处于同一个命名空间;编号不同,说明它们已经被隔开。理解这个区别后,后续配置就不容易混淆。
cat /proc/self/cgroup ls -l /proc/self/ns
从实际运维角度看,只做Namespace而不做Cgroups,进程虽然看不见其他进程,但仍可能吃满CPU和内存;只做Cgroups而不做Namespace,进程的资源受限了,却仍能枚举宿主机进程列表、访问宿主挂载点。因此完整的Agent隔离方案通常必须同时启用两者。
二、用Cgroups给Agent设置硬限制
在cgroup v2环境中,资源限制通过向/sys/fs/cgroup下的目录写入文件完成。先创建一个独立的cgroup目录,例如agent.slice,然后把限制值写入memory.max、cpu.max等文件,最后把Agent进程的PID写入cgroup.procs,整个进程组就会立即受到限制。这里以限制256MB内存、1个CPU核心的50%配额为例。
mkdir -p /sys/fs/cgroup/agent.slice echo "256M" > /sys/fs/cgroup/agent.slice/memory.max echo "50000 100000" > /sys/fs/cgroup/agent.slice/cpu.max echo "8:0 rbps=1048576 wbps=1048576" > /sys/fs/cgroup/agent.slice/io.max echo $$ > /sys/fs/cgroup/agent.slice/cgroup.procs
memory.max接受字节数或带单位的字符串,256M表示最大内存占用为256MB。cpu.max的格式是quota period,默认周期为100000微秒,配额50000表示每个周期最多运行50毫秒,也就是最多占用0.5个CPU核心。io.max可以按设备主次编号限制读写带宽,示例中限制了8:0设备的读写速度各为1MB每秒。把当前shell进程写入cgroup.procs后,由该shell启动的所有子进程都会继承这个cgroup。
如果系统仍然使用cgroup v1,则需要分别操作cpu.cfs_quota_us、cpu.cfs_period_us和memory.limit_in_bytes等文件。cgroup v1的控制器相互独立,配置起来更繁琐,也容易出现目录层级不一致的问题。判断cgroup版本可以查看/sys/fs/cgroup/cgroup.controllers是否存在,存在就是v2。新部署的服务器建议统一使用cgroup v2,它的文件接口更简单,压力统计也集中。
除了静态配额,还可以通过memory.high设置软限制,让进程在内存接近上限时主动回收,而不是立刻触发OOM。对于Agent这种需要长期稳定运行的进程,软限制可以在不杀死进程的前提下降低内存压力,配合memory.oom.group可以在整组内存超限时一并处理。
三、用Namespace隔离Agent的可见范围
Namespace的作用是给进程一个被裁剪过的系统视图。PID Namespace让进程从1开始编号,看起来像是系统里的第一个进程;Mount Namespace让进程拥有独立的挂载点列表,可以挂载自己的/proc、/tmp或配置文件目录;UTS Namespace隔离主机名;Network Namespace隔离网络接口、路由表和防火墙规则;IPC Namespace隔离信号量和共享内存。对Agent而言,至少应该启用PID、Mount和UTS Namespace,如果有网络隔离需求,再启用Network Namespace。
使用unshare命令可以快速启动一个带命名空间的进程。下面这条命令会把后续执行的Agent关进独立的PID、Mount、UTS、IPC和Network Namespace。进入PID Namespace后需要重新挂载proc,否则ps等工具读到的仍然是宿主机的进程表。
unshare --pid --fork --mount --uts --ipc --net \ sh -c 'mount -t proc proc /proc && exec /usr/local/bin/agent'
执行完成后,Agent看到的主机名、进程列表和挂载点都和其他进程隔离。需要注意的是,创建Network Namespace需要相应权限,而且新的网络命名空间默认只有回环接口,需要手动创建veth pair或桥接设备才能访问外部网络。对于不需要独立网络的Agent,可以去掉--net参数,复用宿主机网络栈,但这样会损失网络视图隔离能力。
Mount Namespace在Agent隔离中非常关键,因为它可以隐藏宿主机上的敏感目录。比如把Agent的根目录切换到一个只读镜像,或者把宿主机的/var/run/docker.sock从Agent的挂载空间中移除,即使Agent内部存在漏洞,也无法轻易访问Docker守护进程。结合PID Namespace重新挂载proc后,ps命令只会显示Agent自己及其子进程,调试时更像一个独立的小环境。
四、用systemd或容器运行时简化配置
手动创建cgroup目录和unshare命令适合学习原理,但在生产环境中更推荐使用systemd或容器运行时统一管理。systemd-run可以把一个命令直接放进带有资源限制的临时scope或service中,无需手动操作/sys/fs/cgroup目录,也能避免与systemd自身管理的cgroup层级发生冲突。下面示例启动Agent并限制内存256MB、CPU配额50%、最大任务数64。
systemd-run --scope \ -p MemoryMax=256M \ -p CPUQuota=50% \ -p TasksMax=64 \ /usr/local/bin/agent
systemd会自动创建对应的cgroup目录,并在命令退出后回收。对于需要长期运行的Agent,可以把--scope换成--unit=agent.service,交给systemd管理生命周期。CPUQuota的百分比对应整个CPU资源池,100%表示占用一个核心,50%表示半个核心。MemoryMax是硬限制,超过后会触发OOM处理,如果需要软限制可以用MemoryHigh。
如果Agent本身已经打包成容器镜像,Docker或Podman等工具底层同样依赖Cgroups和Namespace。下面这条Docker命令通过参数直接生成隔离环境,省去了手工配置。
docker run --rm \ --cpus 1.0 \ --memory 256m \ --pids-limit 64 \ --name agent \ agent-image:latest
容器运行时会创建独立的PID、Mount、Network、UTS、IPC等命名空间,并根据参数向cgroup写入限制。使用容器化的好处是隔离配置和镜像打包绑定在一起,部署到不同节点时行为一致,适合控制面统一管理大量Agent的场景。不过容器仍然共享宿主机内核,隔离强度不如虚拟机,安全敏感环境还需配合Seccomp、Capabilities和只读根文件系统。
五、实践中的避坑点与监控方法
一个常见的坑是在cgroup v2系统上手工创建/sys/fs/cgroup下的目录,然后发现配置被systemd覆盖。systemd会按照自己的层级重新组织cgroup,如果自定义目录没有正确纳入systemd的slice结构,可能被移动或清理。推荐用systemd-run或systemctl set-property来修改资源限制,不要直接mkdir。如果必须手工创建,需要先确认系统未启用systemd统一层级,或者把目录放在systemd已管理的某个scope下。
另一个容易忽略的问题是PID Namespace中的proc挂载。很多工程师使用unshare进入新PID Namespace后忘记重新挂载proc,导致ps命令一直显示宿主机进程,误以为隔离失效。正确做法是挂载proc时使用-t proc proc /proc,且该操作需要Mount Namespace的支持。对于容器运行时,这个步骤已经内置,但自己用unshare调试时需要格外注意。
资源隔离配置完成后,监控同样重要。可以周期性读取cgroup目录下的memory.events和cpu.stat,观察是否出现OOM事件、节流时间或内存峰值。下面两条命令可以快速获取指标。
cat /sys/fs/cgroup/agent.slice/memory.events cat /sys/fs/cgroup/agent.slice/cpu.stat
如果memory.events中的oom_kill计数持续增长,说明限制过紧,需要提高memory.max或优化Agent内存使用。如果cpu.stat中的throttled_usec增长明显,说明CPU配额不足,可以适当提高cpu.max的配额值。把这类信息接入现有监控系统后,就能在不进入容器的情况下判断Agent是否逼近资源边界。