我有一个 Linux Web 服务器(Debian,以防万一),它是 Apache + PHP-FPM。每个 ApacheVirtualHost
使用专用的 PHP-FPM 池(通过专用的 unix 套接字),该池在专用的系统用户下运行。
例如,假设我有 example.com VirtualHost
。然后,在VirtualHost
Apache 配置中,我有类似的内容
ProxyPassMatch ^/(.*\.php(/.*)?)$ unix:/run/php/php7.3-fpm-examplecom.sock|fcgi://127.0.0.1:9000/home/examplecom/htdocs/www.example.com/
并且,/etc/php/7.3/fpm/pool.d/
我有 PHP-FPM 池的相应配置文件,即examplecom.conf
,其中包含
[examplecom]
user = examplecom
listen = /run/php/php7.3-fpm-examplecom.sock
; and other things...
经常发生的情况是,某些网站被黑客攻击(忘记更新、安装了无耻的不安全插件,凡是你能想到的)并开始占用可用的服务器资源:在我的例子中,硬盘 I/O 带宽是最常见的。
为了避免对同一服务器上的所有网站进行 DoS,我想限制每个网站的硬盘 I/O 带宽,以便被黑的网站单独崩溃,而不会影响所有其他网站。
如何限制每个网站的硬盘 I/O 带宽?好吧,我想我可以限制每个用户,因为每个用户VirtualHost
都有一个专用用户。我该怎么做?好吧,Google 建议我使用 cgroup。
但是我找不到任何有关如何添加用户,而不是进程 ID,到控制组。
我在寻找错误的东西吗?或者我搜索了错误的关键词?我不知道,但问题是:如何在 Linux 下使用 cgroups 限制每个用户的硬盘 I/O 带宽?
答案1
谢谢LL3对我的问题的评论,我发现我必须求助于自定义脚本。所以就是这样。它查找 PHP-FPM 进程,获取相应的运行用户和 PID,创建cgroups
并将 PID 插入正确的cgroups
.
然后我配置了一个crontab
条目,以便脚本每小时运行一次,这对于我的情况来说已经足够了:Linux 自动将子进程放入父进程中cgroup
,因此该脚本实际上只需要在每次 PHP-FPM 重新启动后运行一次(例如在启动时或添加新网站/用户时)。每小时运行一次,这有助于避免忘记在到期时手动运行它。
该脚本假设该/cgroups
目录已随文件系统一起挂载cgroup2
,因此您应该提前进行安排。您可以添加一个条目/etc/fstab
,如果您的内核cgroups
默认安装版本 1,则配置/etc/default/grub
为禁用 v1 并重新启动(提示:添加cgroup_no_v1=all
到GRUB_CMDLINE_LINUX_DEFAULT
然后运行update-grub && reboot
)。
现在它已经在生产环境中运行了一个月,并且似乎有效。我非常确定至少在我放置此服务器的两台服务器中的一台中存在受到损害的网站,并且它在限制损害方面做得很好(不相关的网站偶尔只会关闭几秒钟)。
请注意,自从这个脚本的第一个版本以来,我必须编辑它以使其也监视 CPU 和内存,因为单独监视磁盘 I/O 是不够的。
#!/bin/bash
DEVICE="254:0" # see /proc/partitions, this is /dev/vda in my case
WRITE_IOLIMIT=2097152 # 2MB/s, choose this as a fraction of your disk max write rate, I tested mine with `dd if=/dev/vda of=/testmaxspeed.dd bs=1M count=10000 status=progress`
READ_IOLIMIT=4194304 # 4MB/s, choose this as a fraction of your disk max read rate, I tested mine with `dd if=/dev/vda of=/dev/null bs=1M count=10000 status=progress`
function cleanup
{
rm -f "$PLISTFILE" >/dev/null 2>&1
}
trap cleanup INT TERM EXIT
PLISTFILE=$(mktemp)
ps axo user:40,pid,comm | grep php-fpm | grep -v 'grep php-fpm' | grep -v '^root ' | tr -s ' ' | cut -d' ' -f 1-2 | sort | uniq > $PLISTFILE
cat $PLISTFILE | while read ROW ; do
USERNAME=$(echo "$ROW" | cut -d' ' -f1)
mkdir /cgroups/$USERNAME 2>/dev/null
echo "$DEVICE rbps=$READ_IOLIMIT" > /cgroups/$USERNAME/io.max
echo "$DEVICE wbps=$WRITE_IOLIMIT" > /cgroups/$USERNAME/io.max
echo 402653184 > /cgroups/$USERNAME/memory.high # throttle when user is above 384MB of RAM
echo 603979776 > /cgroups/$USERNAME/memory.max # OOM-Kill when user is above 576MB of RAM
done
echo "+io" > /cgroups/cgroup.subtree_control
echo "+cpu" > /cgroups/cgroup.subtree_control
echo "+memory" > /cgroups/cgroup.subtree_control
cat $PLISTFILE | while read ROW ; do
USERNAME=$(echo "$ROW" | cut -d' ' -f1)
PROCESSID=$(echo "$ROW" | cut -d' ' -f2)
echo $PROCESSID > /cgroups/$USERNAME/cgroup.procs
done