Docker Swarm服務調度分析

本文主要介紹 docker swarm 對于容器應用的調度管理分析。Docker swarm 1.12.0 是一個非常重要的版本,1.12.0之前的版本 docker swarm 是一個獨立的項目,需要額外下載swarm鏡像進行安裝。1.12.0 之后的版本,swarm 功能直接繼承到 docker engine 當中。直接使用 docker CLI 可創建一個 swarm 集群、可在 swarm 集群中部署應用程序,同時具備管理能力。

關于swarm的詳細介紹,可參考官方文檔:https://docs.docker.com/engine/swarm/

1.Docker Swarm 集群架構

Swarm 集群中,有兩種類型的節點:managerworker

要部署服務到swarm,需要將服務定義提交給 manager 節點,manager 節點將工作單元分派 給 worker 節點

swarm-node.png

1.1 Manager Node

manager 節點處理集群管理任務:

  • 維護集群狀態
  • 調度服務
  • 為 Swarm 提供外部可調用的 API 接口

manager 節點需要時刻維護和保存當前 Swarm 集群中各個節點的一致性狀態,這里主要是指各個 tasks 的執行的狀態和其它節點的狀態。因為 Swarm 集群是一個典型的分布式集群,在保證一致性上,manager 節點采用 Raft 協議來保證分布式場景下的數據一致性。

通常為了保證 manager 節點的高可用,Docker 建議采用奇數個 Manager 節點,這樣的話,你可以在 manager 節點失敗的時候不用關機維護,Docker 官方給出如下的建議:

  • 3 個 manager 節點最多可以同時容忍 1 個 manager 節點失效的情況下保證高可用;
  • 5 個 manager 節點最多可以同時容忍 2 個 manager 節點失效的情況下保證高可用;
  • N 個 manager 節點最多可以同時容忍 (N?1)/2個 manager 節點失效的情況下保證高可用;
  • 最多最多的情況下,使用 7 個 manager 節點就夠了,否則反而會降低集群的性能了。

重要說明:添加更多管理器并不意味著可擴展性更高或性能更高。一般而言,情況正好相反。

群體大小 多數 容錯
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3
8 5 3

1.2 Worker Node

worker 節點接收并執行從manager 節點分派的任務。默認情況下,manager 節點也作為worker 節點運行,但也可以將它們配置為僅運行manage任務。 可將manager 節點可用性設置為Drain ,這樣服務就不會被分配到manager

worker不參與 Raft 分布狀態,做出調度決策或提供群模式 HTTP API

通過 docker node promote 命令將一個 worker 節點提升為 manager 節點。通常情況下,該命令使用在維護的過程中,需要將 manager 節點暫時下線進行維護操作。同樣可以使用 docker node demote 將某個manager 節點降級為 worker 節點。

1.3 小結

綜上可知, Swarm 集群的管理工作是由manager 節點實現。如上圖所示,manager 節點實現的功能主要包括:node discovery,scheduler,cluster管理等。同時,為了保證manager 節點的高可用,manager 節點需要時刻維護和保存當前 Swarm 集群中各個節點的一致性狀態。在保證一致性上,manager 節點采用 [Raft]協議來保證分布式場景下的數據一致性;

Docker Swarm 內置了Raft一致性算法,可以保證分布式系統的數據保持一致性同步。Etcd,Consul 等高可用鍵值存儲系統也是采用了這種算法。這個算法的作用簡單點說就是隨時保證集群中有一個 Leader,由 Leader 接收數據更新,再同步到其他各個 Follower 節點。在 Swarm 中的作用表現為當一個 Leader 節點 down 掉時,系統會立即選取出另一個 Leader 節點,由于這個節點同步了之前節點的所有數據,所以可以無縫地管理集群。

Raft的詳細解釋可以參考《The Secret Lives of Data--Raft: Understandable Distributed Consensus》

為保證manager 節點高可用,一般部署3-7個manager 節點。而在云桌面的場景下,可能一個集群就只有兩臺服務器。顯然此時是無法滿足 manager 3個節點的高可用要求。因此我們面對的挑戰就是如何修改 swarm Manager Raft相關部分代碼,使其在只有2個節點的情況下,也可以保證一個節點宕機的高可用。

2.Docker Swarm 網絡架構

2.1 網絡概述

我們知道,Docker 的幾種網絡方案:none、host、bridge 和 joined 容器,它們解決了單個 Docker Host 內容器通信的問題。那么跨主機容器間通信的方案又有哪些呢?

跨主機網絡方案包括:

  1. docker 原生的 overlay 和 macvlan
  2. 第三方方案:常用的包括 flannel、weave 和 calico

Docker 網絡是一個非常活躍的技術領域,不斷有新的方案開發出來,那么要問個非常重要的問題了: 如此眾多的方案是如何與 docker 集成在一起的?答案是:libnetwork 以及 CNM

libnetwork 是 docker 容器網絡庫,最核心的內容是其定義的 Container Network Model (CNM),這個模型對容器網絡進行了抽象,由以下三類組件組成:

  1. Sandbox:Sandbox 是容器的網絡棧,包含容器的 interface、路由表和 DNS 設置。 Linux Network Namespace 是 Sandbox 的標準實現。Sandbox 可以包含來自不同 Network 的 Endpoint。
  2. Endpoint:Endpoint 的作用是將 Sandbox 接入 Network。Endpoint 的典型實現是 veth pair,后面我們會舉例。一個 Endpoint 只能屬于一個網絡,也只能屬于一個 Sandbox。
  3. Network:Network 包含一組 Endpoint,同一 Network 的 Endpoint 可以直接通信。Network 的實現可以是 Linux Bridge、VLAN 等。

如圖所示兩個容器,一個容器一個 Sandbox,每個 Sandbox 都有一個 Endpoint 連接到 Network 1,第二個 Sandbox 還有一個 Endpoint 將其接入 Network 2.

libnetwork CNM 定義了 docker 容器的網絡模型,按照該模型開發出的 driver 就能與 docker daemon 協同工作,實現容器網絡。docker 原生的 driver 包括 none、bridge、overlay 和 macvlan,第三方 driver 包括 flannel、weave、calico 等。

2.2 Overlay網絡

Docker Swarm 內置的跨主機容器通信方案是overlay網絡,這是一個基于VxLAN 協議的網絡實現。VxLAN 可將二層數據封裝到 UDP 進行傳輸,VxLAN 提供與 VLAN 相同的以太網二層服務,但是擁有更強的擴展性和靈活性。 overlay 通過虛擬出一個子網,讓處于不同主機的容器能透明地使用這個子網。所以跨主機的容器通信就變成了在同一個子網下的容器通信,看上去就像是同一主機下的bridge網絡通信。

[圖片上傳失敗...(image-6eb6c-1607072692844)]

根據vxlan的作用知道,它是要在三層網絡中虛擬出二層網絡,即跨網段建立虛擬子網。簡單的理解就是把發送到虛擬子網地址10.0.0.3的報文封裝為發送到真實IP192.168.1.3的報文。這必然會有更大的數據開銷,但卻簡化了集群的網絡連接,讓分布在不同主機的容器好像都在同一個主機上一樣 。

