Systemd 启动后立即终止服务

Dan*_*lov 16 kill centos systemd

我正在systemd为 OSSEC HIDS编写单元文件。问题是,当systemd启动服务时,它会立即停止它们。

当我使用以下ExecStart指令时,一切正常。

ExecStart=/var/ossec/bin/ossec-control start
Run Code Online (Sandbox Code Playgroud)

但是当我进行以下小的改进时,我在 OSSEC 日志中发现它SIG 15在启动后收到。

ExecStart=/bin/sh -c '${DIRECTORY}/bin/ossec-control start'
Run Code Online (Sandbox Code Playgroud)

如果我再做一个小的更改,服务将SIG 15在 20 秒后收到。

ExecStart=/bin/sh -c '${DIRECTORY}/bin/ossec-control start && sleep 20'
Run Code Online (Sandbox Code Playgroud)

所以,我想,这会在服务启动后systemd杀死/bin/sh进程,/bin/sh然后杀死OSSEC.

我怎么解决这个问题?

Jde*_*eBP 40

准备协议不匹配

正如 Wieland 暗示的那样,Type服务的重要性非常重要。该设置表示systemd 期望服务说话的准备协议。一个simple服务被认为是立即准备。一个forking服务被认为其初始过程派生一个子项,然后退出后要准备好。一个dbus服务被认为当一个服务器出现在桌面总线做好准备。等等。

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

你正是这样做的。

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

simple协议中,初始处理被取为服务进程。但实际上,sh -c包装器将实际服务程序作为子进程运行。对于初学者来说,因此MAINPID出错并ExecReload停止工作。使用时Type=simple,首先必须使用sh -c 'exec …'不使用 sh -c。后者往往比某些人认为的更正确。

sh -c也不匹配Type=forking。服务的就绪协议forking非常具体。初始进程必须fork一个子进程,然后退出。systemd 对此协议应用超时。如果初始进程没有在规定的时间内分叉,则无法做好准备。如果初始进程没有在分配的时间内退出,那也是失败。

不必要的恐怖是 ossec-control

这给我们带来了复杂的东西:那个ossec-control脚本。

事实证明,这是一个 System 5rc脚本,它在 4 到 10 个进程之间分叉,这些进程本身也分叉并退出。它是 System 5rc脚本中的一个,它试图在一个脚本中管理一整套服务器进程,带有for循环、竞争条件、任意sleeps 以试图避免它们,故障模式可以使系统处于半启动状态,以及所有其他让人们在 20 年前发明 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. 该服务工序都很大,great-孙子,不过因为工作的匹配这样既不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=multi-user.target

将此保存为/etc/systemd/system/ossec@.service.

各种实际服务是这个模板的实例,命名为:

  • ossec@dbd.service
  • ossec@agentlessd.service
  • ossec@csyslogd.service
  • ossec@execd.service
  • ossec@agentd.service
  • ossec@logcollector.service
  • ossec@syscheckd.service
  • ossec@maild.service
  • ossec@analysisd.service
  • ossec@remoted.service
  • ossec@monitord.service

然后启用和禁用功能直接来自服务管理系统修复RedHat 错误 752774),不需要隐藏的 shell 脚本。

 systemctl 启用 ossec@dbd ossec@agentlessd ossec@csyslogd ossec@maild ossec@execd ossec@analysisd ossec@logcollector ossec@remoted ossec@syscheckd ossec@monitord

此外,systemd 可以直接了解和跟踪每个实际服务。它可以过滤他们的日志journalctl -u。它可以知道单个服务何时失败。它知道应该启用和运行哪些服务。

顺便说一句:Type=simple这个-f选项在这里就像在许多其他情况下一样。实际上,在野外很少有服务通过 来表示它们已准备就绪exit,这里的这些也不是这种情况。但这就是forking类型的含义。主要的野外服务只是分叉并退出,因为一些错误的智慧观念认为这就是守护程序应该做的事情。事实上,事实并非如此。自 1990 年代以来就没有了。是时候赶上来了。

进一步阅读

  • 很详细的回答!我还建议创建一个“分组”目标,例如 ossec.target,其中 `Requires=` 所有需要的实例,然后在 ossec@.service 中设置 `PartOf=ossec.target`。这将允许通过启动和停止 ossec.target 来启动和停止 ossec。 (2认同)