使用Docker Stack部署應用

2020-08-13 07:01:03

如果瞭解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:\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網路方式新建一個網路。

(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》

本書是一本Docker入門圖書,全書分爲17章,從Docker概覽和Docker技術兩部分進行全面解析,深入淺出地介紹了Docker的相關知識,清晰詳細的操作步驟結合大量的實際程式碼幫助讀者學以致用,將Docker知識應用到真實的專案開發當中。 
本書適合對Docker感興趣的入門新手、Docker技術開發人員以及運維人員閱讀,本書也可作爲Docker認證工程師考試的參考圖書。