overlay網絡會創建多個Docker主機之間的分布式網絡。該網絡位于(覆蓋)特定于主機的網絡之上,允許連接到它的容器(包括群集服務容器)安全地進行通信。Docker透明地處理每個數據包與正確的Docker守護程序主機和正確的目標容器的路由。

初始化swarm或將Docker主機加入現有swarm時,會在該Docker主機上創建兩個新網絡:

  • ingress overlay 網絡,處理與swarm集群服務相關的控制和數據流量。創建群組服務并且不將其連接到用戶定義的覆蓋網絡時,服務將默認連接到ingress overlay網絡。集群中只能有一個ingress overlay 網絡。
  • docker_gwbridge 橋接網絡,它將各個Docker守護程序連接到參與該群集的其他Docker守護進程。同時該docker_gwbridge 網絡將為主機上的容器提供訪問外網的能力。

服務或容器一次可以連接到多個網絡。服務或容器只能通過它們各自連接的網絡進行通信。

2.2.1 創建overlay網絡

先決條件

  • 使用overlay 網絡的Docker守護程序的防火墻規則

    您需要以下端口打開來往于overlay 網絡上的每個Docker主機的流量:

    • 用于集群管理通信的TCP端口2377
    • TCP和UDP端口7946用于節點之間的通信
    • UDP端口4789用于覆蓋網絡流量
  • 在創建overlay 網絡之前,您需要將Docker守護程序初始化為swarm管理器,docker swarm init或者使用它將其連接到現有的swarm docker swarm join。這些中的任何一個都會創建默認ingress overlay 網絡,默認情況下 由群服務使用。即使您從未計劃使用群組服務,也需要執行此操作。之后,您可以創建其他用戶定義的overlay 網絡。

要創建用于swarm服務的覆蓋網絡,請使用如下命令:

$ docker network create -d overlay my-overlay

要創建可由群集服務或獨立容器用于與在其他Docker守護程序上運行的其他獨立容器通信的覆蓋網絡,請添加--attachable標志:

$ docker network create -d overlay --attachable my-attachable-overlay

同時可以指定IP地址范圍,子網,網關和其他選項。詳情docker network create --help請見

2.2.2 加密overlay網絡

默認情況下,使用GCM模式下的AES算法加密所有swarm群集服務管理流量 。集群中的管理器節點每隔12小時輪換用于加密數據的密鑰。

要加密應用程序數據,請--opt encrypted在創建覆蓋網絡時添加。這樣可以在vxlan級別啟用IPSEC加密。此加密會產生不可忽視的性能損失,因此您應該在生產中使用此選項之前對其進行測試。

啟用覆蓋加密后,Docker會在所有節點之間創建IPSEC隧道,在這些節點上為連接到覆蓋網絡的服務安排任務。這些隧道還在GCM模式下使用AES算法,管理器節點每12小時自動旋轉密鑰。

docker network create --opt encrypted --driver overlay my-encrypted-network

2.2.3 暴露服務端口

對于連接到同一個swarm集群的服務來說,它們之間所有的端口都是互相暴露的。對于可在服務外部訪問的端口,必須使用 -p ( 或者 --publish) 發布該端口。

Flag value Description
-p 8080:80 or -p published=8080,target=80 將服務上的TCP端口80映射到routing mesh上的端口8080。
-p 8080:80/udp or -p published=8080,target=80,protocol=udp 將服務上的UDP端口80映射到routing mesh上的端口8080。

2.2.4 實現原理

下面我們討論下overlay 網絡的具體實現:

docker 會為每個 overlay 網絡創建一個獨立的 network namespace,其中會有一個 linux bridge br0,endpoint 還是由 veth pair 實現,一端連接到容器中(即 eth0),另一端連接到 namespace 的 br0 上。

br0 除了連接所有的 endpoint,還會連接一個 vxlan 設備,用于與其他 host 建立 vxlan tunnel。容器之間的數據就是通過這個 tunnel 通信的。邏輯網絡拓撲結構如圖所示:

[圖片上傳失敗...(image-d7aacc-1607072692844)]

2.3 Ingress Routing Mesh

上文暴露服務端口中有講到routing mesh,現在來詳細介紹這一網絡功能。

默認情況下,發布端口的swarm服務使用routing mesh來實現。當客戶端連接到任何swarm節點上的已發布端口(無論它是否正在運行給定服務)時,客戶端請求將被透明地重定向到正在運行該服務的worker。實際上,Docker充當集群服務的負載均衡器。使用routing mesh的服務以虛擬IP(VIP)模式運行。即使在每個節點上運行的服務(通過--global標志)也使用routing mesh。使用routing mesh時,無法保證哪個Docker節點服務客戶端請求。

要在群集中使用 ingress 網絡,您需要在啟用群集模式之前在群集節點之間打開以下端口:

  • 端口7946 TCP / UDP用于容器網絡發現。
  • 端口4789 UDP 用于容器 ingress 網絡。

例如,以下命令將nginx容器中的端口80發布到群集中任何節點的端口8080:

$ docker service create \
  --name my-web \
  --publish published=8080,target=80 \
  --replicas 2 \
  nginx

當您在任何節點上訪問端口8080時,Docker會將您的請求路由到活動容器。在群集節點本身上,端口8080實際上可能不受約束,但routing mesh 知道如何路由流量并防止發生任何端口沖突。

routing mesh在已發布的端口上偵聽分配給該節點的任何IP地址。對于可外部路由的IP地址,該端口可從主機外部獲得。對于所有其他IP地址,只能從主機內訪問。

[圖片上傳失敗...(image-384271-1607072692844)]

如果想要繞過routing mesh,可以使用DNS循環(DNSRR)模式啟動服務,方法是將--endpoint-mode標志設置為dnsrr。這樣就必須在服務前運行自己的負載均衡器。Docker主機上的服務名稱的DNS查詢返回運行該服務的節點的IP地址列表。配置負載均衡器以使用此列表并平衡節點之間的流量。

對于我們云桌面應用場景來說,直接利用routing mesh 功能即可,不需要額外搭建負載均衡器。

2.4 服務發現

Docker Swarm 原生就提供了服務發現的功能。要使用服務發現,需要相互通信的 services 必須屬于同一個 overlay 網絡,所以我們先得創建一個新的 overlay 網絡。 直接使用 ingress 行不行?很遺憾,目前 ingress 沒有提供服務發現,必須創建自己的 overlay 網絡。

Docker Swarm mode下會為每個節點的Docker engine內置一個DNS server,各個節點間的DNS server通過control plane的gossip協議互相交互信息。此處DNS server用于容器間的服務發現。swarm mode會為每個 --net=自定義網絡 的service分配一個DNS entry。目前必須是自定義網絡,比如overaly。而bridge和routing mesh 的service,是不會分配DNS的。

那么,下面就來詳細介紹服務發現的原理。

