利用systemd按需激活Docker容器
本文講述了一種在systemd下按需啟動docker容器的實現框架,給出了按需啟動nginx容器的一個實例。當在服務器配置不高的情況下,我們需要構建大量的容器時,這種方法非常有效。
systemd有這樣一個特性,它可以通過 socket激活進程延遲啟動網絡服務應用,直到此服務收到請求。這個方式并非首創,systemd借用的是 OS X自2005年 Tiger版本以來的 launched的實現思路,再往前追溯,古老的Unix inetd在 上世紀80年代就已經實現了這種啟動方式的一個簡單版本。不管是在腳本驅動還是在事件驅動的啟動系統中,Socket激活的方式都具有很多優點,最為顯著 的是,它能夠有效地對應用的啟動順序和服務描述進行解耦合,甚至可以解決一些應用的循環依賴問題。Docker容器(或者其他類型的應用服務例如 systemd's nspawn或 LXC)大都是一些專用的網絡進程,所以用socket激活的方式來啟動這些進程會比較有用。
Socket激活
Socket激活是這樣工作的:systemd的守護進程代表其他應用監聽相關的sockets,只有當連接傳入的時候才會啟動相應的應用服務,之后此連接交由新啟動的應用服務負責響應。
但是這里的限制之一是,需要被激活的應用知曉它是可以被socket激活的,并且對現有socket的處理——盡管 簡單——卻不同于從頭創建一個監聽socket。因此,很多被廣泛應用的軟件(如 ngix)并不支持這種方式,軟件容器化的趨勢添加了需要激活的層,這又進一步加劇了這種限制條件的影響。但是,可以通過將其交給容器的方式解決此問題,這對任意的容器化應用都適用。
Socket激活和Docker
如果你在Google查找 "docker container systemd socket activation" 你會發現相關的討論很少,大部分的結論是:除非Docker明確給出了這方面的支持,否則是不可能的。雖然Dcoker支持是最優的解決方式,但并不是全 部。Sytemd的開發者知道,想要達到在任意情況下都可以激活的目標,可能還需要些時日。因此,在 209版本中引入了 systemd-socket-proxyd——一個小的TCP和UNIX域socket代理。這個東西才是真正的理解激活的含義,它可以在網絡和我們的容器之間透明地轉發包。因此,借助少許的 units(systemd的配置系統),我們可以為Docker容器創建一個socket激活框架。警示:當前的Ubuntu發行版本systemd是208版本,還沒有systemd-socket- proxyd,要想嘗試下述例子需要配備systemd 209或更高版本,Debian的Jessie 預發行版本、基于RedHat的最新發行版本(如Fedora)應該也可以。
如何工作的
通常,我們用一個演示來簡化說明問題:我們看一下到底發生了什么事情:
- 我們創建了一個socket監聽相應的端口,該端口由代理/容器依事件驅動合并進行服務;
- 當第一個連接進來的時候,systemd激活代理服務來處理這個socket;
- 還有一個為容器提供的被動式服務——代理依賴于此服務,代理啟動之前首先要啟動這個容器;
- 代理負責在網絡和容器之前傳送所有的流量。
雖然需要一些技巧,但從概念上講相當簡單。那么我們以systemd和一個Nginx的容器來練習一下如何實現。
運行
創建容器
首先我們要創建目標容器。這里我們利用 官方nginx鏡像創建一個空的nginx容器。docker create --name nginx8080 -p 8080:80 nginx
這與你執行任何容器均類似,不過注意我們用的是 create而不是 run,因為我們并不想這個容器馬上啟動,我們還對此窗口進行了命名,以便于之后的啟動。
這里唯一一點技巧是你需要將此容器綁定到另外一個端口代替目標端口(這里我們用8080代替了80),這是因為這個端口(80)將由代理/容器持有,而且你不能將兩個進程同時綁定到同一個端口進行交互。
現在我們有了一個容器,這樣便可以創建激活管道了。首先我們需要初始的監聽socket也就是 socket unit,這個socket的行為是高度可配置的,在我們的例子中它的創建相當簡單。
[Socket] ListenStream=80 [Install] WantedBy=socktes.target
[Socket]節描述一個簡單的TCP socket用于監聽80端口, [Install]節告訴systemd何時啟動這個socket,在此例中它與系統配置的其他socket一起啟動。我們將此unit寫入名為 /etc/systedm/system/nginx-proxy.socket的文件中,這是鏈條中唯一需要啟動后激活的部分,因此我們需要告訴systemd啟動它:
systemctl enable nginx-proxy.socket systemctl start nginx-proxy.socket
代理服務
當systemd接收到此socket上的連接時,它將自動查找并啟動同名 服務。因此,我們需要創建服務文件 /etc/systemd/systedm/nginx-proxy.service:[Unit] Requires=nginx-docker.service After=nginx-docker.service[Service] ExecStart=/lib/systemd/systemd-socket-proxyd 127.0.0.1:8080</pre>
[Unit]節描述了此服務的依賴,此例中我們要告訴systemd,在代理啟動之前必須先將實際的容器啟動(下面我們將要配置此服務)。 [Section]節負責啟動代理進程,當前此節還可以配置 大量重要內容, 比如對進程啟動失敗的處理,但是對于我們來說,簡單的啟動一下就OK了。注意我們將socket轉發到了8080端口,這是我們的容器將要監聽的端口;我 們也并沒有告訴systemd監聽80端口,它只是用了systemd處理的socket。還要注意到上述配置文件中沒有 [Install]節,此服務并非是缺省啟動,而是由socket激活它。啟動Docker容器
正如前面提到了,代理利用 Requre/After機制觸發了容器的啟動,此容器的啟動文件在 /etc/systemd/system/nginx-docker.service,它是這樣的:
[Unit] Description=nginx container[Service] ExecStart=/usr/bin/docker start -a nginx8080 ExecStartPost=/bin/sleep 1
ExecStop=/usr/bin/docker stop nginx8080</pre>
基本概念與上述代理服務相同, ExecStart行告訴systemd如何啟動這個容器,systemd偏愛不在后臺運行的進程,所以我們添加了 -a參數,它可在前臺運行此容器,并且可將nginx的運行日志轉發到 journaldlogger中, ExecStop告訴systemd當運行 systemctl stop ngix-docker時如何停止容器。
這里我們還利用 ExecStartPost行耍了一個小聰明,這是systemd在主進程啟動后馬上運行的進程,此例中,我們 在繼續往下走之前讓其sleep了1秒鐘。這是必要的,因為盡管docker啟動容器非常快,但是容器中的進程可能需要稍微長一點的時間完成初始化。 systemd也非常快,所以有可能在nginx準備好接收之前,代理就開始轉發了。因此我們添加了一點延遲,給Dcoker/nginx一個空閑啟動, 這有點耍賴但是很有效(但是看 下面)。一切就緒
好了,當systemd啟動的時候會開啟一個socket,當第一個連接到達時,級聯的依賴關系會使nginx Docker通過代理對此連接進行響應。Easy。
容器服務改進
任何想要優化系統啟動時間的人都恨死了在代碼或配置文件中隨意添加的 sleep語句。他們要么是因系統很快而不需要此延遲,要么是因此引起了很多難以定位的隨機錯誤。上述 ExecStartPost中的 sleep語句也同樣讓我神經過敏,所以我也想把它刪了。我們真正想做的是檢查端口是否已啟動,只有它沒啟動的時候我們才需要 sleep。我們還想在端口啟動時間過長的時候返回失敗。為了做到這些,我使用了 netcat和一個wrapper腳本。
#!/bin/bashhost=$1 port=$2 tries=600
for i in
seq $tries
; do if /bin/nc -z $host $port > /dev/null ; then # Ready exit 0 fi/bin/sleep 0.1 done
FAIL
exit -1</pre>
這個腳本需要一個host和port的參數,檢查一下是否有響應,每秒鐘檢查10次,如果1分鐘之內還沒有響應就返回失敗。我們將此腳本安裝到 /usr/local/bin/waitport(設置其為可運行)。現在我們的 nginx-docker.service文件是這樣的:[Unit] Description=nginx container[Service] ExecStart=/usr/bin/docker start -a nginx8080 ExecStartPost=/usr/local/bin/waitport 127.0.0.1 8080
ExecStop=/usr/bin/docker stop nginx8080</pre>
也就是說,waitport腳本可以根據你的系統應用靈活調整,如果你的容器啟動速度很快的話通常都會立即返回。
原文鏈接:On-demand activation of Docker containers with systemd(翻譯:deerlux 校對:夕口夕)
================
譯者介紹:< deerlux@163.com>,現就職于一家軍工科研機構,深度的技術控、Linux控、python控。
來自: http://dockerone.com/article/232