有没有办法在 systemd 服务单元文件中动态分配环境变量?
我们有一台有 4 个 GPU 的机器,我们希望每个 GPU 启动某个服务的多个实例。例如:
- gpu_service@1:1.service
- gpu_service@2:1.service
- gpu_service@3:1.service
- gpu_service@4:1.service
- gpu_service@1:2.service
- gpu_service@2:2.service
- gpu_service@3:2.service
- gpu_service@4:2.service
- 令人作呕
因此 1:1、2:1 等实际上是服务单元文件中的 %i。
为了将服务绑定到特定的 GPU,服务可执行文件会检查某个环境变量,例如:
USE_GPU=4
有没有办法可以在服务单元文件中获取 %i 并通过某些(shell)函数运行它来导出 GPU 编号,然后我可以相应地设置 USE_GPU 环境变量?
最重要的是,我不想/etc/systemd/system/gpu_service@x:y.service/local.conf
为了启动更多实例而编写多个文件的麻烦。
答案1
如果您小心的话,您可以在实例服务文件中合并一个小的 bash 脚本序列作为 exec 命令。例如
ExecStart=/bin/bash -c 'v=%i; USE_GPU=$${v%:*} exec /bin/mycommand'
$$
字符串中的 将会在传递给 bash 的结果中变成单个,$
但更重要的是将停止${...}
被 systemd 插值。 (早期版本的 systemd 没有记录 的使用$$
,所以我不知道当时是否支持它)。
答案2
没有内置方式。您需要在服务开始之前执行这些操作。一种方法是将其放入环境文件中。
[Service]
# Note you need to escape percentage sign
ExecStartPre=/bin/sh -c "my_awesome_parser %%i > /run/gpu_service_%i"
EnvironmentFile=/run/gpu_service_%i
ExecStart=...
答案3
看起来你确实可以在 systemd 单元文件中设置环境变量......
根据评论者的建议,解决方案如下:
在 systemd 单元中使用环境变量
环境指令
systemd 有一个环境指令,它为执行的进程设置环境变量。它需要一个以空格分隔的变量分配列表。可以多次指定此选项,在这种情况下,将设置所有列出的变量。如果同一个变量被设置两次,则后面的设置将覆盖前面的设置。如果将空字符串分配给此选项,则会重置环境变量列表,所有先前的分配都无效。环境指令用于内置 Container Linux systemd 单元,例如在 etcd2 和 flannel 中。
通过下面的示例,您可以将 etcd2 守护进程配置为使用加密。只需为 etcd2.service 创建
/etc/systemd/system/etcd2.service.d/30-certificates.conf
drop-in:[Service] # Client Env Vars Environment=ETCD_CA_FILE=/path/to/CA.pem Environment=ETCD_CERT_FILE=/path/to/server.crt Environment=ETCD_KEY_FILE=/path/to/server.key # Peer Env Vars Environment=ETCD_PEER_CA_FILE=/path/to/CA.pem Environment=ETCD_PEER_CERT_FILE=/path/to/peers.crt Environment=ETCD_PEER_KEY_FILE=/path/to/peers.key
然后运行
sudo systemctl daemon-reload
并将sudo systemctl restart etcd2.service
新环境应用到 etcd2 守护进程。
引用的文本取自以下 URL: https://coreos.com/os/docs/latest/using-environment-variables-in-systemd-units.html
答案4
使用EnvironmentFile
指向带有变量的 env 文件的指令。该文件是ExecStartPre
在实际服务启动之前通过命令创建的。您的变量将可以在ExecStart
指令中使用 访问${GPU_COUNT}
。
[Unit]
Description=Dynamic variables in systemd units
[Service]
Type=simple
EnvironmentFile=-/tmp/gpu.env
ExecStartPre=sh -c 'printf "%%s\n" GPU_COUNT=$( (...) ) > /tmp/gpu.env'
ExecStart=(...)
ExecStartPost=sh -c 'rm /tmp/gpu.env'
您必须在文件路径前加上 前缀-
,这使得 systemd 在初始检查期间忽略该文件不存在的事实。稍后,该ExecStartPre
命令创建 env 文件。在另一个单位状态文件位置将再次被评估(仍在之前ExecStart
),如果存在,systemd 将加载变量。man systemd.exec
解释如下:
The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated. This means you can generate these files in one unit state, and read it with this option in the next. The files are read from the file system of the service manager, before any file system changes like bind mounts take place).