每個Docker容器都有一個DNS解析器,它將DNS查詢轉發到docker engine,該引擎充當DNS服務器。docker 引擎收到請求后就會在發出請求的容器所在的所有網絡中,檢查域名對應的是不是一個容器或者是服務,如果是,docker引擎就會從存儲的key-value建值對中查找這個容器名、任務名、或者服務名對應的IP地址,并把這個IP地址或者是服務的虛擬IP地址返回給發起請求的域名解析器。

由上可知,docker的服務發現的作用范圍是網絡級別,也就意味著只有在同一個網絡上的容器或任務才能利用內嵌的DNS服務來相互發現,不在同一個網絡里面的服務是不能解析名稱的,另外,為了安全和性能只有當一個節點上有容器或任務在某個網絡里面時,這個節點才會存儲那個網絡里面的DNS記錄。

如果目的容器或服務和源容器不在同一個網絡里面,Docker引擎會把這個DNS查詢轉發到配置的默認DNS服務 。

[圖片上傳失敗...(image-4e8543-1607072692844)]

在上面的例子中,總共有兩個服務myservice和client,其中myservice有兩個容器,這兩個服務在同一個網里面。在client里針對docker.com和myservice各執行了一個curl操作,下面時執行的流程:

  • 為了client解析docker.com和myservice,DNS查詢進行初始化
  • 容器內建的解析器在127.0.0.11:53攔截到這個DNS查詢請求,并把請求轉發到docker引擎的DNS服務
  • myservice 被解析成服務對應的虛擬IP(10.0.0.3)。在接下來的內部負載均衡階段再被解析成一個具體任務容器的IP地址。如果是容器名稱這一步直接解析成容器對應的IP地址(10.0.0.4或者10.0.0.5)。
  • docker.com 在docker引擎或者mynet網絡上不能被解析成服務,所以這個請求被轉發到外部DNS,例如配置好的默認DNS服務器(8.8.8.8)上。

Docker1.12+的服務發現和負載均衡是結合到一起的, 實現辦法有兩種,DNS輪詢和IPVS。 配置參數 --endpoint-mode (vip or dnsrr) ,默認使用VIP方式。DNS輪詢有一些缺點,比如一些應用可能緩存DNS請求, 導致容器變化之后DNS不能實時更新;DNS生效時間也導致不能實時反映服務變化情況。VIP和IPVS原理上比較容易理解, 就是Docker為每個服務分配了一個VIP,DNS解析服務名稱或者自定義的別名到這個VIP上。由于VIP本身沒有容器提供服務,Docker把到VIP的請求通過IPVS技術負載到后面的容器上。

因此正常情況下,使用默認的VIP方式即可。

3.5 負載均衡

負載均衡分為兩種:

Swarm集群內的service之間的相互訪問需要做負載均衡,稱為內部負載均衡(Internal LB);

從Swarm集群外部訪問服務的公開端口,也需要做負載均衡,稱外部負載均衡(Exteral LB or Ingress LB)。

3.5.1 Internal LB

內部負載均衡就是我們在上一段提到的服務發現,集群內部通過DNS訪問service時,Swarm默認通過VIP(virtual IP)、iptables、IPVS轉發到某個容器。

[圖片上傳失敗...(image-bd3ff2-1607072692844)]

當在docker swarm集群模式下創建一個服務時,會自動在服務所屬的網絡上給服務額外的分配一個虛擬IP,當解析服務名字時就會返回這個虛擬IP。對虛擬IP的請求會通過overlay網絡自動的負載到這個服務所有的健康任務上。這個方式也避免了客戶端的負載均衡,因為只有單獨的一個IP會返回到客戶端,docker會處理虛擬IP到具體任務的路由,并把請求平均的分配給所有的健康任務。

[圖片上傳失敗...(image-918705-1607072692844)]

# 創建overlay網絡:mynet 
$ docker network create -d overlay mynet  
a59umzkdj2r0ua7x8jxd84dhr 
# 利用mynet網絡創建myservice服務,并復制兩份  
$ docker service create --network mynet --name myservice --replicas 2 busybox ping localhost  
78t5r8cr0f0h6k2c3k7ih4l6f5
# 通過下面的命令查看myservice對應的虛擬IP 
$ docker service inspect myservice  
...
"VirtualIPs": [ 
    {  
     "NetworkID": "a59umzkdj2r0ua7x8jxd84dhr",  
                "Addr": "10.0.0.3/24"  
      },  
]   

注:swarm中服務還有另外一種負載均衡技術可選DNS round robin (DNS RR) (在創建服務時通過--endpoint-mode配置項指定),在DNSRR模式下,docker不再為服務創建VIP,docker DNS服務直接利用輪詢的策略把服務名稱直接解析成一個容器的IP地址。

3.5.2 Exteral LB

Exteral LB(Ingress LB 或者 Swarm Mode Routing Mesh) 看名字就知道,這個負載均衡方式和前面提到的Ingress網絡有關。Swarm網絡要提供對外訪問的服務就需要打開公開端口,并映射到宿主機。Exteral LB就是外部通過公開端口訪問集群時做的負載均衡。

當創建或更新一個服務時,你可以利用--publish選項把一個服務暴露到外部,在docker swarm模式下發布一個端口意味著在集群中的所有節點都會監聽這個端口,這時當訪問一個監聽了端口但是并沒有對應服務運行在其上的節點會發生什么呢?接下來就該我們的路由網(routing mesh)出場了,路由網時docker1.12引入的一個新特性,它結合了IPVS和iptables創建了一個強大的集群范圍的L4層負載均衡,它使所有節點接收服務暴露端口的請求成為可能。當任意節點接收到針對某個服務暴露的TCP/UDP端口的請求時,這個節點會利用預先定義過的Ingress overlay網絡,把請求轉發給服務對應的虛擬IP。ingress網絡和其他的overlay網絡一樣,只是它的目的是為了轉換來自客戶端到集群的請求,它也是利用我們前一小節介紹過的基于VIP的負載均衡技術。

啟動服務后,你可以為應用程序創建外部DNS記錄,并將其映射到任何或所有Docker swarm節點。你無需擔心你的容器具體運行在那個節點上,因為有了路由網這個特性后,你的集群看起來就像是單獨的一個節點一樣。

#在集群中創建一個復制兩份的服務,并暴露在8000端口  
$ docker service create --name app --replicas 2 --network appnet --publish 8000:80 nginx  

[圖片上傳失敗...(image-b58163-1607072692844)]

上面這個圖表明了路由網是怎么工作的:

  • 服務(app)擁有兩份復制,并把端口映射到外部端口的8000
  • 路由網在集群中的所有節點上都暴露出8000
  • 外部對服務app的請求可以是任意節點,在本例子中外部的負載均衡器將請求轉發到了沒有app服務的主機上
  • docker swarm的IPVS利用ingress overlay網路將請求重新轉發到運行著app服務的節點的容器中

注:以上服務發現和負載均衡參考文檔 https://success.docker.com/article/ucp-service-discovery

3.Docker Swarm 存儲架構

