

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

# 在 AL2023 中使用 systemd 限制进程资源使用量
<a name="resource-limiting-systemd"></a>

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

 有关全面信息，请参阅上游 `systemd` 关于 [systemd.resource-control](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html) 的文档，或在 AL2023 实例上查看 `systemd.resource-control` 的 man 手册页。

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

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

## 使用 `systemd-run` 对运行一次性命令进行资源控制
<a name="resource-limiting-systemd-interactive"></a>

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

**Example 在命令行使用 `systemd-run` 运行进程，不限制资源使用量**  

1.  确保已安装 `stress-ng` 程序包，因为我们将以其为例。

   ```
   [ec2-user ~]$ sudo dnf install -y stress-ng
   ```

1.  使用 `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` 为其设置的最长持续时间限制。

**Example 在命令行使用 `systemd-run` 运行进程，将 CPU 使用量限制为 1 秒**  

1. 确保已安装 `stress-ng` 以运行此示例。

1.  `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 时间最大百分比，以便为处理用户请求的进程空出资源。

**Example 使用 `systemd-run` 将进程限制在单颗 CPU 10% 的 CPU 时间**  

1. 确保已安装 `stress-ng` 以运行此示例。

1.  我们将使用 `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](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html) 的文档，或在 AL2023 实例上查看 `systemd.resource-control` 的 man 手册页。

 在底层，`systemd` 利用 Linux 内核的特性（如 `cgroups`）来实现这些限制，同时避免了您手动配置的需要。[Linux 内核关于 `cgroup-v2`](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) 的文档包含了关于 `cgroups` 工作的详尽细节。

## 在 `systemd` 服务中进行资源控制
<a name="resource-limiting-systemd-service"></a>

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

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

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

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

   ```
   [ec2-user ~]$ sudo dnf install -y memcached libmemcached-awesome-tools
   ```

1.  启用 `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
   ```

1.  检查 `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.
   ```

1.  既然 `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.
   ```

1.  使用 `MemoryMax` 属性为 `memcached.service` 设置一个硬限制，如果达到此限制，将调用 OOM Killer。可以通过将其他选项添加到覆盖文件来为服务设置它们。这可以通过直接编辑 `/etc/systemd/system/memcached.service.d/override.conf` 文件或使用 `systemctl` 的 `edit` 命令交互式完成。

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

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

   ```
   [Service]
   MemoryMax=32M
   ```

1. 告诉 `systemd` 重新加载其配置

   ```
   [ec2-user ~]$ sudo systemctl daemon-reload
   ```

1. 观察 `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.
   ```

1.  在使用少于 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.
   ```

1.  我们现在可以通过尝试使用默认 `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'.
   ```