在 AL2023 中使用 systemd 限制进程资源使用量 - Amazon Linux 2023

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

在 AL2023 中使用 systemd 限制进程资源使用量

在 Amazon Linux 2023(AL2023)上,我们推荐使用 systemd 来控制进程或进程组可以使用的资源。使用 systemd 是一个强大且易于使用的替代方案,可以替代手动操作 cgroups 或使用诸如 cpulimit 之类的工具,后者先前仅在第三方 EPEL 存储库中可用于 Amazon Linux。

有关全面信息,请参阅上游 systemd 关于 systemd.resource-control 的文档,或在 AL2023 实例上查看 systemd.resource-control 的 man 手册页。

以下示例将使用 stress-ng CPU 压力测试(来自 stress-ng 程序包)来模拟 CPU 密集型应用程序,并使用 memcached 来模拟内存密集型应用程序。

以下示例涵盖对一次性命令施加 CPU 限制和对服务施加内存限制。systemd 提供的大多数资源约束可以在任何 systemd 运行进程的地方使用,并且可以同时使用多个约束。为了说明目的,以下示例仅限于单一约束。

使用 systemd-run 对运行一次性命令进行资源控制

虽然通常与系统服务关联,但非 root 用户也可使用 systemd 来运行服务、调度定时器或运行一次性进程。在以下示例中,我们将使用 stress-ng 作为示例应用程序。在第一个示例中,我们将使用 systemd-runec2-user 默认账户中运行它;在第二个示例中,我们将对其 CPU 使用量施加限制。

例 在命令行使用 systemd-run 运行进程,不限制资源使用量
  1. 确保已安装 stress-ng 程序包,因为我们将以其为例。

    [ec2-user ~]$ sudo dnf install -y stress-ng
  2. 使用 systemd-run 执行一个 10 秒的 CPU 压力测试,不限制其可使用的 CPU 量。

    [ec2-user ~]$ systemd-run --user --tty --wait --property=CPUAccounting=1 stress-ng --cpu 1 --timeout 10 Running as unit: run-u6.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [339368] setting to a 10 second run per stressor stress-ng: info: [339368] dispatching hogs: 1 cpu stress-ng: info: [339368] successful run completed in 10.00s Finished with result: success Main processes terminated with: code=exited/status=0 Service runtime: 10.068s CPU time consumed: 9.060s

    --user 选项指示 systemd-run 以当前登录用户身份执行命令,--tty 选项表示附加一个 TTY,--wait 表示等待服务完成,--property=CPUAccounting=1 选项指示 systemd-run 记录运行该进程所使用的 CPU 时间。--property 命令行选项可用于传递可在 systemd.unit 配置文件中配置的 systemd-run 设置。

当被指示对 CPU 施加负载时,stress-ng 程序将在您要求的运行时间内使用所有可用的 CPU 时间执行其测试。对于实际应用,可能需要对进程的总运行时间设置限制。在以下示例中,我们将要求 stress-ng 运行的时间长于我们使用 systemd-run 为其设置的最长持续时间限制。

例 在命令行使用 systemd-run 运行进程,将 CPU 使用量限制为 1 秒
  1. 确保已安装 stress-ng 以运行此示例。

  2. LimitCPU 属性等效于 ulimit -t,它将限制该进程允许使用的 CPU 最长时间。在这种情况下,由于我们要求进行 10 秒的压力运行,但将 CPU 使用量限制为 1 秒,该命令将收到 SIGXCPU 信号并失败。

    [ec2-user ~]$ systemd-run --user --tty --wait --property=CPUAccounting=1 --property=LimitCPU=1 stress-ng --cpu 1 --timeout 10 Running as unit: run-u12.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [340349] setting to a 10 second run per stressor stress-ng: info: [340349] dispatching hogs: 1 cpu stress-ng: fail: [340349] cpu instance 0 corrupted bogo-ops counter, 1370 vs 0 stress-ng: fail: [340349] cpu instance 0 hash error in bogo-ops counter and run flag, 3250129726 vs 0 stress-ng: fail: [340349] metrics-check: stressor metrics corrupted, data is compromised stress-ng: info: [340349] unsuccessful run completed in 1.14s Finished with result: exit-code Main processes terminated with: code=exited/status=2 Service runtime: 1.201s CPU time consumed: 1.008s