從業務數據的角度看,容器可以分為兩類:無狀態(stateless)容器和有狀態(stateful)容器。

無狀態是指容器在運行過程中不需要保存數據,每次訪問的結果不依賴上一次訪問,比如提供靜態頁面的 web 服務器。有狀態是指容器需要保存數據,而且數據會發生變化,訪問的結果依賴之前請求的處理結果,最典型的就是數據庫服務器。

簡單來講,狀態(state)就是數據,如果容器需要處理并存儲數據,它就是有狀態的,反之則無狀態。

對于有狀態的容器,如何保存數據呢?

3.1 Storage Driver

Docker在啟動容器的時候,需要創建文件系統,為rootfs提供掛載點。最底層的引導文件系統bootfs主要包含 bootloader和kernel,bootloader主要是引導加載kernel,當kernel被加載到內存中后 bootfs就被umount了。 rootfs包含的就是典型 Linux 系統中的/dev,/proc,/bin,/etc等標準目錄和文件。

Docker 模型的核心部分是有效利用分層鏡像機制,鏡像可以通過分層來進行繼承,基于基礎鏡像(沒有父鏡像),可以制作各種具體的應用鏡像。Docker1.10引入新的可尋址存儲模型,使用安全內容哈希代替隨機的UUID管理鏡像。同時,Docker提供了遷移工具,將已經存在的鏡像遷移到新模型上。不同 Docker 容器就可以共享一些基礎的文件系統層,同時再加上自己獨有的可讀寫層,大大提高了存儲的效率。其中主要的機制就是分層模型和將不同目錄掛載到同一個虛擬文件系統。

Docker存儲方式提供管理分層鏡像和容器的可讀寫層的具體實現。最初Docker僅能在支持AUFS文件系統的ubuntu發行版上運行,但是由于AUFS未能加入Linux內核,為了尋求兼容性、擴展性,Docker在內部通過graphdriver機制這種可擴展的方式來實現對不同文件系統的支持。

[圖片上傳失敗...(image-3bab94-1607072692844)]

如上圖所示,容器由最上面一個可寫的容器層,以及若干只讀的鏡像層組成,容器的數據就存放在這些層中。這樣的分層結構最大的特性是 Copy-on-Write:

  1. 新數據會直接存放在最上面的容器層。
  2. 修改現有數據會先從鏡像層將數據復制到容器層,修改后的數據直接保存在容器層中,鏡像層保持不變。
  3. 如果多個層中有命名相同的文件,用戶只能看到最上面那層中的文件。

分層結構使鏡像和容器的創建、共享以及分發變得非常高效,而這些都要歸功于 Docker storage driver。正是 storage driver 實現了多層數據的堆疊并為用戶提供一個單一的合并之后的統一視圖。

