Systemd 進程管理相關

Systemd 進程管理相關

服務進程管理

本節將從service單元文件的編寫(基礎選項、起停及重載選項、依賴性處理選項,資源限制選項等),對服務進程基本操做,進程和進程組的查看和如何更好的殺死服務進程 四個方面介紹systemd下服務進程的管理。php

service單元文件

systemd 的單元文件是受 XDG Desktop Entry .desktop 文件啓發而產生,而最初起源是 Windows 下的 .ini 文件。 .service 文件是systemd的本地配置文件,相似於 sysvinit 中的 /etc/init.d 裏腳本的做用。html

一個.service 文件必定包含三個Sections,分別是[Unit]、[Service]、[Install]。每一個Section有不一樣的Key,例如Unit常見 有Description、Documentation、Requires等,Service常見有Type等,Install常見有Alias、 WantedBy=、RequiredBy等,具體查看 man systemd.unit,以及 man systemd.servicenode

systemd單元文件的加載路徑(優先級從上到下依次下降):linux

系統模式nginx

/etc/systemd/system/*
/run/systemd/system/*
/usr/lib/systemd/system/*
...

注:debian 下,關注:/lib/systemd/systemd/*目錄。通常來講,本身定義的.service 放在/etc/systemd/system下。git

用戶模式github

$XDG_CONFIG_HOME/systemd/user/*
$HOME/.config/systemd/user/*
/etc/systemd/user/*
/run/systemd/user/*
/usr/lib/systemd/user/*
...

先看個例子:web

nginx.serviceapache

[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -s quit

[Install]
WantedBy=multi-user.target

經過[Unit] section能夠知道這是HTTP服務器與反向代理服務器--nginx的單元文件(Description,關於服務的說明),此單元需在某些 target(network.target)以後纔可啓動(After),關於target的介紹請查看Target一節。api

經過[Server] section,能夠知道服務的啓動方式爲forking,PIDFile文件的路徑爲/run/nginx.pid(需 與nginx.conf中指定的PID文件路徑相同,讓systemd追蹤服務的主進程,若未指定或不一致將沒法啓動服務),ExecStartPre用 於指定在服務啓動以前須要執行的命令,ExecStart即服務啓動調用的命令。ExecReload和ExecStop分別指定服務進程重載和中止的命 令。更多配置選項介紹見下文。

[Install] section 中的WantedBy表示此服務被multi-user.target wants,在對應.wants/子目錄中爲服務創建相應的連接,當systemd啓用此target,本服務也會啓動。

基本介紹

service單元文件中的[Unit]和[Install]同其餘單元文件相似,其特有的seciton是[Service]。單元文件中空行和 以#和;開始的行會被忽略,因此能夠用來寫註釋。以反斜槓(\)鏈接在一塊兒的行,處理時會將反斜槓替換成空格,因此能夠用這個來把太長的行分紅多行。若是 文件是空的,或連接到/dev/null,它將不被加載,這樣能夠用來禁用服務,讓它即便手工也沒法啓動。

[Unit]
  • Description=   :一些描述,顯示給用戶界面看的,能夠是任何字符串,通常是關於服務的說明。

  • Documentation=  :指定參考文檔的列表,以空格分開的 URI 形式,如http://, https://, file:, info:, man:,這是有順序的,最好是先解釋這個服務的目的是什麼,而後是它是如何配置的,再而後是其它文件,這個選項能夠屢次指定,會將多行的合併,若是指定 了一個空的,那麼會重置此項,前面的配置再也不起做用。

  • Requires=   :指定此服務依賴的其它服務,若是本服務被激活,那麼 Requires 後面的服務也會被激活,反之,若是 Requires 後面的服務被中止或沒法啓動,則本服務也會中止。這個選項能夠指定屢次,那麼就要求全部指定的服務都被激活。須要注意的是這個選項不影響啓動或中止的順 序,啓動順序使用單句的 After= 和 Before= 來配置。例如,若是 foo.service 依賴 bar.serivce,可是隻配置了 Requires= 而沒有 After= 或 Before=,那麼 foo.service 啓動時會同時激活 foo.service 和 bar.service。一般使用 Wants= 代替 Requires= 是更好的選擇,由於系統會更好的處理服務失敗的狀況。注意,這種依賴關係,也能夠在文件以外來處理,即便用 .requires/ 目錄,能夠參看上面的說明。

  • RequiresOverridable= :相似上面的 Requires= ,不過這種狀況下,只要用戶明確要求它啓動,纔會影響到被依賴的服務,否則服務出錯什麼的,不會影響被依賴服務的啓動。

  • Requisite=, RequisiteOverridable=  :分別相似上面的兩個,不過若是是這個指定服務沒有啓動,被依賴的服務會不啓動,當即失敗。

  • Wants= :相對弱化的 Requires= ,這裏列出的服務會被啓動,但若是沒法啓動或沒法添加到事務處理,並不影響本服務作爲一個總體的啓動。這是推薦的兩個服務關聯的方式。這種依賴也能夠配置 文件外,經過 .wants/ 目錄添加,具體能夠看上面的說明。

  • BindsTo= :和 Requires= 很像,可是這種狀況,若是他後面列出的服務中止運行或崩潰之類的,本服務也會同時中止。

  • PartOf=  :又一個相似 Requires= 的選項,可是限制在中止或重啓動服務,若是這裏列出的服務被中止或重啓動,那麼本服務也會中止或重啓動,注意這個依賴是意向,即本服務中止或重啓動,不會影響到這裏列出服務的運行狀態。

  • Conflicts= :配置一個依賴衝突,若是配置了些項,那麼,當一個服務啓動時,或中止此處列出的服務,反過來,若是這裏列出的服務啓動,那 麼本服務就會中止,即後啓動的才起做用。注意,此設置和 After= 和 Before= 是互相獨立的。若是服務 A 和 B 衝突,且在 B 啓動的時候同時啓動,那麼有可能會啓動失敗(兩都都是必需的)或修改以修復它(二者之一或兩都都不是必需的),後一種狀況,會將不須要的依賴刪除,或中止 衝突。

  • Before=, After=  :配置服務間的啓動順序,好比一個 foo.service 包含了一行 Before=bar.service,那麼當他們同時啓動時,bar.service 會等待 foo.service 啓動完成後才啓動。注意這個設置和 Requires= 的相互獨立的,同時包含 After= 和 Requires= 也是常見的。此選項能夠指定一次以上,這時是按順序所有啓動。

  • OnFailure=   :列出一個或更多的服務,當本服務啓動狀態是 failed 的時候,激活這些服務。

  • PropagatesReloadTo=, ReloadPropagatedFrom=  :這兩個是列出一些服務,當其它服務 reload 時同時 reload 這個服務,或者反之。

  • RequiresMountsFor=  :用空格分開的絕對路徑列表,是 Requires= 和 After= 添加的依賴中的 mount 文件須要訪問的指定的路徑。

  • OnFailureIsolate=  :是一個布爾值,若是是真,那麼 OnFailure= 後面的服務會進入隔離模式,即全部不是它依賴的服務都會中止。若是隻設置一個服務,能夠放在 OnFailure= 後,默認值是假。

  • IgnoreOnIsolate=  :一個布爾值.若是是真則當隔離其它服務時本服務不會中止(不明白隔離是什麼意思,大概在後面)。默認是假。

  • IgnoreOnSnapshot=  :一個布爾值.若是是真則本服務不包含快照(snapshots)。對 device 和 snapshot 服務默認爲真,其它服務默認爲假。

  • StopWhenUnneeded=  :一個布爾值。若是是真則當本服務不使用時會中止。 注意,爲了儘可能減小 systemd 的工做,默認狀況下是不會中止不使用的服務的,除非和其它服務衝突,或用戶明確要求中止。若是設置了這個選項,那麼若是沒有其它活動的服務須要此服務,它 會自動中止。默認值是假。

  • RefuseManualStart=, RefuseManualStop=  :布爾值。若是設爲真值,則此服務只能間接的激活或中止。這種狀況下,用戶直接啓動或中止此服務會被拒絕,只有作 爲其它的服務依賴關係,由其它服務進行啓動或中止才能夠。這主要是爲了中止用戶誤操做。默認值是假。

  • AllowIsolate=      :布爾值。若是是真值,則此服務可使用 systemctl isolate 命令進行操做。不然會拒絕此操做。最好的辦法是不要動這處選項,除非目標服務的行爲相似於 SysV 啓動系統中的 runlevels。只是一種預防措施,避免系統沒法使用的狀態。默認值是假。

  • DefaultDependencies=  :布爾值。若是是真(默認值),一些本服務默認的依賴會隱式的創建,具體是哪些依賴,則於服務的類型 決定。好比,對於普通的服務(.service類型),它會確保在系統基本服務啓動後才啓動本服務,會在系統關機前確保本服務已關閉。通常來講,只有早期 開機服務和後期的關機服務,才須要把這個設成假。強烈對大多數普通服務,讓這個選項啓用便可。若是設成假,也不會禁用全部的隱式依賴,只是禁用那些非必要 的。

  • JobTimeoutSec=  :當一個客戶端等待本服務的某個 Job 完成時,所指定的超時時間。若是達到了限制的時間,此 Job 會取消運行,但服務不會更改狀態,包括進入「failed」狀態。除了設備服務(即.device類型),其它的默認值是0(即沒有超時設置)。注意,這 個是獨立於特定服務所設置的超時設置的(好比對 .service 類型所設置的 Timeout=),它對服務自己沒有影響,但特定服務的設置是有影響的(能用來更改服務狀態)。《-這段不明白究竟是什麼意思,因此翻譯的也是亂七八 糟,真對不起)。

  • ConditionPathExists=, ConditionPathExistsGlob=, ConditionPathIsDirectory=, ConditionPathIsSymbolicLink=, ConditionPathIsMountPoint=, ConditionPathIsReadWrite=, ConditionDirectoryNotEmpty=,ConditionFileNotEmpty=, ConditionFileIsExecutable=, ConditionKernelCommandLine=, ConditionVirtualization=, ConditionSecurity=, ConditionCapability=, ConditionHost=, ConditionACPower=,ConditionNull=  :這是一組相似的東西。檢測特定的條件是否是真值,若是不是真值,服務會略過啓 動,可是它依賴的服務仍是會正常運行的。這個條件測試失敗不會讓服務進入失敗狀態。條件是在服務開始運行時檢查的。

  • ConditionPathExists= 是指定在服務啓動時檢查指定文件的存在狀態。若是指定的絕對路徑名不存在,這個條件的結果就是失敗。若是絕對路徑的帶有!前綴,則條件反轉,即只有路徑不存在時服務才啓動。

  • ConditionPathExistsGlob= 相似上面的選項,但支持通配符。

  • ConditionPathIsDirectory= 判斷指定路徑是否是目錄。

  • ConditionPathIsSymbolicLink= 判斷指定路徑是否是連接。

  • ConditionPathIsMountPoint= 判斷指定路徑是否是一個掛載點。

  • ConditionPathIsReadWrite= 多年指定路徑是否可讀寫(即不是作爲只讀系統掛載的)

  • ConditionDirectoryNotEmpty= 判斷指定目錄是否存在且不爲空。

  • ConditionFileNotEmpty= 判斷指定文件是不是常規文件且不爲空(即大小不是0)。

  • ConditionFileIsExecutable= 判斷指定文件是不是常規文件且可執行。

  • 相似的,ConditionKernelCommandLine=是判斷有沒有指定的內核命令行啓動參數(或帶有!反之),這個參數必須是一個單詞或用=分開的兩個單詞,前一種狀況下,會尋找內核參數是否有此單詞或是賦值的左邊。後一種狀況則必須是賦值的左右同時符合。

  • ConditionVirtualization= 是判斷是否是在虛擬化環境下執行的服務。這能夠是個布爾值以判斷是否是任意的虛擬化環境,或者 下列的字符串之一: qemu, kvm, vmware, microsoft, oracle, xen, bochs, chroot, openvz, lxc, lxc-libvirt, systemd-nspawn,以判斷是否是特定的虛擬化環境,多重嵌套的虛擬化環境,只判斷最後一層。可使用!進行反轉判斷。

  • ConditionSecurity= 是判斷系統是否啓用了安全環境,當前僅能識別selinux, apparmor, 和 smack。可使用!進行反轉判斷。

  • ConditionCapability= 是判斷服務管理器綁定的 capability 是否存在。(能夠查看其它部分的詳細信息。)設置爲 capability 的名字,好比 CAP_MKNOD。能夠經過在前面加!反轉判斷。

  • ConditionHost= 是判斷主機名 (hostname)或機器ID(machine ID)是否匹配。能夠加!反轉。

  • ConditionACPower= 是判斷機器是否在使用交流電源。若是設成 true,而只有至少鏈接一個交流電源時結果才爲真,反過來,設成 false,則不鏈接全部交流電源時才爲真。

  • ConditionNull= 是一個常量性質的判斷條件,它應該是布爾值,若是設成 false ,則條件永遠失敗,反過來則永遠成立。

  • 若是指定多個條件,則全部條件都須要成立(即條件之間是 AND 的關係)。條件前面能夠加上 | 符號,這時條件變成一個觸發條件,服務定義了觸發條件,那麼在知足其它非觸發條件和這個觸發條件的狀況下,服務會至少執行一次。同時指定|和!前綴時,先 處理|,後處理!。除了ConditionPathIsSymbolicLink=,其它條件均跟隨連接。若是這些條件指定爲空,則至關於重置,前面的任 何設置都再也不起做用。

  • SourcePath=  :這個服務生成的配置文件所在的路徑,這主要是用在生成工具從外部配置文件的格式轉換到本地服務的配置格式中。所以,對通常的服務不要使用此選項。

[Service]

介紹部分常見選項,其他選項請查看本節參考連接2。

  • Type= :

    • simple(默認值):systemd認爲該服務將當即啓動。服務進程不會fork。若是該服務要啓動其餘服務,不要使用此類型啓動,除非該服務是socket激活型。
    • forking:systemd認爲當該服務進程fork,且父進程退出後服務啓動成功。對於常規的守護進程(daemon),除非你肯定此啓動 方式沒法知足需求,使用此類型啓動便可。使用此啓動類型應同時指定 - PIDFile=,以便systemd可以跟蹤服務的主進程。
    • oneshot:這一選項適用於只執行一項任務、隨後當即退出的服務。可能須要同時設置 RemainAfterExit=yes 使得 systemd 在服務進程退出以後仍然認爲服務處於激活狀態。
    • notify:與 Type=simple 相同,但約定服務會在就緒後向 systemd 發送一個信號。這一通知的實現由 libsystemd-daemon.so 提供。
    • dbus:若以此方式啓動,當指定的 BusName 出如今DBus系統總線上時,systemd認爲服務就緒。
  • PIDFile= :指定PID 文件絕對路徑,當Type=forking時,必須設置此項。

  • ExecStart= :服務啓動命令,可帶參數。!!關於此選項有大量注意事項與細節(例如:命令中不可使用重定向符號,管道,後臺運行&以及其餘特殊符號等),請查看用戶手冊。

  • ExecStartPre=, ExecStartPost= :以前ExecStart以前或者以後執行的命令。注意語法格式要求同ExecStart選項。若包含多條此選項,將按順序串行執行。

  • ExecReload= :重載服務時候觸發的命令。

  • ExecStop= :服務中止命令。

  • ExecStopPost= :中止服務以後執行的命令。語法格式要求同ExecStart選項。

  • Restart= :指定服務進程自動重啓的條件。

    • no:默認選項,服務不會被systemd自動重啓。
    • on-success:當服務進程成功退出後重啓(exit code=0,signals SIGHUP, SIGINT, SIGTERM or SIGPIPE, and additionally, 或SuccessExitStatus=選項指定的退出信號)。
    • on-failure:服務進程不正常退出時進行重啓(exit code 爲非0,或被信號中斷)。
    • on-abnormal:服務進程被信號中斷時進行重啓。
    • no-watchdog:watchdog觀測到服務進程過時後重啓服務。
    • no-abort:進程被未捕獲的信號中斷時將進行重啓。
    • always:服務在不管何種狀況退出後或者超時時老是重啓。

exit causes

關於啓動、中止的超時設置請參考用戶手冊。

服務進程限制
  • PrivateNetwork=[BOOL] :若服務不須要網絡鏈接可開啓本選項,更加安全。

  • PrivateTmp=[BOOl] :因爲傳統/tmp目錄是全部本地用戶和服務共用,會帶來不少安全性問題,開啓本選項後,服務將有一個私有的tmp,可防止攻擊。

  • InaccessibleDirectories= :限制服務進程訪問某些目錄。

  • ReadOnlyDirectories= :設置服務進程對某些目錄只讀,保證目錄下數據不被服務意外撰改。

  • OOMScoreAdjust= :調整服務OOM值,從-1000(對該服務進程關閉OOM)到1000(嚴格)。

  • IOSchedulingClass= :IO調度類型,可設置爲0,1,2,3中的某個數值,分配對應none,realtime,betst-effort和idle。

  • IOSchedulingPriority= :IO調度優先級,0~7(高到低)。

  • CPUSchedulingPriority= :CPU調度優先級,99~1(高到低)

  • Nice= :進程調度等級。

-(更多介紹請查看本節參考連接7,8)

關於服務進程的權限限制、資源限制、安全性管理將在進階部分中進一步介紹。

[Install]
  • Alias= :在安裝使用應該使用的額外名字(即別名)。名字必須和服務自己有一樣的後綴(即一樣的類型)。這個選項能夠指定屢次,全部的名字都起做用,當執行 systemctl enable 命令時,會創建至關的連接

  • WantedBy=, RequiredBy= :在 .wants/ 或 .requires/ 子目錄中爲服務創建相應的連接。這樣作的效果是當列表中的服務啓動,本服務也會啓動。 在 bar.service 中的 WantedBy=foo.service  和 Alias=foo.service.wants/bar.service 基本是一個意思。

  • Also=  :當此服務安裝時同時須要安裝的附加服務。 若是用戶請求安裝的服務中配置了此項,則 systemctl enable 命令執行時會自動安裝本項所指定的服務。

  • 在 [Install] 段使用這些字符串有特定含義: %n, %N, %p, %i, %U, %u, %m, %H, %b。請查看查看本節參考連接1。

注:修改service單元文件以後,需使用命令systemctl daemon-reload從新載入systemd,掃描新的或有變更的單元。

服務進程執行環境配置:

更多關於服務進程執行環境的配置請參考:systemd.exec

服務進程基本操做

systemd的主要命令行工具是systemctl,可用於管理單元文件,不只僅是service。

注:若只給出名稱,而爲給出擴展名,將默認爲service單元,例:systemctl start foosystemctl start foo.service等價。

在sysv下,經常使用的管理命令是service,在systemd中此命令可用(參考本文檔「SysV兼容性」一節),更建議使用systemctl命令,如下爲兩者的命令基礎操做對照表:

systemctl

服務進程(組)查看

使用systemctl能夠查看服務進程的狀態。

查看服務進程運行狀態(同時查看到其下屬進程們的PID)

# systemctl status nginx.service

查看系統全部service及其狀態,將會打印4列信息,分別爲UNIT、LOAD、ACTIVE、SUB、DESCRIPTION

# systemctl list-units --type service

查看特定服務的依賴關係

# systemctl list-dependencies nginx

更多關於systemctl在服務進程查看方面的用法請查看systemctl用戶手冊,另外也可經過 pstree命令查看系統進程樹。

殺死服務進程(組)

Systemd 採用 Linux 的 Cgroup 特性跟蹤和管理進程的生命週期。CGroup 提供了相似文件系統的接口,使用方便。當進程建立子進程時,子進程會繼承父進程的 CGroup。所以不管服務如何啓動新的子進程,全部的這些相關進程都會屬於同一個 CGroup,systemd 只須要簡單地遍歷指定的 CGroup 便可正確地找到全部的相關進程,將它們一一中止便可。 例如:一個CGI程序派生兩次,脫離和Apache的父子關係,當apache進程被中止後,該CGI程序還在繼續運行的狀況,在systemd的管理下 將不會存在。

中止服務進程建議使用systemctl工具。

殺死一個服務的全部進程(傳遞信號到指定服務的全部進程):

$ sudo systemctl kill crond.service
#指定信號類型
$ sudo systemctl kill -s SIGKILL crond.service
或
$ sudo systemctl kill -s 9 crond.service
#不管服務進程通過多少層fork,使用以上命令便可殺死全部進程。

# 發送指定信號到服務的主進程
$ sudo systemctl kill -s HUP --kill-who=main crond.service

另外在單元文件中,能夠指定KillMode,KillSignal,SengSIGHUP,SendSIGKILL,請查看本節參考連接4。

實例

fork.c

fork.c

編譯生成二進制程序fork,編寫fork.service,調用fork程序。運行fork.service,使用pstree查看進程樹:

pstree1

kill 掉某個fork出的子進程,使該子進程的子進程們脫離主進程。

pstree2

查看fork.service 的狀態,發現脫離父進程的進程們依然在其crgoup中,未脫離。

fork status

kill掉fork.service,發現該服務的全部進程,包括脫離主進程的進程都被中止,無一例外。

fork stop

本節參考連接

  1. systemd.unit
  2. systemd.service
  3. Killing Services
  4. systemd.kill
  5. systemctl
  6. ArchWiki-systemd
  7. Securing Your Services
  8. systemd.exec

socket-based activation

絕大多數的服務依賴是套接字依賴。好比服務 A 經過一個套接字端口 S1 提供本身的服務,其餘的服務若是須要服務 A,則須要鏈接 S1。所以若是服務 A 還沒有啓動,S1 就不存在,其餘的服務就會獲得啓動錯誤。因此傳統地,人們須要先啓動服務 A,等待它進入就緒狀態,再啓動其餘須要它的服務。Systemd 認爲,只要咱們預先把 S1 創建好,那麼其餘全部的服務就能夠同時啓動而無需等待服務 A 來建立 S1 了。若是服務 A 還沒有啓動,那麼其餘進程向 S1 發送的服務請求實際上會被 Linux 操做系統緩存,其餘進程會在這個請求的地方等待。一旦服務 A 啓動就緒,就能夠當即處理緩存的請求,一切都開始正常運行。

Linux 操做系統有一個特性,當進程調用 fork 或者 exec 建立子進程以後,全部在父進程中被打開的文件句柄 (file descriptor) 都被子進程所繼承。套接字也是一種文件句柄,進程 A 能夠建立一個套接字,此後當進程 A 調用 exec 啓動一個新的子進程時,只要確保該套接字的 close_on_exec 標誌位被清空,那麼新的子進程就能夠繼承這個套接字。子進程看到的套接字和父進程建立的套接字是同一個系統套接字,就彷彿這個套接字是子進程本身建立的一 樣,沒有任何區別。

這個特性之前被一個叫作 inetd 的系統服務所利用。Inetd 進程會負責監控一些經常使用套接字端口,好比 Telnet,當該端口有鏈接請求時,inetd 才啓動 telnetd 進程,並把有鏈接的套接字傳遞給新的 telnetd 進程進行處理。這樣,當系統沒有 telnet 客戶端鏈接時,就不須要啓動 telnetd 進程。Inetd 能夠代理不少的網絡服務,這樣就能夠節約不少的系統負載和內存資源,只有當有真正的鏈接請求時才啓動相應服務,並把套接字傳遞給相應的服務進程。

和 inetd 相似,systemd 是全部其餘進程的父進程,它能夠先創建全部須要的套接字,而後在調用 exec 的時候將該套接字傳遞給新的服務進程,而新進程直接使用該套接字進行服務便可。

systemd中這個並行化Socket服務極大的加快了系統的啓動速度(固然,不全是它的功勞,此處不贅述)。

systemd的並行啓動能力(與sysv、upstart對比)

parallelization

Socket Activation 帶來的益處(摘自Systemd主做者Blog):

  • parallelization.
  • We no longer need to configure dependencies explicitly. Since the sockets are initialized before all services they are simply available, and no userspace ordering of service start-up needs to take place anymore. Socket activation hence drastically simplifies configuration and development of services.
  • If a service dies its listening socket stays around, not losing a single message. After a restart of the crashed service it can continue right where it left off.
  • If a service is upgraded we can restart the service while keeping around its sockets, thus ensuring the service is continously responsive. Not a single connection is lost during the upgrade.
  • We can even replace a service during runtime in a way that is invisible to the client. For example, all systems running systemd start up with a tiny syslog daemon at boot which passes all log messages written to /dev/log on to the kernel message buffer. That way we provide reliable userspace logging starting from the first instant of boot-up. Then, when the actual rsyslog daemon is ready to start we terminate the mini daemon and replace it with the real daemon. And all that while keeping around the original logging socket and sharing it between the two daemons and not losing a single message. Since rsyslog flushes the kernel log buffer to disk after start-up all log messages from the kernel, from early-boot and from runtime end up on disk.

上文提到inetd能夠代理不少網絡服務以此節約系統資源,Systemd 也能夠提供按需啓動的能力,只有在某個服務被真正請求的時候才啓動它。空閒時使該服務結束,等待下次須要時再次啓動它。例:

服務器上的SSHD服務,只有當系統管理員或者用戶登陸時候,此服務才被使用到,而後它卻一直駐留在後臺,極大的浪費系統資源。而在systemd下,咱們能夠作得按需啓動,即有人經過SSH訪問服務時,SSHD服務纔會被啓動。(具體實現請查看/lib/systemd/system下的ssh.servicessh.socket文件)

更多例子:Socket Activation with Popular Daemons

注:systemd的socket單元負責監控端口時,當外部鏈接帶來,而systmed沒法將socket轉移給對應服務單元時候(若服務進程崩 潰、中止退出、沒法重啓等),對應socket單元將進入failed狀態,外部鏈接將不通(telnet ip pot 測試對應服務端口將不通)。

按需啓動實例:

使用systemd部署Node.js應用(myservice.service),見圖myservice.service:

myservice.service

$ sudo systemctl enable myservice.service # 設置服務隨機啓動
$ sudo systemctl start myservice.service #啓動服務

在這種配置下,沒有使用socket激活,systemd運行且監控着服務的守護進程,Node.js監聽TCP端口和服務器請求,進程是時刻運行着,即便沒有外部訪問,極大的浪費了系統的資源。

系統運行原理,見圖no-socket-activation:

no-socket-activation

須要將其改成socket激活。空閒時,systemd監控着TCP端口,當有請求來到時,systemd激活Node.js服務並將socket 移教給Node.js處理,而後Nojde.js接管並處理所有的客戶端請求,systemd從新變成監控Node.js的角色。 當 Node.js 處理完任務後,它自動中止。而再由 systemd 來監控 TCP 端口,直至下次客戶端請求的到來並再啓動 Node.js 的應用,循環往復,見圖socket-activation1,2

socket-activation 1

socket-activation 2

要達到以上目的,Node.js須要安裝systemd相關模塊,同時安裝node-autoquit模塊在空閒時關閉應用,以及作好應用數據的保存工做,具體此處不贅述,請參考本節參考連接。

systemd的相關配置:須要修改以前的service單元文件(去除自動重啓以及自動啓動),並增長一個同名socket單元文件讓systemd監聽TCP端口,見圖socket-activation3

socket-activation 3

$ sudo systemctl enable myservice.socket #使socket單元自啓動
$ sudo systemctl start myservice.socket #啓動socket

本節參考連接


instantiated services

部分服務例如syslog、apache等,在系統上通常是隻有單進程在運行。而另一些服務須要多實例化,例如Dovecot-IMAP服務會有 多個實例進程運行在不一樣的IP端口或者本地IP地址上。 在systemd中,咱們不須要爲每一個實例化進程編寫一份配置文件,只須要編寫一個模板文件,而後在啓用時候調用模板文件便可,模板文件能夠經過匹配模板 時傳入的字符串實例化進程,例如咱們系統上有3塊網卡,分別爲eth0、eth一、eth2,咱們但願經過每塊網卡都經過dhcpd配置動態ip。利用 systemd的模板\實例機制,咱們只須要寫一份dhcpd的模板文件,便可爲3塊網卡所用。

dhcpd@.service

[Unit]
Description=dhcpcd on %I
Wants=network.target
Before=network.target
BindsTo=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device

[Service]
Type=forking
PIDFile=/run/dhcpcd-%I.pid
ExecStart=/usr/bin/dhcpcd -4qb %I
ExecStop=/usr/bin/dhcpcd -x %I

[Install]
WantedBy=multi-user.target

經過如下命令啓用:

# systemctl start dhcpcd@eth0.service
# systemctl start dhcpcd@eth1.service
# systemctl start dhcpcd@eth2.service

命令中的字符串ethN將在service文件中被匹配展開:

dhcp@eth0.service

[Unit]
Description=dhcpcd on eth0
Wants=network.target
Before=network.target
BindsTo=sys-subsystem-net-devices-eth0.device
After=sys-subsystem-net-devices-eth0.device

[Service]
Type=forking
PIDFile=/run/dhcpcd-eth0.pid
ExecStart=/usr/bin/dhcpcd -4qb eth0
ExecStop=/usr/bin/dhcpcd -x eth0

[Install]
WantedBy=multi-user.target

模板文件中的%I%i被展開爲命令中@以後.service以前的字符串,即ethN%I%i的區別在此例中看不出來,實際上%I展開後是不轉義的字符串,而%i展開後是轉移的字符串。例如若是咱們調用串口tty的模板文件實例化某個接口的tty服務, systemctl start 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service',實際上接口地址爲 「serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0」 ,以上命令是通過轉義的, /被替換爲-,特殊符號被替換爲16進制符號等。service文件中的%I將展開爲轉義前的「serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0」, 而%i將展開爲serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service

  • "%I" Unescaped instance name Same as "%i", but with escaping undone
  • "%i" Instance name For instantiated units: this is the string between the "@" character and the suffix of the unit name

單元文件的特殊符號更多解釋請查看 systemd.unit#Specifiers

另一個例子: systemd-fsck@.service

[Unit]
Description=File System Check on %f
Documentation=man:systemd-fsck@.service(8)
DefaultDependencies=no
BindsTo=%i.device
After=systemd-readahead-collect.service systemd-readahead-replay.service %i.device
Before=shutdown.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/lib/systemd/systemd-fsck %f
StandardOutput=journal+console
TimeoutSec=0

systemd-fsck是systemd的磁盤檢查工具,經過此模板文件能夠對多塊磁盤設備進行實例化後檢查(%f表示未轉義的文件名)。

本節參考連接


進程資源管理

基礎介紹

systemd內部使用cgroups對其下的單元進行資源管理,包括CPU、BlcokIO以及MEM方面。systemd的資源管理主要基於三個單元service、scope以及slice。

  • service單元主要用於配置單項服務進程,具體請參考上文的基礎--進程管理--服務進程管理一節。
  • scope 單元由 systemd 在已有進程外自動建立。經過將某個進程和其子進程分組,scope 單元可用來組織進程,應用資源單元,或者殺死進程組。用戶會話就是一個進程都包含在單個 scope 單元中的實例。
  • slice 單元用於將管理進程的單元分組成層級,層級可容許控制分配給 slice 的資源。例:默認的 slice 有用於虛擬機及容器 (container) 的 machine.slice;用於系統服務的 system.slice;用於用戶會話的 user.slice。在service單元文件中使用 Slice=slicename,可將服務可添加到特定slice。

每個用戶登陸時候會自動建立一個此用戶的slice單元,同時用戶的每一個對話會自動建立一個scope用於管理對話下的進程。 每個模板service單元文件實例化時會自動建立一個對應的slcie,例如getty@.service進行實例化爲一個 getty@tty1.service時會自動建立一個systemd-getty.slice用於管理此模板下的全部實例化的進程,關於service 模板文件的介紹請查看上文的基礎--進程管理-instandtiatied serivices一節。

可以使用systemd-cgls( Recursively show control group contents)命令查看系統中以上三種單元的層級管理,見圖systemd-cgls。

systemd-cgls

systemd另外提供了一個工具systemd-cgtop( Show top control groups by their resource usage)用於監控各個單元實時、動態的資源佔用狀況,見圖systemd-cgtop。

systemd-cgtop

配置與實例

本小節僅簡單介紹關於CPU、MEM、BlockIO常見的資源管理選項,更多配置選項詳情請查看本節參考連接四、6等。

CPU
  • CPUAccounting=
    • [true,false],是否打開服務單元的CPU用量統計
    • Note that turning on CPU accounting for one unit might also implicitly turn it on for all units contained in the same slice and for all its parent slices and the units contained therein.
  • CPUShare=,StartupCPUShare=
    • 設置服務單元的CPU時間比重,默認值爲1024。設置此兩項以後,即表示默認開啓"CPUAccounting=true"。
    • StartupCPUShare做用於系統啓動階段,CPUShares做用與系統啓動以後運行階段。
  • CPUQuota=
    • 服務進程執行時的CPU時間配額設置。設置的值是百分數的形式,表示佔一個CPU總共時間的百分比。若值大於100%,表示分配的的CPU時間多於一個CPU。
    • Example: CPUShares=20% ensures that the executed processes will never get more than 20% CPU time on one CPU.
    • 設置此項以後,默認設置"CPUAccounting=true"。
  • CPUSchedulingPolicy=
    • 設置服務進程執行時CPU的調度策略,可設置爲other, batch, idle, fifo 或 rr,具體請查看sched_setscheduler(2)
  • CPUSchedulingPriorityp=
    • 設置服務進程執行時CPU的調度優先級,設置的數值範圍依上文CPUSchedulingPolicy指定的類型不一樣而不通,例如real-time類型的調度方式,優先級數值範圍爲1(低優先級)到99(高優先級)。更多信息請參考sched_setscheduler(2)

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
CPUShares=1500

而後重載systemd deamon,重啓http.service便可。

#systemctl daemon-reload
#systemctl restart httpd.service
MEM
  • MemoryAccounting=
  • [true,false],是否打開服務單元的內存用量統計。
  • MemoryLimit=bytes
  • 指定執行進程的內存使用量,默認單位爲bytes。數值後可加單元:K,M,G或T,表示KB,MB,GB,TB。設置此項即意味着設置了 "MemoryAccounting=true"。

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
MemoryLimit=1G

在debian testing 上沒法完成內存限制的實驗,緣由不明。正常配置,service單元與slice單元可正常啓動,可是slice單元中限制的內存無效,日誌正常。更換到fedora 20上實驗成功。 注意若在service單元中指定了slice單元,而限制選項主要在slice單元中,最好slice單元比service單元先啓動,不然可能報錯。 若服務程序內存超過MemoryLimit的限制,服務單元將退出進入failed狀態。

BlockIO
  • BlockIOAccounting=
    • [true,false],是否開啓服務單元的塊設備的IO統計。
  • BlockIOWeight=weight, StartupBlockIOWeight=weight
  • 設置服務單元執行時候的塊設備IO比重,默認數值爲1000,可設置範圍爲10到1000。BlcokcIOWeight與StartupBlockIOWeight,前者爲系統運行時作的限制,後者爲系統啓動階段作的限制。
  • 例1:BlockIOWeight=500
  • 例2,指定設備:BlockIOWeight=/dev/disk/by-id/ata-SAMSUNG_MMCRE28G8MXP-0VBL1_DC06K01009SE009B5252 750
  • 例3,指定實際的塊設備點:BlockIOWeight=/home/lennart 750
  • BlockIOReadBandwidth=device bytes, BlockIOWriteBandwidth=device bytes
  • 設置服務單元執行時對執行塊設備的讀寫速率設置。
  • 例:BlockIOReadBandwith=/var/log 5M,表示服務單元對/var/log的讀限制在5Mb/s內。
Others

上文提到的這些設置選項其實都是與cgroup對應的控制組相關,例如MemoryLimit選項對應memory.limit_in_bytes控制組。systemd的單元文件中能夠經過ControlGroupAttribute配置對應cgroup的控制組,例如:controlGroupAttribute=memory.swappiness 70。更多介紹請查看本節參考連接9,10。

ControlGroupAttribute= Set a specific control group attribute for executed processes, and(if needed) add the executed processes to a cgroup in the hierarchy of the controller the attribute belongs to.

實例

咱們將上文關於CPU,IO,BlockIO等相關的設置整合在一塊,可獲得以下的service文件:

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
CPUShares=1500
MemoryLimit=1G
BlockIOWeight=500
BlockIOReadBandwith=/var/log 5M
ControlGroupAttribute=memory.swappiness 70

咱們知道service單元文件中service 段能夠經過Slice=選項指定所屬的slice單元,因此除了將資源管理配置寫在service單元文件中,還能夠將服務進程的資源限制寫在特定slice單元中。

例:

/etc/systemd/system/limits.slice:

[Unit]
Description=Limited resources Slice
DefaultDependencies=no
Before=slices.target

[Slice]
CPUShares=512
MemoryLimit=1G

/lib/systemd/system/nginx.service:

[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -s quit
Slice=limits.slice

[Install]
WantedBy=multi-user.target

而後執行如下命令:

# systemctl daemon-reload
# systemctl restart nginx.service

使用systemd-cgls查看,結果如圖 nginx-slice。

nginx-slice

另外也可經過systemctl永久性或者臨時性的對正在運行的service、slice、scope單元進行即時的資源控制。

例如:

#systemctl set-property nginx.service CPUShares=500 MemoryLimit=500M

會發現/etc/systemd/system出現了nginx.service.d目錄,其中包含着與nginx.service相關的資源控制配置文件:90-CPUShare.conf,90-MemoryLimit.conf。 這種更改是永久性的,即下次重啓依然有效。若是須要臨時性的更改使用選項--runtime替代set-property便可。

本節參考連接

  1. systemd.service
  2. systemd.scope
  3. systemd.slice
  4. systemd.resource-control
  5. systemd.cgroup
  6. systemd.exec
  7. Resource Management with systemd
  8. The New Control Group Interfaces
  9. systemd for Administrators, Part XVIII
  10. Man systemd.exec

進程權限與安全管理

關於本節內容建議閱讀本節參考連接2:《Securing Your Services》。

chroot()與目錄訪問控制

service單元中可使用 RootDirectory= 選項來達到chroot的效果。

RootDirectory=

Takes an absolute directory path. Sets the root directory for executed processes, with the chroot(2) system call. If this is used, it must be ensured that the process and all its auxiliary files are available in the chroot() jail.

例:

[Unit]
Description=A chroot()ed Service

[Service]
RootDirectory=/srv/chroot/foobar
ExecStartPre=/usr/local/bin/setup-foobar-chroot.sh
ExecStart=/usr/bin/foobard
RootDirectoryStartOnly=yes

如上文服務單元,對於foobard來講,根目錄爲/srv/chroot/foobar,注意若指定了RootDirectory,則ExecStart(ExecStop等等)選項的路徑需以新指定的RootDirectory爲準, 例如本例中ExecStart實際指向爲/srv/chroot/foobar/usr/bin/foobard,但在service單元中需寫成相對於RootDirectory的路徑:/usr/bin/foorbad。

注:systemd中提供了一個方便的輕量級的容器管理工具systemd-naspawn,具體請查看container(systemd-nspawn)一節。

另外若是想控制服務單元對特定目錄的訪問,systemd的service單元提供瞭如下選項(詳細解釋請查看本節參考連接1對應條目):

  • User=,Group=
    • 指定服務單元執行時候的用戶與用戶組,能夠配合目錄權限設置與此選項的設置而控制服務單元的訪問各目錄的權限。
  • InaccessibleDirectories=
    • 指定服務單元不可訪問的目錄,指定的目錄對於服務單元來講將爲空,且訪問模式爲000,若值以'-'開頭,則目錄不存在時將自動忽略。
  • ReadWriteDirectories=
    • 指定服務單元可讀寫的目錄。
  • ReadOnlyDirectories=
    • 指定服務單元只讀的目錄,若值以'-'開頭,則目錄不存在時將自動忽略。
    • 注意一個已知bug:ReadOnlyDirectories不支持遞歸,例如即便指定了/var目錄,而/var目錄下若包含其餘子文件夾,這些子文件夾對於服務單元來講依舊可讀寫,後續版本將修復。

例(/home目錄對於此服務單元不可訪問,而/var目錄對於此服務單元只讀):

...
[Service]
ExecStart=...
InaccessibleDirectories=/home
ReadOnlyDirectories=/var
...

Isolating Services from the Network

若服務單元無需網絡服務,可將其與網絡隔離,以保證安全性。

PrivateNetwork= :

Takes a boolean argument. If true, sets up a new network namespace for the executed processes and configures only the loopback network device "lo" inside it. No other network devices will be available to the executed process. This is useful to securely turn off network access by the executed process. Defaults to false. It is possible to run two or more units within the same private network namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. Note that this option will disconnect all socket families from the host, this includes AF_NETLINK and AF_UNIX. The latter has the effect that AF_UNIX sockets in the abstract socket namespace will become unavailable to the processes (however, those located in the file system will continue to be accessible).

例:

...
[Service]
ExecStart=...
PrivateNetwork=yes
...

Service-Private /tmp

因爲/tmp目錄的權限問題,容易帶來一些安全問題,systemd中能夠在服務單元中設置私有/tmp目錄,可以使服務進程更加安全。

PrivateTmp=

Takes a boolean argument. If true, sets up a new file system namespace for the executed processes and mounts private /tmp and /var/tmp directories inside it that is not shared by processes outside of the namespace. This is useful to secure access to temporary files of the process, but makes sharing between processes via /tmp or /var/tmp impossible. If this is enabled, all temporary files created by a service in these directories will be removed after the service is stopped. Defaults to false. It is possible to run two or more units within the same private /tmp and /var/tmp namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. Note that using this setting will disconnect propagation of mounts from the service to the host (propagation in the opposite direction continues to work). This means that this setting may not be used for services which shall be able to install mount points in the main mount namespace.

注意:使用privatetmp以後,基於/tmp或/var/tmp目錄進行進程間通信(IPC)將不可用。

/privatetmp目錄位於/tmp目錄下,如圖privatetmp (在nginx.service服務單元中啓用了PrivateTmp):

privatetmp

例:

...
[Service]
ExecStart=...
PrivateTmp=yes
...

Capabilities

systemd還可對服務進程的Capabilities(能力)進行控制,相關的選項有Capabilities=,CapabilityBoundingSet=,SecureBits=。 具體請查看本節參考連接1與 capabilities 用戶手冊。

注:此部分選項未實驗。

服務進程運行中的資源限制

LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME=

以上選項可用於服務進程運行時的資源限制,選項具體意義請參考setrlimit

例:

...
[Service]
ExecStart=...
LimitNPROC=1
LimitFSIZE=0
...

查看setrlimit手冊,以下:

  • RLIMIT_NPROC
    • The maximum number of processes (or, more precisely on Linux,threads) that can be created for the real user ID of thecalling process.
  • RLIMIT_FSIZE
    • The maximum size of files that the process may create.Attempts to extend a file beyond this limit result in delivery of a SIGXFSZ signal

可知以上單元文件限制了此服務進程最多隻能有一個進程,即禁止了其fork,且此服務進程能建立的最大文件大小爲0。

設備節點訪問控制

DeviceAllow選項可指定服務進程可訪問的設備節點以及相關權限,

例如:

...
[Service]
ExecStart=...
DeviceAllow=/dev/null rw
...

該服務單元只對/dev/null有訪問權限,且權限爲讀寫。

另外還有PrivateDevices=,ProtectSystem=,ProtectHome=等等選項,具體用法請參考本節參考連接1。

本節參考連接

  1. systemd.exec
  2. Securing Your Services
  3. http://0pointer.de/blog/projects/changing-roots.html

監控與報警

思路:

  • service單元中的ExecStartPre與ExecStopPost等選項可用於監控與報警。關於service單元介紹請查看服務進程管理一節。
    • 服務啓動前可經過ExecStartPre執行腳本進行通知或作環境的初始化
    • 服務退出後,可經過ExecStopPost執行腳本,例如報警或清理工做。
    • 注:除了正常中止服務進程會觸發此選項指定的命令,即便是kill -9使服務進程異常停止,ExecStopPost指定的命令或腳本依舊成功運行,此特性可用於服務進程異常退出後的報警
    • 注:若ExecStart失敗,ExecStopPost將不會執行!若能在ExecStopPost中進行了某些清除工做,需注意是否會由於清除工做不徹底而帶來後續的進程啓動運行的異常。
    • 可是ExecStartPost在ExecStart執行失敗以後依舊可執行,能夠在此處檢測服務單元日誌狀態判斷ExecStart是否執行失敗而執行清理工做。因爲journal的日誌轉發到syslog是經過socket, 存在buffer,若讀取/var/log下日誌內容可能存在必定延時,因此請經過journal的接口查看服務單元日誌。
  • service單元中的FailureAction選項可指定服務進程進入failed狀態後的自動執行動做(操做系統的重啓、強制重啓或當即重啓),默認爲none。
  • 經過後臺自定義守護進程或輪詢任務檢測 systemd journal 日誌信息,對服務狀態進行監控和報警。關於日誌信息的過濾與查詢等內容請查看日誌的記錄、分類、分發和監控一節。
    • 注:使用systemctl查看特定進程systemctl status name.service,除了輸出服務進程基礎狀態,還可輸出關於此服務進程最近的10條日誌記錄。

經過服務單元中指定ExecStopPost等命令,在服務單元異常退出時候,經過ExecStopPost指定的程序檢測對應日誌可即時報警,須要注意目前若ExecStart執行失敗,EcecStopPost將不會執行(這多是一個bug,不知後續是否會修復)!

ExecStopPost以及ExecStartPost等命令中能夠傳入Environment=等選項配置的環境變量以及Unit單元中內置的特殊變量。前者請man systemd.exec查看Exviroment相關條目,後者請man systemd.unit查看Specifiers。 因此在ExecStopPost中,除了經過檢測日誌獲得服務進程信息做出報警,還能夠經過服務進程中設置的環境變量做爲參數傳入ExecStopPost,從而獲得主服務進程的相關信息而執行對應操做。

例:/etc/default/ssh 文件中指定了須要的環境變量($SSHD_OPTS),而$MAINPID是sshd daemon中設置的。

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure

[Install]
WantedBy=multi-user.target
Alias=sshd.service

另外經過後臺程序輪詢查看過濾分析各個服務單元的日誌也可作監控與報警,但因爲是輪詢可能存在必定的延時性。

本節參考連接

相關文章
相關標籤/搜索