如何为“systemd”目标动态创建单元列表?

如何为“systemd”目标动态创建单元列表?

新手systemd用户在这里;如果这个问题看起来“太基础”并且最好在 StackOverflow 生态系统的其他地方询问,我深表歉意......

我正在将init12 年前左右编写的一些古老的服务文件转换为systemd.其中一些使用了相当令人讨厌的技巧来弄清楚如何配置一批服务;我其实不是一个有经验的系统程序员,我喜欢快速而肮脏的黑客来让事情作为概念验证工作......它们总是可以在以后得到改进。

也就是说,这就是我想要实现的目标:我正在托管一个在线虚拟世界平台(不,它不是 Minecraft...),该平台运行相同二进制文件的多个实例,但具有不同的参数(想想分片)。如果我知道要提前启动多少个实例(即在启动时),或者至少知道它们将采用哪些参数集,或者即使我有一个实际列表(可能提前生成),那么这将很容易做到该信息。

实际上,会发生以下情况:有一个特殊的“配置”目录(至少是一个提前知道!)它有几个子目录(数量是不是提前知道!),每个都以特定实例命名。名字重要的是(即我不能通过使用连续名称来使事情变得更容易,这将使一些技巧更容易应用,即使假设我事先知道有多少个子目录 - 但我不知道!)。假设子目录名称是城市名称,例如 , NewYork, Chicago, LAKansasCity

因此,概括地说,您可以将整个目录树视为如下所示:

game-root-folder 
|
\___ bin
|     \___________ myapp (the actual game engine for each instance)
|     \___________ myapp-single-instance.sh (explained below)
|
\___ config-folder
       \__________ NewYork
       |              \______ config.ini
       |          
       \__________ Chicago
       |              \______ config.ini
       |          
       \__________ LA
       |              \______ config.ini
       |          
       \__________ KansasCity
       :              \______ config.ini

(抱歉,我不擅长 ASCII 图表...)

假设每个坐标config.ini只是相关城市的一组 GPS 坐标(实际上,它不是很多比这更复杂,但重要的是这些文件中有足够的项目——这些项目可能不是由人类编写的,而是从“管理应用程序”自动生成的——以使其很难对环境变量中的所有内容进行编码),即各个城市独特且不同的东西,并且被硬编码到该城市的启动配置中。

在实践中,实际布局要复杂得多,但这应该足以解释我的问题。

什么时候不是使用systemd,而不是从 shell 手动启动所有内容,任务很简单 - 启动每个实例的命令将如下所示:

# Call this script with `myapp-single-instance.sh start <city name>`
case "$1" in
    start)
        cd /full/path/to/game-root-folder/bin
        /usr/bin/screen -S $2 -d -m -l myapp \
          --config=/path/to/game-root-folder/config-folder/$2/config.ini
        ;;
    stop)
        # discussed below
        ;;
    *)
        # show usage
        exit 1
        ;;
esac
        

systemd配置文件只有一个附加脚本,该脚本将列出该配置目录的内容并提取子目录的名称,为每个城市名称启动启动/停止脚本,即如下所示:

cd /full/path/to/game-root-folder/bin
for CITY_NAME in `ls /path/to/game-root-folder/config-folder`
do
    myapp-single-instance.sh start $CITY_NAME
done

作为旁注,停止在这些情况下,人们可以这样做:

/usr/bin/screen -S $CITY_NAME -X eval 'stuff "quit"\015'

(假设这quit是关闭该实例的控制台命令)或者,如果上述内容由于某种原因不起作用(实例处于无限循环中,不接受命令):

/usr/bin/screen -X -S $CITY_NAME kill

在这种情况下,kill它是对自身的命令screen,优雅地终止自身及其内部的任何内容。SIGKILL当然,最后一个选项是发送一个(也通过当前现有的脚本完成)。

正如你所看到的,如果有人想添加新的例如,对于一个新城市,他们所需要做的就是创建一个新的子目录,例如,Boston并使用单行启动/停止脚本启动它;如果需要,可以手动停止或启动各个实例;新的实例可能无论如何都需要手动干预(将它们config.ini放置到位),因此可以期望有额外的命令来设置新的实例——只要每个实例基本上独立于其他实例,并且不直接与它们交互,就不需要向实例发出任何信号现存的添加时的实例新的一个(或删除现有的一个)。同样,实际上,有一个“主”实例监控服务器,它在幕后执行一些内务处理,但为了这个问题的目的,我们暂时忽略它的存在。

