Systemd

systemd 服務因服務文件中的 User= 而失敗

  • August 23, 2021

我正在將一些舊的 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 下將 systemctl 命令作為 sudo 執行。我的最終目標是看到實際執行的程序在我的使用者 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服務程序?

複雜之處在於啟動守護程序的底層腳本中的“su -c”命令。系統不喜歡那樣。但由於該腳本也必須在其他平台上執行,因此我使用 Linux 的 case 語句對其進行了修改。由於 systemctl 無論如何都是在 sudo 下執行的,所以這不是問題。現在 pid 文件可以寫入任何我需要的地方,並且系統看起來很開心。

您遇到的問題是 System V 初始化腳本希望以 root 身份執行,這是關於它們如何工作的“規範”的一部分,並且它們通常會有需要 root 才能完成的步驟。

在您的情況下,它是關於su <userid> -c ...以非 root 使用者身份實際開始執行的執行,但如果您已經在該使用者下執行,則該部分實際上會失敗。System V init 腳本通常會使用諸如或類似的工具surunas切換到非 root 使用者,但這些工具通常並不完全適合該目的(su最初是為了從互動式 shell 執行,並將與不支持的 PAM 集成)在這裡沒有多大意義。)

更糟糕的是,一些 System V 初始化腳本不會處理更改使用者,最終會不必要地以 root 身份執行守護程序,因為這在 System V 初始化腳本中感覺更“自然”。在我看來,這是 System V 初始化腳本中最糟糕的問題之一,它們使在這裡做錯事變得容易,而做正確的事卻很難。

如果您想保持與 System V 初始化腳本的兼容性,您可以從 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 腳本生成的守護程序仍應在前台執行。

使用Type=simple服務允許您User=在 systemd 單元本身中進行配置。此外,您通常可以通過 systemd 配置應用更多安全措施,這可能會觸發 System V 初始化腳本並阻止其使用。此外,使用simple而不是forking使此設置更可靠,它在系統上也更有效。

您可以輕鬆地將 systemd 服務單元Type=simple System V init 腳本一起發送到您的包中。只要它們具有相同的名稱,systemd 就會更喜歡本機服務單元(因此在這種情況下,systemd-sysv-generator 的遺留程式碼不會觸發 init 腳本。)這樣您就可以保持與非 Linux 和其他系統的兼容性非 systemd 設置,同時您可以使用 systemd 充分利用現代 Linux 系統。

引用自:https://unix.stackexchange.com/questions/534032