更常见的情况是,您可能希望限制特定进程可消耗的 CPU 时间百分比。在以下示例中,我们将限制 stress-ng 可消耗的 CPU 时间百分比。对于实际的服务,可能需要限制后台进程可消耗的 CPU 时间最大百分比,以便为处理用户请求的进程空出资源。

例 使用 systemd-run 将进程限制在单颗 CPU 10% 的 CPU 时间
  1. 确保已安装 stress-ng 以运行此示例。

  2. 我们将使用 CPUQuota 属性来告知 systemd-run 约束即将运行的命令的 CPU 使用量。我们不限制进程的运行时间,只限制其可以使用的 CPU 量。

    [ec2-user ~]$ systemd-run --user --tty --wait --property=CPUAccounting=1 --property=CPUQuota=10% stress-ng --cpu 1 --timeout 10 Running as unit: run-u13.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [340664] setting to a 10 second run per stressor stress-ng: info: [340664] dispatching hogs: 1 cpu stress-ng: info: [340664] successful run completed in 10.08s Finished with result: success Main processes terminated with: code=exited/status=0 Service runtime: 10.140s CPU time consumed: 1.014s

    请注意 CPU 统计信息显示,虽然服务运行了 10 秒,但它仅消耗了 1 秒的实际 CPU 时间。

有多种方法可以配置 systemd 来限制 CPU、内存、网络和 IO 的资源使用量。有关完整文档,请参阅上游 systemd 关于 systemd.resource-control 的文档,或在 AL2023 实例上查看 systemd.resource-control 的 man 手册页。

在底层,systemd 利用 Linux 内核的特性(如 cgroups)来实现这些限制,同时避免了您手动配置的需要。Linux 内核关于 cgroup-v2 的文档包含了关于 cgroups 工作的详尽细节。

systemd 服务中进行资源控制

有多个参数可以添加到 systemd 服务的 [Service] 部分,以控制系统资源使用量。这些参数包括硬限制和软限制。关于每个选项的确切行为,请参考上游 systemd 关于 systemd.resource-control 的文档,或在 AL2023 实例上查看 systemd.resource-control 的 man 手册页。

常用的限制包括:使用 MemoryHigh 指定内存使用的节流限制,使用 MemoryMax 设置硬性上限(一旦达到,将调用 OOM Killer),以及使用 CPUQuota(如前一节所示)。也可以配置权重和优先级,而不是固定的数值。

例 使用 systemd 为服务设置内存使用限制

