Stack 文件就是 Docker Compose 文件。唯一的要求就是 version:一項需要是“3.0”或者更高的值。具體可以關注 Docker 文檔中關于 Compose 文件的最新版本信息。
在 Docker 根據某個 Stack 文件部署應用的時候,首先會檢查并創建 networks:關鍵字對應的網絡。如果對應網絡不存在,Docker 會進行創建。
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 個網絡均會先于密鑰和服務被創建。
密鑰屬于頂級對象,在當前 Stack 文件中定義了 4 個。
secrets:
postgres_password:
external: true
staging_token:
external: true
revprox_key:
external: true
revprox_cert:
external: true
注意,4 個密鑰都被定義為 external。這意味著在 Stack 部署之前,這些密鑰必須存在。
當然在應用部署時按需創建密鑰也是可以的,只需要將 file: 替換為 external: true。但該方式生效的前提是,需要在主機文件系統的對應路徑下有一個文本文件,其中包含密鑰所需的值,并且是未加密的。這種方式存在明顯的安全隱患。
部署中的主要操作都在服務這個環節。
每個服務都是一個 JSON 集合(字典),其中包含了一系列關鍵字。我們會依次介紹每個關鍵字,并解釋操作的具體內容。
正如讀者所見,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:\ProgramData\Docker\secrets。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 網絡方式新建一個網絡。
數據庫服務也在 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。
注意: == 和 != 操作符均支持。
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。
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
⒋ 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 套接字綁定掛載到每個服務副本中。綁定掛載如下圖所示。
⒌ 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 個密鑰。此外還知道了每個服務都會連接到哪個網絡、有哪些端口需要發布、應用會使用到哪些鏡像以及哪些服務需要在特定的節點上發布。