我正在systemd
为 OSSEC HIDS 编写单元文件。问题是,当systemd
启动服务时,它会立即停止它们。
当我使用以下ExecStart
指令时,一切正常。
ExecStart=/var/ossec/bin/ossec-control start
但是,当我进行以下小改进时,我在SIG 15
启动后收到的 OSSEC 日志中发现。
ExecStart=/bin/sh -c '${DIRECTORY}/bin/ossec-control start'
如果我再做一个小的更改,该服务将SIG 15
在 20 秒后收到。
ExecStart=/bin/sh -c '${DIRECTORY}/bin/ossec-control start && sleep 20'
所以,我想,这会在服务启动后systemd
杀死进程,然后杀死./bin/sh
/bin/sh
OSSEC
我怎么解决这个问题?
答案1
准备协议不匹配
正如维兰德所暗示的,Type
服务的质量很重要。该设置表示什么准备协议systemd 期望服务能够说话。simple
假定服务立即准备就绪。服务forking
在其初始进程分叉子进程然后退出后就被视为准备就绪。dbus
当桌面总线上出现服务器时,服务就被视为准备就绪。等等。
如果您没有在服务单元中声明与服务功能相匹配的就绪协议,那么事情就会出错。就绪协议不匹配会导致服务无法正确启动,或者(更常见)被 systemd(错误)诊断为失败。当服务被视为无法启动时,systemd 确保服务的每个孤立的附加进程作为故障的一部分(从其角度来看),可能一直在运行的服务被终止,以便使服务正确返回到非活动状态。
你正是这样做的。
首先,简单的事情:sh -c
不匹配Type=simple
或Type=forking
。
在simple
协议中,初始过程是是服务流程。但实际上sh -c
包装器运行实际的服务程序作为子进程。首先,会出现问题并停止工作MAINPID
。ExecReload
使用时Type=simple
,必须使用sh -c 'exec …'
或不使用 sh -c
首先。后者往往比一些人想象的更正确。
sh -c
也不匹配Type=forking
。服务的准备协议forking
非常具体。初始进程必须分叉一个子进程,然后退出。 systemd 对此协议应用超时。如果初始进程没有在分配的时间内分叉,则表明未做好准备。如果初始进程没有在指定的时间内退出,那么也将失败。
不必要的恐怖ossec-control
这给我们带来了复杂的东西:脚本ossec-control
。
事实证明这是一个 System 5rc
脚本,可以分叉出 4 到 10 个进程,而这些进程本身也会分叉并退出。它是 System 5 脚本之一rc
,尝试在一个脚本中管理一整套服务器进程,其中包含for
循环、竞争条件、sleep
试图避免它们的任意 s 以及可能使系统处于半启动状态的故障模式,以及所有其他令人恐惧的事情促使人们在二十年前发明了 AIX 系统资源控制器和 daemontools 之类的东西。我们不要忘记二进制目录中隐藏的 shell 脚本,它会动态重写,以实现特殊功能enable
和disable
动词。
所以当你/bin/sh -c '/var/ossec/bin/ossec-control start'
发生的事情是:
- systemd 分叉出它期望的服务进程。
- 这就是外壳,它分叉
ossec-control
。 - 这又会产生 4 到 10 个孙子。
- 孙子们都依次分叉并退出。
- 曾孙全部并行分叉和退出。
ossec-control
退出。- 第一个 shell 退出。
- 服务流程是很棒很棒-孙子们,但因为这种工作方式很匹配两者都不这
forking
也不根据就绪协议simple
,systemd 认为整个服务已失败并将其重新关闭。
在 systemd 下,这些可怕的事情实际上根本没有必要。都没有。
systemd 模板服务单元
相反,写一个非常简单的模板单元:
[单元] 描述=OSSEC HIDS %i 服务器 之后=网络.目标 [服务] 类型=简单 ExecStartPre=/usr/bin/env /var/ossec/bin/%p-%i -t ExecStart=/usr/bin/env /var/ossec/bin/%p-%i -f [安装] WantedBy=多用户.target
将其另存为./etc/systemd/system/[email protected]
各种实际服务是实例化该模板的名称为:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
然后启用和禁用功能直接来自服务管理系统(和红帽错误 752774已修复),无需隐藏 shell 脚本。
systemctl 启用 ossec@dbd ossec@agentlessd ossec@csyslogd ossec@maild ossec@execd ossec@analysisd ossec@logcollector ossec@远程 ossec@syscheckd ossec@monitord
此外,systemd 可以直接了解并跟踪每个实际服务。它可以使用 过滤他们的日志journalctl -u
。它可以知道单个服务何时失败。它知道应该启用和运行哪些服务。
顺便说一句:这里的Type=simple
选项-f
与许多其他情况下一样正确。野外服务很少实际上表明他们已做好准备凭借exit
,这里的这些也不是这样的情况。但这就是forking
类型的含义。野外服务主要只是分叉和退出,因为一些错误的普遍观念认为这就是守护进程应该做的事情。事实上,事实并非如此。自 20 世纪 90 年代以来就没有这样了。是时候追上来了。
进一步阅读
- 乔纳森·德博因·波拉德 (2015)。Unix 守护进程的就绪协议问题。经常给出的答案。
答案2
如果启动服务/应用程序正在维护任何 pid,则保留Type=forking
并提供 PID 文件位置。
[Unit]
Description="Run app on boot"
After=network.target syslog.target auditd.service
[Service]
Type=forking
PIDFile=/var/run/apache2/apache2.pid
ExecStart=/etc/init.d/apache2 start
ExecStop=/etc/init.d/apache2 stop
StandardOutput=syslog
StandardError=syslog
Restart=on-failure
SyslogIdentifier=webappslog
[Install]
WantedBy=multi-user.target
Alias=webapps
答案3
有点相关的是,我有一个 systemd 服务,看起来 systemd 会在 30 秒后“杀死”它。
systemctl status service-name
main process exited, code=exited, status=1/FAILURE
将在 30 秒后显示。
它会“独立”运行良好(就像在终端中手动使用相同的环境)。
原来是
Type=forking
...
Environment=ABC="TRUE"
ExecStart=/path/to/my_script_to_spawn_process.sh
里面my_script_to_spawn_process.sh
正在做
/bin/something > /dev/null 2>&1 &
它有效,但丢弃了输出日志信息(通常它会进入一个文件,或者,如果不是的话,可能会journalctl
)。
将其更改为登录到其他地方,例如/bin/something > /tmp/my_file
然后跟踪/tmp/my_file
揭示了真正的原因。 (切线)你不能像在 bash 中那样使用语法Environment=ABC="true"
,它必须没有引号或键值都在引号内,Environment="ABC=true"
这导致我的进程在大约 30 秒后退出“在其设置阶段”。
答案4
请注意,systemd 的守护进程模型非常简单,并且与许多执行多个 fork、exec'ing 和 setuid'ing 的现有守护进程不兼容。最常见的是守护进程,它们以 root 身份启动进行设置,然后切换到特权较低的 UID 进行例行操作。例如,Pid 文件初始化是在 systemd 下由于权限问题而失败的一件事。有解决方法(不是修复),但记录很差。
JdeBP 的解释值得欢迎,但并不完整,他声称这都是 ossec-control 的错,这根本不是事实。即使是非常琐碎的事情也会有问题,例如在杀死进程时获取未截断的日志行来调试问题或从 systemd 本身获取有意义的错误消息。