在本示例中,我们将为 memcached(一个简单的键值缓存)设置内存使用硬限制,并展示 OOM Killer 是如何针对该服务而非整个系统被调用的。

  1. 首先,我们需要安装此示例所需的程序包。

    [ec2-user ~]$ sudo dnf install -y memcached libmemcached-awesome-tools
  2. 启用 memcached.service 然后启动服务,使 memcached 运行。

    [ec2-user ~]$ sudo systemctl enable memcached.service Created symlink /etc/systemd/system/multi-user.target.wants/memcached.service → /usr/lib/systemd/system/memcached.service. [ec2-user ~]$ sudo systemctl start memcached.service
  3. 检查 memcached.service 是否正在运行。

    [ec2-user ~]$ sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Active: active (running) since Fri 2025-01-31 22:36:42 UTC; 1s ago Main PID: 356294 (memcached) Tasks: 10 (limit: 18907) Memory: 1.8M CPU: 20ms CGroup: /system.slice/memcached.service └─356294 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Jan 31 22:35:36 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  4. 既然 memcached 已安装并运行,我们可以通过向缓存中插入一些随机数据来观察其功能

    /etc/sysconfig/memcached 中,CACHESIZE 变量默认设置为 64,即 64 兆字节。通过向缓存中插入超过最大缓存大小的数据,我们可以看到缓存被填满,并且使用 memcached-tool 驱逐了一些项目,同时 memcached.service 使用了大约 64MB 的内存。

    [ec2-user ~]$ for i in $(seq 1 150); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done [ec2-user ~]$ memcached-tool localhost display # Item_Size Max_age Pages Count Full? Evicted Evict_Time OOM 2 120B 0s 1 0 no 0 0 0 39 512.0K 4s 63 126 yes 24 2 0 [ec2-user ~]$ sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Active: active (running) since Fri 2025-01-31 22:36:42 UTC; 7min ago Main PID: 356294 (memcached) Tasks: 10 (limit: 18907) Memory: 66.7M CPU: 203ms CGroup: /system.slice/memcached.service └─356294 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Jan 31 22:36:42 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  5. 使用 MemoryMax 属性为 memcached.service 设置一个硬限制,如果达到此限制,将调用 OOM Killer。可以通过将其他选项添加到覆盖文件来为服务设置它们。这可以通过直接编辑 /etc/systemd/system/memcached.service.d/override.conf 文件或使用 systemctledit 命令交互式完成。

    [ec2-user ~]$ sudo systemctl edit memcached.service

    将以下内容添加到覆盖文件中,为服务设置 32MB 内存的硬限制。

    [Service] MemoryMax=32M
  6. 告诉 systemd 重新加载其配置

    [ec2-user ~]$ sudo systemctl daemon-reload
  7. 观察 memcached.service 现在正以 32MB 的内存限制运行。

    [ec2-user ~]$ sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /etc/systemd/system/memcached.service.d └─override.conf Active: active (running) since Fri 2025-01-31 23:09:13 UTC; 49s ago Main PID: 358423 (memcached) Tasks: 10 (limit: 18907) Memory: 1.8M (max: 32.0M available: 30.1M) CPU: 25ms CGroup: /system.slice/memcached.service └─358423 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Jan 31 23:09:13 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  8. 在使用少于 32MB 内存时,服务将正常运行,我们可以通过向缓存加载少于 32MB 的随机数据,然后检查服务状态来验证。

    [ec2-user ~]$ for i in $(seq 1 30); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done
    [ec2-user ~]$ sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /etc/systemd/system/memcached.service.d └─override.conf Active: active (running) since Fri 2025-01-31 23:14:48 UTC; 3s ago Main PID: 359492 (memcached) Tasks: 10 (limit: 18907) Memory: 18.2M (max: 32.0M available: 13.7M) CPU: 42ms CGroup: /system.slice/memcached.service └─359492 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Jan 31 23:14:48 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  9. 我们现在可以通过尝试使用默认 memcached 配置所允许的完整 64MB 缓存,使 memcached 使用超过 32MB 的内存。

    [ec2-user ~]$ for i in $(seq 1 150); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done

    您将观察到在上述命令的某个时刻,会出现连接到 memcached 服务器的错误。这是因为 OOM Killer 由于我们施加的限制而终止了该进程。系统的其余部分将正常运行,OOM Killer 不会考虑其他进程,因为我们只限制了 memcached.service

    [ec2-user ~]$ sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /etc/systemd/system/memcached.service.d └─override.conf Active: failed (Result: oom-kill) since Fri 2025-01-31 23:20:28 UTC; 2s ago Duration: 2.901s Process: 360130 ExecStart=/usr/bin/memcached -p ${PORT} -u ${USER} -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS (code=killed, signal=KILL) Main PID: 360130 (code=killed, signal=KILL) CPU: 94ms Jan 31 23:20:25 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon. Jan 31 23:20:28 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: memcached.service: A process of this unit has been killed by the OOM killer. Jan 31 23:20:28 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: memcached.service: Main process exited, code=killed, status=9/KILL Jan 31 23:20:28 ip-1-2-3-4.us-west-2.compute.internal systemd[1]: memcached.service: Failed with result 'oom-kill'.