准备协议不匹配

准备协议不匹配

我正在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/shOSSEC

我怎么解决这个问题?

答案1

准备协议不匹配

正如维兰德所暗示的,Type服务的质量很重要。该设置表示什么准备协议systemd 期望服务能够说话。simple假定服务立即准备就绪。服务forking在其初始进程分叉子进程然后退出后就被视为准备就绪。dbus当桌面总线上出现服务器时,服务就被视为准备就绪。等等。

如果您没有在服务单元中声明与服务功能相匹配的就绪协议,那么事情就会出错。就绪协议不匹配会导致服务无法正确启动,或者(更常见)被 systemd(错误)诊断为失败。当服务被视为无法启动时,systemd 确保服务的每个孤立的附加进程作为故障的一部分(从其角度来看),可能一直在运行的服务被终止,以便使服务正确返回到非活动状态。

你正是这样做的。

首先,简单的事情:sh -c不匹配Type=simpleType=forking

simple协议中,初始过程是服务流程。但实际上sh -c包装器运行实际的服务程序作为子进程。首先,会出现问题并停止工作MAINPIDExecReload使用时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 脚本,它会动态重写,以实现特殊功能enabledisable动词。

所以当你/bin/sh -c '/var/ossec/bin/ossec-control start'发生的事情是:

  1. systemd 分叉出它期望的服务进程。
  2. 这就是外壳,它分叉ossec-control
  3. 这又会产生 4 到 10 个孙子。
  4. 孙子们都依次分叉并退出。
  5. 曾孙全部并行分叉和退出。
  6. ossec-control退出。
  7. 第一个 shell 退出。
  8. 服务流程是很棒很棒-孙子们,但因为这种工作方式很匹配两者都不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]

各种实际服务是实例化该模板的名称为:

然后启用和禁用功能直接来自服务管理系统(和红帽错误 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 年代以来就没有这样了。是时候追上来了。

进一步阅读

答案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-namemain 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 本身获取有意义的错误消息。

相关内容