如果了解Docker Compose,就會發現Docker Stack非常簡單。事實上在許多方面,Stack一直是期望的Compose——完全集成到Docker中,并能夠管理應用的整個生命周期。
從體系結構上來講,Stack位于Docker應用層級的最頂端。Stack基于服務進行構建,而服務又基于容器,如圖14.1所示。

圖14.1 AtSea商店架構圖
接下來的章節分為如下幾部分。
- 簡單應用。
- 深入分析Stack文件。
- 部署應用。
- 管理應用。
14.2.1 簡單應用
本章后續的內容會一直使用示例應用AtSea Shop。該示例托管在Github的dockersamples/atsea-sample-shop-App庫中,基于Apache 2.0許可證開源。
使用該應用是因為其復雜度適中,不會因為太復雜而難以完整解釋。除此之外,該應用還是個多服務應用,并且利用了認證和安全相關的技術。應用架構如圖14.2所示。
如圖所示,該應用由5個服務、3個網絡、4個密鑰以及3組端口映射構成。具體細節將會結合Stack文件進行分析。
注:
在本章中用到服務一詞時,指的是Docker服務(由若干容器組成的集合,作為一個整體進行統一管理,并且在Docker API中存在對應的服務對象)。

圖14.2 AtSea商店架構圖
復制Github倉庫,以獲取全部源代碼文件。
$ git clone https://github.com/dockersamples/atsea-sample-shop-app.git Cloning
into 'atsea-sample-shop-app'...
remote: Counting objects: 636, done.
remote: Total 636 (delta 0), reused 0 (delta 0), pack-reused 636
Receiving objects: 100% (636/636), 7.23 MiB | 28.25 MiB/s, done.
Resolving deltas: 100% (197/197), done.
該應用的代碼由若干目錄和源碼文件組成。讀者可以隨意瀏覽這些文件。但是接下來,重點關注的文件是docker-stack.yml。該文件通常被稱為Stack文件,在該文件中定義了應用及其依賴。
在該文件整體結構中,定義了4種頂級關鍵字。
version:
services:
networks:
secrets:
version代表了Compose文件格式的版本號。為了應用于Stack,需要3.0或者更高的版本。services中定義了組成當前應用的服務都有哪些。networks列出了必需的網絡,secrets定義了應用用到的密鑰。
如果展開頂級的關鍵字,可以看到類似圖14.2中的結構。Stack文件由5個服務構成,分別為“reverse_proxy”“database”“appserver”“visualizer”“payment_gateway”。Stack文件中包含3個網絡,分別為“front-tier”“back-tier”“payment”。最后,Stack文件中有4個密鑰,分別為“postgres_password”“staging_token”“revprox_key”“revprox_cert”。
version: "3.2"
services:
reverse_proxy:
database:
appserver:
visualizer:
payment_gateway:
networks:
front-tier:
back-tier:
payment:
secrets:
postgres_password:
staging_token:
revprox_key:
revprox_cert:
Stack文件定義了應用的很多依賴要素,理解這一點很重要。因此,Stack文件是應用的一個自描述文件,并且作為一個很好的工具彌合了開發和運維之間的隔閡。
接下來一起深入分析Stack文件的細節。
14.2.2 深入分析Stack文件
Stack文件就是Docker Compose文件。唯一的要求就是version:一項需要是“3.0”或者更高的值。具體可以關注Docker文檔中關于Compose文件的最新版本信息。
在Docker根據某個Stack文件部署應用的時候,首先會檢查并創建networks:關鍵字對應的網絡。如果對應網絡不存在,Docker會進行創建。
一起看一下Stack文件中的網絡定義。
1.網絡
networks:
front-tier:
back-tier:
payment:
driver: overlay
driver_opts:
encrypted: 'yes'
該文件中定義了3個網絡:front-tier、back-tier以及payment。默認情況下,這些網絡都會采用overlay驅動,新建對應的覆蓋類型的網絡。但是payment網絡比較特殊,需要數據層加密。
默認情況下,覆蓋網絡的所有控制層都是加密的。如果需要加密數據層,有兩種選擇。
- 在docker network create命令中指定-o encrypted參數。
- 在Stack文件中的driver_opts之下指定encrypted:'yes'。
數據層加密會導致額外開銷,而影響額外開銷大小的因素有很多,比如流量的類型和流量的多少。但是,通常額外開銷會在10%的范圍之內。
正如前面提到的,全部的3個網絡均會先于密鑰和服務被創建。
2.密鑰
密鑰屬于頂級對象,在當前Stack文件中定義了4個。
secrets:
postgres_password:
external: true
staging_token:
external: true
revprox_key:
external: true
revprox_cert:
external: true
注意,4個密鑰都被定義為external。這意味著在Stack部署之前,這些密鑰必須存在。
當然在應用部署時按需創建密鑰也是可以的,只需要將file: <filename>替換為external: true。但該方式生效的前提是,需要在主機文件系統的對應路徑下有一個文本文件,其中包含密鑰所需的值,并且是未加密的。這種方式存在明顯的安全隱患。
稍后會展示在部署的時候究竟是如何創建這些密鑰的。現在,讀者只需知道應用定義了4個密鑰,并且需要提前創建即可。
下面對服務逐一進行分析。
3.服務
部署中的主要操作都在服務這個環節。
每個服務都是一個JSON集合(字典),其中包含了一系列關鍵字。本書會依次介紹每個關鍵字,并解釋操作的具體內容。
(1)reverse_proxy服務
正如讀者所見,reverse_proxy服務定義了鏡像、端口、密鑰以及網絡。
reverse_proxy:
image: dockersamples/atseasampleshopapp_reverse_proxy
ports:
- "80:80"
- "443:443"
secrets:
- source: revprox_cert
target: revprox_cert
- source: revprox_key
target: revprox_key
networks:
- front-tier
image關鍵字是服務對象中唯一的必填項。顧名思義,該關鍵字定義了將要用于構建服務副本的Docker鏡像。
Docker是可選項,除非指定其他值,否則鏡像會從Docker Hub拉取。讀者可以通過在鏡像前添加對應第三方鏡像倉庫服務API的DNS名稱的方式,來指定某個鏡像從第三方服務拉取。例如google的容器服務的DNS名稱為gcr.io。
Docker Stack和Docker Compose的一個區別是,Stack不支持構建。這意味著在部署Stack之前,所有鏡像必須提前構建完成。
ports關鍵字定義了兩個映射。
- 80:80將Swarm節點的80端口映射到每個服務副本的80端口。
- 443:443將Swarm節點的443端口映射到每個服務副本的443端口。
默認情況下,所有端口映射都采用Ingress模式。這意味著Swarm集群中每個節點的對應端口都會映射并且是可訪問的,即使是那些沒有運行副本的節點。另一種方式是Host模式,端口只映射到了運行副本的Swarm節點上。但是,Host模式需要使用完整格式的配置。例如,在Host模式下將端口映射到80端口的語法如下所示。
ports:
- target: 80
published: 80
mode: host
推薦使用完整語法格式,這樣可以提高易讀性,并且更靈活(完整語法格式支持Ingress模式和Host模式)。但是,完整格式要求Compose文件格式的版本至少是3.2。
secret關鍵字中定義了兩個密鑰:revprox_cert以及revprox_key。這兩個密鑰必須在頂級關鍵字secrets下定義,并且必須在系統上已經存在。
密鑰以普通文件的形式被掛載到服務副本當中。文件的名稱就是stack文件中定義的target屬性的值,其在linux下的路徑為/run/secrets,在windows下的路徑為C:ProgramDataDockersecrets。Linux將/run/secrets作為內存文件系統掛載,但是Windows并不會這樣。
本服務密鑰中定義的內容會在每個服務副本中被掛載,具體路徑為/run/secrets/revprox_cert和/run/secrets/revprox_key。若將其中之一掛載為/run/secrets/uber_secret,需要在stack文件中定義如下內容。
secrets:
- source: revprox_cert
target: uber_secret
networks關鍵字確保服務所有副本都會連接到front-tier網絡。網絡相關定義必須位于頂級關鍵字networks之下,如果定義的網絡不存在,Docker會以Overlay網絡方式新建一個網絡。
(2)database服務
數據庫服務也在Stack文件中定義了,包括鏡像、網絡以及密鑰。除上述內容之外,數據庫服務還引入了環境變量和部署約束。
database:
image: dockersamples/atsea_db
environment:
POSTGRES_USER: gordonuser
POSTGRES_DB_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_DB: atsea
networks:
- back-tier
secrets:
- postgres_password
deploy:
placement:
constraints:
- 'node.role == worker'
environment關鍵字允許在服務副本中注入環境變量。在該服務中,使用了3個環境變量來定義數據庫用戶、數據庫密碼的位置(掛載到每個服務副本中的密鑰)以及數據庫服務的名稱。
environment:
POSTGRES_USER: gordonuser
POSTGRES_DB_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_DB: atsea
注:
將三者作為密鑰傳遞會更安全,因為這樣可以避免將數據庫名稱和數據庫用戶以明文變量的方式記錄在文件當中。
該服務還在deploy關鍵字下定義了部署約束。這樣保證了當前服務只會運行在Swarm集群的worker節點之上。
deploy:
placement:
constraints:
- 'node.role == worker'
部署約束是一種拓撲感知定時任務,是一種很好的優化調度選擇的方式。Swarm目前允許通過如下幾種方式進行調度。
- 節點ID,如node.id==o2p4kw2uuw2a。
- 節點名稱,如node.hostname==wrk-12。
- 節點角色,如node.role!=manager。
- 節點引擎標簽,如engine.labels.operatingsystem==ubuntu16.04。
- 節點自定義標簽,如node.labels.zone==prod1。
注意==和!=操作符均支持。
(3)appserver服務
appserver服務使用了一個鏡像,連接到3個網絡,并且掛載了一個密鑰。此外appserver服務還在deploy關鍵字下引入了一些額外的特性。
appserver:
image: dockersamples/atsea_app
networks:
- front-tier
- back-tier
- payment
deploy:
replicas: 2
update_config:
parallelism: 2
failure_action: rollback
placement:
constraints:
- 'node.role == worker'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
secrets:
- postgres_password
接下來進一步了解deploy關鍵字中新增的內容。
首先,services.appserver.deploy.replicas = 2設置期望服務的副本數量為2。缺省情況下,默認值為1。如果服務正在運行,并且需要修改副本數,則讀者需要顯示聲明該值。這意味著需要更新stack文件中的services.appserver.deploy.replicas,設置一個新值,然后重新部署當前stack。后面會進行具體展示,但是重新部署stack并不會影響那些沒有改動的服務。
services.appserver.deploy.update_config定義了Docker在服務滾動升級的時候具體如何操作。對于當前服務,Docker每次會更新兩個副本(parallelism),并且在升級失敗后自動回滾。回滾會基于之前的服務定義啟動新的副本。failure_action的默認操作是pause,會在服務升級失敗后阻止其他副本的升級。failure_action還支持continue。
update_config:
parallelism: 2
failure_action: rollback
services.appserver.deploy.restart-policy定義了Swarm針對容器異常退出的重啟策略。當前服務的重啟策略是,如果某個副本以非0返回值退出(condition: onfailure),會立即重啟當前副本。重啟最多重試3次,每次都會等待至多120s來檢測是否啟動成功。每次重啟的間隔是5s。
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
(4)visualizer服務
visualizer服務中指定了鏡像,定義了端口映射規則、更新配置以及部署約束。此外還掛載了一個指定卷,并且定義了容器的優雅停止方式。
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8001:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
update_config:
failure_action: rollback
placement:
constraints:
- 'node.role == manager'
當Docker停止某個容器的時候,會給容器內部PID為1的進程發送SIGTERM信號。容器內PID為1的進程會有10s的優雅停止時間來執行一些清理操作。如果進程沒有處理該信號,則10s后就會被SIGKILL信號強制結束。stop_grace_period屬性可以調整默認為10s的優雅停止時長。
volumes關鍵字用于掛載提前創建的卷或者主機目錄到某個服務副本當中。在本例中,會掛載Docker主機的/var/run/docker.sock目錄到每個服務副本的/var/run/docker.sock路徑。這意味著在服務副本中任何對/var/run/docker.sock的讀寫操作都會實際指向Docker主機的對應目錄中。
/var/run/docker.sock恰巧是Docker提供的IPC套接字,Docker daemon通過該套接字對其他進程暴露其API終端。這意味著如果給某個容器訪問該文件的權限,就是允許該容器接收全部的API終端,即等價于給予了容器查詢和管理Docker daemon的能力。在大部分場景下這是決不允許的。但是,這是一個實驗室環境中的示例應用。
該服務需要Docker套接字訪問權限的原因是需要以圖形化方式展示當前Swarm中服務。為了實現這個目標,當前服務需要能訪問管理節點的Docker daemon。為了確保能訪問管理節點Docker daemon,當前服務通過部署約束的方式,強制服務副本只能部署在管理節點之上,同時將Docker套接字綁定掛載到每個服務副本中。綁定掛載如圖14.3所示。

