Service文件
開門見山,直接來看兩個實際的服務配置文件吧。
第一個配置是 CoreOS 系統中 Docker 服務的 Unit 文件,路徑是 /usr/lib/systemd/system/docker.service,可以看到其中的內容相當精簡易讀。
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=docker.socket early-docker.target network.target
Requires=docker.socket early-docker.target
[Service]
Environment=TMPDIR=/var/tmp
Environment=DOCKER_OPTS='--insecure-registry="0.0.0.0/0"'
EnvironmentFile=-/run/docker_opts.env
LimitNOFILE=1048576
LimitNPROC=1048576
ExecStart=/usr/lib/coreos/dockerd --daemon --host=fd:// $DOCKER_OPTS
[Install]
WantedBy=multi-user.target
第二個配置的寫法風格與前一個有所差異,但同樣的內容清晰,條理明確。這個配置來自 CoreOS 的一篇文檔,作用是啟動一個 Apache 服務容器然后將服務的運行信息注冊到 Etcd 中。
(注意,這篇文檔原文中的示例中似乎有一個錯誤,在啟動 docker 時,ExecStart 中的命令參數 -p 80:80 應當為 -p 8081:80,下面代碼已修正)
[Unit]
Description=My Advanced Service
After=etcd.service
After=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill apache1
ExecStartPre=-/usr/bin/docker rm apache1
ExecStartPre=/usr/bin/docker pull coreos/apache
ExecStart=/usr/bin/docker run --name apache1 -p 8081:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/10.10.10.123:8081 running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/10.10.10.123:8081
[Install]
WantedBy=multi-user.target
仔細觀察着兩個服務配置,其中有一些很明顯的共同點。我們接下來就以這兩個 Unit 文件為例,一步步的分析一下 Systemd 服務配置的寫法。
Service 的 Unit 文件可以分為3個配置區段,其中 Unit 和 Install 段是所有 Unit 文件通用的,用於配置服務(或其他系統資源)的描述、依賴和隨系統啟動方式。而 Service 段則是服務類型的 Unit 文件(后綴.service)特有的,用於定義服務的具體管理和操作方法。其他的每種配置文件也都會有一個特有的配置段,這就是幾種不同 Unit 配置文件最明顯的區別。
來看看每個配置段常用的參數有哪些。
一、Unit 段
Description
一段描述這個 Unit 文件的文字,通常只是簡短的一句話。
Documentation
指定服務的文檔,可以是一個或多個文檔的URL路徑。
Requires
依賴的其他 Unit 列表,列在其中的 Unit 模塊會在這個服務啟動的同時被啟動,並且如果其中有任意一個服務啟動失敗,這個服務也會被終止。
Wants
與 Requires 相似,但只是在被配置的這個 Unit 啟動時,觸發啟動列出的每個 Unit 模塊,而不去考慮這些模塊啟動是否成功。
After
與 Requires 相似,但會在后面列出的所有模塊全部啟動完成以后,才會啟動當前的服務。
Before
與 After 相反,在啟動指定的任一個模塊之前,都會首先確保當前服務已經運行。
BindsTo
與 Requires 相似,但是一種更強的關聯。啟動這個服務時會同時啟動列出的所有模塊,當有模塊啟動失敗時終止當前服務。反之,只要列出的模塊全部啟動以后,也會自動啟動當前服務。並且這些模塊中有任意一個出現意外結束或重啟,這個服務會跟着終止或重啟。
PartOf
這是一個 BindTo 作用的子集,僅在列出的任何模塊失敗或重啟時,終止或重啟當前服務,而不會隨列出模塊的啟動而啟動。
OnFailure
當這個模塊啟動失敗時,就自動啟動列出的每個模塊。
Conflicts
與這個模塊有沖突的模塊,如果列出模塊中有已經在運行的,這個服務就不能啟動,反之亦然。
上面這些配置中,除了 Description 外,都能夠被添加多次。比如前面第一個例子中的After參數在一行中使用空格分隔指定所有值,也可以像第二個例子中那樣使用多個After參數,在每行參數中指定一個值。
二、Install 段
這個段中的配置與 Unit 有幾分相似,但是這部分配置需要通過 systemctl enable 命令來激活,並且可以通過 systemctl disable 命令禁用。另外這部分配置的目標模塊通常是特定啟動級別的 .target 文件,用來使得服務在系統啟動時自動運行。
WantedBy
和前面的 Wants 作用相似,只是后面列出的不是服務所依賴的模塊,而是依賴當前服務的模塊。
RequiredBy
和前面的 Requires 作用相似,同樣后面列出的不是服務所依賴的模塊,而是依賴當前服務的模塊。
Also
當這個服務被 enable/disable 時,將自動 enable/disable 后面列出的每個模塊。
上面的兩個例子中使用的都是 “WantedBy=multi-user.target” 表明當系統以多用戶方式(默認的運行級別)啟動時,這個服務需要被自動運行。當然還需要 systemctl enable 激活這個服務以后自動運行才會生效。關於 Linux 系統啟動時的運行級別,可以參看這篇文章。
三、Service 段
這個段是 .service 文件獨有的,也是對於服務配置最重要的部分。這部分的配置選項非常多,主要分為服務生命周期控制和服務上下文配置兩個方面,下面是比較常用到的一些參數。
服務生命周期控制相關的參數:
Type
服務的類型,常用的有 simple(默認類型) 和 forking。默認的 simple 類型可以適應於絕大多數的場景,因此一般可以忽略這個參數的配置。而如果服務程序啟動后會通過 fork 系統調用創建子進程,然后關閉應用程序本身進程的情況,則應該將 Type 的值設置為 forking,否則 systemd 將不會跟蹤子進程的行為,而認為服務已經退出。
RemainAfterExit
值為 true 或 false(也可以寫 yes 或 no),默認為 false。當配置值為 true 時,systemd 只會負責啟動服務進程,之后即便服務進程退出了,systemd 仍然會認為這個服務是在運行中的。這個配置主要是提供給一些並非常駐內存,而是啟動注冊后立即退出然后等待消息按需啟動的特殊類型服務使用
ExecStart
這個參數是幾乎每個 .service 文件都會有的,指定服務啟動的主要命令,在每個配置文件中只能使用一次。
ExecStartPre
指定在啟動執行 ExecStart 的命令前的准備工作,可以有多個,如前面第二個例子中所示,所有命令會按照文件中書寫的順序依次被執行。
ExecStartPost
指定在啟動執行 ExecStart 的命令后的收尾工作,也可以有多個。
TimeoutStartSec
啟動服務時的等待的秒數,如果超過這個時間服務任然沒有執行完所有的啟動命令,則 systemd 會認為服務自動失敗。這一配置對於使用 Docker 容器托管的應用十分重要,由於 Docker 第一次運行時可以能會需要從網絡下載服務的鏡像文件,因此造成比較嚴重的延時,容易被 systemd 誤判為啟動失敗而殺死。通常對於這種服務,需要將 TimeoutStartSec 的值指定為 0,從而關閉超時檢測,如前面的第二個例子。
ExecStop
停止服務所需要執行的主要命令。
ExecStopPost
指定在 ExecStop 命令執行后的收尾工作,也可以有多個。
TimeoutStopSec
停止服務時的等待的秒數,如果超過這個時間服務仍然沒有停止,systemd 會使用 SIGKILL 信號強行殺死服務的進程。
Restart
這個值用於指定在什么情況下需要重啟服務進程。常用的值有 no,on-success,on-failure,on-abnormal,on-abort 和 always。默認值為 no,即不會自動重啟服務。這些不同的值分別表示了在哪些情況下,服務會被重新啟動,參見下表。
服務退出原因
no
always
on-failure
on-abnormal
on-abort
no-success
正常退出
√
√
異常退出
√
√
啟動/停止超時
√
√
√
被異常KILL
√
√
√
√
RestartSec
如果服務需要被重啟,這個參數的值為服務被重啟前的等待秒數。
ExecReload
重新加載服務所需執行的主要命令。
服務上下文配置相關的參數:
Environment
為服務添加環境變量,如前面的第一個例子中所示。
EnvironmentFile
指定加載一個包含服務所需的環境變量列表的文件,文件中的每一行都是一個環境變量的定義。
Nice
服務的進程優先級,值越小優先級越高,默認為0。-20為最高優先級,19為最低優先級。
WorkingDirectory
指定服務的工作目錄。
RootDirectory
指定服務進程的根目錄( / 目錄),如果配置了這個參數后,服務將無法訪問指定目錄以外的任何文件。
User
指定運行服務的用戶,會影響服務對本地文件系統的訪問權限。
Group
指定運行服務的用戶組,會影響服務對本地文件系統的訪問權限。
LimitCPU / LimitSTACK / LimitNOFILE / LimitNPROC 等
限制特定服務可用的系統資源量,例如 CPU,程序堆棧,文件句柄數量,子進程數量… 不再展開說明了,值的含義可參考 Linux 文檔資源配額部分中 RLIMIT_ 開頭的那些參數們。
列完這么一大推參數的我也是醉了(這些都是常用的參數,不常用的還沒寫咧),但其實嘛,Systemd 的精華也就在此了。再仔細一推敲,這么些冗長的參數之間還是有些規律的,並且大多可以望文生義,因此寫 Unit 文件的差事本身倒並不讓人覺得枯燥。反觀過去需要學習N種不同配置格式來管理N種不同的系統資源的方法,Systemd的理念實在是先進了太多了。而這些參數雲雲,大概只有用得多了,才會覺得它們看起來不那么討厭了吧o(//_//)o
Fleet 的 X-Fleet 段
前面討論的都是 Systemd 使用的 Unit 文件。在這個系列的 Fleet 那篇中,演示了 Fleet 中的服務配置。
Fleet 的 Unit 服務描述文件,實際上就是 Systemd 的 .service 配置文件的翻版。但為了方便服務在集群環境的自適應管理,Fleet 在 Systemd 的 Unit 配置基礎上添加了一個 X-Fleet 段,專門用於描述服務應該被分配到集群的哪些節點啟動。它的可用參數只有5個,可以請出來一一亮相。
MachineID
直接了當的告訴 Fleet 這個服務只能運行在特定的一個節點上,注意這里的值必須是完整的節點 ID,這個 ID 可以通過 “fleetctl list-machines -l” 命令獲得。
MachineOf
值是另一個 .service 文件,表示當前服務必須運行在與指定的這個服務在同一個節點上。
MachineMetadata
值是一個節點的 Metadata 內容,例如 "region=us-east-1" 。這些 Metadata 是在啟動節點時通過 Cloudinit 寫進去的,具體方法在系列的 Fleet 那篇文章有提及。這個參數可以使用多次,或在通過空格分隔將多個值同時傳進去。
Conflicts
值是一個 .service 文件的,Conflicts參數也可以使用多次,並且其值可以使用通配符,例如 apache* 表示所有以 “apache” 開頭的服務。
Global
如果值為 true,則這個服務會被部署到集群中符合 MachineMetadata限定條件的每一個節點上。注意,當 Global 值為 true 時,除了 MachineMetadata外的所有其他約束條件都會被忽略。
前四個參數在 Fleet v0.8 版本前被命名為 X-ConditionMachineID、X-ConditionMachineOf、X-ConditionMachineMetadata和 X-Conflicts,這些寫法現在已經停止使用了,但仍然可能會在一些早期的文檔或網絡文章中出現,如果看見了,淡定的飄過吧。
Unit模板
在現實中,往往有一些應用需要被復制多份運行,例如在一個負載均衡實例后面運行的多個相同的服務實例。但是按照之前的例子,每個服務都需要一個單獨的 Unit 文件,這樣復制多份相同文件的做顯然不便於服務的管理。為此 Systemd 定義了一種特殊的 Service Unit文件,稱為 Unit 模板。
模板文件的主要特點是,文件名以@符號結尾,而啟動的時候指定的Unit名稱為模板名稱附加一個參數字符串。例如,將之前的例子第二個 Unit 文件修改為可以用於啟動多個實例的模板。
一、首先修改文件名,添加一個@符號
例如原來的文件名是 apache.service,那么可以將它修改為 apache@.service,這樣做的目的是表面這個文件是一個模板文件。而在服務啟動時可以在@后面放置一個用於區分服務實例的附加字符串參數,通常這個參數會使用監聽的端口號或使用的控制台TTY編號等。例如 “systemctl start apache@8080.service”。
二、然后修改 Unit 文件內容
Unit 文件中可以獲取服務啟動時的附加參數,因此通常需要修改 Unit 文件中不應固定的部分,例如服務監聽的 IP 和端口,替換為從附加參數中獲取。
[Unit]
Description=My Advanced Service Template
After=etcd.servicedocker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull coreos/apache
ExecStart=/usr/bin/docker run --name apache%i -p %i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/%H:%i running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/%H:%i
[Install]
WantedBy=multi-user.target
仔細觀察一下變化了的地方,上面使用到了占位符 %H 和 %i,常用的占位符有6種(一共19種,其余不怎么常用的查文檔吧),這些占位符會在 Unit 啟動時被實際的值動態的替換掉。
占位符
作用
%n
完整的 Unit 文件名字,包括 .service 后綴名
%m
實際運行的節點的 Machine ID,適合用來做Etcd路徑的一部分,例如 /machines/%m/units
%b
作用有點像 Machine ID,但這個值每次節點重啟都會改變,稱為 Boot ID
%H
實際運行節點的主機名
%p
Unit 文件名中在 @ 符號之前的部分,不包括 @ 符號
%i
Unit 文件名中在 @ 符號之后的部分,不包括 @ 符號和 .service 后綴名
順帶一提,這些參數中除了 %i 以外,同樣可以用於非模板的 Unit 文件中。%p 在普通 Unit 文件中會被動態替換為服務名稱去掉 .service 后綴的名字。
三、啟動 Unit 模板的服務實例
模板服務的啟動對於 Systemd 和 Fleet 大致相同。
Systemd 的情況略簡單一些,只需要運行時加上后綴參數。例如 “systemctl start apache@8080.service”。Systemd 首先會在其特定的目錄下尋找名為 apache@8080.service的文件,如果沒有找到,而文件名中包含@字符,它就會嘗試去掉后綴參數匹配模板文件。例如沒有找到 apache@8080.service,那么Systemd會找到apache@.service,並將它通過模板文件中實例化。
Fleet 沒有特定的 Unit 文件存放目錄,不過在通過 fleetctl start 或 fleetctl submit 命令指定 Unit 文件路徑時加上后綴參數,Fleet 同樣會自動匹配去掉后綴參數后的模板文件。例如 “fleetctl submit ${HOME}/apache@8080.service”,就會匹配到 ${HOME} 目錄下面的 apache@.service 模板文件。
后續綜合案例的文章中,還會結合實際例子詳細的介紹模板的使用場景。
小結
這一篇的內容略為零碎,主要是對 CoreOS 中的系統資源和服務起着管理作用的 Unit 配置文件做了比較深入的說明。特別是最后的 Unit 模板部分在一定程度上賦予了服務橫向拓展的能力,在實際的項目環境中使用得相當普遍。這些系統管理方面的技巧,需要一定的實戰磨練才能體會其中的好處。
最近有同事問我,介紹了這么多的 CoreOS,能否用一句話來評述一下這個系統,以及它最適用於什么樣的應用場景呢?
對於第一個問題,CoreOS 並不是什么神秘的銀彈,它只是一個“理念比較先進(具體見系列第一篇)”並且“對集群和應用容器比較友好”的服務器 Linux 發行版。有些人會追問,要是把 CoreOS 和其他發行版進行對比哪個好用呢。這個…專注的領域不一樣啊,CoreOS 永遠也不會替代 Fedora 和 Ubuntu 這些桌面 Linux 發行版的地位,因為它實際上是一個高度精簡的沒有 GUI 和 x-window 的操作系統(但並不是說 CoreOS 不能夠提供需要 GUI 的服務,因為可以在容器中安裝 x-window 和 VNC 服務)。
對於第二個問題,其實是沒有准確答案的,Linux 系統發行版的選擇完全是個人偏好問題。普遍來說,基於 CoreOS 的自動無縫升級和對容器和集群友好的特性,它會比較適用於需要長期運行,並且具備橫向擴展架構,特別是 Micro Service 架構的以對外提供服務為目的的集群。但並不是說 CoreOS 就不能用在一般的服務器場景,美國的SaaS雲服務網站Iron.io和購物網站Shopify都使用了CoreOS 作為其業務支撐的平台,它們的業務場景除了都使用大規模的集群外,各方面都很不一樣。
