利用systemd按需激Docker容器

原文出自這裏,由[email protected]於2015年3月8日翻譯。

能夠延遲啓動網絡服務應用直到此服務被請求時,是systemd的一個特性,它通過一個socket激活的進程實現。這倒並不是一個新的創意,systemd借用了OS X自2005年Tiger版本以來的launched的實現思路,再往前追溯,古老的Unix inetd在上世紀80年代就實現了這種啓動方式的一個簡單版本。不管是在腳本驅動還是在事件驅動的啓動系統中,Socket激活的方式都具有很多優點,尤其是它能夠有效地對應用的啓動順序和服務描述進行解耦合,甚至可以解決一些應用的循環依賴問題。Docker容器(或者其他類型的應用服務例如systemd’s nspawnLXC)大都是一些專用的網絡進程,所以將socket激活的方式來啓動這些進程會比較有用。

Socket激活

Creation of Adam

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 pre-release版本可以,基於RedHat的最新的發行版如Fedora應該也可以。

如何工作的

通常,我們用一個演示來簡化說明問題:

system activation animation

我們看一下到底發生了什麼事情:

  1. 我們創建了一個socket監聽相應的端口,它是由代理/容器依事件驅動合併進行服務的;
  2. 當第一個連接來的時候,systemd激活了代理服務用來處理這個socket;
  3. 還有一個由容器提供的被動式服務——代理依賴於此服務,代理啓動之前首先要啓動這個容器;
  4. 代理負責在網絡和容器之前傳送所有的流量。

雖然需要一些技巧,但從概念上講這是相當簡單的,那麼我們以systemd和一個ngix的容器來練習一下如何實現。

使其運行

創建容器

首先我們要創建目標容器,這裏我們利用官方鏡像創建一個空的ngix容器。

docker create --name ngix8080 -p 8080:80 ngix

這與你執行任何容器均類似,不過注意我們用的是create而不是run,因爲我們並不想這個容器馬上啓動,我們還對此窗口進行了命名以便於後面啓動它。

這裏唯一一點技巧是你需要將此容器綁定到另外一個端口(這裏我們用的是8080代替的80),這是因爲這個端口(80)

現在我們有了一個容器,這樣便可以創建激活管道,首先我們需要初始的監聽socket也就是socket unit,這個socket的行爲是高度可配置的,但是我們的例子中非常簡單。

[Socket]
ListenStream=80
[Install]
WantedBy=socktes.target

[Socket]節表示一個簡單的80端口的和TCP監聽,[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

[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

與上述代理服務的基本概念相同,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/bash

host=$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

這個腳本需要一個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

也就是說,waitport腳本可以根據你的系統應用靈活調整,如果你的容器啓動速度很快的話通常都會立即返回。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章