服務(wù)是自 Docker 1.12 后新引入的概念,并且僅適用于 Swarm 模式。
使用服務(wù)仍能夠配置大多數(shù)熟悉的容器屬性,比如容器名、端口映射、接入網(wǎng)絡(luò)和鏡像。此外還增加了額外的特性,比如可以聲明應(yīng)用服務(wù)的期望狀態(tài),將其告知 Docker 后,Docker 會(huì)負(fù)責(zé)進(jìn)行服務(wù)的部署和管理。
舉例說明,假如某應(yīng)用有一個(gè) Web 前端服務(wù),該服務(wù)有相應(yīng)的鏡像。測試表明對于正常的流量來說 5 個(gè)實(shí)例可以應(yīng)對。那么就可以將這一需求轉(zhuǎn)換為一個(gè)服務(wù),該服務(wù)聲明了容器使用的鏡像,并且服務(wù)應(yīng)該總是有 5 個(gè)運(yùn)行中的副本。
下面通過示例來看看如何創(chuàng)建剛剛描述的內(nèi)容。
使用 docker service create 命令創(chuàng)建一個(gè)新的服務(wù)。
在 Windows 上創(chuàng)建新服務(wù)的命令也是一樣的。然而本例中使用的是 Linux 鏡像,它在 Windows 上并不能運(yùn)行。請使用 Windows 的小伙伴將鏡像替換為一個(gè) Windows Web Server 的鏡像,以便能正常運(yùn)行。
再次強(qiáng)調(diào),在 PowerShell 終端中輸入命令的時(shí)候,使用反引號(`)進(jìn)行換行。
$ docker service create --name web-fe \
-p 8080:8080 \
--replicas 5 \
nigelpoulton/pluralsight-docker-ci
z7ovearqmruwk0u2vc5o7ql0p
需要注意的是,該命令與熟悉的 docker container run 命令的許多參數(shù)是相同的。這個(gè)例子中,使用 --name 和 -p 定義服務(wù)的方式,與單機(jī)啟動(dòng)容器的定義方式是一樣的。
通過上面的命令和輸出可以看出。使用 docker service creale 命令告知 Docker 正在聲明一個(gè)新服務(wù),并傳遞 --name 參數(shù)將其命名為 web-fe。將每個(gè)節(jié)點(diǎn)上的 8080 端口映射到服務(wù)副本內(nèi)部的 8080 端口。接下來,使用 --replicas 參數(shù)告知 Docker 應(yīng)該總是有 5 個(gè)此服務(wù)的副本。最后,告知 Docker 哪個(gè)鏡像用于副本,重要的是,要了解所有的服務(wù)副本使用相同的鏡像和配置。
敲擊回車鍵之后,主管理節(jié)點(diǎn)會(huì)在 Swarm 中實(shí)例化 5 個(gè)副本,管理節(jié)點(diǎn)也會(huì)作為工作節(jié)點(diǎn)運(yùn)行。相關(guān)各工作節(jié)點(diǎn)或管理節(jié)點(diǎn)會(huì)拉取鏡像,然后啟動(dòng)一個(gè)運(yùn)行在 8080 端口上的容器。
這還沒有結(jié)束。所有的服務(wù)都會(huì)被 Swarm 持續(xù)監(jiān)控,Swarm 會(huì)在后臺進(jìn)行輪訓(xùn)檢查(Reconciliation Loop),來持續(xù)比較服務(wù)的實(shí)際狀態(tài)和期望狀態(tài)是否一致。如果一致,則無須任何額外操作;如果不一致,Swarm 會(huì)使其一致。換句話說,Swarm 會(huì)一直確保實(shí)際狀態(tài)能夠滿足期望狀態(tài)的要求。
舉例說明,假如運(yùn)行有 web-fe 副本的某個(gè)工作節(jié)點(diǎn)宕機(jī)了,則 web-fe 的實(shí)際狀態(tài)從 5 個(gè)副本降為 4 個(gè),從而不能滿足期望狀態(tài)的要求。Docker 變回啟動(dòng)一個(gè)新的 web-fe 副本來使實(shí)際狀態(tài)與期望狀態(tài)保持一致。這一特性功能強(qiáng)大,使得服務(wù)在面對節(jié)點(diǎn)宕機(jī)等問題時(shí)具有自愈能力。
⒈ 查看服務(wù)
使用 docker service ls 命令可以查看 Swarm 中所有運(yùn)行中的服務(wù)。
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z7o...uw web-fe replicated 5/5 nigel...ci:latest *:8080->8080/tcp
輸出顯示有一個(gè)運(yùn)行中的服務(wù)及其相關(guān)狀態(tài)信息。比如,可以了解服務(wù)的名稱,以及 5 個(gè)期望的副本(容器)中有 5 個(gè)是運(yùn)行狀態(tài)。
如果在部署服務(wù)后立即執(zhí)行該命令,則可能并非所有的副本都處于運(yùn)行狀態(tài)。這通常取決于各個(gè)節(jié)點(diǎn)拉取鏡像的時(shí)間。
執(zhí)行 docker service ps 命令可以查看服務(wù)副本列表及各副本的狀態(tài)。
$ 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>。每一個(gè)副本會(huì)作為一行輸出,其中顯示了各副本分別運(yùn)行在 Swarm 的哪個(gè)節(jié)點(diǎn)上,以及期望的狀態(tài)和實(shí)際狀態(tài)。
關(guān)于服務(wù)更為詳細(xì)的信息可以使用 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 參數(shù),限制輸出中僅包含最感興趣的內(nèi)容,并以易于閱讀的格式打印出來。
不加 --pretty 的話會(huì)給出更加詳盡的輸出。建議大家通讀 docker inspect 命令的輸出內(nèi)容,其中不僅包含大量信息,也是了解底層運(yùn)行機(jī)制的途徑。
⒉ 副本服務(wù) vs 全局服務(wù)
服務(wù)的默認(rèn)復(fù)制模式(Replication Mode)是副本模式(replicated)。
這種模式會(huì)部署期望數(shù)量的服務(wù)副本,并盡可能均勻地將各個(gè)副本分布在整個(gè)集群中。
另一種模式是全局模式(global),在這種模式下,每個(gè)節(jié)點(diǎn)上僅運(yùn)行一個(gè)副本。可以通過給 docker service create 命令傳遞 --mode global 參數(shù)來部署一個(gè)全局服務(wù)。
⒊ 服務(wù)的擴(kuò)縮容
服務(wù)的另一個(gè)強(qiáng)大特性是能夠方便地進(jìn)行擴(kuò)縮容。
假設(shè)業(yè)務(wù)呈爆發(fā)式增長,則 Web 前端服務(wù)接收到雙倍的流量壓力。所幸通過一個(gè)簡單的 docker service scale 命令即可對 web-fe 服務(wù)進(jìn)行擴(kuò)容。
$ docker service scale web-fe=10
web-fe scaled to 10
該命令會(huì)將服務(wù)副本數(shù)由 5 個(gè)增加到 10 個(gè)。后臺會(huì)將服務(wù)的期望狀態(tài)從 5 個(gè)增加到 10 個(gè)。
運(yùn)行 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
執(zhí)行 docker service ps 命令會(huì)顯示服務(wù)副本在各個(gè)節(jié)點(diǎn)上是均衡分布的。
$ 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
在底層實(shí)現(xiàn)上,Swarm 執(zhí)行了一個(gè)調(diào)度算法,默認(rèn)將副本盡量均衡分配給 Swarm 中的所有節(jié)點(diǎn)。
各節(jié)點(diǎn)分配的副本數(shù)是平均分配的,并未將 CPU 負(fù)載等指標(biāo)考慮在內(nèi)。
再次執(zhí)行 docker service scale 命令將副本數(shù)從 10 個(gè)降為 5 個(gè)。
$ docker service scale web-fe=5
web-fe scaled to 5
⒋ 刪除服務(wù)
刪除一個(gè)服務(wù)的操作相對比較簡單。
如下 docker service rm 命令可用于刪除之前部署的服務(wù)。
$ docker service rm web-fe
web-fe
執(zhí)行 docker service ls命令以驗(yàn)證服務(wù)確實(shí)已被刪除。
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
請謹(jǐn)慎使用 docker service rm 命令,因?yàn)樗趧h除所有服務(wù)副本時(shí)并不會(huì)進(jìn)行確認(rèn)。
⒌ 滾動(dòng)升級
對部署的應(yīng)用進(jìn)行滾動(dòng)升級是常見的操作。長期以來,這一過程是令人痛苦的。我曾經(jīng)犧牲了許多的周末時(shí)光來進(jìn)行應(yīng)用程序主版本的升級,而且再也不想這樣做了。
然而,多虧了 Docker 服務(wù),對一個(gè)設(shè)計(jì)良好的應(yīng)用來說,實(shí)施滾動(dòng)升級已經(jīng)變得簡單多了!
為了演示如何操作,下面將部署一個(gè)新的服務(wù)。但是在此之前,先創(chuàng)建一個(gè)新的覆蓋網(wǎng)絡(luò)(Overlay Network)給服務(wù)使用。
這并非必須的操作,只是希望能夠讓大家了解如何創(chuàng)建網(wǎng)絡(luò)并將服務(wù)接入網(wǎng)絡(luò)。
$ docker network create -d overlay uber-net
43wfp6pzea470et4d57udn9ws
該命令會(huì)創(chuàng)建一個(gè)名為 uber-net 的覆蓋網(wǎng)絡(luò),接下來會(huì)將其與要?jiǎng)?chuàng)建的服務(wù)結(jié)合使用。覆蓋網(wǎng)絡(luò)是一個(gè)二層網(wǎng)絡(luò),容器可以接入該網(wǎng)絡(luò),并且所有接入的容器均可互相通信。
即使這些容器所在的 Docker 主機(jī)位于不同的底層網(wǎng)絡(luò)上,該覆蓋網(wǎng)絡(luò)依然是相通的。本質(zhì)上說,覆蓋網(wǎng)絡(luò)是創(chuàng)建于底層異構(gòu)網(wǎng)絡(luò)之上的一個(gè)新的二層容器網(wǎng)絡(luò)。
如下圖所示,兩個(gè)底層網(wǎng)絡(luò)通過一個(gè)三層交換機(jī)連接,而基于這兩個(gè)網(wǎng)絡(luò)之上是一個(gè)覆蓋網(wǎng)絡(luò)。

Docker 主機(jī)通過兩個(gè)底層網(wǎng)絡(luò)相連,而容器則通過覆蓋網(wǎng)絡(luò)相連。對于同一覆蓋網(wǎng)絡(luò)中的容器來說,即使其各自所在的 Docker 主機(jī)接入的是不同的底層網(wǎng)絡(luò),也是互通的。
執(zhí)行 docker network ls 來查看網(wǎng)絡(luò)是否創(chuàng)建成功,且在 Docker 主機(jī)可見。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
43wfp6pzea47 uber-net overlay swarm
可見,uber-net 網(wǎng)絡(luò)已被成功創(chuàng)建,其 SCOPE 為 swarm,并且目前僅在 Swarm 的管理節(jié)點(diǎn)可見。
下面創(chuàng)建一個(gè)新的服務(wù),并將其接入 uber-net 網(wǎng)絡(luò)。
$ docker service create --name uber-svc \
--network uber-net \
-p 80:80 --replicas 12 \
nigelpoulton/tu-demo:v1
dhbtgvqrg2q4sg07ttfuhg8nz
看一下上面的 docker service create 命令中做了哪些聲明。
首先,將服務(wù)命名為 uber-svc,并用 --network 參數(shù)聲明所有的副本都連接到 uber-net 網(wǎng)絡(luò)。
然后,在整個(gè) swarm 中將 80 端口暴露出來,并將其映射到 12 個(gè)容器副本的 80 端口。
最后,聲明所有的副本都基于 nigelpoulton/tu-demo:v1 鏡像。
執(zhí)行 docker service ls 和 docker service ps 命令以檢查新創(chuàng)建服務(wù)的狀態(tài)。
$ 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
通過對服務(wù)聲明 -p 80:80 參數(shù),會(huì)建立 Swarm 集群范圍的網(wǎng)絡(luò)流量映射,到達(dá) Swarm 任何節(jié)點(diǎn) 80 端口的流量,都會(huì)映射到任何服務(wù)副本的內(nèi)部 80 端口。
默認(rèn)的模式,是在 Swarm 中的所有節(jié)點(diǎn)開放端口,稱為入站模式(Ingress Mode)。此外還有主機(jī)模式(Host Mode),即僅在運(yùn)行有容器副本的節(jié)點(diǎn)上開放端口。
以主機(jī)模式開放服務(wù)端口,需要較長格式的聲明語法,代碼如下。
docker service create --name uber-svc \
--network uber-net \
--publish published=80,target=80,mode=host \
--replicas 12 \
nigelpoulton/tu-demo:v1
打開瀏覽器,使用 Swarm 中任何一個(gè)節(jié)點(diǎn)的 IP,進(jìn)入 80 端口的界面,查看服務(wù)運(yùn)行情況,如下圖所示。

如上圖所示,這是一個(gè)簡單的投票程序,它能夠注冊對“footbal”或“soccer”的投票??呻S意在瀏覽器中使用其他節(jié)點(diǎn)的 IP,均能夠打開該頁面,因?yàn)?-p 80:80 參數(shù)會(huì)在所有 Swarm 節(jié)點(diǎn)創(chuàng)建一個(gè)入站模式的端口映射。
即使某個(gè)節(jié)點(diǎn)上并未運(yùn)行服務(wù)的副本,依然可以進(jìn)入該頁面,所有節(jié)點(diǎn)都配置有映射,因此會(huì)將請求轉(zhuǎn)發(fā)給運(yùn)行有服務(wù)副本的節(jié)點(diǎn)。
假設(shè)本次投票已經(jīng)結(jié)束,而公司希望開啟一輪新的投票。現(xiàn)在已經(jīng)為下一輪投票構(gòu)建了一個(gè)新鏡像,并推送到了 Docker Hub 倉庫,新鏡像的 tag 由 v1 變更為 v2。
此外還假設(shè),本次升級任務(wù)在將新鏡像更新到 Swarm 中時(shí)采用一種階段性的方式,每次更新兩個(gè)副本,并且中間間隔 20s。
那么就可以采用如下的 docker service update 命令來完成。
$ docker service update \
--image nigelpoulton/tu-demo:v2 \
--update-parallelism 2 \
--update-delay 20s uber-svc
仔細(xì)觀察該命令,docker service update 通過變更該服務(wù)期望狀態(tài)的方式來更新運(yùn)行中的服務(wù)。
這一次我們指定了 tag 為 v2 的新鏡像。接下來用 --update-parallelism 和 --update-delay 參數(shù)聲明每次使用新鏡像更新兩個(gè)副本,其間有 20s 的延遲。最終,告知 Docker 以上變更是對 uber-svc 服務(wù)展開的。
如果對該服務(wù)執(zhí)行 docker service ps 命令會(huì)發(fā)現(xiàn),有些副本的版本號是 v2 而有些依然是 v1。
如果給予該操作足夠的時(shí)間(4min),則所有的副本最終都會(huì)達(dá)到新的期望狀態(tài),即基于 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 中任一節(jié)點(diǎn)的 IP 進(jìn)入頁面,并多次單擊刷新按鈕,就會(huì)看到滾動(dòng)更新的效果。
有些請求會(huì)被舊版本的副本處理,而有些請求會(huì)被新版本的副本處理。一段時(shí)間之后,所有的請求都會(huì)被新版本的服務(wù)副本處理。
此時(shí)如果對服務(wù)執(zhí)行 docker inspect --pretty 命令,會(huì)發(fā)現(xiàn)更新時(shí)對并行和延遲的設(shè)置已經(jīng)成為服務(wù)定義的一部分了。
這意味著,之后的更新操作將會(huì)自動(dòng)使用這些設(shè)置,直到再次使用 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
如上還應(yīng)注意到關(guān)于服務(wù)的網(wǎng)絡(luò)配置的內(nèi)容。Swarm 中的所有運(yùn)行副本的節(jié)點(diǎn)都會(huì)使用前面創(chuàng)建的 uber-net 覆蓋網(wǎng)絡(luò)。
可以通過在運(yùn)行副本的任一節(jié)點(diǎn)執(zhí)行 docker network ls 命令來驗(yàn)證這一點(diǎn)。
此外,請注意 docker inspect 輸出的 Networks 部分,不僅顯示了 uber-ne t網(wǎng)絡(luò),還顯示了 Swarm 范圍的 80:80 端口映射。