圖14.3 綁定掛載
(5)payment_gateway服務
payment_gateway服務中指定了鏡像,掛載了一個密鑰,連接到網絡,定義了部分部署策略,并且使用了兩個部署約束。
payment_gateway:
image: dockersamples/atseasampleshopapp_payment_gateway
secrets:
- source: staging_token
target: payment_token
networks:
- payment
deploy:
update_config:
failure_action: rollback
placement:
constraints:
- 'node.role == worker'
- 'node.labels.pcidss == yes'
除了部署約束node.label之外,其余配置項在前面都已經出現過了。通過docker node update命令可以自定義節點標簽,并添加到Swarm集群的指定節點。因此,node.label配置只適用于Swarm集群中指定的節點上(不能用于單獨的容器或者不屬于Swarm集群的容器之上)。
在本例中,payment_gateway服務被要求只能運行在符合PCI DSS(支付卡行業標準,譯者注)標準的節點之上。為了使其生效,讀者可以將某個自定義節點標簽應用到Swarm集群中符合要求的節點之上。本書在搭建應用部署實驗環境的時候完成了該操作。
因為當前服務定義了兩個部署約束,所以服務副本只會部署在兩個約束條件均滿足的節點之上,即具備pcidss=yes節點標簽的worker節點。
關于Stack文件的分析到這里就結束了,目前對于應用需求應該有了較好的理解。前文中提到,Stack文件是應用文檔化的重要部分之一。讀者已經了解該應用包含5個服務、3個網絡以及4個密鑰。此外讀者還知道了每個服務都會連接到哪個網絡、有哪些端口需要發布、應用會使用到哪些鏡像以及哪些服務需要在特定的節點上發布。
下面開始部署。
14.2.3 部署應用
在部署應用之前,有幾個前置處理需要完成。
- Swarm模式:應用將采用Docker Stack部署,而Stack依賴Swarm模式。
- 標簽:某個Swarm worker節點需要自定義標簽。
- 密鑰:應用所需的密鑰需要在部署前創建完成。
1.搭建應用實驗環境
在本節中會完成基于Linux的三節點Swarm集群搭建,同時能滿足上面應用的全部前置依賴。完成之后,實驗環境如圖14.4所示。

