Bob*_*obP 5 systemd not-root-user services
我正在将一些旧的 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
Run Code Online (Sandbox Code Playgroud)
我的启动脚本定义了守护进程如何在各种平台上运行,设置一些环境变量,然后调用启动实际进程的 shell 脚本。当使用 sudo 权限调用时,被调用的守护程序脚本可直接运行。我的用户 ID 在 sudoers 列表中,但当然,运行进程的所有者也是 root。
如果User=.service 文件中没有“ ”属性,启动服务就没有问题。我总是在我自己的用户 ID 下以 sudo 身份运行 systemctl 命令。我的最终目标是看到实际运行的进程在我的用户 ID 下运行,而不是在 root 下运行。我认为添加User=<myid>到.service文件可以实现这一点,但是这一行会使服务启动失败。
用户存在(我自己的用户 ID)并且也在 sudoers 列表中。/run 归 root 所有,当使用 sudo 将 pid 文件写入那里时,pid 文件的所有者是 root。我尝试su <userid>在我的启动脚本中使用该命令,但这导致守护进程根本无法启动。
基本上,我在 SuSE 和 Redhat 之间有不同的行为。我的守护进程(多年来)在程序的安装路径中提供了一个 pid 文件。使用su <userid> -c在用户“a”的上下文中启动进程的命令启动进程。pid 文件位于应用程序的安装路径中,并归用户“a”所有。SuSE Linux:完全没问题。但是,在 Redhat 上,当进程运行时,systemd 会抱怨:
新的主 PID 26979 不属于服务,PID 文件不属于 root。拒绝。
为什么会有差异?我正在努力实现的“可行”吗?或者 root 现在是否有必要拥有所有 systemd 服务进程?
您遇到的问题是 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 init 脚本的兼容性,您可以从 systemd 以 root 身份运行它们,因为这是调用此类脚本时的“协议”。事实上,如果您希望保持兼容性,您甚至不需要运送 systemd 单元,因为 systemd 将能够通过systemd-sysv-generator自行生成一个单元。生成的单元看起来很像您提供的单元,只是它将由 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 系统。
复杂的是底层脚本中启动守护进程的“su -c”命令。系统不喜欢这样。但由于该脚本也必须在其他平台上运行,因此我使用 Linux 的 case 语句对其进行了修改。由于systemctl无论如何都是在sudo下运行的,所以这不是问题。现在 pid 文件可以写在我需要的任何地方,系统看起来很高兴。