在一台 Apache 服务器上托管多个虚拟主机是常见的部署方式,但默认情况下所有虚拟主机共享同一组 Apache 工作进程,操作系统并不会区分哪个请求来自哪个站点。一旦某个站点出现异常流量或者跑了一段低效脚本,CPU、内存被瞬间吃光,其余站点跟着一起遭殃。要解决这个问题,光靠 Apache 自身的配置是不够的,需要在操作系统层面引入资源隔离机制,也就是 cgroup。

cgroup 是什么,为什么它能隔离虚拟主机资源
cgroup 全称 control groups,是 Linux 内核从 2.6.24 版本开始引入的资源控制机制。它的核心思想是把一组进程放进一个"控制组",然后针对这个组设置资源上限,内核调度器和内存管理器会依据这些上限来分配资源。对 Apache 来说,只要能让不同虚拟主机的请求由不同进程池处理,再把每个进程池放进不同的 cgroup,就实现了站点级别的资源隔离。
cgroup 有两个主要版本。cgroup v1 中各个子系统(cpu、memory、blkio 等)各自挂载,管理灵活但配置分散;cgroup v2 使用统一的层级结构,所有资源控制器都挂在同一个树上,配置更简洁。CentOS 8、Ubuntu 21.10 之后的发行版默认都切换到了 v2。可以通过下面的命令确认当前系统使用的版本:
# 查看 cgroup 挂载信息,出现 cgroup2 字样说明是 v2 mount | grep cgroup # 或者查看内核启动参数 cat /proc/filesystems | grep cgroup
需要提醒的是,v1 和 v2 的接口文件名完全不同。比如限制 CPU 在 v1 中是 cpu.shares,而在 v2 中是 cpu.weight。网上很多教程混用两种写法,照抄之前一定要先确认自己系统的版本,否则写入接口文件时会直接报错。
让每个虚拟主机使用独立的进程池
cgroup 管理的是进程,而 Apache 默认的 prefork 或 worker 模式下,所有虚拟主机共用同一批工作进程,这种情况下根本没有办法按站点隔离。解决办法是在 httpd.conf 中为每个虚拟主机配置独立的 MPM 进程池,让特定站点的请求只由专属进程处理。
以 event MPM 为例,先定义两个进程池,再分别指定给不同的虚拟主机:
<IfModule mpm_event_module>
# 站点 A 的进程池,进程名前缀 sitea_
<Proxy "unix:/run/sitea.sock|http://sitea-pool/">
ProxySet min=2 max=10
</Proxy>
</IfModule>
<VirtualHost *:80>
ServerName www.a-ipipp.com
# 通过 ProxyPass 把请求交给独立进程池
ProxyPass "/" "http://sitea-pool/"
</VirtualHost>
除了这种代理方式,更直接的做法是使用 mod_cgid 配合不同的 Listen 端口,或者干脆为每个站点部署独立的 httpd 实例,每个实例监听不同端口、使用独立配置文件和运行用户。多实例方案虽然管理成本略高,但进程归属最清晰,后续绑定 cgroup 时不需要任何额外判断,特别适合站点数量不多的场景。
创建 cgroup 并绑定 Apache 进程
准备好独立进程之后,接下来创建控制组。生产环境推荐使用 systemd 来管理,避免手工创建的 cgroup 在重启后丢失。编辑或新建一个 slice 配置文件:
# /etc/systemd/system/sitea.slice # 重新加载并启动 systemctl daemon-reload systemctl start sitea.slice
对应的 slice 单元内容如下,限制该组最多使用 2 个 CPU 核心、4GB 内存:
[Unit] Description=Resource limit for site A [Slice] CPUQuota=200% MemoryMax=4G CPUWeight=100
然后把 Apache 站点 A 的进程移入这个组。最可靠的方式是让对应的 httpd 实例由 systemd 直接管理,在其 service 文件中加入 Slice=sitea.slice,这样进程启动时自动归组,不存在遗漏。如果进程已经存在,也可以手工迁移:
# 找到站点 A 的进程号 pgrep -f "sitea" # 写入 cgroup.procs 完成迁移(v2 示例) echo 12345 > /sys/fs/cgroup/sitea.slice/cgroup.procs # 验证进程归属 cat /proc/12345/cgroup
迁移完成后,无论站点 A 的脚本怎么失控,它的 CPU 使用率会被压制在 2 核以内,内存超过 4G 时内核会触发 OOM 杀掉越界进程,其他站点完全不受影响。这就是资源隔离的实际效果。
限制磁盘 IO 带宽并验证隔离效果
CPU 和内存之外,磁盘 IO 也常常是争抢的重点,尤其是有站点频繁写日志或做备份时。cgroup v2 中通过 io.max 接口限制带宽,systemd 单元里对应 IODeviceBandwidth 选项:
[Slice] # 限制对 nvme0n1 设备的写入带宽为 50MB/s IODeviceBandwidth=/dev/nvme0n1 50M
配置完成后务必做压力验证,不能想当然认为配置生效了。可以在站点 A 上跑一个死循环脚本模拟 CPU 打满,同时观察站点 B 的响应时间:
# 在站点 A 触发负载 ab -n 100000 -c 200 http://www.a-ipipp.com/heavy.php # 同时观察各 cgroup 的实时资源消耗 systemd-cgtop # 查看站点 A 是否被限制在配额内 cat /sys/fs/cgroup/sitea.slice/cpu.stat
如果看到 sitea.slice 的 CPU 使用稳定在 200% 封顶,而站点 B 的延迟曲线基本平稳,说明隔离已经生效。还要注意定期检查内存事件统计 memory.events,如果 oom_kill 计数持续增长,说明该站点的配额定得太紧,需要结合实际负载重新评估。资源隔离不是一次性配置,配额本身应该随着站点流量变化持续调整,才能真正在稳定性和资源利用率之间找到平衡点。
Apache虚拟主机cgroup资源隔离Linux资源限制修改时间:2026-09-10 21:59:03