我正在致力于将一些旧的 System V 类型服务迁移到 RHEL 7/8 上的“真正的”systemd 服务。我在 Redhat 7、SLES 12 和 SLES 15 上运行得很好。但是当我尝试在 RHEL 8 上运行服务时,我发现系统现在要求我的应用程序的 pidfile 位于该/run
目录中......或者至少它希望它在那里。 (我们的应用程序通常在应用程序的安装目录中写入 pidfile。)
我发现我可以通过更改应用程序启动脚本并写入/run
目录来完成此任务。所以成功了!但现在有一个问题我似乎无法克服。我需要正在运行的服务在特定用户(我的登录 ID)的上下文中运行,而不是以 root 身份运行。我所有的研究告诉我,我只需要User=
在文件中指定.service
即可完成此任务。但是每当我将这一行添加到文件中时,我的服务启动就会失败。看起来在 pidfile 写入目录时失败了/run
。 pidfile无法写入,进程退出。
我的服务文件:
Description={removed}
After=remote-fs.target
After=network-online.target
Wants=remote-fs.target
Wants=network-online.target
[Service]
Type=forking
Restart=no
User=myid
TimeoutSec=5min
IgnoreSIGPIPE=no
KillMode=none
GuessMainPID=no
RemainAfterExit=no
SuccessExitStatus=5 6 255
PIDFile=/run/adidmn.pid
ExecStart=<fullPathToScript> start
ExecStop=<fullPathToScript> stop
[Install]
WantedBy=multi-user.target
我的启动脚本定义了守护进程如何在各种平台上运行,设置一些环境变量,然后调用启动实际进程的 shell 脚本。当使用 sudo 权限调用时,被调用的守护进程脚本可以直接运行。我的用户 ID 在 sudoers 列表中,但是当然,运行进程的所有者也是 root。
如果 .service 文件中没有“ User=
”属性,启动服务就没有问题。我总是在我自己的用户 ID 下以 sudo 身份运行 systemctl 命令。我的最终目标是看到实际运行的进程在我的用户 ID 下运行,而不是作为 root 运行。我认为添加User=<myid>
到文件中.service
可以完成此任务,但是这一行会使服务启动失败。
该用户存在(我自己的用户 ID)并且也在 sudoers 列表中。 /run 由 root 所有,当使用 sudo 将 pid 文件写入此处时,pid 文件的所有者是 root。我尝试su <userid>
在启动脚本中使用 for 命令,但这导致守护进程根本无法启动。
基本上,我在 SuSE 和 Redhat 之间有不同的行为。我的守护进程(多年来)在程序的安装路径中提供了一个 pid 文件。使用su <userid> -c
在用户“a”的上下文中启动进程的命令来启动该进程。 pid 文件放置在应用程序的安装路径中,并由用户“a”拥有。 SuSE Linux:完全没问题。然而,在 Redhat 上,当进程运行时,systemd 会抱怨:
新的主PID 26979不属于服务,并且PID文件不属于root所有。拒绝。
为什么有区别?我想要完成的事情“可行”吗?或者 root 现在是否需要拥有所有 systemd 服务进程?
答案1
您遇到的问题是 System V 初始化脚本期望以 root 身份运行,这是它们如何工作的“规范”的一部分,并且它们通常会有需要 root 才能完成的步骤。
在您的情况下,它是关于运行su <userid> -c ...
以实际开始以非 root 用户身份运行,但是如果您已经在该用户下运行。 System V init 脚本通常会使用诸如su
或runas
或类似的工具来切换到非 root 用户,但这些工具通常并不完全适合该目的(su
最初是为了从交互式 shell 运行,并将与 PAM 集成,而 PAM 不会这里没有多大意义。)
更糟糕的是,某些 System V init 脚本不会处理用户的更改,并且最终会不必要地以 root 身份运行守护程序,因为这在 System V init 脚本中感觉更“自然”。在我看来,这是 System V init 脚本最严重的问题之一,它们使得在这里很容易做错误的事情,而很难做正确的事情。
如果您想保持与 System V 初始化脚本的兼容性,您可以可以只需从 systemd 以 root 身份运行它们,因为这是调用此类脚本时的“协议”。事实上,如果您希望保持兼容性,您甚至不需要发送 systemd 单元,因为 systemd 将能够通过以下方式自行生成一个 systemd 单元:systemd-sysv-生成器。生成的单元看起来很像您提供的单元,只是它将由 root 运行。
如果你做想要发布一个 systemd 服务单元(我建议您这样做),那么您应该认真考虑发布一个使用Type=simple
,而不是 的单元forking
。
唯一的先决条件是您能够在前台启动守护进程,许多守护进程可以通过传递额外的命令行标志或通过某些配置来完成此操作。 (实际上需要相当大的较少的努力这样做,因此如果您的守护程序当前不支持该功能并且您可以控制源,请考虑添加或请求该功能。)
那时,您所需要做的就是从ExecStart=
指令在前台调用守护进程。您不需要ExecStop=
,只要您的守护进程在收到终止它的信号后正确终止即可。
您不再需要 pidfile!由于 systemd 在前台启动守护进程,因此知道守护进程的主 PID 是什么。这是巨大的,因为 pidfiles 经常/通常被错误地实现(它们应该只在守护进程准备好服务时创建),所以摆脱这个要求是一个相当大的事情。
如果您需要在启动主进程之前导出特定的环境变量,您可以使用 systemdEnvironment=
或EnvironmentFile=
设置这些变量。 (只要将变量设置为固定值而不是动态生成的值,这就可以正常工作。)如果需要在守护程序启动之前执行步骤,可以使用ExecStartPre=
.
如果您需要更大的灵活性(例如,有条件地设置变量或运行命令,或者将变量设置为动态值等),那么您应该将启动包装在 shell 脚本(或 Python、Perl 等)中,并在ExecStart=
。在执行主守护进程之前,该脚本将设置并导出所需的任何变量,运行任何需要运行的命令。
使用 shell 脚本启动守护进程时,重要的部分是使用exec
命令,以便用守护程序替换 shell。这意味着 shell 将不再存在,并且守护进程将在 shell 使用的相同 PID 下运行,因此 systemd 仍然可以可靠地知道守护进程的主 PID。当然,exec
由 shell 脚本创建的守护进程仍应在前台运行。
使用服务允许您在 systemd 单元本身中Type=simple
进行配置。User=
此外,您通常可以通过 systemd 配置应用更多安全措施,这可能会触发 System V init 脚本并阻止其使用。此外,使用simple
而不是forking
使此设置更加可靠,并且在系统上也更加高效。
您可以轻松地运送 systemd 服务单元Type=simple
和软件包上的 System V 初始化脚本。只要它们具有相同的名称,systemd 就会优先选择本机服务单元(因此在这种情况下,systemd-sysv-generator 的遗留代码将不会触发 init 脚本。)这样您就可以保持与非 Linux 和其他系统的兼容性。非 systemd 设置,同时您可以通过 systemd 充分利用现代 Linux 系统。
答案2
复杂的是底层脚本中启动守护进程的“su -c”命令。系统不喜欢这样。但由于该脚本也必须在其他平台上运行,因此我使用 Linux 的 case 语句对其进行了修改。由于systemctl无论如何都是在sudo下运行的,所以这不是问题。现在 pid 文件可以写在我需要的任何地方,系统看起来很高兴。
答案3
这个答案是为了系统 >= 230(2016 年 5 月发布)。对于 Fedora 或 RedHat 以及其他启用了 SELinux 且无法运行su
或sudo
无法通过 systemd 服务的系统来说,这是必需的。
"+"
解决方案是在应以特权用户身份运行的行上使用 systemd.service 中的前缀。
例子:
[Service]
Type=forking
User=myid
ExecStartPre=+sh -c "if ! test -d /run/myid; then mkdir /run/myid; chown myid:myid /run/myid; fi"
ExecStart=<my script>
PIDFile=/run/myid/my_service.pid
...
然后可以通过在非特权用户下运行的脚本创建 PIDFile。不需要使用“ExecStop=”行,因为默认情况下,服务是通过向 PIDFile 中的 pid 发送终止信号来终止的。
系统服务:
ExecStartPre=、ExecStartPre=、ExecStartPost=
...
"+"
如果可执行路径以“+”为前缀,则该进程将以完全权限执行。在此模式下,使用 User=、Group=、CapabilityBoundingSet= 或各种文件系统命名空间选项(例如 PrivateDevices=、PrivateTmp=)配置的权限限制不会应用于调用的命令行(但仍影响任何其他 ExecStart=、ExecStop= ,……行)。