如果了解Docker Compose,就會(huì)發(fā)現(xiàn)Docker Stack非常簡(jiǎn)單。事實(shí)上在許多方面,Stack一直是期望的Compose——完全集成到Docker中,并能夠管理應(yīng)用的整個(gè)生命周期。
從體系結(jié)構(gòu)上來(lái)講,Stack位于Docker應(yīng)用層級(jí)的最頂端。Stack基于服務(wù)進(jìn)行構(gòu)建,而服務(wù)又基于容器,如圖14.1所示。

圖14.1 AtSea商店架構(gòu)圖
接下來(lái)的章節(jié)分為如下幾部分。
- 簡(jiǎn)單應(yīng)用。
- 深入分析Stack文件。
- 部署應(yīng)用。
- 管理應(yīng)用。
14.2.1 簡(jiǎn)單應(yīng)用
本章后續(xù)的內(nèi)容會(huì)一直使用示例應(yīng)用AtSea Shop。該示例托管在Github的dockersamples/atsea-sample-shop-App庫(kù)中,基于Apache 2.0許可證開(kāi)源。
使用該應(yīng)用是因?yàn)槠鋸?fù)雜度適中,不會(huì)因?yàn)樘珡?fù)雜而難以完整解釋。除此之外,該應(yīng)用還是個(gè)多服務(wù)應(yīng)用,并且利用了認(rèn)證和安全相關(guān)的技術(shù)。應(yīng)用架構(gòu)如圖14.2所示。
如圖所示,該應(yīng)用由5個(gè)服務(wù)、3個(gè)網(wǎng)絡(luò)、4個(gè)密鑰以及3組端口映射構(gòu)成。具體細(xì)節(jié)將會(huì)結(jié)合Stack文件進(jìn)行分析。
注:
在本章中用到服務(wù)一詞時(shí),指的是Docker服務(wù)(由若干容器組成的集合,作為一個(gè)整體進(jìn)行統(tǒng)一管理,并且在Docker API中存在對(duì)應(yīng)的服務(wù)對(duì)象)。

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

圖14.3 綁定掛載
(5)payment_gateway服務(wù)
payment_gateway服務(wù)中指定了鏡像,掛載了一個(gè)密鑰,連接到網(wǎng)絡(luò),定義了部分部署策略,并且使用了兩個(gè)部署約束。
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之外,其余配置項(xiàng)在前面都已經(jīng)出現(xiàn)過(guò)了。通過(guò)docker node update命令可以自定義節(jié)點(diǎn)標(biāo)簽,并添加到Swarm集群的指定節(jié)點(diǎn)。因此,node.label配置只適用于Swarm集群中指定的節(jié)點(diǎn)上(不能用于單獨(dú)的容器或者不屬于Swarm集群的容器之上)。
在本例中,payment_gateway服務(wù)被要求只能運(yùn)行在符合PCI DSS(支付卡行業(yè)標(biāo)準(zhǔn),譯者注)標(biāo)準(zhǔn)的節(jié)點(diǎn)之上。為了使其生效,讀者可以將某個(gè)自定義節(jié)點(diǎn)標(biāo)簽應(yīng)用到Swarm集群中符合要求的節(jié)點(diǎn)之上。本書在搭建應(yīng)用部署實(shí)驗(yàn)環(huán)境的時(shí)候完成了該操作。
因?yàn)楫?dāng)前服務(wù)定義了兩個(gè)部署約束,所以服務(wù)副本只會(huì)部署在兩個(gè)約束條件均滿足的節(jié)點(diǎn)之上,即具備pcidss=yes節(jié)點(diǎn)標(biāo)簽的worker節(jié)點(diǎn)。
關(guān)于Stack文件的分析到這里就結(jié)束了,目前對(duì)于應(yīng)用需求應(yīng)該有了較好的理解。前文中提到,Stack文件是應(yīng)用文檔化的重要部分之一。讀者已經(jīng)了解該應(yīng)用包含5個(gè)服務(wù)、3個(gè)網(wǎng)絡(luò)以及4個(gè)密鑰。此外讀者還知道了每個(gè)服務(wù)都會(huì)連接到哪個(gè)網(wǎng)絡(luò)、有哪些端口需要發(fā)布、應(yīng)用會(huì)使用到哪些鏡像以及哪些服務(wù)需要在特定的節(jié)點(diǎn)上發(fā)布。
下面開(kāi)始部署。
14.2.3 部署應(yīng)用
在部署應(yīng)用之前,有幾個(gè)前置處理需要完成。
- Swarm模式:應(yīng)用將采用Docker Stack部署,而Stack依賴Swarm模式。
- 標(biāo)簽:某個(gè)Swarm worker節(jié)點(diǎn)需要自定義標(biāo)簽。
- 密鑰:應(yīng)用所需的密鑰需要在部署前創(chuàng)建完成。
1.搭建應(yīng)用實(shí)驗(yàn)環(huán)境
在本節(jié)中會(huì)完成基于Linux的三節(jié)點(diǎn)Swarm集群搭建,同時(shí)能滿足上面應(yīng)用的全部前置依賴。完成之后,實(shí)驗(yàn)環(huán)境如圖14.4所示。

