服務是自 Docker 1.12 后新引入的概念,并且僅適用于 Swarm 模式。
使用服務仍能夠配置大多數熟悉的容器屬性,比如容器名、端口映射、接入網絡和鏡像。此外還增加了額外的特性,比如可以聲明應用服務的期望狀態,將其告知 Docker 后,Docker 會負責進行服務的部署和管理。
舉例說明,假如某應用有一個 Web 前端服務,該服務有相應的鏡像。測試表明對于正常的流量來說 5 個實例可以應對。那么就可以將這一需求轉換為一個服務,該服務聲明了容器使用的鏡像,并且服務應該總是有 5 個運行中的副本。
下面通過示例來看看如何創建剛剛描述的內容。
使用 docker service create 命令創建一個新的服務。
在 Windows 上創建新服務的命令也是一樣的。然而本例中使用的是 Linux 鏡像,它在 Windows 上并不能運行。請使用 Windows 的小伙伴將鏡像替換為一個 Windows Web Server 的鏡像,以便能正常運行。
再次強調,在 PowerShell 終端中輸入命令的時候,使用反引號(`)進行換行。
$ docker service create --name web-fe \
-p 8080:8080 \
--replicas 5 \
nigelpoulton/pluralsight-docker-ci
z7ovearqmruwk0u2vc5o7ql0p
需要注意的是,該命令與熟悉的 docker container run 命令的許多參數是相同的。這個例子中,使用 --name 和 -p 定義服務的方式,與單機啟動容器的定義方式是一樣的。
通過上面的命令和輸出可以看出。使用 docker service creale 命令告知 Docker 正在聲明一個新服務,并傳遞 --name 參數將其命名為 web-fe。將每個節點上的 8080 端口映射到服務副本內部的 8080 端口。接下來,使用 --replicas 參數告知 Docker 應該總是有 5 個此服務的副本。最后,告知 Docker 哪個鏡像用于副本,重要的是,要了解所有的服務副本使用相同的鏡像和配置。
敲擊回車鍵之后,主管理節點會在 Swarm 中實例化 5 個副本,管理節點也會作為工作節點運行。相關各工作節點或管理節點會拉取鏡像,然后啟動一個運行在 8080 端口上的容器。
這還沒有結束。所有的服務都會被 Swarm 持續監控,Swarm 會在后臺進行輪訓檢查(Reconciliation Loop),來持續比較服務的實際狀態和期望狀態是否一致。如果一致,則無須任何額外操作;如果不一致,Swarm 會使其一致。換句話說,Swarm 會一直確保實際狀態能夠滿足期望狀態的要求。
舉例說明,假如運行有 web-fe 副本的某個工作節點宕機了,則 web-fe 的實際狀態從 5 個副本降為 4 個,從而不能滿足期望狀態的要求。Docker 變回啟動一個新的 web-fe 副本來使實際狀態與期望狀態保持一致。這一特性功能強大,使得服務在面對節點宕機等問題時具有自愈能力。
⒈ 查看服務
使用 docker service ls 命令可以查看 Swarm 中所有運行中的服務。
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z7o...uw web-fe replicated 5/5 nigel...ci:latest *:8080->8080/tcp
輸出顯示有一個運行中的服務及其相關狀態信息。比如,可以了解服務的名稱,以及 5 個期望的副本(容器)中有 5 個是運行狀態。
如果在部署服務后立即執行該命令,則可能并非所有的副本都處于運行狀態。這通常取決于各個節點拉取鏡像的時間。
執行 docker service ps 命令可以查看服務副本列表及各副本的狀態。
$ docker service ps web-fe
ID NAME IMAGE NODE DESIRED CURRENT
817...f6z web-fe.1 nigelpoulton/... mgr2 Running Running 2 mins
a1d...mzn web-fe.2 nigelpoulton/... wrk1 Running Running 2 mins
cc0...ar0 web-fe.3 nigelpoulton/... wrk2 Running Running 2 mins
6f0...azu web-fe.4 nigelpoulton/... mgr3 Running Running 2 mins
dyl...p3e web-fe.5 nigelpoulton/... mgr1 Running Running 2 mins
此命令格式為 docker service ps <service-name or serviceid>。每一個副本會作為一行輸出,其中顯示了各副本分別運行在 Swarm 的哪個節點上,以及期望的狀態和實際狀態。
關于服務更為詳細的信息可以使用 docker service inspect 命令查看。
$ docker service inspect --pretty web-fe
ID: z7ovearqmruwk0u2vc5o7ql0p
Name: Service
Mode: Replicated
Replicas: 5
Placement:
UpdateConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
Image: nigelpoulton/pluralsight-docker-ci:latest@sha256:7a6b01...d8d3d
Resources: Endpoint
Mode: vip Ports:
PublishedPort = 8080
Protocol = tcp
TargetPort = 8080
PublishMode = ingress
以上例子使用了 --pretty 參數,限制輸出中僅包含最感興趣的內容,并以易于閱讀的格式打印出來。
不加 --pretty 的話會給出更加詳盡的輸出。建議大家通讀 docker inspect 命令的輸出內容,其中不僅包含大量信息,也是了解底層運行機制的途徑。
⒉ 副本服務 vs 全局服務
服務的默認復制模式(Replication Mode)是副本模式(replicated)。
這種模式會部署期望數量的服務副本,并盡可能均勻地將各個副本分布在整個集群中。
另一種模式是全局模式(global),在這種模式下,每個節點上僅運行一個副本。可以通過給 docker service create 命令傳遞 --mode global 參數來部署一個全局服務。
⒊ 服務的擴縮容
服務的另一個強大特性是能夠方便地進行擴縮容。
假設業務呈爆發式增長,則 Web 前端服務接收到雙倍的流量壓力。所幸通過一個簡單的 docker service scale 命令即可對 web-fe 服務進行擴容。
$ docker service scale web-fe=10
web-fe scaled to 10
該命令會將服務副本數由 5 個增加到 10 個。后臺會將服務的期望狀態從 5 個增加到 10 個。
運行 docker service ls 命令來檢查操作是否成功。
$ docker service ls
ID NAME NODE REPLICAS IMAGE PORTS
z7o...uw web-fe replicated 10/10 nigel...ci:latest *:8080->8080/tcp
執行 docker service ps 命令會顯示服務副本在各個節點上是均衡分布的。
$ docker service ps web-fe
ID NAME IMAGE NODE DESIRED CURRENT
nwf...tpn web-fe.1 nigelpoulton/... mgr1 Running Running 7 mins
yb0...e3e web-fe.2 nigelpoulton/... wrk3 Running Running 7 mins
mos...gf6 web-fe.3 nigelpoulton/... wrk2 Running Running 7 mins
utn...6ak web-fe.4 nigelpoulton/... wrk3 Running Running 7 mins
2ge...fyy web-fe.5 nigelpoulton/... mgr3 Running Running 7 mins
64y...m49 web-fe.6 igelpoulton/... wrk3 Running Running about a min
ild...51s web-fe.7 nigelpoulton/... mgr1 Running Running about a min
vah...rjf web-fe.8 nigelpoulton/... wrk2 Running Running about a mins
xe7...fvu web-fe.9 nigelpoulton/... mgr2 Running Running 45 seconds ago
l7k...jkv web-fe.10 nigelpoulton/... mgr2 Running Running 46 seconds ago
在底層實現上,Swarm 執行了一個調度算法,默認將副本盡量均衡分配給 Swarm 中的所有節點。
各節點分配的副本數是平均分配的,并未將 CPU 負載等指標考慮在內。
再次執行 docker service scale 命令將副本數從 10 個降為 5 個。
$ docker service scale web-fe=5
web-fe scaled to 5
⒋ 刪除服務
刪除一個服務的操作相對比較簡單。
如下 docker service rm 命令可用于刪除之前部署的服務。
$ docker service rm web-fe
web-fe
執行 docker service ls命令以驗證服務確實已被刪除。
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
請謹慎使用 docker service rm 命令,因為它在刪除所有服務副本時并不會進行確認。
⒌ 滾動升級
對部署的應用進行滾動升級是常見的操作。長期以來,這一過程是令人痛苦的。我曾經犧牲了許多的周末時光來進行應用程序主版本的升級,而且再也不想這樣做了。
然而,多虧了 Docker 服務,對一個設計良好的應用來說,實施滾動升級已經變得簡單多了!
為了演示如何操作,下面將部署一個新的服務。但是在此之前,先創建一個新的覆蓋網絡(Overlay Network)給服務使用。
這并非必須的操作,只是希望能夠讓大家了解如何創建網絡并將服務接入網絡。
$ docker network create -d overlay uber-net
43wfp6pzea470et4d57udn9ws
該命令會創建一個名為 uber-net 的覆蓋網絡,接下來會將其與要創建的服務結合使用。覆蓋網絡是一個二層網絡,容器可以接入該網絡,并且所有接入的容器均可互相通信。
即使這些容器所在的 Docker 主機位于不同的底層網絡上,該覆蓋網絡依然是相通的。本質上說,覆蓋網絡是創建于底層異構網絡之上的一個新的二層容器網絡。
如下圖所示,兩個底層網絡通過一個三層交換機連接,而基于這兩個網絡之上是一個覆蓋網絡。
Docker 主機通過兩個底層網絡相連,而容器則通過覆蓋網絡相連。對于同一覆蓋網絡中的容器來說,即使其各自所在的 Docker 主機接入的是不同的底層網絡,也是互通的。
執行 docker network ls 來查看網絡是否創建成功,且在 Docker 主機可見。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
43wfp6pzea47 uber-net overlay swarm
可見,uber-net 網絡已被成功創建,其 SCOPE 為 swarm,并且目前僅在 Swarm 的管理節點可見。
下面創建一個新的服務,并將其接入 uber-net 網絡。
$ docker service create --name uber-svc \
--network uber-net \
-p 80:80 --replicas 12 \
nigelpoulton/tu-demo:v1
dhbtgvqrg2q4sg07ttfuhg8nz
看一下上面的 docker service create 命令中做了哪些聲明。
首先,將服務命名為 uber-svc,并用 --network 參數聲明所有的副本都連接到 uber-net 網絡。
然后,在整個 swarm 中將 80 端口暴露出來,并將其映射到 12 個容器副本的 80 端口。
最后,聲明所有的副本都基于 nigelpoulton/tu-demo:v1 鏡像。
執行 docker service ls 和 docker service ps 命令以檢查新創建服務的狀態。
$ docker service ls
ID NAME REPLICAS IMAGE
dhbtgvqrg2q4 uber-svc 12/12 nigelpoulton/tu-demo:v1
$ docker service ps uber-svc
ID NAME IMAGE NODE DESIRED CURRENT STATE
0v...7e5 uber-svc.1 nigelpoulton/...:v1 wrk3 Running Running 1 min
bh...wa0 uber-svc.2 nigelpoulton/...:v1 wrk2 Running Running 1 min
23...u97 uber-svc.3 nigelpoulton/...:v1 wrk2 Running Running 1 min
82...5y1 uber-svc.4 nigelpoulton/...:v1 mgr2 Running Running 1 min
c3...gny uber-svc.5 nigelpoulton/...:v1 wrk3 Running Running 1 min
e6...3u0 uber-svc.6 nigelpoulton/...:v1 wrk1 Running Running 1 min
78...r7z uber-svc.7 nigelpoulton/...:v1 wrk1 Running Running 1 min
2m...kdz uber-svc.8 nigelpoulton/...:v1 mgr3 Running Running 1 min
b9...k7w uber-svc.9 nigelpoulton/...:v1 mgr3 Running Running 1 min
ag...v16 uber-svc.10 nigelpoulton/...:v1 mgr2 Running Running 1 min
e6...dfk uber-svc.11 nigelpoulton/...:v1 mgr1 Running Running 1 min
e2...k1j uber-svc.12 nigelpoulton/...:v1 mgr1 Running Running 1 min
通過對服務聲明 -p 80:80 參數,會建立 Swarm 集群范圍的網絡流量映射,到達 Swarm 任何節點 80 端口的流量,都會映射到任何服務副本的內部 80 端口。
默認的模式,是在 Swarm 中的所有節點開放端口,稱為入站模式(Ingress Mode)。此外還有主機模式(Host Mode),即僅在運行有容器副本的節點上開放端口。
以主機模式開放服務端口,需要較長格式的聲明語法,代碼如下。
docker service create --name uber-svc \
--network uber-net \
--publish published=80,target=80,mode=host \
--replicas 12 \
nigelpoulton/tu-demo:v1
打開瀏覽器,使用 Swarm 中任何一個節點的 IP,進入 80 端口的界面,查看服務運行情況,如下圖所示。
如上圖所示,這是一個簡單的投票程序,它能夠注冊對“footbal”或“soccer”的投票。可隨意在瀏覽器中使用其他節點的 IP,均能夠打開該頁面,因為 -p 80:80 參數會在所有 Swarm 節點創建一個入站模式的端口映射。
即使某個節點上并未運行服務的副本,依然可以進入該頁面,所有節點都配置有映射,因此會將請求轉發給運行有服務副本的節點。
假設本次投票已經結束,而公司希望開啟一輪新的投票。現在已經為下一輪投票構建了一個新鏡像,并推送到了 Docker Hub 倉庫,新鏡像的 tag 由 v1 變更為 v2。
此外還假設,本次升級任務在將新鏡像更新到 Swarm 中時采用一種階段性的方式,每次更新兩個副本,并且中間間隔 20s。
那么就可以采用如下的 docker service update 命令來完成。
$ docker service update \
--image nigelpoulton/tu-demo:v2 \
--update-parallelism 2 \
--update-delay 20s uber-svc
仔細觀察該命令,docker service update 通過變更該服務期望狀態的方式來更新運行中的服務。
這一次我們指定了 tag 為 v2 的新鏡像。接下來用 --update-parallelism 和 --update-delay 參數聲明每次使用新鏡像更新兩個副本,其間有 20s 的延遲。最終,告知 Docker 以上變更是對 uber-svc 服務展開的。
如果對該服務執行 docker service ps 命令會發現,有些副本的版本號是 v2 而有些依然是 v1。
如果給予該操作足夠的時間(4min),則所有的副本最終都會達到新的期望狀態,即基于 v2 版本的鏡像。
$ docker service ps uber-svc
ID NAME IMAGE NODE DESIRED CURRENT STATE
7z...nys uber-svc.1 nigel...v2 mgr2 Running Running 13 secs
0v...7e5 \_uber-svc.1 nigel...v1 wrk3 Shutdown Shutdown 13 secs
bh...wa0 uber-svc.2 nigel...v1 wrk2 Running Running 1 min
e3...gr2 uber-svc.3 nigel...v2 wrk2 Running Running 13 secs
23...u97 \_uber-svc.3 nigel...v1 wrk2 Shutdown Shutdown 13 secs
82...5y1 uber-svc.4 nigel...v1 mgr2 Running Running 1 min
c3...gny uber-svc.5 nigel...v1 wrk3 Running Running 1 min
e6...3u0 uber-svc.6 nigel...v1 wrk1 Running Running 1 min
78...r7z uber-svc.7 nigel...v1 wrk1 Running Running 1 min
2m...kdz uber-svc.8 nigel...v1 mgr3 Running Running 1 min
b9...k7w uber-svc.9 nigel...v1 mgr3 Running Running 1 min
ag...v16 uber-svc.10 nigel...v1 mgr2 Running Running 1 min
e6...dfk uber-svc.11 nigel...v1 mgr1 Running Running 1 min
e2...k1j uber-svc.12 nigel...v1 mgr1 Running Running 1 min
如果在更新操作完成前打開瀏覽器,使用 Swarm 中任一節點的 IP 進入頁面,并多次單擊刷新按鈕,就會看到滾動更新的效果。
有些請求會被舊版本的副本處理,而有些請求會被新版本的副本處理。一段時間之后,所有的請求都會被新版本的服務副本處理。
此時如果對服務執行 docker inspect --pretty 命令,會發現更新時對并行和延遲的設置已經成為服務定義的一部分了。
這意味著,之后的更新操作將會自動使用這些設置,直到再次使用 docker service update 命令覆蓋它們。
$ docker service inspect --pretty uber-svc
ID: mub0dgtc8szm80ez5bs8wlt19
Name:Service uber-svc
Mode: Replicated
Replicas: 12
UpdateStatus:
State: updating
Started: About a minute
Message: update in progress
Placement:
UpdateConfig:
Parallelism: 2
Delay: 20s
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
Image: nigelpoulton/tu-demo:v2@sha256:d3c0d8c9...cf0ef2ba5eb74c
Resources: Networks:
uber-net Endpoint
Mode: vip Ports:
PublishedPort = 80
Protocol = tcp
TargetPort = 80
PublishMode = ingress
如上還應注意到關于服務的網絡配置的內容。Swarm 中的所有運行副本的節點都會使用前面創建的 uber-net 覆蓋網絡。
可以通過在運行副本的任一節點執行 docker network ls 命令來驗證這一點。
此外,請注意 docker inspect 輸出的 Networks 部分,不僅顯示了 uber-ne t網絡,還顯示了 Swarm 范圍的 80:80 端口映射。