Docker 支持多種 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。它們都能實現分層的架構,同時又有各自的特性。關于這些方案的對比可以查閱相關文檔(http://dockone.io/article/1513)。對于 Docker 用戶來說,具體選擇使用哪個 storage driver 是一個難題,不過 Docker 官方給出了一個簡單的答案: 優先使用 Linux 發行版默認的 storage driver。 使用docker info查看系統默認的storage driver。

使用的storage driver是與主機上的Backing Filesystem有關的。storage driver 是專門用來存放docker容器和鏡像的,Backing Filesystem指的是主機的文件系統。默認的centos7 driver 是overlay2,對應的Backing Filesystem 是ext4。這個應該是沒有問題的。storage driver 和Backing Filesystem 對應關系見下表。

[圖片上傳失敗...(image-736925-1607072692844)]

對于某些容器,直接將數據放在由 storage driver 維護的層中是很好的選擇,比如那些無狀態的應用。無狀態意味著容器沒有需要持久化的數據,隨時可以從鏡像直接創建。

比如 busybox,它是一個工具箱,我們啟動 busybox 是為了執行諸如 wget,ping 之類的命令,不需要保存數據供以后使用,使用完直接退出,容器刪除時存放在容器層中的工作數據也一起被刪除,這沒問題,下次再啟動新容器即可。

但對于另一類應用這種方式就不合適了,它們有持久化數據的需求,容器啟動時需要加載已有的數據,容器銷毀時希望保留產生的新數據,也就是說,這類容器是有狀態的。

3.2 數據持久化方案

那么對于有狀態的容器,如何保存數據呢?

選項一:打包在容器里。

顯然不行。除非數據不會發生變化,否則,如何在多個副本直接保持同步呢?

選項二:數據放在 Docker 主機的本地目錄中,通過 volume 映射到容器里。

位于同一個主機的副本倒是能夠共享這個 volume,但不同主機中的副本如何同步呢?

選項三:利用 Docker 的 volume driver,由外部 storage provider 管理和提供 volume,所有 Docker 主機 volume 將掛載到各個副本。

這是目前最佳的方案。volume 不依賴 Docker 主機和容器,生命周期由 storage provider 管理,volume 的高可用和數據有效性也全權由 provider 負責,Docker 只管使用。

我們知道Docker 單機的存儲方案里面,通過 data volume 可以存儲容器的狀態。不過其本質是 Docker 主機本地的目錄。 本地目錄就存在一個隱患:如果 Docker Host 宕機了,如何恢復容器? 一個辦法就是定期備份數據,但這種方案還是會丟失從上次備份到宕機這段時間的數據。更好的方案是由專門的 storage provider 提供 volume,Docker 從 provider 那里獲取 volume 并掛載到容器。這樣即使 Host 掛了,也可以立刻在其他可用 Host 上啟動相同鏡像的容器,掛載之前使用的 volume,這樣就不會有數據丟失。

還有一個容易混淆的存儲方案,是通過在Docker主機上掛載共享文件系統(例如Ceph,GlusterFS,NFS)。如下圖所示:

[圖片上傳失敗...(image-77f9ea-1607072692844)]

在運行Docker容器的每個主機上配置分布式文件系統。通過創建一致的命名約定和統一命名空間,所有正在運行的容器都可以訪問底層的共享存儲后端。共享文件系統存儲方案利用分布式文件系統與顯式存儲技術相結合。由于掛載點在所有節點上都可用,因此可以利用它在容器之間創建共享掛載點。這種方案其實是屬于選項二的一種變種。

盡管該方案對于特定用例而言,是Docker 存儲方案的一個有價值的補充,但它具有限制容器到特定主機的可移植性的顯著缺點。它也沒有利用針對數據密集型工作負載優化的專用存儲后端。為了解決這些限制,volume plugins 被添加到Docker中,將容器的功能擴展到各種存儲后端,并且而無需強制更改應用程序設計或部署體系結構。 這就是我們下節將詳細介紹的 Volume Driver。

3.3 Volume Driver

假設有兩個 Dokcer 主機,Host1 運行了一個 MySQL 容器,為了保護數據,data volume 由 storage provider 提供,如下圖所示。

[圖片上傳失敗...(image-73f82e-1607072692844)]

當 Host1 發生故障,我們會在 Host2 上啟動相同的 MySQL 鏡像,并掛載 data volume。

[圖片上傳失敗...(image-473e7c-1607072692844)]

Docker 是如何實現這個跨主機管理 data volume 方案的呢?

答案是 volume driver。

利用 Docker 的 volume driver,由外部 storage provider 管理和提供 volume,所有 Docker 主機 volume 將掛載到各個副本。任何一個 data volume 都是由 driver 管理的,創建 volume 時如果不特別指定,將使用 local 類型的 driver,即從 Docker Host 的本地目錄中分配存儲空間。如果要支持跨主機的 volume,則需要使用第三方 driver。

目前已經有很多可用的 driver,比如使用 Azure File Storage 的 driver,使用 GlusterFS 的 driver,完整的列表可參考 https://docs.docker.com/engine/extend/legacy_plugins/#volume-plugins

[圖片上傳失敗...(image-2399d8-1607072692844)]

Volume drivers 讓我們將應用程序與底層存儲系統隔離。例如,如果原先服務使用一個 NFS驅動器,現在可以在不改變應用程序的邏輯下,直接更新服務使用不同的驅動程序,例如將數據存儲在云端,。

通過docker volume create 命令可以直接創建volume,同時在參數中指定driver 類型。 下面的例子使用vieux/sshfs 這種類型的Volume drivers。首先創建一個單獨的 volume,然后創建容器引用該volume。

3.3.1 安裝driver插件

這個示例假設有兩個節點,兩個節點直接可以通過ssh互相連接。在Docker 主機上,安裝vieux/sshfs 插件。

docker plugin install --grant-all-permissions vieux/sshfs

3.3.2 創建volume

本例中指定一個SSH密碼,但如果兩個主機配置了共享密鑰,可以省略的密碼。每個volume driver 可以有零個或多個可配置選項,每一個都指定使用 -o 標志。

docker volume create --driver vieux/sshfs \
  -o sshcmd=test@node2:/home/test \
  -o password=testpassword \
  sshvolume

3.3.3 使用volume

本例中指定一個SSH密碼,但如果兩個主機配置了共享密鑰,您可以省略的密碼。每個卷司機可以有零個或多個可配置的選項。如果 volume driver 需要傳遞參數選項,你必須使用 --mount 掛載卷,而不是使用 -v。

$ docker run -d \
  --name sshfs-container \
  --volume-driver vieux/sshfs \
  --mount src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
  nginx:latest

關于volume driver更多的介紹,可以查看官方文檔:https://docs.docker.com/storage/volumes/#use-a-volume-driver

https://docs.docker.com/storage/volumes/

4.Docker Swarm 服務架構

我們知道,Docker 場景下,應用是以容器的形式運行并提供服務的。而在Swarm場景下,任務是一個個具體的容器,通過抽象出服務的概念,來管理具有一定關聯關系的任務容器。

為了將某一個服務封裝為 Service 在Swarm 中部署執行,我們需要通過指定容器 image 以及需要在容器中執行的 Commands 來創建你的 Service,除此之外,還需要配置如下選項,

  • 指定可以在 Swarm 之外可以被訪問的服務端口號 port
  • 指定加入某個 Overlay 網絡以便 Service 與 Service 之間可以建立連接并通訊,
  • 指定該 Service 所要使用的 CPU 和 內存的大小,
  • 指定一個滾動更新的策略 (Rolling Update Policy)
  • 指定多少個 Task 的副本 (replicas) 在 Swarm 集群中同時存在

如下所示,docker-swarm.yml文件是一個YAML文件,它定義了如何Docker容器在生產中應表現。

version: "3"
services:
  web:
    # replace username/repo:tag with your name and image details
    image: username/repo:tag
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: "0.1"
          memory: 50M
      restart_policy:
        condition: on-failure
    ports:
      - "4000:80"
    networks:
      - webnet
networks:
  webnet:

docker-swarm.yml文件告訴Docker執行以下操作:

  • 拉username/repo 倉庫中拉取鏡像。
  • 將該映像的5個實例作為一個被調用的服務運行web,限制每個實例使用,最多10%的CPU(跨所有內核)和50MB的RAM。
  • 如果一個失敗,立即重啟容器。
  • 將主機上的端口4000映射到web端口80。
  • 指示web容器通過稱為負載平衡的網絡共享端口80 webnet。(在內部,容器本身web在短暫的端口發布到 80端口。)
  • webnet使用默認設置(負載平衡的覆蓋網絡)定義網絡。

4.1 服務和任務

我們一般使用如下命令來部署一個服務到swarm中:

docker stack deploy -c docker-swarm.yml myservice。

docker-swarm.yml中定義了服務所需要達到的狀態。我們不需要告訴swarm如何做,我們只需要告訴swarm 我們想要的服務狀態是什么,swarm最終會幫我們達到這個狀態。

swarm管理器將服務定義為服務的所需狀態。swarm將群中節點上的服務調度為一個或多個副本任務。任務在群集中的節點上彼此獨立地運行。

例如,想要在HTTP偵聽器的三個實例之間進行負載平衡。下圖顯示了具有三個副本的HTTP偵聽器服務。監聽器的三個實例中的每一個都是集群中的任務。容器是一個孤立的過程。在swarm模式模型中,每個任務只調用一個容器。任務類似于調度程序放置容器的“槽”。容器生效后,調度程序會識別該任務處于運行狀態。如果容器未通過運行狀況檢查或終止,則任務將終止。

[圖片上傳失敗...(image-fd0e3b-1607072692844)]

Services,Tasks 和 Containers 之間的關系可以用上面這張圖來描述。來看這張圖的邏輯,表示用戶想要通過 Manager 節點部署一個有 3 個副本的 Nginx 的 Service,Manager 節點接收到用戶的 Service definition 后,便開始對該 Service 進行調度,將會在當前可用的Worker(或者Manager )節點中啟動相應的 Tasks 以及相關的副本;所以可以看到,Service 實際上是 Task 的定義,而 Task 則是執行在節點上的程序。

Task 是什么呢?其實就是一個 Container,只是,在 Swarm 中,每個 Task 都有自己的名字和編號,如圖,比如 nginx.1、nginx.2 和 nginx.3,這些 Container 各自運行在各自的 node 上,當然,一個 node 上可以運行多個 Container;

4.2 任務調度

任務是swarm集群內調度的原子單位。在創建或更新服務時,聲明定義服務狀態,協調器通過調度任務來實現所需的狀態。例如上例的服務,該服務指示協調器始終保持三個HTTP偵聽器實例的運行。協調器通過創建三個任務來響應。每個任務都是調度程序通過生成容器來填充的插槽。容器是任務的實例化。如果HTTP偵聽器任務隨后未通過其運行狀況檢查或崩潰,則協調器會創建一個新的副本任務,該任務會生成一個新容器。

任務是單向機制。它通過一系列狀態單調進行:已分配,準備,運行等。如果任務失敗,則協調器將刪除任務及其容器,然后根據服務指定的所需狀態創建新任務以替換它。

Docker swarm mode 模式的底層技術實際上就是指的是調度器( scheduler )和編排器( orchestrator );下面這張圖展示了 Swarm mode 如何從一個 Service 的創建請求并且成功將該 Service 分發到 worker 節點上執行的過程。

[圖片上傳失敗...(image-8e0c6c-1607072692844)]

  • 首先,看上半部分Swarm manager
    1. 用戶通過 Docker Engine Client 使用命令 docker service create 提交 Service definition,
    2. 根據 Service definition 創建相應的 Task,
    3. 為 Task 分配 IP 地址,
      注意,這是分配運行在 Swarm 集群中 Container 的 IP 地址,該 IP 地址最佳的分配地點是在這里,因為 Manager 節點上保存得有最新最全的 Tasks 的狀態信息,為了保證不與其他的 Task 分配到相同的 IP,所以在這里就將 IP 地址給初始化好;
    4. 將 Task 分發到 Node 上,可以是 Manager 節點也可以使 Worker 節點,
    5. 對 Worker 節點進行相應的初始化使得它可以執行 Task
  • 接著,看下半部分Swarm Work
    1. 首先連接 manager 的分配器( scheduler)檢查該 task
    2. 驗證通過以后,便開始通過 Worker 節點上的執行器( executor )執行;

注意,上述 task 的執行過程是一種單向機制,比如它會按順序的依次經歷 assigned, prepared 和 running 等執行狀態,不過在某些特殊情況下,在執行過程中,某個 task 失敗了( fails ),編排器( orchestrator )直接將該 task 以及它的 container 給刪除掉,然后在其它節點上另外創建并執行該 task;

4.3 任務狀態

通過運行docker service ps <service-name>以獲取任務的狀態。該 CURRENT STATE字段顯示任務的狀態以及任務的持續時間 。

[root@swarm01 ~]# docker service ps myweb_web
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR                         PORTS
52lolsfykj4t        myweb_web.1         httpd:v2            swarm03             Running             Running 16 minutes ago
zwpufyfacagy         \_ myweb_web.1     httpd:v2            swarm03             Shutdown            Failed 16 minutes ago    "task: non-zero exit (255)"
00fhp9k1byf6        myweb_web.2         httpd:v2            swarm02             Running             Running 16 minutes ago
kgtiyw39xpcc         \_ myweb_web.2     httpd:v2            swarm02             Shutdown            Failed 17 minutes ago    "task: non-zero exit (255)"
vefk4yn0b00m        myweb_web.3         httpd:v2            swarm01             Running             Running 16 minutes ago
pnj4z0j281ng         \_ myweb_web.3     httpd:v2            swarm01             Shutdown            Failed 17 minutes ago    "task: non-zero exit (255)"

任務具有如下幾種狀態:

任務狀態 描述
NEW 任務已初始化。
PENDING 分配了任務的資源。
ASSIGNED Docker將任務分配給節點。
ACCEPTED 該任務被工作節點接受。如果工作節點拒絕該任務,則狀態將更改為REJECTED
PREPARING Docker正在準備任務。
STARTING Docker正在開始這項任務。
RUNNING 任務正在執行。
COMPLETE 任務退出時沒有錯誤代碼。
FAILED 任務退出并顯示錯誤代碼。
SHUTDOWN Docker請求關閉任務。
REJECTED 工作節點拒絕了該任務。
ORPHANED 該節點停機時間過長。
REMOVE 該任務不是終端,但關聯的服務已被刪除或縮小。

如果在某個 service 的調度過程中,發現當前沒有可用的 node 資源可以執行該 service,這個時候,該 service 的狀態將會保持為 pending 的狀態;下面,我們來看一些例子可能使得 service 維持在 pending 狀態,

  • 如果當所有的節點都被停止或者進入了 drained 狀態,這個時候,你試圖創建一個 service,該 service 將會一直保持 pending 狀態直到當前某個節點可用為止;不過要注意的是,第一個恢復的 node 將會得到所有的 task 調度請求,接收并執行,因此,這種情況在 production 環境上要盡量避免;
  • 你可以為你的 service 設置執行所需的內存大小,在調度的過程當中,發現沒有一個 node 能夠滿足你的內存請求,那么你當前的 service 將會一直處于 pending 狀態直到有滿足需求的 node 出現;
  • 可以對服務施加放置約束,并且可能無法在給定時間遵守約束。

上面的例子都表明一個事實,就是你期望的執行條件與 Swarm 現有的可用資源的情況不匹配,不吻合,因此,作為 Swarm 的管理者,應該考慮對 Swarm 集群整體進行擴容;

4.4 服務副本與全局服務

Docker Swarm 有兩種類型的服務部署模式:復制和全局。

Swarm通過--mode選項設置服務類型,提供了兩種模式:一種是replicated,我們可以指定服務Task的個數(也就是需要創建幾個冗余副本),這也是Swarm默認使用的服務類型;另一種是global,這樣會在Swarm集群的每個Node上都創建一個服務。如下圖所示(出自Docker官網),是一個包含replicated和global模式的Swarm集群:

[圖片上傳失敗...(image-3f1a62-1607072692844)]

上圖中,黃色表示的replicated模式下的Service Replicas,灰色表示global模式下Service的分布。

在Swarm mode下使用Docker,可以實現部署運行服務、服務擴容縮容、刪除服務、滾動更新等功能。

4.5 調度策略

Swarm提供了幾種不同的方法來控制服務任務可以在哪些節點上運行。

  • 可以指定服務是需要運行特定數量的副本還是應該在每個工作節點上全局運行。請參閱2.4服務副本與全局服務。

  • 您可以配置服務的 CPU或內存要求,該服務僅在滿足這些要求的節點上運行。

  • 通過設置約束標簽 --constraint ,您可以將服務配置為僅在具有特定(任意)元數據集的節點上運行,并且如果不存在適當的節點,則會導致部署失敗。例如,您可以指定您的服務只應在任意標簽pci_compliant設置為的節點上運行 true

  • 通過設置首選項 --preferences,您可以將具有一系列值的任意標簽應用于每個節點,并使用算法將服務的任務分布到這些節點上。目前,唯一支持的算法是spread,該算法將嘗試均勻放置它們。例如,如果rack使用值為1-10 的標簽標記每個節點,然后指定鍵入的放置首選項rack,服務任務將盡可能均勻地放置在具有標簽的所有節點上。如果具有rack標簽的節點個數,大于任務個數,那么則會根據rack 值的大小排序,然后優先選擇排名靠前的節點。

    與約束不同,放置首選項是盡力而為,如果沒有節點可以滿足首選項,則服務不會失敗。如果為服務指定放置首選項,則當集群管理器決定哪些節點應運行服務任務時,與該首選項匹配的節點的排名會更高。其他因素,例如服務的高可用性,也是計劃節點運行服務任務的因素。例如,如果您有N個節點帶有機架標簽(然后是其他一些節點),并且您的服務配置為運行N + 1個副本,那么+1將在一個尚未擁有該服務的節點上進行調度,如果無論該節點是否具有rack標簽。

4.6 服務管理

docker swarm 還可以使用 stack 這一模型,用來部署管理和關聯不同的服務。stack 功能比service 還要強大,因為它具備多服務編排部署的能力。stack file 是一種 yaml 格式的文件,類似于 docker-compose.yml 文件,它定義了一個或多個服務,并定義了服務的環境變量、部署標簽、容器數量以及相關的環境特定配置等。

以下實驗僅僅創建利用stack 創建單一服務。

4.6.1 創建服務

服務配置文件docker-swarm.yml ,內容如下

version: '3'
services:
  web:
    image: "httpd:v1"
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 256M
      restart_policy:
        condition: on-failure
    ports:
     - "7080:80"
    volumes:
     - /var/www/html:/usr/local/apache2/htdocs

image: "httpd" :httpd是docker hub 下載的鏡像。提供簡單的httpd服務,此處用來做服務都高可用驗證。

replicas: 3 表示該服務副本有3個,可對服務副本進行擴縮容,后續會講到。

/var/www/html:/usr/local/apache2/htdocs :表示httpd 首頁的內容。各個swarm節點上展示的內容分別是其主機名信息。

在docker-swarm.yml 同級目錄下,執行命令

[root@swarm01 swarm]# docker stack deploy -c docker-swarm.yml myservice
Creating network myservice_default
Creating service myservice_web

4.6.2 查看服務

查看服務創建是否成功。

[root@swarm01 swarm]# docker stack ls
NAME                SERVICES
myservice           1
[root@swarm01 swarm]# docker stack services myservice
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
kom8x49f7qv2        myservice_web       replicated          3/3                 httpd:latest        *:7080->80/tcp
[root@swarm01 swarm]# docker stack ps myservice
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
sybn1dur1wmw        myservice_web.1     httpd:latest        swarm03             Running             Running 45 seconds ago
mlcb5r9vzv6z        myservice_web.2     httpd:latest        swarm03             Running             Running 45 seconds ago
fw21lcu3zp83        myservice_web.3     httpd:latest        swarm01             Running             Running 45 seconds ago

可以看到 swarm01 上面運行了1個容器,swarm03上面運行了2個,而swarm02上面一個容器都沒有,這是咋回事呢?哦,原來我們在創建集群過程中,對swarm02 做了狀態變更的操作,將其設置為不運行任務。docker node update --availability drain swarm02. 查看集群狀態。

[root@swarm01 swarm]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
epoic1y0vv830vnwbc6nnacjc *   swarm01             Ready               Active              Leader              18.03.1-ce
nsqqv181e0r7mu77azixtqhzl     swarm02             Ready               Drain               Reachable           18.03.1-ce
mkkxaeqergz8xw21bbkd47q1c     swarm03             Ready               Active              Reachable           18.03.1-ce

此時打開瀏覽器,輸入HTTP URL。任意swarm集群節點都可以訪問httpd服務。

[圖片上傳失敗...(image-4b7cd9-1607072692844)]

需要說明的是,由于3個節點的/var/www/html 路徑未做共享存儲。因此swarm會隨機顯示一個swarm節點的網頁內容。

4.6.3 服務擴縮容

修改docker-swarm.yml 配置字段 replicas: 5 ,將容器個數提升到5個。由于實驗環境資源有限,因此這里將swarm02狀態變更為可以狀態,用以分配容器。

docker node update --availability active swarm02.

重新執行部署命令docker stack deploy -c docker-swarm.yml myservice

也可以通過命令直接對服務進行操作,但是不推薦。 docker service scale 服務ID=服務個數

[root@swarm01 swarm]# docker stack ps myservice
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                    ERROR               PORTS
unwepav5z9nz        myservice_web.1     httpd:v1            swarm02             Running             Running less than a second ago
3j5o1tktcg1l        myservice_web.2     httpd:v1            swarm01             Running             Preparing 3 seconds ago
wl1unt6lynft        myservice_web.3     httpd:v1            swarm03             Running             Running less than a second ago
[root@swarm01 swarm]# vi docker-swarm.yml
[root@swarm01 swarm]# docker stack deploy -c docker-swarm.yml myservice
Updating service myservice_web (id: 85quzwi5k0btkn5wlht4jbco9)
image httpd:v1 could not be accessed on a registry to record
its digest. Each node will access httpd:v1 independently,
possibly leading to different nodes running different
versions of the image.

[root@swarm01 swarm]# docker stack ps myservice
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                    ERROR               PORTS
unwepav5z9nz        myservice_web.1     httpd:v1            swarm02             Running             Running 24 seconds ago
3j5o1tktcg1l        myservice_web.2     httpd:v1            swarm01             Running             Running 31 seconds ago
wl1unt6lynft        myservice_web.3     httpd:v1            swarm03             Running             Running 24 seconds ago
ujedjxbuffxp        myservice_web.4     httpd:v1            swarm01             Running             Running less than a second ago
ml9qvjh2add7        myservice_web.5     httpd:v1            swarm02             Running             Running less than a second ago

同樣,對于服務縮容,也僅需要修改replicas: 配置即可。這里不再演示。

4.6.4 刪除服務

例如,刪除myredis應用服務,執行docker service rm myredis,則應用服務myredis的全部副本都會被刪除。

本例使用的是stack ,刪除stack ,即可刪除其下的服務。沒錯,docker語法非常類似,docker stack rm myservice

需要注意的是,對swarm上面服務的操作,都必須在manager節點上運行。

4.6.5 服務升降級

服務升降級,對應到docker中,則利用了容器鏡像的更新,升級很好理解,直接把tag 標簽版本往上提。那么降級,其實也可以理解為另外一種“升級“,只是鏡像內容是上一個版本的而已。對于鏡像標簽的命令應該遵循一套嚴格的規范,這里不再贅述。

先介紹利用service 命令行工具的用法。

服務的滾動更新,這里我參考官網文檔的例子說明。在Manager Node上執行如下命令:

docker service create --replicas 3  --name redis --update-delay 10s redis:3.0.6

上面通過指定 --update-delay 選項,表示需要進行更新的服務,每次成功部署一個,延遲10秒鐘,然后再更新下一個服務。如果某個服務更新失敗,則Swarm的調度器就會暫停本次服務的部署更新。

另外,也可以更新已經部署的服務所在容器中使用的Image的版本,例如執行如下命令:

將Redis服務對應的Image版本有3.0.6更新為3.0.7,同樣,如果更新失敗,則暫停本次更新。

那么,stack file 如何定義滾動更新呢?

      update_config:
        parallelism: 2
        delay: 10s

修改docker-swarm.yml 如下

version: '3'
services:
  web:
    image: "httpd:v2"
    deploy:
      replicas: 3
      update_config:
        parallelism: 2
        delay: 30s
      resources:
        limits:
          cpus: "0.5"
          memory: 256M
      restart_policy:
        condition: on-failure
    ports:
     - "7080:80"
    volumes:
     - /var/www/html:/usr/local/apache2/htdocs

parallelism 表示同時升級的個數,delay 則表示間隔多長時間升級。

同時將httpd 由v1 升級到v2,這里我們使用同一個鏡像,只是tag 修改了一下。查看服務狀態

[root@swarm01 swarm]# docker stack ps myservice
ID                  NAME                  IMAGE               NODE                DESIRED STATE       CURRENT STATE                 ERROR               PORTS
qk8pvvc9nmv0        myservice_web.1       httpd:v2            swarm01             Running             Running 56 seconds ago
unwepav5z9nz         \_ myservice_web.1   httpd:v1            swarm02             Shutdown            Shutdown 48 seconds ago
uyj0pm1df9hl        myservice_web.2       httpd:v2            swarm02             Running             Running 46 seconds ago
3j5o1tktcg1l         \_ myservice_web.2   httpd:v1            swarm01             Shutdown            Shutdown about a minute ago
qdjov5zf5alb        myservice_web.3       httpd:v2            swarm03             Running             Running 10 seconds ago
wl1unt6lynft         \_ myservice_web.3   httpd:v1            swarm03             Shutdown            Shutdown 12 seconds ago

可以看到同時只有2個容器在升級,同時第3個容器也是在間隔了30s 之后,才開始升級。

--filter 過濾條件docker stack ps --filter "desired-state=running" myservice

4.6.6 服務健康檢查

對于容器而言,最簡單的健康檢查是進程級的健康檢查,即檢驗進程是否存活。Docker Daemon會自動監控容器中的PID1進程,如果docker run命令中指明了restart policy,可以根據策略自動重啟已結束的容器。在很多實際場景下,僅使用進程級健康檢查機制還遠遠不夠。比如,容器進程雖然依舊運行卻由于應用死鎖無法繼續響應用戶請求,這樣的問題是無法通過進程監控發現的 。

下面先介紹Docker容器健康檢查機制,之后再介紹Docker Swarm mode的新特性。

Docker 原生健康檢查能力

而自 1.12 版本之后,Docker 引入了原生的健康檢查實現,可以在Dockerfile中聲明應用自身的健康檢測配置。HEALTHCHECK 指令聲明了健康檢測命令,用這個命令來判斷容器主進程的服務狀態是否正常,從而比較真實的反應容器實際狀態。

HEALTHCHECK 指令格式:

  • HEALTHCHECK [選項] CMD <命令>:設置檢查容器健康狀況的命令
  • HEALTHCHECK NONE:如果基礎鏡像有健康檢查指令,使用這行可以屏蔽掉

注:在Dockerfile中 HEALTHCHECK 只可以出現一次,如果寫了多個,只有最后一個生效。

使用包含 HEALTHCHECK 指令的dockerfile構建出來的鏡像,在實例化Docker容器的時候,就具備了健康狀態檢查的功能。啟動容器后會自動進行健康檢查。

HEALTHCHECK 支持下列選項:

  • interval=<間隔>:兩次健康檢查的間隔,默認為 30 秒;
  • timeout=<間隔>:健康檢查命令運行超時時間,如果超過這個時間,本次健康檢查就被視為失敗,默認 30 秒;
  • retries=<次數>:當連續失敗指定次數后,則將容器狀態視為 unhealthy,默認 3 次。
  • start-period=<間隔>: 應用的啟動的初始化時間,在啟動過程中的健康檢查失效不會計入,默認 0 秒; (從17.05)引入

在 HEALTHCHECK [選項] CMD 后面的命令,格式和 ENTRYPOINT 一樣,分為 shell 格式,和 exec 格式。命令的返回值決定了該次健康檢查的成功與否:

  • 0:成功;
  • 1:失敗;
  • 2:保留值

容器啟動之后,初始狀態會為 starting (啟動中)。Docker Engine會等待 interval 時間,開始執行健康檢查命令,并周期性執行。如果單次檢查返回值非0或者運行需要比指定 timeout 時間還長,則本次檢查被認為失敗。如果健康檢查連續失敗超過了 retries 重試次數,狀態就會變為 unhealthy (不健康)。

注:

  • 一旦有一次健康檢查成功,Docker會將容器置回 healthy (健康)狀態
  • 當容器的健康狀態發生變化時,Docker Engine會發出一個 health_status 事件。

假設我們有個鏡像是個最簡單的 Web 服務,我們希望增加健康檢查來判斷其 Web 服務是否在正常工作,我們可以用 curl來幫助判斷,其 Dockerfile 的 HEALTHCHECK 可以這么寫:

FROM elasticsearch:5.5 
 
HEALTHCHECK --interval=5s --timeout=2s --retries=12 \ 
  CMD curl --silent --fail localhost:9200/_cluster/health || exit 1

然后編譯鏡像,并運行

docker build -t test/elasticsearch:5.5 . 
docker run --rm -d \ 
    --name=elasticsearch \ 
    test/elasticsearch:5.5 

執行 docker ps容器檢查命令,發現過了幾秒之后,Elasticsearch容器從 starting 狀態進入了 healthy 狀態

$ docker ps 
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                            PORTS                NAMES 
c9a6e68d4a7f        test/elasticsearch:5.5   "/docker-entrypoin..."   2 seconds ago       Up 2 seconds (health: starting)   9200/tcp, 9300/tcp   elasticsearch 
$ docker ps 
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                    PORTS                NAMES 
c9a6e68d4a7f        test/elasticsearch:5.5   "/docker-entrypoin..."   14 seconds ago      Up 13 seconds (healthy)   9200/tcp, 9300/tcp   elasticsearch 

Docker Swarm健康檢查能力

在Docker 1.13之后,在Docker Swarm mode中提供了對健康檢查策略的支持。

可以在 docker service create 命令中指明健康檢查策略

$ docker service create -d \
    --name=elasticsearch \
    --health-cmd="curl --silent --fail localhost:9200/_cluster/health || exit 1" \
    --health-interval=5s \
    --health-retries=12 \
    --health-timeout=2s \
    elasticsearch

在Swarm模式下,Swarm manager會監控服務task的健康狀態,如果容器進入 unhealthy 狀態,它會停止容器并且重新啟動一個新容器來取代它。這個過程中會自動更新服務的 load balancer (routing mesh) 后端或者 DNS記錄,可以保障服務的可用性。

在1.13版本之后,在服務更新階段也增加了對健康檢查的支持,這樣在新容器完全啟動成功并進入健康狀態之前,load balancer/DNS解析不會將請求發送給它。這樣可以保證應用在更新過程中請求不會中斷。

[圖片上傳失敗...(image-122b8a-1607072692844)]

docker 官方網站有些常用容器的健康檢查的例子:https://github.com/docker-library/healthcheck

5.附錄

5.1 參考文檔

官網文檔:

https://docs.docker.com/engine/swarm/

https://docs.docker.com/get-started/part4/

https://docs.docker.com/network/overlay/#publish-ports-on-an-overlay-network

技術博客:

http://www.dockerinfo.net/2552.html

https://www.cnblogs.com/bigberg/p/8761047.html

http://blog.51cto.com/cloudman/1968287

https://neuvector.com/network-security/docker-swarm-container-networking/

http://www.uml.org.cn/yunjisuan/201708282.asp

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。