圖14.4 示例環(huán)境
接下來(lái)內(nèi)容分為3個(gè)步驟。
(1)創(chuàng)建新的Swarm。
(2)添加節(jié)點(diǎn)標(biāo)簽。
(3)創(chuàng)建密鑰。
首先創(chuàng)建新的三節(jié)點(diǎn)Swarm集群。
(1)初始化Swarm。
在讀者期望成為Swarm管理節(jié)點(diǎn)的機(jī)器上,運(yùn)行下面的命令。
$ docker swarm init
Swarm initialized: current node (lhma...w4nn) is now a manager.
<Snip>
(2)添加工作節(jié)點(diǎn)。
復(fù)制前面輸出中出現(xiàn)的docker swarm join命令。將復(fù)制內(nèi)容粘貼到工作節(jié)點(diǎn)上并運(yùn)行。
//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由一個(gè)管理節(jié)點(diǎn)和兩個(gè)工作節(jié)點(diǎn)構(gòu)成。在管理節(jié)點(diǎn)中運(yùn)行下面的命令。
$ 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ù)只能運(yùn)行在有pcidss=yes標(biāo)簽的工作節(jié)點(diǎn)之上。本步驟中將在wrk-1上添加該節(jié)點(diǎn)標(biāo)簽。
在現(xiàn)實(shí)世界中,添加該標(biāo)簽之前必須將某個(gè)Docker節(jié)點(diǎn)按PCI規(guī)范進(jìn)行標(biāo)準(zhǔn)化。但是,這只是一個(gè)實(shí)驗(yàn)環(huán)境,所以就暫且跳過(guò)這一過(guò)程,直接將標(biāo)簽添加到wrk-1節(jié)點(diǎn)。
在Swarm管理節(jié)點(diǎn)運(yùn)行下面的命令。
(1)添加節(jié)點(diǎn)標(biāo)簽到wrk-1。
$ docker node update --label-add pcidss=yes wrk-1
Node標(biāo)簽只在Swarm集群之內(nèi)生效。
(2)確認(rèn)節(jié)點(diǎn)標(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é)點(diǎn)現(xiàn)在已經(jīng)配置完成,所以該節(jié)點(diǎn)可以運(yùn)行payment_gateway服務(wù)副本了。
應(yīng)用定義了4個(gè)密鑰,這些都需要在應(yīng)用部署前創(chuàng)建。
- postgress_password。
- staging_token。
- revprox_cert。
- revprox_key。
在管理節(jié)點(diǎn)運(yùn)行下面的命令,來(lái)創(chuàng)建這些密鑰。
(1)創(chuàng)建新的鍵值對(duì)。
密鑰中有3個(gè)是需要加密key的。在本步驟中會(huì)創(chuàng)建加密key,下一步會(huì)將加密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)備。是時(shí)候開(kāi)始部署應(yīng)用了!
2.部署示例應(yīng)用
如果還沒(méi)有代碼,請(qǐng)先復(fù)制應(yīng)用的GitHub倉(cāng)庫(kù)到Swarm管理節(jié)點(diǎn)。
$ 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)擁有了源碼,可以開(kāi)始部署應(yīng)用了。
Stack通過(guò)docker stack deploy命令完成部署?;A(chǔ)格式下,該命令允許傳入兩個(gè)參數(shù)。
- Stack文件的名稱。
- Stack的名稱。
應(yīng)用的GitHub倉(cāng)庫(kù)中包含一個(gè)名為docker-stack.yml的Stack文件。這里會(huì)使用該文件。本書中為Stack起名seastack,如果讀者不喜歡,也可以選擇其他名稱。
在Swarm管理節(jié)點(diǎn)的atsea-sample-shop-app目錄下運(yùn)行下面的命令。
部署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
讀者可以運(yùn)行docker network ls以及docker service ls命令來(lái)查看應(yīng)用的網(wǎng)絡(luò)和服務(wù)情況。
下面是命令輸出中幾個(gè)需要注意的地方。
網(wǎng)絡(luò)是先于服務(wù)創(chuàng)建的。這是因?yàn)榉?wù)依賴于網(wǎng)絡(luò),所以網(wǎng)絡(luò)需要在服務(wù)啟動(dòng)前創(chuàng)建。
Docker將Stack名稱附加到由他創(chuàng)建的任何資源名稱前作為前綴。在本例中,Stack名為seastack,所以所有資源名稱的格式都如:seastack_<resource>。例如,payment網(wǎng)絡(luò)的名稱是seastack_payment。而在部署之前創(chuàng)建的資源則沒(méi)有被重命名,比如密鑰。
另一個(gè)需要注意的點(diǎn)是出現(xiàn)了新的名為seastack_default的網(wǎng)絡(luò)。該網(wǎng)絡(luò)并未在Stack文件中定義,那為什么會(huì)創(chuàng)建呢?每個(gè)服務(wù)都需要連接到網(wǎng)絡(luò),但是visualizer服務(wù)并沒(méi)有指定具體的網(wǎng)絡(luò)。因此,Docker創(chuàng)建了名為seastack_default的網(wǎng)絡(luò),并將visualizer連接到該網(wǎng)絡(luò)。
讀者可以通過(guò)兩個(gè)命令來(lái)確認(rèn)當(dāng)前Stack的狀態(tài)。docker stack ls列出了系統(tǒng)中全部Stack,包括每個(gè)Stack下面包含多少服務(wù)。docker stack ps <stack-name>針對(duì)某個(gè)指定Stack展示了更詳細(xì)的信息,例如期望狀態(tài)以及當(dāng)前狀態(tài)。下面一起來(lá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ù)啟動(dòng)失敗時(shí),docker stack ps命令是首選的問(wèn)題定位方式。該命令展示了Stack中每個(gè)服務(wù)的概況,包括服務(wù)副本所在節(jié)點(diǎn)、當(dāng)前狀態(tài)、期望狀態(tài)以及異常信息。從下面的輸出信息中能看出reverse_proxy服務(wù)在wrk-2節(jié)點(diǎn)上兩次嘗試啟動(dòng)副本失敗。
$ 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)"
如果想查看具體某個(gè)服務(wù)的詳細(xì)信息,可以使用docker service logs命令。讀者需要將服務(wù)名稱/ID或者副本ID作為參數(shù)傳入。如果傳入服務(wù)名稱或ID,讀者可以看到所有服務(wù)副本的日志信息。如果傳入的是副本ID,讀者只會(huì)看到對(duì)應(yīng)副本的日志信息。
下面的docker service logs命令展示了seastack_reverse_proxy服務(wù)的全部副本日志,其中包含了前面輸出中的兩次副本啟動(dòng)失敗的日志。
$ 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)頁(yè)面展示,已經(jīng)經(jīng)過(guò)裁剪,但是讀者還是可以看到全部3個(gè)服務(wù)副本的日志(兩個(gè)啟動(dòng)失敗,1個(gè)正在運(yùn)行)。每行的開(kāi)始都是副本的名稱,包括服務(wù)名稱、副本序號(hào)、副本ID以及副本所在主機(jī)的名稱。接下來(lái)是具體的日志輸出。
注:
讀者可能已經(jīng)注意到前面日志中全部副本的序號(hào)都是1。這是因?yàn)镈ocker每次只創(chuàng)建一個(gè)副本,并且只有當(dāng)前面的副本啟動(dòng)失敗時(shí)才會(huì)創(chuàng)建新的。
因?yàn)檩敵鰞?nèi)容經(jīng)過(guò)裁剪,所以具體原因很難明確,但看起來(lái)前兩次副本啟動(dòng)失敗原因是其依賴的某個(gè)服務(wù)仍然在啟動(dòng)中(一種啟動(dòng)時(shí)服務(wù)間依賴導(dǎo)致的競(jìng)爭(zhēng)條件)。
讀者可以繼續(xù)跟蹤日志(--follow),查看日志尾部?jī)?nèi)容(--tail),或者獲取額外的詳細(xì)信息(--details)。
現(xiàn)在Stack已經(jīng)啟動(dòng)并且處于運(yùn)行中,看一下如何管理stack。
14.2.4 管理應(yīng)用
Stack是一組相關(guān)聯(lián)的服務(wù)和基礎(chǔ)設(shè)施,需要進(jìn)行統(tǒng)一的部署和管理。雖然這句話里充斥著術(shù)語(yǔ),但仍提醒我們Stack是由普通的Docker資源構(gòu)建而來(lái):網(wǎng)絡(luò)、卷、密鑰、服務(wù)等。這意味著可以通過(guò)普通的Docker命令對(duì)其進(jìn)行查看和重新配置,例如docker network、docker volume、docker secret、docker service等。
在此前提之下,通過(guò)docker service命令來(lái)管理Stack中某個(gè)服務(wù)是可行的。一個(gè)簡(jiǎn)單的例子是通過(guò)docker service scale命令來(lái)擴(kuò)充appserver服務(wù)的副本數(shù)。但是,這并不是推薦的方式!
推薦方式是通過(guò)聲明式方式修改,即將Stack文件作為配置的唯一聲明。這樣,所有Stack相關(guān)的改動(dòng)都需要體現(xiàn)在Stack文件中,然后更新重新部署應(yīng)用所需的Stack文件。
下面是一個(gè)簡(jiǎn)單例子,闡述了為什么通過(guò)命令修改的方式不好(通過(guò)CLI進(jìn)行變更)。
假設(shè)讀者已經(jīng)部署了一個(gè)Stack,采用的Stack文件是前面章節(jié)中從GitHub復(fù)制的倉(cāng)庫(kù)中的docker-stack.yml。這意味著目前appserver服務(wù)有兩個(gè)副本。如果通過(guò)docker service scale命令將副本修改為4個(gè),當(dāng)前運(yùn)行的集群會(huì)有4個(gè)副本,但是Stack文件中仍然是兩個(gè)。得承認(rèn)目前看起來(lái)還不是特別糟糕。但是,假設(shè)讀者又通過(guò)修改Stack文件對(duì)Stack做了某些改動(dòng),然后通過(guò)docker stack deploy命令進(jìn)行滾動(dòng)部署。這會(huì)導(dǎo)致appserver服務(wù)副本數(shù)被回滾到兩個(gè),因?yàn)镾tack文件就是這么定義的。因此,推薦對(duì)Stack所有的變更都通過(guò)修改Stack文件來(lái)進(jìn)行,并且將該文件放到一個(gè)合適的版本控制系統(tǒng)當(dāng)中。
一起來(lái)回顧對(duì)Stack進(jìn)行兩個(gè)聲明式修改的過(guò)程。目標(biāo)是進(jìn)行如下改動(dòng)。
- 增加appserver副本數(shù),數(shù)量為2~10。
- 將visualizer服務(wù)的優(yōu)雅停止時(shí)間增加到2min。
修改docker-stack.yml文件,更新兩個(gè)值: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)用的方式,只會(huì)更新存在變更的部分。
運(yùn)行docker stack ps命令來(lái)確認(rèn)appserver副本數(shù)量確實(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)容。其中一行表示某個(gè)副本在3s前停止,另一行表示新副本已經(jīng)運(yùn)行了1s。這是因?yàn)閯偛艑?duì)visualizer服務(wù)作了修改,所以Swarm集群終止了正在運(yùn)行的副本,并且啟動(dòng)了新的副本,新副本中更新了stop_grace_period的值。
還需要注意的是,appserver服務(wù)目前擁有10個(gè)副本,但不同副本的“CURRENT STATE”一列狀態(tài)并不相同:有些處于running狀態(tài),而有些仍在starting狀態(tài)。
經(jīng)過(guò)足夠的時(shí)間,集群的狀態(tài)會(huì)完成收斂,期望狀態(tài)和當(dāng)前狀態(tài)就會(huì)保持一致。在那時(shí),集群中實(shí)際部署和觀察到的狀態(tài),就會(huì)跟Stack文件中定義的內(nèi)容完全一致。這真是讓人開(kāi)心的事情。
所有應(yīng)用/Stack都應(yīng)采用該方式進(jìn)行更新。所有的變更都應(yīng)該通過(guò)Stack文件進(jìn)行聲明,然后通過(guò)docker stack deploy進(jìn)行部署。
正確的刪除某個(gè)Stack方式是通過(guò)docker stack rm命令。一定要謹(jǐn)慎!刪除Stack不會(huì)進(jìn)行二次確認(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)刪除,但是密鑰并沒(méi)有。這是因?yàn)槊荑€是在Stack部署前就創(chuàng)建并存在了。在Stack最上層結(jié)構(gòu)中定義的卷同樣不會(huì)被docker stack rm命令刪除。這是因?yàn)榫淼脑O(shè)計(jì)初衷是保存持久化數(shù)據(jù),其生命周期獨(dú)立于容器、服務(wù)以及Stack之外。
本文摘自《深入淺出Docker》