如果了解Docker Compose,就會發(fā)現(xiàn)Docker Stack非常簡單。事實上在許多方面,Stack一直是期望的Compose——完全集成到Docker中,并能夠管理應(yīng)用的整個生命周期。
從體系結(jié)構(gòu)上來講,Stack位于Docker應(yīng)用層級的最頂端。Stack基于服務(wù)進行構(gòu)建,而服務(wù)又基于容器,如圖14.1所示。
圖14.1 AtSea商店架構(gòu)圖
接下來的章節(jié)分為如下幾部分。
- 簡單應(yīng)用。
- 深入分析Stack文件。
- 部署應(yīng)用。
- 管理應(yīng)用。
14.2.1 簡單應(yīng)用
本章后續(xù)的內(nèi)容會一直使用示例應(yīng)用AtSea Shop。該示例托管在Github的dockersamples/atsea-sample-shop-App庫中,基于Apache 2.0許可證開源。
使用該應(yīng)用是因為其復(fù)雜度適中,不會因為太復(fù)雜而難以完整解釋。除此之外,該應(yīng)用還是個多服務(wù)應(yīng)用,并且利用了認(rèn)證和安全相關(guān)的技術(shù)。應(yīng)用架構(gòu)如圖14.2所示。
如圖所示,該應(yīng)用由5個服務(wù)、3個網(wǎng)絡(luò)、4個密鑰以及3組端口映射構(gòu)成。具體細(xì)節(jié)將會結(jié)合Stack文件進行分析。
注:
在本章中用到服務(wù)一詞時,指的是Docker服務(wù)(由若干容器組成的集合,作為一個整體進行統(tǒng)一管理,并且在Docker API中存在對應(yīng)的服務(wù)對象)。
圖14.2 AtSea商店架構(gòu)圖
復(fù)制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.
該應(yīng)用的代碼由若干目錄和源碼文件組成。讀者可以隨意瀏覽這些文件。但是接下來,重點關(guān)注的文件是docker-stack.yml。該文件通常被稱為Stack文件,在該文件中定義了應(yīng)用及其依賴。
在該文件整體結(jié)構(gòu)中,定義了4種頂級關(guān)鍵字。
version:
services:
networks:
secrets:
version代表了Compose文件格式的版本號。為了應(yīng)用于Stack,需要3.0或者更高的版本。services中定義了組成當(dāng)前應(yīng)用的服務(wù)都有哪些。networks列出了必需的網(wǎng)絡(luò),secrets定義了應(yīng)用用到的密鑰。
如果展開頂級的關(guān)鍵字,可以看到類似圖14.2中的結(jié)構(gòu)。Stack文件由5個服務(wù)構(gòu)成,分別為“reverse_proxy”“database”“appserver”“visualizer”“payment_gateway”。Stack文件中包含3個網(wǎng)絡(luò),分別為“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文件定義了應(yīng)用的很多依賴要素,理解這一點很重要。因此,Stack文件是應(yīng)用的一個自描述文件,并且作為一個很好的工具彌合了開發(fā)和運維之間的隔閡。
接下來一起深入分析Stack文件的細(xì)節(jié)。
14.2.2 深入分析Stack文件
Stack文件就是Docker Compose文件。唯一的要求就是version:一項需要是“3.0”或者更高的值。具體可以關(guān)注Docker文檔中關(guān)于Compose文件的最新版本信息。
在Docker根據(jù)某個Stack文件部署應(yīng)用的時候,首先會檢查并創(chuàng)建networks:關(guān)鍵字對應(yīng)的網(wǎng)絡(luò)。如果對應(yīng)網(wǎng)絡(luò)不存在,Docker會進行創(chuàng)建。
一起看一下Stack文件中的網(wǎng)絡(luò)定義。
1.網(wǎng)絡(luò)
networks:
front-tier:
back-tier:
payment:
driver: overlay
driver_opts:
encrypted: 'yes'
該文件中定義了3個網(wǎng)絡(luò):front-tier、back-tier以及payment。默認(rèn)情況下,這些網(wǎng)絡(luò)都會采用overlay驅(qū)動,新建對應(yīng)的覆蓋類型的網(wǎng)絡(luò)。但是payment網(wǎng)絡(luò)比較特殊,需要數(shù)據(jù)層加密。
默認(rèn)情況下,覆蓋網(wǎng)絡(luò)的所有控制層都是加密的。如果需要加密數(shù)據(jù)層,有兩種選擇。
- 在docker network create命令中指定-o encrypted參數(shù)。
- 在Stack文件中的driver_opts之下指定encrypted:'yes'。
數(shù)據(jù)層加密會導(dǎo)致額外開銷,而影響額外開銷大小的因素有很多,比如流量的類型和流量的多少。但是,通常額外開銷會在10%的范圍之內(nèi)。
正如前面提到的,全部的3個網(wǎng)絡(luò)均會先于密鑰和服務(wù)被創(chuàng)建。
2.密鑰
密鑰屬于頂級對象,在當(dāng)前Stack文件中定義了4個。
secrets:
postgres_password:
external: true
staging_token:
external: true
revprox_key:
external: true
revprox_cert:
external: true
注意,4個密鑰都被定義為external。這意味著在Stack部署之前,這些密鑰必須存在。
當(dāng)然在應(yīng)用部署時按需創(chuàng)建密鑰也是可以的,只需要將file: <filename>替換為external: true。但該方式生效的前提是,需要在主機文件系統(tǒng)的對應(yīng)路徑下有一個文本文件,其中包含密鑰所需的值,并且是未加密的。這種方式存在明顯的安全隱患。
稍后會展示在部署的時候究竟是如何創(chuàng)建這些密鑰的?,F(xiàn)在,讀者只需知道應(yīng)用定義了4個密鑰,并且需要提前創(chuàng)建即可。
下面對服務(wù)逐一進行分析。
3.服務(wù)
部署中的主要操作都在服務(wù)這個環(huán)節(jié)。
每個服務(wù)都是一個JSON集合(字典),其中包含了一系列關(guān)鍵字。本書會依次介紹每個關(guān)鍵字,并解釋操作的具體內(nèi)容。
(1)reverse_proxy服務(wù)
正如讀者所見,reverse_proxy服務(wù)定義了鏡像、端口、密鑰以及網(wǎng)絡(luò)。
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關(guān)鍵字是服務(wù)對象中唯一的必填項。顧名思義,該關(guān)鍵字定義了將要用于構(gòu)建服務(wù)副本的Docker鏡像。
Docker是可選項,除非指定其他值,否則鏡像會從Docker Hub拉取。讀者可以通過在鏡像前添加對應(yīng)第三方鏡像倉庫服務(wù)API的DNS名稱的方式,來指定某個鏡像從第三方服務(wù)拉取。例如google的容器服務(wù)的DNS名稱為gcr.io。
Docker Stack和Docker Compose的一個區(qū)別是,Stack不支持構(gòu)建。這意味著在部署Stack之前,所有鏡像必須提前構(gòu)建完成。
ports關(guān)鍵字定義了兩個映射。
- 80:80將Swarm節(jié)點的80端口映射到每個服務(wù)副本的80端口。
- 443:443將Swarm節(jié)點的443端口映射到每個服務(wù)副本的443端口。
默認(rèn)情況下,所有端口映射都采用Ingress模式。這意味著Swarm集群中每個節(jié)點的對應(yīng)端口都會映射并且是可訪問的,即使是那些沒有運行副本的節(jié)點。另一種方式是Host模式,端口只映射到了運行副本的Swarm節(jié)點上。但是,Host模式需要使用完整格式的配置。例如,在Host模式下將端口映射到80端口的語法如下所示。
ports:
- target: 80
published: 80
mode: host
推薦使用完整語法格式,這樣可以提高易讀性,并且更靈活(完整語法格式支持Ingress模式和Host模式)。但是,完整格式要求Compose文件格式的版本至少是3.2。
secret關(guān)鍵字中定義了兩個密鑰:revprox_cert以及revprox_key。這兩個密鑰必須在頂級關(guān)鍵字secrets下定義,并且必須在系統(tǒng)上已經(jīng)存在。
密鑰以普通文件的形式被掛載到服務(wù)副本當(dāng)中。文件的名稱就是stack文件中定義的target屬性的值,其在linux下的路徑為/run/secrets,在windows下的路徑為C:ProgramDataDockersecrets。Linux將/run/secrets作為內(nèi)存文件系統(tǒng)掛載,但是Windows并不會這樣。
本服務(wù)密鑰中定義的內(nèi)容會在每個服務(wù)副本中被掛載,具體路徑為/run/secrets/revprox_cert和/run/secrets/revprox_key。若將其中之一掛載為/run/secrets/uber_secret,需要在stack文件中定義如下內(nèi)容。
secrets:
- source: revprox_cert
target: uber_secret
networks關(guān)鍵字確保服務(wù)所有副本都會連接到front-tier網(wǎng)絡(luò)。網(wǎng)絡(luò)相關(guān)定義必須位于頂級關(guān)鍵字networks之下,如果定義的網(wǎng)絡(luò)不存在,Docker會以O(shè)verlay網(wǎng)絡(luò)方式新建一個網(wǎng)絡(luò)。
(2)database服務(wù)
數(shù)據(jù)庫服務(wù)也在Stack文件中定義了,包括鏡像、網(wǎng)絡(luò)以及密鑰。除上述內(nèi)容之外,數(shù)據(jù)庫服務(wù)還引入了環(huán)境變量和部署約束。
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關(guān)鍵字允許在服務(wù)副本中注入環(huán)境變量。在該服務(wù)中,使用了3個環(huán)境變量來定義數(shù)據(jù)庫用戶、數(shù)據(jù)庫密碼的位置(掛載到每個服務(wù)副本中的密鑰)以及數(shù)據(jù)庫服務(wù)的名稱。
environment:
POSTGRES_USER: gordonuser
POSTGRES_DB_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_DB: atsea
注:
將三者作為密鑰傳遞會更安全,因為這樣可以避免將數(shù)據(jù)庫名稱和數(shù)據(jù)庫用戶以明文變量的方式記錄在文件當(dāng)中。
該服務(wù)還在deploy關(guān)鍵字下定義了部署約束。這樣保證了當(dāng)前服務(wù)只會運行在Swarm集群的worker節(jié)點之上。
deploy:
placement:
constraints:
- 'node.role == worker'
部署約束是一種拓?fù)涓兄〞r任務(wù),是一種很好的優(yōu)化調(diào)度選擇的方式。Swarm目前允許通過如下幾種方式進行調(diào)度。
- 節(jié)點ID,如node.id==o2p4kw2uuw2a。
- 節(jié)點名稱,如node.hostname==wrk-12。
- 節(jié)點角色,如node.role!=manager。
- 節(jié)點引擎標(biāo)簽,如engine.labels.operatingsystem==ubuntu16.04。
- 節(jié)點自定義標(biāo)簽,如node.labels.zone==prod1。
注意==和!=操作符均支持。
(3)appserver服務(wù)
appserver服務(wù)使用了一個鏡像,連接到3個網(wǎng)絡(luò),并且掛載了一個密鑰。此外appserver服務(wù)還在deploy關(guān)鍵字下引入了一些額外的特性。
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關(guān)鍵字中新增的內(nèi)容。
首先,services.appserver.deploy.replicas = 2設(shè)置期望服務(wù)的副本數(shù)量為2。缺省情況下,默認(rèn)值為1。如果服務(wù)正在運行,并且需要修改副本數(shù),則讀者需要顯示聲明該值。這意味著需要更新stack文件中的services.appserver.deploy.replicas,設(shè)置一個新值,然后重新部署當(dāng)前stack。后面會進行具體展示,但是重新部署stack并不會影響那些沒有改動的服務(wù)。
services.appserver.deploy.update_config定義了Docker在服務(wù)滾動升級的時候具體如何操作。對于當(dāng)前服務(wù),Docker每次會更新兩個副本(parallelism),并且在升級失敗后自動回滾?;貪L會基于之前的服務(wù)定義啟動新的副本。failure_action的默認(rèn)操作是pause,會在服務(wù)升級失敗后阻止其他副本的升級。failure_action還支持continue。
update_config:
parallelism: 2
failure_action: rollback
services.appserver.deploy.restart-policy定義了Swarm針對容器異常退出的重啟策略。當(dāng)前服務(wù)的重啟策略是,如果某個副本以非0返回值退出(condition: onfailure),會立即重啟當(dāng)前副本。重啟最多重試3次,每次都會等待至多120s來檢測是否啟動成功。每次重啟的間隔是5s。
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
(4)visualizer服務(wù)
visualizer服務(wù)中指定了鏡像,定義了端口映射規(guī)則、更新配置以及部署約束。此外還掛載了一個指定卷,并且定義了容器的優(yōu)雅停止方式。
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'
當(dāng)Docker停止某個容器的時候,會給容器內(nèi)部PID為1的進程發(fā)送SIGTERM信號。容器內(nèi)PID為1的進程會有10s的優(yōu)雅停止時間來執(zhí)行一些清理操作。如果進程沒有處理該信號,則10s后就會被SIGKILL信號強制結(jié)束。stop_grace_period屬性可以調(diào)整默認(rèn)為10s的優(yōu)雅停止時長。
volumes關(guān)鍵字用于掛載提前創(chuàng)建的卷或者主機目錄到某個服務(wù)副本當(dāng)中。在本例中,會掛載Docker主機的/var/run/docker.sock目錄到每個服務(wù)副本的/var/run/docker.sock路徑。這意味著在服務(wù)副本中任何對/var/run/docker.sock的讀寫操作都會實際指向Docker主機的對應(yīng)目錄中。
/var/run/docker.sock恰巧是Docker提供的IPC套接字,Docker daemon通過該套接字對其他進程暴露其API終端。這意味著如果給某個容器訪問該文件的權(quán)限,就是允許該容器接收全部的API終端,即等價于給予了容器查詢和管理Docker daemon的能力。在大部分場景下這是決不允許的。但是,這是一個實驗室環(huán)境中的示例應(yīng)用。
該服務(wù)需要Docker套接字訪問權(quán)限的原因是需要以圖形化方式展示當(dāng)前Swarm中服務(wù)。為了實現(xiàn)這個目標(biāo),當(dāng)前服務(wù)需要能訪問管理節(jié)點的Docker daemon。為了確保能訪問管理節(jié)點Docker daemon,當(dāng)前服務(wù)通過部署約束的方式,強制服務(wù)副本只能部署在管理節(jié)點之上,同時將Docker套接字綁定掛載到每個服務(wù)副本中。綁定掛載如圖14.3所示。
圖14.3 綁定掛載
(5)payment_gateway服務(wù)
payment_gateway服務(wù)中指定了鏡像,掛載了一個密鑰,連接到網(wǎng)絡(luò),定義了部分部署策略,并且使用了兩個部署約束。
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之外,其余配置項在前面都已經(jīng)出現(xiàn)過了。通過docker node update命令可以自定義節(jié)點標(biāo)簽,并添加到Swarm集群的指定節(jié)點。因此,node.label配置只適用于Swarm集群中指定的節(jié)點上(不能用于單獨的容器或者不屬于Swarm集群的容器之上)。
在本例中,payment_gateway服務(wù)被要求只能運行在符合PCI DSS(支付卡行業(yè)標(biāo)準(zhǔn),譯者注)標(biāo)準(zhǔn)的節(jié)點之上。為了使其生效,讀者可以將某個自定義節(jié)點標(biāo)簽應(yīng)用到Swarm集群中符合要求的節(jié)點之上。本書在搭建應(yīng)用部署實驗環(huán)境的時候完成了該操作。
因為當(dāng)前服務(wù)定義了兩個部署約束,所以服務(wù)副本只會部署在兩個約束條件均滿足的節(jié)點之上,即具備pcidss=yes節(jié)點標(biāo)簽的worker節(jié)點。
關(guān)于Stack文件的分析到這里就結(jié)束了,目前對于應(yīng)用需求應(yīng)該有了較好的理解。前文中提到,Stack文件是應(yīng)用文檔化的重要部分之一。讀者已經(jīng)了解該應(yīng)用包含5個服務(wù)、3個網(wǎng)絡(luò)以及4個密鑰。此外讀者還知道了每個服務(wù)都會連接到哪個網(wǎng)絡(luò)、有哪些端口需要發(fā)布、應(yīng)用會使用到哪些鏡像以及哪些服務(wù)需要在特定的節(jié)點上發(fā)布。
下面開始部署。
14.2.3 部署應(yīng)用
在部署應(yīng)用之前,有幾個前置處理需要完成。
- Swarm模式:應(yīng)用將采用Docker Stack部署,而Stack依賴Swarm模式。
- 標(biāo)簽:某個Swarm worker節(jié)點需要自定義標(biāo)簽。
- 密鑰:應(yīng)用所需的密鑰需要在部署前創(chuàng)建完成。
1.搭建應(yīng)用實驗環(huán)境
在本節(jié)中會完成基于Linux的三節(jié)點Swarm集群搭建,同時能滿足上面應(yīng)用的全部前置依賴。完成之后,實驗環(huán)境如圖14.4所示。
圖14.4 示例環(huán)境
接下來內(nèi)容分為3個步驟。
(1)創(chuàng)建新的Swarm。
(2)添加節(jié)點標(biāo)簽。
(3)創(chuàng)建密鑰。
首先創(chuàng)建新的三節(jié)點Swarm集群。
(1)初始化Swarm。
在讀者期望成為Swarm管理節(jié)點的機器上,運行下面的命令。
$ docker swarm init
Swarm initialized: current node (lhma...w4nn) is now a manager.
<Snip>
(2)添加工作節(jié)點。
復(fù)制前面輸出中出現(xiàn)的docker swarm join命令。將復(fù)制內(nèi)容粘貼到工作節(jié)點上并運行。
//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)確認(rèn)當(dāng)前Swarm由一個管理節(jié)點和兩個工作節(jié)點構(gòu)成。在管理節(jié)點中運行下面的命令。
$ 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服務(wù)配置了部署約束,限制該服務(wù)只能運行在有pcidss=yes標(biāo)簽的工作節(jié)點之上。本步驟中將在wrk-1上添加該節(jié)點標(biāo)簽。
在現(xiàn)實世界中,添加該標(biāo)簽之前必須將某個Docker節(jié)點按PCI規(guī)范進行標(biāo)準(zhǔn)化。但是,這只是一個實驗環(huán)境,所以就暫且跳過這一過程,直接將標(biāo)簽添加到wrk-1節(jié)點。
在Swarm管理節(jié)點運行下面的命令。
(1)添加節(jié)點標(biāo)簽到wrk-1。
$ docker node update --label-add pcidss=yes wrk-1
Node標(biāo)簽只在Swarm集群之內(nèi)生效。
(2)確認(rèn)節(jié)點標(biāo)簽。
$ 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工作節(jié)點現(xiàn)在已經(jīng)配置完成,所以該節(jié)點可以運行payment_gateway服務(wù)副本了。
應(yīng)用定義了4個密鑰,這些都需要在應(yīng)用部署前創(chuàng)建。
- postgress_password。
- staging_token。
- revprox_cert。
- revprox_key。
在管理節(jié)點運行下面的命令,來創(chuàng)建這些密鑰。
(1)創(chuàng)建新的鍵值對。
密鑰中有3個是需要加密key的。在本步驟中會創(chuàng)建加密key,下一步會將加密key放到Docker密鑰文件當(dāng)中。
$ openssl req -newkey rsa:4096 -nodes -sha256
-keyout domain.key -x509 -days 365 -out domain.crt
(2)創(chuàng)建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)創(chuàng)建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
上面已經(jīng)完成了全部的前置準(zhǔn)備。是時候開始部署應(yīng)用了!
2.部署示例應(yīng)用
如果還沒有代碼,請先復(fù)制應(yīng)用的GitHub倉庫到Swarm管理節(jié)點。
$ 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
現(xiàn)在已經(jīng)擁有了源碼,可以開始部署應(yīng)用了。
Stack通過docker stack deploy命令完成部署?;A(chǔ)格式下,該命令允許傳入兩個參數(shù)。
- Stack文件的名稱。
- Stack的名稱。
應(yīng)用的GitHub倉庫中包含一個名為docker-stack.yml的Stack文件。這里會使用該文件。本書中為Stack起名seastack,如果讀者不喜歡,也可以選擇其他名稱。
在Swarm管理節(jié)點的atsea-sample-shop-app目錄下運行下面的命令。
部署Stack(應(yīng)用)。
$ 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命令來查看應(yīng)用的網(wǎng)絡(luò)和服務(wù)情況。
下面是命令輸出中幾個需要注意的地方。
網(wǎng)絡(luò)是先于服務(wù)創(chuàng)建的。這是因為服務(wù)依賴于網(wǎng)絡(luò),所以網(wǎng)絡(luò)需要在服務(wù)啟動前創(chuàng)建。
Docker將Stack名稱附加到由他創(chuàng)建的任何資源名稱前作為前綴。在本例中,Stack名為seastack,所以所有資源名稱的格式都如:seastack_<resource>。例如,payment網(wǎng)絡(luò)的名稱是seastack_payment。而在部署之前創(chuàng)建的資源則沒有被重命名,比如密鑰。
另一個需要注意的點是出現(xiàn)了新的名為seastack_default的網(wǎng)絡(luò)。該網(wǎng)絡(luò)并未在Stack文件中定義,那為什么會創(chuàng)建呢?每個服務(wù)都需要連接到網(wǎng)絡(luò),但是visualizer服務(wù)并沒有指定具體的網(wǎng)絡(luò)。因此,Docker創(chuàng)建了名為seastack_default的網(wǎng)絡(luò),并將visualizer連接到該網(wǎng)絡(luò)。
讀者可以通過兩個命令來確認(rèn)當(dāng)前Stack的狀態(tài)。docker stack ls列出了系統(tǒng)中全部Stack,包括每個Stack下面包含多少服務(wù)。docker stack ps <stack-name>針對某個指定Stack展示了更詳細(xì)的信息,例如期望狀態(tài)以及當(dāng)前狀態(tài)。下面一起來了解下這兩條命令。
$ 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
在服務(wù)啟動失敗時,docker stack ps命令是首選的問題定位方式。該命令展示了Stack中每個服務(wù)的概況,包括服務(wù)副本所在節(jié)點、當(dāng)前狀態(tài)、期望狀態(tài)以及異常信息。從下面的輸出信息中能看出reverse_proxy服務(wù)在wrk-2節(jié)點上兩次嘗試啟動副本失敗。
$ 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)"
如果想查看具體某個服務(wù)的詳細(xì)信息,可以使用docker service logs命令。讀者需要將服務(wù)名稱/ID或者副本ID作為參數(shù)傳入。如果傳入服務(wù)名稱或ID,讀者可以看到所有服務(wù)副本的日志信息。如果傳入的是副本ID,讀者只會看到對應(yīng)副本的日志信息。
下面的docker service logs命令展示了seastack_reverse_proxy服務(wù)的全部副本日志,其中包含了前面輸出中的兩次副本啟動失敗的日志。
$ 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
輸出內(nèi)容為了適應(yīng)頁面展示,已經(jīng)經(jīng)過裁剪,但是讀者還是可以看到全部3個服務(wù)副本的日志(兩個啟動失敗,1個正在運行)。每行的開始都是副本的名稱,包括服務(wù)名稱、副本序號、副本ID以及副本所在主機的名稱。接下來是具體的日志輸出。
注:
讀者可能已經(jīng)注意到前面日志中全部副本的序號都是1。這是因為Docker每次只創(chuàng)建一個副本,并且只有當(dāng)前面的副本啟動失敗時才會創(chuàng)建新的。
因為輸出內(nèi)容經(jīng)過裁剪,所以具體原因很難明確,但看起來前兩次副本啟動失敗原因是其依賴的某個服務(wù)仍然在啟動中(一種啟動時服務(wù)間依賴導(dǎo)致的競爭條件)。
讀者可以繼續(xù)跟蹤日志(--follow),查看日志尾部內(nèi)容(--tail),或者獲取額外的詳細(xì)信息(--details)。
現(xiàn)在Stack已經(jīng)啟動并且處于運行中,看一下如何管理stack。
14.2.4 管理應(yīng)用
Stack是一組相關(guān)聯(lián)的服務(wù)和基礎(chǔ)設(shè)施,需要進行統(tǒng)一的部署和管理。雖然這句話里充斥著術(shù)語,但仍提醒我們Stack是由普通的Docker資源構(gòu)建而來:網(wǎng)絡(luò)、卷、密鑰、服務(wù)等。這意味著可以通過普通的Docker命令對其進行查看和重新配置,例如docker network、docker volume、docker secret、docker service等。
在此前提之下,通過docker service命令來管理Stack中某個服務(wù)是可行的。一個簡單的例子是通過docker service scale命令來擴充appserver服務(wù)的副本數(shù)。但是,這并不是推薦的方式!
推薦方式是通過聲明式方式修改,即將Stack文件作為配置的唯一聲明。這樣,所有Stack相關(guān)的改動都需要體現(xiàn)在Stack文件中,然后更新重新部署應(yīng)用所需的Stack文件。
下面是一個簡單例子,闡述了為什么通過命令修改的方式不好(通過CLI進行變更)。
假設(shè)讀者已經(jīng)部署了一個Stack,采用的Stack文件是前面章節(jié)中從GitHub復(fù)制的倉庫中的docker-stack.yml。這意味著目前appserver服務(wù)有兩個副本。如果通過docker service scale命令將副本修改為4個,當(dāng)前運行的集群會有4個副本,但是Stack文件中仍然是兩個。得承認(rèn)目前看起來還不是特別糟糕。但是,假設(shè)讀者又通過修改Stack文件對Stack做了某些改動,然后通過docker stack deploy命令進行滾動部署。這會導(dǎo)致appserver服務(wù)副本數(shù)被回滾到兩個,因為Stack文件就是這么定義的。因此,推薦對Stack所有的變更都通過修改Stack文件來進行,并且將該文件放到一個合適的版本控制系統(tǒng)當(dāng)中。
一起來回顧對Stack進行兩個聲明式修改的過程。目標(biāo)是進行如下改動。
- 增加appserver副本數(shù),數(shù)量為2~10。
- 將visualizer服務(wù)的優(yōu)雅停止時間增加到2min。
修改docker-stack.yml文件,更新兩個值:services.appserver.deploy.replicas=10和services.visualizer.stop_grace_period=2m。
目前,Stack文件中的內(nèi)容如下。
<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
保存文件并重新部署應(yīng)用。
$ 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)
以上重新部署應(yīng)用的方式,只會更新存在變更的部分。
運行docker stack ps命令來確認(rèn)appserver副本數(shù)量確實增加。
$ 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
為了本書的排版效果,輸出內(nèi)容有所裁剪,只展示了受變更影響的服務(wù)。
注意關(guān)于visualizer服務(wù)有兩行內(nèi)容。其中一行表示某個副本在3s前停止,另一行表示新副本已經(jīng)運行了1s。這是因為剛才對visualizer服務(wù)作了修改,所以Swarm集群終止了正在運行的副本,并且啟動了新的副本,新副本中更新了stop_grace_period的值。
還需要注意的是,appserver服務(wù)目前擁有10個副本,但不同副本的“CURRENT STATE”一列狀態(tài)并不相同:有些處于running狀態(tài),而有些仍在starting狀態(tài)。
經(jīng)過足夠的時間,集群的狀態(tài)會完成收斂,期望狀態(tài)和當(dāng)前狀態(tài)就會保持一致。在那時,集群中實際部署和觀察到的狀態(tài),就會跟Stack文件中定義的內(nèi)容完全一致。這真是讓人開心的事情。
所有應(yīng)用/Stack都應(yīng)采用該方式進行更新。所有的變更都應(yīng)該通過Stack文件進行聲明,然后通過docker stack deploy進行部署。
正確的刪除某個Stack方式是通過docker stack rm命令。一定要謹(jǐn)慎!刪除Stack不會進行二次確認(rèn)。
$ 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
注意,網(wǎng)絡(luò)和服務(wù)已經(jīng)刪除,但是密鑰并沒有。這是因為密鑰是在Stack部署前就創(chuàng)建并存在了。在Stack最上層結(jié)構(gòu)中定義的卷同樣不會被docker stack rm命令刪除。這是因為卷的設(shè)計初衷是保存持久化數(shù)據(jù),其生命周期獨立于容器、服務(wù)以及Stack之外。
本文摘自《深入淺出Docker》