上面的例子对于适应init场景来说很简单。

现在,在 下该怎么做systemd?嗯,两层方法似乎非常适合使用systemd 目标,从一个启动多个实例模板。这是我天真的尝试,松散地基于这个答案(第 3 点),但也适用于其他方面:

; [email protected]

[Unit]
Description=Game city name %I
After=syslog.target
After=network.target
Requires=mariadb.service mysqld.service
PartOf=myapp.target

[Service]
Type=forking
User=myappuser
Group=myappgroup
WorkingDirectory=/full/path/to/game-root-folder/bin
ExecStart=myapp-single-instance.sh start "%I"
ExecStop=myapp-single-instance.sh stop "%I"
ExecStop=/bin/sleep 5
KillSignal=SIGCONT
Restart=always
RestartSec=30s
Environment=USER=myappuser HOME=/full/path/to/game-root-folder/bin
RemainAfterExit=false
SuccessExitStatus=1

[Install]
WantedBy=multi-user.target

到目前为止,一切顺利,我基本上只是将现有脚本封装在systemd配置中。

但现在要如何处理目标呢?

; myapp.target
[Unit]
Description=Launch all game instances
[email protected] [email protected] [email protected]

[Install]
WantedBy=multi-user.target

哎呀,我忘了芝加哥!看,这就是手动编辑配置时发生的情况......

基本上,我想要一个动态的这样做的方法,就像我使用令人惊叹的`ls /path/to/game-root-folder/config-folder`命令所做的那样 - 如此简单,但如此强大!然而,从我读到的有关该主题的内容来看,不可能将 shell 命令的输出通过管道传输到该Wants=...行 — 事实上,唯一的行是run shell 命令就是这些Exec...=命令(出于显而易见的原因)。我的运气真不好!

其他人也遇到过类似的问题。解决方案是将选择要运行的实例的逻辑推送到环境变量,然后可以“按摩”并发送到可执行文件本身。我对如何在我的案例中实现这样的解决方案感到有点困惑,但它似乎不符合我的要求。事实上,我觉得很奇怪的是,这样一个“明显”的用例——从目标动态调用服务单元(其中“动态”,在这种情况下,意味着“单个可执行文件的一组服务单元,大小未知,其中每个元素可能具有不同范围的参数,彼此不同,并且事先不知道') - 这不是一个“微不足道”的场景;也许我没有完全理解如何systemd解决这个问题?

我见过两个例子,其中解决方案是包含不同的文件(使用systemd覆盖机制,即将文件放入其中/etc/systemd/myapp.service.d/),并开始systemd搜索这些附加配置;或者,列出所有可能从脚本调用的实例.target,将它们存储在文件中,甚至存储在环境变量中。每当有人添加一个额外的例如,后台定时进程将检查每个实例的子目录,并返回一个包含所收集数据的每一位的环境变量,或者将其写入一个特殊文件(人们会假设该文件将在众所周知的地点共享) ),并从中读取以找出如何在引导时启动每个实例。此类解决方案意味着运行后台守护程序,该守护程序检查哪些文件可用并在通过重新加载调用目标单元之前刷新此类文件。然而,当尝试更改Wants=..Require=..行时,这些解决方案似乎不起作用;它主要是Exec...=可以通过这种方式解决的参数(尽管我看到了一些相互冲突的指令,据称某些systemd版本的代码将采用“动态”方式填充字段......)。

另请注意,我想保留启动/停止/重新加载实例的能力单独地,以及让它们在启动时全部启动(或者例如在强制重新启动之前优雅地停止)。

考虑到上述所有因素,您会推荐什么解决方案?

预先感谢并继续在这个社区中做出出色的工作!

干杯,

  • 格温

答案1

如果部分是服务的一部分[单元]那么目标不需要想要; systemd 会自动知道目标启用了哪些模板。

systemctl enable myapp@Chicago(等)应该与描述中的内容一起使用。

从目标中删除 Wants=然后……systemctl enable myappsystemctl start myapp发生什么?

相关内容