圖14.4 示例環境
接下來內容分為3個步驟。
(1)創建新的Swarm。
(2)添加節點標簽。
(3)創建密鑰。
首先創建新的三節點Swarm集群。
(1)初始化Swarm。
在讀者期望成為Swarm管理節點的機器上,運行下面的命令。
$ docker swarm init
Swarm initialized: current node (lhma...w4nn) is now a manager.
<Snip>
(2)添加工作節點。
復制前面輸出中出現的docker swarm join命令。將復制內容粘貼到工作節點上并運行。
//Worker 1 (wrk-1)
wrk-1$ docker swarm join --token SWMTKN-1-2hl6...-...3lqg 172.31.40.192:2377
This node joined a swarm as a worker.
//Worker 2 (wrk-2)
wrk-2$ docker swarm join --token SWMTKN-1-2hl6...-...3lqg 172.31.40.192:2377
This node joined a swarm as a worker.
(3)確認當前Swarm由一個管理節點和兩個工作節點構成。在管理節點中運行下面的命令。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
lhm...4nn * mgr-1 Ready Active Leader
b74...gz3 wrk-1 Ready Active
o9x...um8 wrk-2 Ready Active
Swarm集群目前就緒。
payment_gateway服務配置了部署約束,限制該服務只能運行在有pcidss=yes標簽的工作節點之上。本步驟中將在wrk-1上添加該節點標簽。
在現實世界中,添加該標簽之前必須將某個Docker節點按PCI規范進行標準化。但是,這只是一個實驗環境,所以就暫且跳過這一過程,直接將標簽添加到wrk-1節點。
在Swarm管理節點運行下面的命令。
(1)添加節點標簽到wrk-1。
$ docker node update --label-add pcidss=yes wrk-1
Node標簽只在Swarm集群之內生效。
(2)確認節點標簽。
$ docker node inspect wrk-1
[
{
"ID": "b74rzajmrimfv7hood6l4lgz3",
"Version": {
"Index": 27
},
"CreatedAt": "2018-01-25T10:35:18.146831621Z",
"UpdatedAt": "2018-01-25T10:47:57.189021202Z",
"Spec": {
"Labels": {
"pcidss": "yes"
},
<Snip>
wrk-1工作節點現在已經配置完成,所以該節點可以運行payment_gateway服務副本了。
應用定義了4個密鑰,這些都需要在應用部署前創建。
- postgress_password。
- staging_token。
- revprox_cert。
- revprox_key。
在管理節點運行下面的命令,來創建這些密鑰。
(1)創建新的鍵值對。
密鑰中有3個是需要加密key的。在本步驟中會創建加密key,下一步會將加密key放到Docker密鑰文件當中。
$ openssl req -newkey rsa:4096 -nodes -sha256
-keyout domain.key -x509 -days 365 -out domain.crt
(2)創建revprox_cert、revprox_key以及postgress_password密鑰。
$ docker secret create revprox_cert domain.crt
cqblzfpyv5cxb5wbvtrbpvrrj
$ docker secret create revprox_key domain.key
jqd1ramk2x7g0s2e9ynhdyl4p
$ docker secret create postgres_password domain.key
njpdklhjcg8noy64aileyod6l
(3)創建stage_token密鑰。
$ echo staging | docker secret create staging_token -
sqy21qep9w17h04k3600o6qsj
(4)列出所有密鑰。
$ docker secret ls
ID NAME CREATED UPDATED
njp...d6l postgres_password 47 seconds ago 47 seconds ago
cqb...rrj revprox_cert About a minute ago About a minute ago
jqd...l4p revprox_key About a minute ago About a minute ago
sqy...qsj staging_token 23 seconds ago 23 seconds ago
上面已經完成了全部的前置準備。是時候開始部署應用了!
2.部署示例應用
如果還沒有代碼,請先復制應用的GitHub倉庫到Swarm管理節點。
$ git clone https://github.com/dockersamples/atsea-sample-shop-app.git
Cloning into 'atsea-sample-shop-app'...
remote: Counting objects: 636, done.
Receiving objects: 100% (636/636), 7.23 MiB | 3.30 MiB/s, done. remote:
Total 636 (delta 0), reused 0 (delta 0), pack-reused 636 Resolving
deltas: 100% (197/197), done.
Checking connectivity... done.
$ cd atsea-sample-shop-app
現在已經擁有了源碼,可以開始部署應用了。
Stack通過docker stack deploy命令完成部署。基礎格式下,該命令允許傳入兩個參數。
- Stack文件的名稱。
- Stack的名稱。
應用的GitHub倉庫中包含一個名為docker-stack.yml的Stack文件。這里會使用該文件。本書中為Stack起名seastack,如果讀者不喜歡,也可以選擇其他名稱。
在Swarm管理節點的atsea-sample-shop-app目錄下運行下面的命令。
部署Stack(應用)。
$ docker stack deploy -c docker-stack.yml seastack
Creating network seastack_default
Creating network seastack_back-tier
Creating network seastack_front-tier
Creating network seastack_payment
Creating service seastack_database
Creating service seastack_appserver
Creating service seastack_visualizer
Creating service seastack_payment_gateway
Creating service seastack_reverse_proxy
讀者可以運行docker network ls以及docker service ls命令來查看應用的網絡和服務情況。
下面是命令輸出中幾個需要注意的地方。
網絡是先于服務創建的。這是因為服務依賴于網絡,所以網絡需要在服務啟動前創建。
Docker將Stack名稱附加到由他創建的任何資源名稱前作為前綴。在本例中,Stack名為seastack,所以所有資源名稱的格式都如:seastack_<resource>。例如,payment網絡的名稱是seastack_payment。而在部署之前創建的資源則沒有被重命名,比如密鑰。
另一個需要注意的點是出現了新的名為seastack_default的網絡。該網絡并未在Stack文件中定義,那為什么會創建呢?每個服務都需要連接到網絡,但是visualizer服務并沒有指定具體的網絡。因此,Docker創建了名為seastack_default的網絡,并將visualizer連接到該網絡。
讀者可以通過兩個命令來確認當前Stack的狀態。docker stack ls列出了系統中全部Stack,包括每個Stack下面包含多少服務。docker stack ps <stack-name>針對某個指定Stack展示了更詳細的信息,例如期望狀態以及當前狀態。下面一起來了解下這兩條命令。
$ docker stack ls
NAME SERVICES
Seastack 5
$ docker stack ps seastack
NAME NODE DESIRED STATE CURRENT STATE
seastack_reverse_proxy.1 wrk-2 Running Running 7 minutes ago
seastack_payment_gateway.1 wrk-1 Running Running 7 minutes ago
seastack_visualizer.1 mgr-1 Running Running 7 minutes ago
seastack_appserver.1 wrk-2 Running Running 7 minutes ago
seastack_database.1 wrk-2 Running Running 7 minutes ago
seastack_appserver.2 wrk-1 Running Running 7 minutes ago
在服務啟動失敗時,docker stack ps命令是首選的問題定位方式。該命令展示了Stack中每個服務的概況,包括服務副本所在節點、當前狀態、期望狀態以及異常信息。從下面的輸出信息中能看出reverse_proxy服務在wrk-2節點上兩次嘗試啟動副本失敗。
$ docker stack ps seastack
NAME NODE DESIRED CURRENT ERROR
STATE STATE
reverse_proxy.1 wrk-2 Shutdown Failed "task: non-zero exit (1)"
_reverse_proxy.1 wrk-2 Shutdown Failed "task: non-zero exit (1)"
如果想查看具體某個服務的詳細信息,可以使用docker service logs命令。讀者需要將服務名稱/ID或者副本ID作為參數傳入。如果傳入服務名稱或ID,讀者可以看到所有服務副本的日志信息。如果傳入的是副本ID,讀者只會看到對應副本的日志信息。
下面的docker service logs命令展示了seastack_reverse_proxy服務的全部副本日志,其中包含了前面輸出中的兩次副本啟動失敗的日志。
$ docker service logs seastack_reverse_proxy
seastack_reverse_proxy.1.zhc3cjeti9d4@wrk-2 | [emerg] 1#1: host not found...
seastack_reverse_proxy.1.6m1nmbzmwh2d@wrk-2 | [emerg] 1#1: host not found...
seastack_reverse_proxy.1.6m1nmbzmwh2d@wrk-2 | Nginx: [emerg] host not found..
seastack_reverse_proxy.1.zhc3cjeti9d4@wrk-2 | nginx: [emerg] host not found..
seastack_reverse_proxy.1.1tmya243m5um@mgr-1 | 10.255.0.2 "GET / HTTP/1.1" 302
輸出內容為了適應頁面展示,已經經過裁剪,但是讀者還是可以看到全部3個服務副本的日志(兩個啟動失敗,1個正在運行)。每行的開始都是副本的名稱,包括服務名稱、副本序號、副本ID以及副本所在主機的名稱。接下來是具體的日志輸出。
注:
讀者可能已經注意到前面日志中全部副本的序號都是1。這是因為Docker每次只創建一個副本,并且只有當前面的副本啟動失敗時才會創建新的。
因為輸出內容經過裁剪,所以具體原因很難明確,但看起來前兩次副本啟動失敗原因是其依賴的某個服務仍然在啟動中(一種啟動時服務間依賴導致的競爭條件)。
讀者可以繼續跟蹤日志(--follow),查看日志尾部內容(--tail),或者獲取額外的詳細信息(--details)。
現在Stack已經啟動并且處于運行中,看一下如何管理stack。
14.2.4 管理應用
Stack是一組相關聯的服務和基礎設施,需要進行統一的部署和管理。雖然這句話里充斥著術語,但仍提醒我們Stack是由普通的Docker資源構建而來:網絡、卷、密鑰、服務等。這意味著可以通過普通的Docker命令對其進行查看和重新配置,例如docker network、docker volume、docker secret、docker service等。
在此前提之下,通過docker service命令來管理Stack中某個服務是可行的。一個簡單的例子是通過docker service scale命令來擴充appserver服務的副本數。但是,這并不是推薦的方式!
推薦方式是通過聲明式方式修改,即將Stack文件作為配置的唯一聲明。這樣,所有Stack相關的改動都需要體現在Stack文件中,然后更新重新部署應用所需的Stack文件。
下面是一個簡單例子,闡述了為什么通過命令修改的方式不好(通過CLI進行變更)。
假設讀者已經部署了一個Stack,采用的Stack文件是前面章節中從GitHub復制的倉庫中的docker-stack.yml。這意味著目前appserver服務有兩個副本。如果通過docker service scale命令將副本修改為4個,當前運行的集群會有4個副本,但是Stack文件中仍然是兩個。得承認目前看起來還不是特別糟糕。但是,假設讀者又通過修改Stack文件對Stack做了某些改動,然后通過docker stack deploy命令進行滾動部署。這會導致appserver服務副本數被回滾到兩個,因為Stack文件就是這么定義的。因此,推薦對Stack所有的變更都通過修改Stack文件來進行,并且將該文件放到一個合適的版本控制系統當中。
一起來回顧對Stack進行兩個聲明式修改的過程。目標是進行如下改動。
- 增加appserver副本數,數量為2~10。
- 將visualizer服務的優雅停止時間增加到2min。
修改docker-stack.yml文件,更新兩個值:services.appserver.deploy.replicas=10和services.visualizer.stop_grace_period=2m。
目前,Stack文件中的內容如下。
<Snip>
appserver:
image: dockersamples/atsea_app
networks:
- front-tier
- back-tier
- payment
deploy:
replicas: 10 <<Updated value
<Snip>
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8001:8080"
stop_grace_period: 2m <<Updated value
<Snip
保存文件并重新部署應用。
$ docker stack deploy -c docker-stack.yml seastack
Updating service seastack_reverse_proxy (id: z4crmmrz7zi83o0721heohsku)
Updating service seastack_database (id: 3vvpkgunetxaatbvyqxfic115)
Updating service seastack_appserver (id: ljht639w33dhv0dmht1q6mueh)
Updating service seastack_visualizer (id: rbwoyuciglre01hsm5fviabjf)
Updating service seastack_payment_gateway (id: w4gsdxfnb5Gofwtvmdiooqvxs)
以上重新部署應用的方式,只會更新存在變更的部分。
運行docker stack ps命令來確認appserver副本數量確實增加。
$ docker stack ps seastack
NAME NODE DESIRED STATE CURRENT STATE
seastack_visualizer.1 mgr-1 Running Running 1 second ago
seastack_visualizer.1 mgr-1 Shutdown Shutdown 3 seconds ago
seastack_appserver.1 wrk-2 Running Running 24 minutes ago
seastack_appserver.2 wrk-1 Running Running 24 minutes ago
seastack_appserver.3 wrk-2 Running Running 1 second ago
seastack_appserver.4 wrk-1 Running Running 1 second ago
seastack_appserver.5 wrk-2 Running Running 1 second ago
seastack_appserver.6 wrk-1 Running Starting 7 seconds ago
seastack_appserver.7 wrk-2 Running Running 1 second ago
seastack_appserver.8 wrk-1 Running Starting 7 seconds ago
seastack_appserver.9 wrk-2 Running Running 1 second ago
seastack_appserver.10 wrk-1 Running Starting 7 seconds ago
為了本書的排版效果,輸出內容有所裁剪,只展示了受變更影響的服務。
注意關于visualizer服務有兩行內容。其中一行表示某個副本在3s前停止,另一行表示新副本已經運行了1s。這是因為剛才對visualizer服務作了修改,所以Swarm集群終止了正在運行的副本,并且啟動了新的副本,新副本中更新了stop_grace_period的值。
還需要注意的是,appserver服務目前擁有10個副本,但不同副本的“CURRENT STATE”一列狀態并不相同:有些處于running狀態,而有些仍在starting狀態。
經過足夠的時間,集群的狀態會完成收斂,期望狀態和當前狀態就會保持一致。在那時,集群中實際部署和觀察到的狀態,就會跟Stack文件中定義的內容完全一致。這真是讓人開心的事情。
所有應用/Stack都應采用該方式進行更新。所有的變更都應該通過Stack文件進行聲明,然后通過docker stack deploy進行部署。
正確的刪除某個Stack方式是通過docker stack rm命令。一定要謹慎!刪除Stack不會進行二次確認。
$ docker stack rm seastack
Removing service seastack_appserver
Removing service seastack_database
Removing service seastack_payment_gateway
Removing service seastack_reverse_proxy
Removing service seastack_visualizer
Removing network seastack_front-tier
Removing network seastack_payment
Removing network seastack_default
Removing network seastack_back-tier
注意,網絡和服務已經刪除,但是密鑰并沒有。這是因為密鑰是在Stack部署前就創建并存在了。在Stack最上層結構中定義的卷同樣不會被docker stack rm命令刪除。這是因為卷的設計初衷是保存持久化數據,其生命周期獨立于容器、服務以及Stack之外。
本文摘自《深入淺出Docker》