Docker Swarm詳解【長文】
Swarm介紹
Swarm是Docker公司自研發的容器叢集管理系統, Swarm在早期是作為一個獨立服務存在, 在Docker Engine v1.12中集成了Swarm的叢集管理和編排功能。可以通過初始化Swarm或加入現有Swarm來啟用Docker引擎的Swarm模式。
Docker Engine CLI和API包括了管理Swarm節點命令,比如新增、刪除節點,以及在Swarm中部署和編排服務。也增加了服務棧(Stack)、服務(Service)、任務(Task)概念。
Swarm的亮點
-
Docker Engine整合叢集管理:使用Docker Engine CLI 建立一個Docker Engine的Swarm模式,在叢集中部署應用程式服務。
-
去中心化設計:Swarm角色分為Manager和Worker節點, Manager節點故障不影響應用使用。
-
擴容縮容:可以宣告每個服務執行的容器數量,通過新增或刪除容器數自動調整期望的狀態。
-
期望狀態協調:Swarm Manager節點不斷監視叢集狀態,並調整當前狀態與期望狀態之間的差異。
-
多主機網路:可以為服務指定overlay網路。當初始化或更新應用程式時, Swarm manager會自動為overlay網路上的容器分配IP地址。
-
服務發現:Swarm manager節點為叢集中的每個服務分配唯一的DNS記錄和負載均衡VIP。可以通過Swarm內建的DNS伺服器查詢叢集中每個執行的容器。
-
負載均衡:實現服務副本負載均衡,提供入口訪問。
-
安全傳輸: Swarm中的每個節點使用TLS相互驗證和加密, 確保安全的其他節點通訊。
-
滾動更新:升級時,逐步將應用服務更新到節點,如果出現問題,可以將任務回滾到先前版本。
swarm關鍵概念
什麼是swarm?
叢集管理和任務編排功能已經整合到了Docker引擎中,通過使用swarmkit。swarmkit是一個獨立的,專門用於Docker容器編排的專案,可以直接在Docker上使用。
Swarm叢集是由多個執行swarm模式的Docker主機組成,關鍵的是,Docker預設集成了swarm mode。swarm叢集中有manager(管理成員關係和選舉)、worker(執行swarm service)。
一個Docker主機可以是manager,也可以是worker角色。當你建立一個service時, 定義了它的理想狀態(副本數、網路、儲存資源、對外暴露的埠等)。Docker會維持它的狀態,例如,如果一個worker node不可用了,Docker會排程不可用node的task到其他nodes上。
執行在容器中的一個task,是swarm service的一部分,且通過swarm manager進行管理和排程,和獨立的容器是截然不同的。
swarm service相比單容器的一個最大優勢就是, 能夠修改一個服務的配置:包括網路、資料卷,不需要手工重啟服務。Docker將會更新配置,把過期配置的task停掉,重新建立一個新配置的容器。
當然,也許 會覺得docker compose也能做swarm類似的事情,某些場景下是可以。但是,swarm相比docker compose,功能更加豐富,比如說自動擴容、縮容,分配至task到不同的nodes等。
https://docs.docker.com/engine/reference/commandline/node_demote/)
Manager節點
Manager節點用來處理叢集管理任務:
- 維護叢集狀態
- 排程service
- 提供swarm模式HTTP API 端點
Manager通過Raft的實現,來使Swarm的整體維持在一個一致的內部狀態上,service則執行在這個一致的內部狀態之上。為了測試的目的,我們可以使用單獨manager節點。如果單獨的manager節點出現問題,service依然會一直執行,但是這樣就需要建立一個新的叢集來恢復一致的內部狀態維護。
利用Swarm模式的容錯特性,推薦使Swarm的節點為奇數個,來實現高可用的要求。當採用多個manager節點時,我們就可以在不停止叢集執行的情況下恢復停止執行的manager節點。
-
3個manager節點最大容錯允許1個manager不可用
-
5個manager節點最大容錯允許2個manager不可用。
-
N個manager節點最大容錯允許
(N-1)/2
個manager節點不可用。 -
建議最大manager節點數為7個。
重要提示:不限制的增加manager節點並不會增加可擴充套件性或者增加執行效能。通常還會起到相反的作用。
Worker節點
Worker節點也是Docker Engine的例項,目的是用來執行Container。Worker節點不會像Manager節點那樣提供叢集的管理、任務排程和API。
可以建立僅有一個manager節點的Swarm,但是不能在沒有manager節點的前提下,建立Worker節點。在預設情況下,manager節點同時也是一個worker節點。在一個manager節點的叢集中,執行docker service create
命令,排程器會將所有的task排程在本地Docker Engine上執行。
要阻止排程器將task分配到manager節點上執行,就緒要將manager節點的可用性狀態設定成DRAIN
。排程器會停止DRAIN
節點上的task,轉移到其他狀態為ACTIVE
的節點上繼續執行停止的task。並且排程器不會再將新的task分配給DRAIN
狀態的節點。
可以檢視docker node update
命令的幫助,學習如何修改節點的可用性狀態。
改變節點角色
我們可以通過docker node promote
來將一個worker節點修改成一個manager節點。例如,某些維護需要,我們將停止一個manager節點,這時我們可以將某一個正在執行的worker節點變成manager節點,來代替停止的那個manager節點。詳情請參考:Node promote
同樣也可將一個manager節點改成一個worker節點。詳情請參考:[Node demote](
services和tasks
task是在docekr容器中執行的命令,Manager節點根據指定數量的任務副本分配任務給worker節點。
在副本集模式下,swarm manager將會基於 需要擴容的需求,把task分發到各個節點。對於全域性service,swarm會在每個可用節點上執行一個task。
task攜帶Docker引擎和一組命令讓其執行在容器中。它是swarm的原子排程單元。manager節點在擴容的時候回交叉分配task到各個節點上,一旦一個task分配到一個node,它就不能移動到其他node。
service就是在manager或woker節點上定義的tasks。service是swarm系統最核心的架構,同時也是和swarm最主要的互動者。
負載均衡
swarm manager使用 ingress負載均衡來暴露 需要讓外部訪問的服務。swarm manager能夠自動的分配一個外部埠到service,當然, 也能夠配置一個外部埠, 可以指定任意沒有使用的port,如果 不指定埠,那麼swarm manager會給service指定30000-32767之間任意一個埠。
swarn模式有一個內部的DNS元件,它能夠自動分發每個服務在swarm裡面。swarm manager使用內部負載均衡機制來接受叢集中節點的請求,基於DNS名字解析來實現。
Swarm架構圖
Swarm manager:
- API: 接受命令,建立一個service(API輸入)
- orchestrator:service物件建立的task進行編排工作(編排)
- allocater:為各個task分配IP地址(分配IP)
- dispatcher:將task分發到nodes(分發任務)
- scheduler:安排一個worker執行task(執行任務)
worker node
- 連線到分發器接受指定的task
- 將task指派到對應的worker節點
Swarm的排程策略
Swarm在排程(scheduler)節點(leader節點)執行容器的時候,會根據指定的策略來計算最適合執行容器的節點,目前支援的策略有:spread, binpack, random.
Random
顧名思義,就是隨機選擇一個Node來執行容器,一般用作除錯用,spread和binpack策略會根據各個節點的可用的CPU, RAM以及正在執行的容器的數量來計算應該執行容器的節點。
Spread(預設)
在同等條件下,Spread策略會選擇執行容器最少的那臺節點來執行新的容器,binpack策略會選擇執行容器最集中的那臺機器來執行新的節點。
使用Spread策略會使得容器會均衡的分佈在叢集中的各個節點上執行,一旦一個節點掛掉了只會損失少部分的容器。
Binpack
Binpack策略最大化的避免容器碎片化,就是說binpack策略儘可能的把還未使用的節點留給需要更大空間的容器執行,儘可能的把容器執行在一個節點上面。
Swarm Cluster模式特性
批量建立服務
建立容器之前先建立一個overlay的網路,用來保證在不同主機上的容器網路互通的網路模式
強大的叢集的容錯性
當容器副本中的其中某一個或某幾個節點宕機後,cluster會根據自己的服務註冊發現機制,以及之前設定的值--replicas n
,在叢集中剩餘的空閒節點上,重新拉起容器副本。整個副本遷移的過程無需人工干預,遷移後原本的叢集的load balance依舊好使!
不難看出,docker service其實不僅僅是批量啟動服務這麼簡單,而是在叢集中定義了一種狀態。Cluster會持續檢測服務的健康狀態並維護叢集的高可用性。
服務節點的可擴充套件性
Swarm Cluster不光只是提供了優秀的高可用性,同時也提供了節點彈性擴充套件或縮減的功能。當容器組想動態擴充套件時,只需通過scale引數即可複製出新的副本出來。
仔細觀察的話,可以發現所有擴展出來的容器副本都run在原先的節點下面,如果有需求想在每臺節點上都run一個相同的副本,方法其實很簡單,只需要在命令中將"--replicas n"
更換成"--mode=global"
即可!
複製服務(--replicas n)
將一系列複製任務分發至各節點當中,具體取決於您所需要的設定狀態,例如“--replicas 3
”。
全域性服務(--mode=global)
適用於叢集內全部可用節點上的服務任務,例如“--mode global”。如果在 Swarm 叢集中設有 7 臺 Docker 節點,則全部節點之上都將存在對應容器。
排程機制
所謂的排程其主要功能是cluster的server端去選擇在哪個伺服器節點上建立並啟動一個容器例項的動作。它是由一個裝箱演算法和過濾器組合而成。每次通過過濾器(constraint)啟動容器的時候,swarm cluster 都會呼叫排程機制篩選出匹配約束條件的伺服器,並在這上面執行容器。
Swarm cluster的建立過程
- 發現Docker叢集中的各個節點,收集節點狀態、角色資訊,並監視節點狀態的變化
- 初始化內部排程(scheduler)模組
- 建立並啟動API監聽服務模組
一旦建立好這個cluster,就可以用命令docker service批量對叢集內的容器進行操作,非常方便!
在啟動容器後,docker 會根據當前每個swarm節點的負載判斷,在負載最優的節點執行這個task任務,用"docker service ls"和
和docker service ps + taskID
"可以看到任務執行在哪個節點上。容器啟動後,有時需要等待一段時間才能完成容器建立。
Docker Swarm 網路
swarm的三個網路
名稱 | 型別 | 註釋 |
---|---|---|
docker_gwbridge | bridge | none |
ingress | overlay | none |
<custom-network> |
overlay | none |
Docker中內建的網路模式包括如下幾種:
- bridge 我們基於該網路模式建立了mynet網路
- host 本地網路模式
- macvlan 這個模式貌似是最新加的
- null 無網路
- overlay 用於swarm叢集中容器的跨主機網路訪問
docker_gwbridge和ingress是當用戶執行了docker swarm init/connect之後自動建立的。
- docker_gwbridge是bridge型別的負責本機container和主機直接的連線。docker_gwbridge是一種橋接網路,將 overlay 網路(包括 ingress 網路)連線到一個單獨的 Docker 守護程序的物理網路。預設情況下,服務正在執行的每個容器都連線到本地 Docker 守護程序主機的 docker_gwbridge 網路。docker_gwbridge 網路在初始化或加入 Swarm 時自動建立。大多數情況下,使用者不需要自定義配置,但是 Docker 允許自定義。
- ingress負責service在多個主機container之間的路由。ingress network 是一個特殊的 overlay 網路,用於服務節點間的負載均衡。當任何 Swarm 節點在釋出的埠上接收到請求時,它將該請求交給一個名為 IPVS 的模組。IPVS 跟蹤參與該服務的所有IP地址,選擇其中的一個,並通過 ingress 網路將請求路由到它。初始化或加入 Swarm 叢集時會自動建立 ingress 網路,大多數情況下,使用者不需要自定義配置,但是 docker 17.05 和更高版本允許你自定義。
<custom-network>
是使用者自己建立的overlay網路,通常我們都需要建立自己的network並把service掛在上面。
- Overlay networks 管理 Swarm 中 Docker 守護程序間的通訊。你可以將服務附加到一個或多個已存在的 overlay 網路上,使得服務與服務之間能夠通訊。
二,swarm service的路由
swarm service的路由辦法通常有兩種,VIP和DSN
service路由之:虛擬IP
這是預設情況設定,當用戶建立service的時候,這個service會被分配一個 VIP,然後每一個具體的container都有一個獨立的IP,ingress會負責從VIP到各個container之間的路由。
Overlay 網路建立及部署
docker network create --driver overlay mynet
docker@manager1:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
669d98d758ac bridge bridge local
042df78b01ec docker_gwbridge bridge local
1c27fcf531c2 host host local
95e47qzos7zn ingress overlay swarm
pybpdb2fibky my-network overlay swarm
i5xm0y9n835s mynet overlay swarm
37d3b44c34e2 none null local
建立新服務並使用 overlay 網路
建立兩個service
docker@manager1:~$ docker service create --replicas 2 --network mynet --name my-web nginx
lpgc43imsl1s3iocgcpy3dhkm
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged
docker@manager1:~$ docker service create --replicas 1 --network mynet --name my-box busybox:1.28.3 sleep 3000
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
部署完成之後 檢視 my-web 服務的虛擬IP 地址
docker service inspect my-web -f "{{ .Endpoint.VirtualIPs }}"
docker@manager1:~$ docker service inspect my-web -f "{{ .Endpoint.VirtualIPs }}"
[{pybpdb2fibkyk9jklcjqkrch7 10.0.1.2/24}]
docker@manager1:~$ docker service inspect my-box -f "{{ .Endpoint.VirtualIPs }}"
[{pybpdb2fibkyk9jklcjqkrch7 10.0.1.19/24}]
檢視容器IP地址 和 service 的VIP
docker@manager1:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fcd9f0e5d0f2 nginx:latest "nginx -g 'daemon of…" 8 hours ago Up 8 hours 80/tcp my-web.2.ssl05byx4s3kr4dku1mw79f6f
docker@manager1:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-web.2.ssl05byx4s3kr4dku1mw79f6f
10.0.1.8
docker@worker1:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-box.1.vc14enzddj8c88rykqszf38fe 10.0.1.20
docker@worker1:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-web.1.50zdanlgukvwxq2fujtcg9o4n 10.0.1.7
docker@worker1:~$ docker exec -it ae92907ac302 sh
/ # nslookup my-web
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: my-web
Address 1: 10.0.1.2 bogon # my-web 的虛擬IP
/ # nslookup tasks.my-web
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: tasks.my-web # 返回服務的容器IP地址
Address 1: 10.0.1.8 my-web.2.ssl05byx4s3kr4dku1mw79f6f.my-network
Address 2: 10.0.1.7 my-web.1.50zdanlgukvwxq2fujtcg9o4n.my-network
/ # nslookup my-box # 返回my-box服務的VIP
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: my-box
Address 1: 10.0.1.19 bogon
/ # nslookup tasks.my-box
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: tasks.my-box
Address 1: 10.0.1.20 ae92907ac302
將現有服務連線到 overlay 網路
docker service update --network-add my-network my-web
刪除正在執行的服務網路連線
docker service update --network-rm my-network my-web
從這個例子我們可以看到:
- service my-web的虛擬IP是10.0.1.2
- 它有兩個containers,IP分別是:10.0.1.7 和 10.0.1.8
- 127.0.0.11是swarm內建的DSN伺服器,用來解析service名字到IP地址
我們再進入container看看:
docker@worker1:~$ docker exec -it 29cce8d4f412 sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:01:15
inet addr:10.0.1.21 Bcast:10.0.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:04
inet addr:172.18.0.4 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:11 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:866 (866.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
每一個container有兩塊網絡卡:
- eth0: 10.0.1.3 這就是我們前面看到的service container IP地址,是屬於網路my-network的。
- eth1: 172.18.0.4,這是另一個網路地址;是誰的呢, 是網路docker_gwbridge。(另外bridge網路使用的是172.17網段)
- 也就是說每一個container屬於兩個網路,my-network和docker_gwbridge,分別用來service路由,和連線主機網路。
補充一點網絡卡eth1: 172.18.0.4,對應的閘道器地址是172.18.0.1,那個這個閘道器地址172.18.0.1是誰呢,它就是主機網路上的docker_gwbridge,在主機上執行ifconfig可以看到:
docker@worker1:~$ ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:F1:8A:BC:80
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
docker_gwbridge Link encap:Ethernet HWaddr 02:42:B0:55:BE:A8
inet addr:172.18.0.1 Bcast:172.18.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:b0ff:fe55:bea8/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:41 errors:0 dropped:0 overruns:0 frame:0
TX packets:126 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2041 (1.9 KiB) TX bytes:9471 (9.2 KiB)
bridge網路的網段地址從172.17.X.X/16開始,第一個內建的docker0使用了172.17.X.X,後面每新增一個bridge網路就新增一個網地址,172.18.X.X, 172.19.X.X,。。。
至此兩個bridge網路都比較清楚了。
另外如果釋出service的時候指定了主機埠對映,那麼container裡面會有三塊網絡卡分別屬於:
-
<my-network>
-
docker_gwbridge
-
ingress, 其網段是10.255.X.X,每一個service在ingress上也有一個VIP,例如VIP=10.255.0.2,對映兩個container:10.255.0.3和10.255.0.4
遇到的一些問題:docker busybox服務中nslookup命令報錯
docker@worker1:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
275444f7dbac busybox:latest "sleep 3000" 32 minutes ago Up 32 minutes my-box.1.lz7cd0j5h86m4h353rjt21wcy
fe7281a21572 nginx:latest "nginx -g 'daemon of…" 8 hours ago Up 8 hours 80/tcp my-web.1.50zdanlgukvwxq2fujtcg9o4n
docker@manager1:~$ docker exec -it my-box.1.zuga19xe4yau0mtgpuos936zs bin/sh
/ #
/ # nslookup my-web
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
*** Can't find my-web: No answer
新版的busybox好像有問題,使用舊版的busybox即可
docker@manager1:~$ docker service create --replicas 1 --network mynet --name my-box busybox:1.28.3 sleep 3000
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
service路由之:DSN輪詢
$ docker service create --replicas 2 --network my-network --name my-dnsrr-web --endpoint-mode dnsrr richarvey/nginx-php-fpm
dns方式是不允許有-p這個引數的,同時vip是 建立VIP型別,也可以不加--endpoint-mode引數。
先看兩個容器的IP地址:
docker@manager1:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
r6agac07qrx4 my-dnsrr-web replicated 2/2 richarvey/nginx-php-fpm:latest
docker@manager1:~$ docker service ps my-dnsrr-web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
i92e357ahd5l my-dnsrr-web.1 richarvey/nginx-php-fpm:latest worker1 Running Running 20 seconds ago
juqnhkk8e4fb my-dnsrr-web.2 richarvey/nginx-php-fpm:latest manager1 Running Running 19 seconds ago
docker@manager1:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3a47e1aacdc6 richarvey/nginx-php-fpm:latest "docker-php-entrypoi…" 24 seconds ago Up 23 seconds 80/tcp, 443/tcp, 9000/tcp my-dnsrr-web.2.juqnhkk8e4fbnz7bs1xpzbz42
docker@manager1:~$ docker exec -it my-dnsrr-web.2.juqnhkk8e4fbnz7bs1xpzbz42 bash
bash-5.0# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:01:83
inet addr:10.0.1.131 Bcast:10.0.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:03
inet addr:172.18.0.3 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:9 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:726 (726.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
docker@worker1:~$ docker exec -it my-dnsrr-web.1.i92e357ahd5lz91lseal6aa00 bash
bash-5.0# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:01:82
inet addr:10.0.1.130 Bcast:10.0.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:03
inet addr:172.18.0.3 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:10 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:796 (796.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
my-dnsrr-web.1.i92e357ahd5lz91lseal6aa00
- eth0 10.0.1.130
- eth1 172.18.0.3
my-dnsrr-web.2.juqnhkk8e4fbnz7bs1xpzbz42
- eth0 10.0.1.131
- eth1 172.18.0.3
檢視DNS解析結果(輪詢訪問)
bash-5.0# ping my-dnsrr-web
PING my-dnsrr-web (10.0.1.131): 56 data bytes
64 bytes from 10.0.1.131: seq=0 ttl=64 time=0.039 ms
64 bytes from 10.0.1.131: seq=1 ttl=64 time=0.074 ms
64 bytes from 10.0.1.131: seq=2 ttl=64 time=0.071 ms
^C
--- my-dnsrr-web ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.039/0.061/0.074 ms
bash-5.0# ping my-dnsrr-web
PING my-dnsrr-web (10.0.1.130): 56 data bytes
64 bytes from 10.0.1.130: seq=0 ttl=64 time=1.029 ms
64 bytes from 10.0.1.130: seq=1 ttl=64 time=0.894 ms
64 bytes from 10.0.1.130: seq=2 ttl=64 time=0.846 ms
64 bytes from 10.0.1.130: seq=3 ttl=64 time=1.233 ms
nslookup訪問
bash-5.0# nslookup my-dnsrr-web
nslookup: can't resolve '(null)': Name does not resolve
Name: my-dnsrr-web
Address 1: 10.0.1.131 3a47e1aacdc6
Address 2: 10.0.1.130 my-dnsrr-web.1.i92e357ahd5lz91lseal6aa00.my-network
bash-5.0#
釋出服務埠
當你建立一個服務時, 你可以使用--publish引數來發布埠。
# docker service create \
--name <Service-Name> \
--publish <Publish-Port>:<Target-Port> \
<Docker Image>
例如, 在Swarm叢集中釋出Nginx應用服務, 將容器中的80埠對映為Swarm叢集的8080埠。
$ docker service create \
--name my-web \
--publish 8080:80 \
--replicas 2 \
nginx
當你在任何節點上訪問8080埠時,Swarm叢集的負載均衡會將您的請求路由至活動容器中。
Swarm叢集的路由網路在釋出的埠上監聽分配給該節點的任何IP地址。對於外部可路由的IP地址,埠可從主機外部獲得。對於其他的IP地址,只能從主機中訪問。
Ingress Network 使用以下命令可以為已經發布的服務釋出埠:
$ docker service update \
--publish-add <PUBLISHED-PORT>:<TARGET-PORT> \
<SERVICE>
使用docker service inspect檢視服務埠。
docker service inspect --format="" nginx-service
僅釋出TCP埠或僅釋出UDP埠
預設情況下, 所釋出的埠一般為TCP埠。你可以專門指定釋出UDP埠,而不是TCP或之外的埠。當釋出TCP和UDP埠時,Docker 1.12.2版本和早前版本需要為TCP埠指定字尾/tcp。字尾為可選的引數。
僅釋出TCP埠
docker service create --name dns-cache -p 53:53 dns-cache
或
docker service create --name dns-cache -p 53:53/tcp dns-cache
釋出TCP和UDP埠
docker service create --name dns-cache -p 53:53/tcp -p 53:53/udp dns-cache
僅釋出UDP埠
docker service create --name dns-cahce -p 53:53/udp dns-cache
配置外部負載均衡
可通過配置外部負載均衡實現Swarm叢集的服務路由。例如, 使用HAProxy實現已釋出埠8080的Nginx服務的負載均衡。
Load Balance 在這種情況下, 必須在Swarm叢集節點和HAProxy之間開啟8080埠,Swarm叢集節點可以駐留在代理伺服器可以訪問的專用網路上,但不能公開訪問。
我們可以將負載均衡配置為Swarm叢集中的每個節點之間的請求平衡,即使該 節點上沒有計劃任務。例如, 我們可以在/etc/haproxy/haproxy.cfg
中配置HAProxy的負載均衡。
global
log dev/log local0
log dev/log local1 notice
...snip...
# Configure HAProxy to listen on port 80
frontend http_front
bind *:80
stats uri /haproxy?stats
default_backend http_back
# Configure HAProxy to route requests to swarm nodes on port 8080
backend http_back
balance roundrobin
server node1 192.168.99.100:8080 check
server node2 192.168.99.101:8080 check
server node3 192.168.99.102:8080 check
當您在埠80上訪問HAProxy負載均衡服務時,它會將請求轉發到Swarm叢集中的節點。Swarm叢集路由網路將請求路由到活動任務中。如果由於任何原因swarm排程程式將任務分派給不同的節點,則不需要重新配置負載均衡。
資料持久化
Docker swarm 資料持久化是通過把宿主機的檔案系統對映到容器中實現的。實現的方式主要有以下三種:
- 資料卷掛載(volume mounts)
- 繫結掛載(bind mounts)
- volume NFS共享儲存模式(推薦)
資料卷掛載
- 卷是繞過聯合檔案系統的一個或多個容器內的特定目錄。卷被設計為保持資料,與容器的生命週期無關。因此,Docker在刪除容器時不會自動刪除卷,也不會“垃圾收集”不再由容器引用的卷。也稱為:資料卷。
- 需要更詳細瞭解volume可參考官方文件
- 使用service的方式建立的volume資料卷,該service中的容器只會使用其所在節點伺服器的中的volume。當使用本地資料卷驅動時,所用的容器都無法共享其資料。
建立資料卷
docker@manager1:~$ docker volume create 321
321
docker@manager1:~$ docker volume ls
DRIVER VOLUME NAME
local 279da60b1cfc623e3a728a9c0dddca06809105892d6c6c89dc18e077cb0ebdfc
local 321
檢視資料卷詳細資訊
docker volume inspect <VOLUME-NAME>
docker@manager1:~$ docker volume inspect 321
[
{
"CreatedAt": "2020-01-13T09:27:17Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/mnt/sda1/var/lib/docker/volumes/321/_data", # 本地位置
"Name": "321",
"Options": {},
"Scope": "local"
}
]
docker@manager1:/mnt/sda1/var/lib/docker$ sudo -i
root@manager1:~# cd mnt/sda1/var/lib/docker/volumes/321/_data/
root@manager1:/mnt/sda1/var/lib/docker/volumes/321/_data# ll
root@manager1:/mnt/sda1/var/lib/docker/volumes/321/_data# ls -l
total 0
docker@manager1:~$ docker service create --name mysql_volume_test --mount type=volume,src=321,dst=/root/my_volume --network mynet --replicas 2 haoxy/mysql:5.6
ms76afxdmswkrxhegdixwmm3i
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged
docker@manager1:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06ef0693cec6 haoxy/mysql:5.6 "/run.sh" 12 seconds ago Up 11 seconds 3306/tcp mysql_volume_test.2.si4z4ko1jw38uvsc7cquu54pd
docker@manager1:~$ docker exec -it 06e bin/bash
root@06ef0693cec6:/# df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 18714000 1750776 15974096 10%
tmpfs 65536 0 65536 0% dev
tmpfs 506608 0 506608 0% sys/fs/cgroup
shm 65536 0 65536 0% dev/shm
/dev/sda1 18714000 1750776 15974096 10% root/my_volume
tmpfs 506608 0 506608 0% proc/asound
tmpfs 506608 0 506608 0% proc/acpi
tmpfs 506608 0 506608 0% /proc/scsi
tmpfs 506608 0 506608 0% /sys/firmware
root@06ef0693cec6:/# cd root/
root@06ef0693cec6:~# ls
my_volume
root@06ef0693cec6:~# cd my_volume/
root@06ef0693cec6:~/my_volume# ls -l
total 0
加入屬性
docker service create --name mysql_volume_test --mount type=volume,src=321,dst=/root/my_volume,readonly --network mynet --replicas 2 haoxy/mysql:5.6
readonly /root/my_volume 只可以讀
為原來的服務新增volume
[root@docker ~]# docker service update website-test --mount-add type=volume,source=data,target=/var/lib/data
bind 方式
與卷相比,(bind)繫結掛到具有有限的功能。當您使用繫結掛載時,宿主機上的檔案或目錄被掛載到容器中。檔案或目錄由宿主機上的完整路徑或相對路徑引用(宿主機上的檔案或目錄要存在)。相比之下,當您使用卷時,會在宿主機上的Docker儲存目錄中建立一個新目錄,並且Docker會管理該目錄的內容。該檔案或目錄不需要已經存在於Docker宿主機上。如果它尚不存在,它會根據需求建立。繫結掛載非常高效,但它們依賴於具有特定目錄結構的主機的檔案系統。
- 讀寫掛載
docker service create \
--mount type=bind,src=<HOST-PATH>,dst=<CONTAINER-PATH> \
--name myservice \
<IMAGE>
- 只讀掛載
docker service create \
--mount type=bind,src=<HOST-PATH>,dst=<CONTAINER-PATH>,readonly \
--name myservice \
<IMAGE>
例:
docker service create --name nginx --mount type=bind,target=/usr/share/nginx/html/,source=/opt/web/ --replicas 2 --publish 80:80/tcp nginx
docker service create --name bind --mount type=bind,src=/root/db_data,dst=/root/db_data --network mynet --replicas 2 website:7.7
docker service create --name bind --mount type=bind,src=/root/db_data,dst=/root/db_data,readonly --network mynet --replicas 2 website:7.7
重要:雖然繫結掛載能用,但是也有可能導致一些問題:
- 如果你掛載了一個主機路徑到你的服務容器中,那麼這個路徑必須存在於 Swarm 叢集中的每一個節點。Docker Swarm 排程器會把容器排程到任何滿足資源可用性和滿足你特定約束、位置偏好的節點上。
- 如果執行中的容器變得不健康或者不可用,那麼 Docker Swarm 排程器可能會隨時重新安排它。
- 主機繫結掛載是完全不可移植的。當你使用繫結掛載時,不能保證你的應用在開發中的執行方式與在生產中的執行方式相同。
通過nfs實現資料持久化
NFS(Network File System)即網路檔案系統,是FreeBSD支援的檔案系統中的一種,它允許網路中的計算機之間通過TCP/IP網路共享資源。在NFS的應用中,本地NFS的客戶端應用可以透明地讀寫位於遠端NFS伺服器上的檔案,就像訪問本地檔案一樣。
環境部署
nfs伺服器:建立/nfs目錄 nfs客戶端:不需要在nfs客戶端提前mount,在Manager上建立副本的時候指定引數自動mount到nfs服務端
nfs服務端部署
[root@nfs ~]# yum -y install nfs-utils
[root@nfs ~]# mkdir /nfs
[root@nfs ~]# vim /etc/exports
/nfs 172.18.18.0/24(rw,sync,no_root_squash)
[root@nfs ~]# systemctl restart nfs
nfs引數說明:
- /nfs:nfs上的共享目錄;
- 172.18.18.0/24:網路中可以訪問這個NFS輸出目錄的主機;
- rw:共享目錄的許可權,rw 是可讀寫的許可權,只讀的許可權是ro;
- sync:同步,資料更安全,速度慢;
- async:非同步,速度快,效率高,安全性低;
- no_root_squash:NFS 服務共享的目錄的屬性, 如果使用者是root, 對這個目錄就有root的許可權;
nfs客戶端部署
在swarm叢集所有節點安裝部署:
[root@Manager ~]# yum -y install nfs-utils
[root@agent01 ~]# yum -y install nfs-utils
[root@agent02 ~]# yum -y install nfs-utils
使用格式
docker service create \
--replicas 1或2或3... \
--name myservice \
--mount \
'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>, \
volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>, \
"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"' \
<IMAGE>
案例演示
我們還是在Manager上建立nginx副本來演示,同樣把nfs共享目錄:/nfs掛載到副本的(/ysr/share/nginx/html)目錄中。
docker service create --name web-nfs \
--replicas 3 \
--mount 'type=volume,src=nfs-vol,dst=/usr/share/nginx/html,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/nfs,"volume-opt=o=addr=172.18.18.90,vers=4,soft,timeo=180,bg,tcp,rw"' \ nginx
引數說明:
- --replicas:建立3個nginx副本;
- --name:自定義命名為web-nfs;
- type=volume:必須為volume掛載,不能用bind的掛載方式;
- src:src掛載源為nginx-vol,相當於執行了docker volume nginx-vol;
- dst:dst掛載目的路徑為nginx容器中的路徑/usr/share/nginx/html;
- volume-opt=type=nfs:指定掛載卷型別為nfs模式;
- volume-opt=device=:/nfs:這是nfs伺服器共享目錄的名稱;
- "volume-opt=o=addr=172.18.18.90:指定nfs伺服器地址;
- vers=4,soft,timeo=180,bg,tcp,rw:指定nfs版本號、超時時間、後臺掛載、協議可選TCP/UDP、讀寫許可權等資訊;
- nginx:通過nginx映象來執行;
檢視副本執行資訊
[root@Manager ~]# docker service ps web-nfs
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
q67t67ucdba2 web-nfs.1 nginx:latest agent01 Running Running 12 minutes ago
3cs5jh8chegz web-nfs.2 nginx:latest Manager Running Running 10 minutes ago
0hykerf7n3a4 web-nfs.3 nginx:latest agnet02 Running Running 13 minutes ago
3個nginx副本分別分配到了swarm叢集的3個節點上。
檢視檔案掛載資訊
df -h
Filesystem Size Used Avail Use% Mounted on
...
:/nfs 50G 9.6G 41G 20% /var/lib/docker/volumes/nfs-vol/_data
...
你在其它幾個agnet節點上df檢視也同樣有這些資訊。
回到nfs服務端,建立測試資料
cd /nfs/
touch nfs.html
進入swarm中所有節點,檢視資料是否同步
進入Manager檢視:
[root@Manager ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f176e66dc9f2 nginx:latest "nginx -g 'daemon of…" 14 minutes ago Up 14 minutes 80/tcp web-nfs.2.3cs5jh8chegz4ncid8qd2as8q
[root@Manager ~]# docker exec -it f176e66dc9f2 bash
root@f176e66dc9f2:/# ls /usr/share/nginx/html/
50x.html index.html nfs.html
進入agnet01檢視:
[root@agent01 ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9b7dcc5bdfa nginx:latest "nginx -g 'daemon of…" 17 minutes ago Up 17 minutes 80/tcp web-nfs.1.q67t67ucdba2c7b5srkm9b58n
[root@agent01 ~]# docker exec -it b9b7dcc5bdfa bash
root@b9b7dcc5bdfa:/# ls /usr/share/nginx/html/
50x.html index.html nfs.html
用docker volume ls驗證資料卷
[root@Manager ~]# docker volume ls
DRIVER VOLUME NAME
local nfs-vol
local nginx-vol
[root@agent01 ~]# docker volume ls
DRIVER VOLUME NAME
local nfs-vol
local nginx-vol
通過rm刪除副本驗證
[root@Manager ~]# docker service rm web-nfs
[root@Manager ~]#df -h
我們把剛才建立的副本刪除掉,然後通過df -h檢視掛載的檔案,發現也跟著不在了,其它節點也一樣不存在了;然後你回到nfs服務端檢視/nfs共享目錄,資料是不變的。這就表現了,通過nfs掛載,副本被刪除,不影響nfs服務端的源資料。只要資料在nfs服務端,就可實現資料的持久化。
管理敏感資料
在動態的、大規模的分散式叢集上,管理和分發 密碼、證書 等敏感資訊是極其重要的工作。傳統的金鑰分發方式(如金鑰放入映象中,設定環境變數,volume 動態掛載等)都存在著潛在的巨大的安全風險。
Docker 目前已經提供了 secrets 管理功能,使用者可以在 Swarm 叢集中安全地管理密碼、金鑰證書等敏感資料,並允許在多個 Docker 容器例項之間共享訪問指定的敏感資料。
secret 也可以在 Docker Compose 中使用。
我們可以用 docker secret 命令來管理敏感資訊。
這裡我們以在 Swarm 叢集中部署 mysql 和 wordpress 服務為例。
建立金鑰
建立金鑰有兩種方式
第一種方式:
openssl rand -base64 20 | docker secret create mysql_password -
openssl rand -base64 20 | docker secret create mysql_root_password -
第二種方式:
echo "my-secret-pw" | docker secret create my_secret_data -
檢視金鑰
docker@manager1:~$ docker secret ls
ID NAME DRIVER CREATED UPDATED
ovkc9nvnlmgirpnlpg8jegg57 mysql_password 25 seconds ago 25 seconds ago
k8f82idwld2tr5kv418j28wy2 mysql_root_password 13 seconds ago 13 seconds ago
檢視金鑰詳細資訊:
docker@manager1:~$ docker secret inspect mysql_password
[
{
"ID": "ovkc9nvnlmgirpnlpg8jegg57",
"Version": {
"Index": 457
},
"CreatedAt": "2020-01-13T08:43:14.862704779Z",
"UpdatedAt": "2020-01-13T08:43:14.862704779Z",
"Spec": {
"Name": "mysql_password",
"Labels": {}
}
}
]
建立 MySQL 服務 建立服務相關命令已經在前邊章節進行了介紹,這裡直接列出命令。
docker network create -d overlay mysql_private
docker service create \
--name mysql \
--replicas 1 \
--network mysql_private \
--mount type=volume,source=mydata,destination=/var/lib/mysql \
--secret source=mysql_root_password,target=mysql_root_password \
--secret source=mysql_password,target=mysql_password \
-e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
-e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \
-e MYSQL_USER="wordpress" \
-e MYSQL_DATABASE="wordpress" \
mysql:latest
如果你沒有在 target 中顯式的指定路徑時,secret 預設通過 tmpfs 檔案系統掛載到容器的 run/secrets
目錄中。
docker service create \
--name wordpress \
--replicas 1 \
--network mysql_private \
--publish target=30000,port=80 \
--mount type=volume,source=wpdata,destination=/var/www/html \
--secret source=mysql_password,target=wp_db_password,mode=0400 \
-e WORDPRESS_DB_USER="wordpress" \
-e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \
-e WORDPRESS_DB_HOST="mysql:3306" \
-e WORDPRESS_DB_NAME="wordpress" \
wordpress:latest
檢視服務
docker service ls
ID NAME MODE REPLICAS IMAGE
wvnh0siktqr3 mysql replicated 1/1 mysql:latest
nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest
現在瀏覽器訪問 IP:30000,即可開始 WordPress 的安裝與使用。
通過以上方法,我們沒有像以前通過設定環境變數來設定 MySQL 密碼, 而是採用 docker secret 來設定密碼,防範了密碼洩露的風險。
swarm服務日誌及相關配置
Docker Swarm 服務的日誌可以通過執行 docker service logs 命令來檢視,然而並非所有的日誌驅動(Logging Driver)都支援該命令。
Docker 節點預設的配置是,服務使用 json-file 日誌驅動,其他的驅動還有 journald(僅用於執行有 systemd 的 Linux 主機)、syslog、splunk 和 gelf。
root@manager1:~# docker info
Client:
Debug Mode: false
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 2
Server Version: 19.03.5
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file # 預設為json-file
Cgroup Driver: cgroupfs
json-file 和 journald 是較容易配置的,二者都可用於 docker service logs 命令。
命令格式為:
docker service logs <service-name>
若使用第三方日誌驅動,那麼就需要用相應日誌平臺的原生工具來檢視日誌。
如下是在 daemon.json 配置檔案中定義使用 syslog 作為日誌驅動的示例。檔案位置為/etc/docker/daemon.json (如檔案不存在,可以進行手動建立)
{
"log-driver": "syslog"
}
- 通過在執行
docker service create
命令時傳入--logdriver
和--log-opts
引數可以強制某服務使用一個不同的日誌驅動,這會覆蓋daemon.json
中的配置。
服務日誌能夠正常工作的前提是,容器內的應用程式運行於 PID 為 1 的程序,並且將日誌傳送給 STDOUT,錯誤資訊傳送給 STDERR。日誌驅動會將這些日誌轉發到其配置指定的位置。
如下的 docker service logs 命令示例顯示了服務 svc1 的所有副本的日誌,可見該服務在啟動副本時出現了一些錯誤。
docker service logs seastack_reverse_proxy
svc1.1.zhc3cjeti9d4@wrk-2 | [emerg] 1#1: host not found...
svc1.1.6m1nmbzmwh2d@wrk-2 | [emerg] 1#1: host not found...
svc1.1.6m1nmbzmwh2d@wrk-2 | nginx: [emerg] host not found..
svc1.1.zhc3cjeti9d4@wrk-2 | nginx: [emerg] host not found..
svc1.1.1tmya243m5um@mgr-1 | 10.255.0.2 "GET HTTP/1.1" 302
以上輸出內容有刪減,不過仍然可以看到來自服務的 3 個副本的日誌(兩個執行失敗,一個執行成功)。
每一行開頭為副本名稱,其中包括服務名稱、副本編號、副本 ID 以及所在的主機。之後是日誌訊息。
由於輸出內容有所刪減,因此失敗原因較難定位,不過看起來似乎是前兩個副本嘗試連線另一個啟動中的服務而導致失敗(一種所依賴的服務未完全啟動導致的競態條件問題)。
對於檢視日誌命令,可以使用
--follow
進行跟蹤、使用--tail
顯示最近的日誌,並使用--details
獲取額外細節。
生產環境注意事項
Docker Swarm 負載
Swarm的負載非常低。據我觀察,Swarm進行排程和通訊的CPU負載非常低。因此,Swarm的管理節點(Manager)可以同時作為工作節點(Worker)。如果你需要搭建一個非常大的叢集(1000+ 節點),管理節點需要更多資源,但是對於中小型叢集來說,管理節點需要的資源可以忽略不計。另外,這篇部落格介紹了Swarm3k(一個4700節點的Swarm叢集的實驗),不妨瞭解一下。
Swarm叢集的網路通訊(服務發現,負載均衡以及容器間通訊)非常可靠。當你開啟一個服務的埠之後,在Swarm叢集中的任何一個節點都可以訪問它。負載均衡也是由Swarm提供的。後文會提到一些之前遇到的問題,但是Docker 1.13之後,這些問題都解決了。
建立attachable network
attachable network是一個非常重要的特性。你最好使用它,否則docker run
建立的容器將無法接入Swarm叢集的網路。這是Docker 1.13之後的版本才有的功能。
建立attachable network的命令如下:
docker network create --driver=overlay --attachable core
先使用環境變數,再考慮Secrets API
如果你按照How to write excellent Dockerfiles構建Docker映象,你很可能會使用環境變數去配置很多東西。如果你這樣做的話,則遷移到Swarm叢集時問題會少很多。示例命令如下:
#建立服務時指定環境變數docker service create \
--env VAR=VALUE \
--env-file FILENAME \
...
#增加、刪除環境變數docker service update \
--env-add VAR=NEW_VALUE \
--env-rm VAR \
..
設定合理的服務容器個數以及並行更新的容器個數
一方面,你需要保證足夠多的容器數來處理負載以及作為災備,另一方面,太多的容器會導致CPU和記憶體資源不足。因此,需要配置合理的服務容器個數。另外,預設的update-parallelism
值是1,這就意味著更新服務時,每次只更新1個容器。通常,這個值太小了。我的建議是將它設為 服務容器數 / 2
.。
#將同時更新的容器數設為10docker service update \
--update-parallelism 10 \
webapp
#同時增加多個服務的容器數
docker service scale redis=1 nginx=4 webapp=20
#檢視服務的詳情(排除關閉的容器)
docker service ps webapp | grep -v "Shutdown"
將Swarm配置程式碼化
最佳方式是使用Docker Compose v3 語法,這樣可以將服務的所有配置選項程式碼化。我將 docker-compose.yml
用於開發環境, docker-compose.prod.yml
用於生產環境。使用docker-compose檔案部署服務的話,需要使用 docker stack deploy
命令(參考docker stack)
docker-compose檔案示例
# docker-compose.prod.yml
version: '3'
services:
webapp:
image: registry.example.com/webapp
networks:
- ingress
deploy:
replicas: ${WEBAPP_REPLICAS}
mode: replicated
restart_policy:
condition: on-failure
proxy:
image: registry.example.com/webapp-nginx-proxy
networks:
- ingress
ports:
- 80:80
- 443:443
deploy:
replicas: ${NGINX_REPLICAS}
mode: replicated
restart_policy:
condition: on-failure
networks:
ingress:
external: true
部署命令:
export NGINX_REPLICAS=2 WEBAPP_REPLICAS=5
docker login registry.example.com
docker stack deploy \
-c docker-compose.prod.yml\
--with-registry-auth \
frontend
另外,docker-compse檔案支援環境變數(${VARIABLE}),這樣你可以動態地調整配置。
設定資源限制
如果一臺機器啟用多個服務注意,需要你合理分配cpu與記憶體資源,因為tomcat在啟動編譯時會很吃記憶體,且docker是多執行緒啟動的,所有最好是限定一下(設定resources.limits
)否者會導致記憶體在同一時刻用光,某些服務啟動失敗當然也可是設定出錯重啟(restart_policy.condition:on-failure)。
另外設定resources.reservations
要注意,不要超出總記憶體或cpu百分比,否者會導致後面服務無法獲取cpu或記憶體資源出現“no suitable node (insufficien”錯誤(這個錯誤很奇怪,某個service不啟動,也不輸出日誌,使用“docker stack ps [xxxx]”檢視狀態會顯示此錯誤)無法啟動
可使用reserve-cpu
,它可以保證每個容器都有足夠地資源。示例:
#限制服務佔用的CPU資源docker service update
--limit-cpu 0.25
--reserve-cpu 0.1
webapp
監控網路連線
我遇到過Swarm網路方面的問題。有時候所有的請求都被轉發到某一個容器,然而還有9個其他容器正在執行。這時,可以嘗試減少/增加例項個數,或者改變路由型別(使用--endpoint-mode
選項)。
如果沒有監控日誌的話,這樣的問題很難被發現。因此,搭建監控系統是非常必要的。
例項
Swarm支援的命令主要有4個:swarm create、swarm manage、swarm join、swarm list。當然還有一個swarm help命令,該命令用於指導大家如何正確使用swarm命令.
swarm create
Swarm中swarm create命令用於建立一個叢集標誌,用於Swarm管理Docker叢集時,Docker Node的節點發現功能。發起該命令之後,Swarm會前往Docker Hub上內建的發現服務中獲取一個全球唯一的token,用以唯一的標識Swarm管理的Docker叢集。
swarm manage
Swarm中swarm manage是最為重要的管理命令。一旦swarm manage命令在Swarm節點上被觸發,則說明使用者需要swarm開始管理Docker叢集。從執行流程的角度來講,swarm經歷的階段主要有兩點:啟動swarm、接收並處理Docker叢集管理請求。
Swarm啟動的過程包含三個步驟:
- 發現Docker叢集中的各個節點,收集節點狀態、角色資訊,並監視節點狀態的變化;
- 初始化內部排程(scheduler)模組;
- 建立並啟動API監聽服務模組;
第一個步驟,Swarm發現Docker叢集中的節點。發現(discovery)是Swarm中用於維護Docker叢集狀態的機制。既然涉及到發現(discovery),那在這之前必須先有註冊(register)。Swarm中有專門負責發現(discovery)的模組,而關於註冊(register)部分,不同的discovery模式下,註冊(register)也會有不同的形式。
目前,Swarm中提供了5種不同的發現(discovery)機制:Node Discovery、File Discovery、Consul Discovery、EtcD Discovery和Zookeeper Discovery。
第二個步驟,Swarm內部的排程(scheduler)模組被初始化。swarm通過發現機制發現所有註冊的Docker Node,並收集到所有Docker Node的狀態以及具體資訊。此後,一旦Swarm接收到具體的Docker管理請求,Swarm需要對請求進行處理,並通過所有Docker Node的狀態以及具體資訊,來篩選(filter)決策到底哪些Docker Node滿足要求,並通過一定的策略(strategy)將請求轉發至具體的一個Docker Node。
第三個步驟,Swarm建立並初始化API監聽服務模組。從功能的角度來講,可以將該模組抽象為Swarm Server。需要說明的是:雖然Swarm Server完全相容Docker的API,但是有不少Docker的命令目前是不支援的,畢竟管理Docker叢集與管理單獨的Docker會有一些區別。當Swarm Server被初始化並完成監聽之後,使用者即可以通過Docker Client向Swarm傳送Docker叢集的管理請求。
Swarm的swarm manage接收並處理Docker叢集的管理請求,即是Swarm內部多個模組協同合作的結果。請求入口為Swarm Server,處理引擎為Scheduler,節點資訊依靠Disocovery。
swarm join
Swarm的swarm join命令用於將Docker Node新增至Swarm管理的Docker叢集中。
從這點也可以看出swarm join命令的執行位於Docker Node,因此在Docker Node上執行該命令,首先需要在Docker Node上安裝Swarm,由於該Swarm只會執行swarm join命令,故可以將其當成Docker Node上用於註冊的agent模組。
功能而言,swarm join可以認為是完成Docker Node在Swarm節點處的註冊(register)工作,以便Swarm在執行swarm manage時可以發現該Docker Node。然而,上文提及的5種discovery模式中,並非每種模式都支援swarm join命令。不支援的discovery的模式有Node Discovery與File Discovery。
Docker Node上swarm join執行之後,標誌著Docker Node向Swarm註冊,請求加入Swarm管理的Docker叢集中。Swarm通過註冊資訊,發現Docker Node,並獲取Docker Node的狀態以及具體資訊,以便處理Docker請求時作為排程依據。
swarm list
Swarm中的swarm list命令用以列舉Docker叢集中的Docker Node。Docker Node的資訊均來源於Swarm節點上註冊的Docker Node。而一個Docker Node在Swarm節點上註冊,僅僅是註冊了Docker Node的IP地址以及Docker監聽的埠號。
使用swarm list命令時,需要指定discovery的型別,型別包括:token、etcd、file、zk以及。而swarm list並未羅列Docker叢集的動態資訊,比如Docker Node真實的執行狀態,或者Docker Node在Docker叢集中扮演的角色資訊。
命令分類
叢集管理
# 加入docker swarm叢集,作為 worker 節點
docker swarm join --token SWMTKN-1-4roc8fx10cyfgj1w1td8m0pkyim08mve578wvl03eqcg5ll3ig-f0apd81qfdwv27rnx4a4y9jej 182.48.115.237:2377
# 加入docker swarm叢集,作為 manager 節點
docker swarm join --token SWMTKN-1-075gaitl18z3v0p37sx7i5cmvzjjur0fbuixzp4tun0xh0cikd-0y8ttp5h0g54j10amn670w6su 172.16.60.220:2377
# 使舊令牌無效並生成新令牌
docker swarm join-token --rotate
docker swarm leave --force #manager節點退出叢集,需要加--force
# 檢視swarm worker的連線令牌
docker swarm join-token worker
# 檢視swarm manager的連線令牌
docker swarm join-token manager
# 初始化swarm manager並制定網絡卡地址
docker swarm init --advertise-addr 182.48.115.237
Node管理
# 刪除叢集,強制退出需要加–force (針對manager節點). 到各個節點上執行退出叢集的命令
docker node rm swarm-node1
# 刪除節點標籤
docker node update --label-rm label1 swarm-node1
# 將worker節點升級為manager節點
docker node promote swarm-node1
# 將manager節點降級為worker節點
docker node demote swarm-manager-node
# 排程程式可以將任務分配給節點
docker node update --availability active swarm-node1
# 排程程式不向節點分配新任務,但是現有任務仍然保持執行
docker node update --availability pause swarm-node1
# 排程程式不會將新任務分配給節點。排程程式關閉任何現有任務並在可用節點上安排它們. 也就是線下節點,不參與任務分配.
docker node update --availability drain swarm-node1
# 檢視叢集中的節點
docker node ls
# 如果之前的leader狀態的manager管理節點掛了後(假如restart docker),
# 則新加入的manager節點狀態由reachable變為leader, 之前的manager節點狀態為unreachable.
# 檢視叢集中節點資訊
docker node inspect swarm-node1 --pretty
# 新增節點標籤
docker node update --label-add label1 --label-add bar=label2 swarm-node1
Service管理
# 建立一個不定義name,不定義replicas的服務. (如下的nginx是docker的nginx映象名稱,不是服務名稱)
docker service create nginx
# 建立一個指定name的服務
ocker service create --name my-nginx nginx
# 建立一個指定name、run cmd的服務
docker service create --name my-nginx nginx ping www.baidu.com
# 建立一個指定name、version、run cmd的服務
docker service create --name my-redis redis:3.0.6
docker service create --name my-nginx nginx:1.8 /bin/bash
# 建立一個指定name、port、replicas的服務
docker service create --name my-nginx --replicas 3 -p 80:80 nginx
# 建立服務並將網路新增至該服務
docker service create --name my-test --replicas 3 --network my-network redis
# 建立群組並配置cpu和記憶體
docker service create --name my_nginx \
--reserve-cpu 2 \
--reserve-memory 512m \
--replicas 3 \
-p 80:80 \
nginx
# 配置執行環境,指定工作目錄及環境變數
docker service create --name my-nginx \
--env MYVAR=myvalue \
--workdir /data/www \
--user my_user nginx \
ping www.baidu.com
# 在每個群組節點上執行web服務
docker service create --name tomcat \
--mode global \
--publish mode=host,target=8080,published=8080 \
tomcat:latest
# 更新服務
docker service update \
--image nginx:alpine \
nginx
# 為指定的服務更新一個埠
docker service update --publish-add 80:80 my-nginx
# 將redis:3.0.6更新至redis:3.0.7
docker service update --image redis:3.0.7 redis
# 更改所分配的cpu和記憶體
docker service update --reserve-cpu 1 --reserve-memory 256m my_nginx
# 更新my-nginx服務的執行命令
docker service update --args "ping www.baidu.com" my-nginx
# 更新叢集網路
docker service update --network-add haha-network my-test
# 為指定的服務刪除一個埠
docker service update --publish-rm 80:80 my-nginx
# 刪除群組網路
docker service update --network-rm my-network my-test
# 刪除一個服務
docker service rm my-nginx
# 檢視服務列表
docker service ls
# 檢視服務的具體資訊
docker service ps my-test
#檢視報錯
docker service ps --no-trunc {serviceName}
#檢視服務的詳細資訊。
docker service inspect nginx
# 增加服務例項
docker service scale nginx=5
#建立服務
docker service create \
--image nginx \
--replicas 2 \
nginx
網路管理
# 建立一個overlay網路
docker network create --driver overlay my-network
docker network create --driver overlay \
--subnet 10.10.10.0/24 \
--gateway 10.10.10.1 \
haha-network
配置管理
# 新增swarm配置
echo "this is a mysql config" | docker config create mysql -
# 檢視配置
docker config ls
# 檢視配置詳細資訊
docker config inspect mysql
# 刪除配置
docker config rm mysql
# 刪除配置
docker service update --config-rm mysql mysql
# 新增配置
docker service update --config-add mysql mysql
# 新增配置
docker config create kevinpage index.html
# 啟動容器的同時新增配置(target如果報錯,就使用dst或destination)
docker service create --name nginx --publish 80:80 --replicas 3 --config src=kevinpage,target=/usr/share/nginx/html/index.html nginx
滾動式更新
建立服務時自定義的幾個引數
--update-parallelism
: 指定每次更新的容器數量
--update-delay
: 指定容器更新的間隔
--update-monitor
: 定義容器啟動後監控失敗的持續時間
--update-max-failure-ratio
: 定義容器失敗的百分比
--update-failure-action
: 定義容器啟動失敗之後所執行的動作
# 比如:建立一個服務並執行3個副本,同步延遲10秒,10%任務失敗則暫停
docker service create --name mysql_5_6_36 \
--replicas 3 \
--update-delay 10s \
--update-parallelism 1 \
--update-monitor 30s \
--update-failure-action pause \
--update-max-failure-ratio 0.1 \
-e MYSQL_ROOT_PASSWORD=123456
mysql:5.6.36
# 回滾至之前版本
docker service update --rollback mysql
# 自動回滾
# 如果服務部署失敗,則每次回滾2個任務,監控20秒,回滾可接受失敗率20%
docker service create --name redis \
--replicas 6 \
--rollback-parallelism 2 \
--rollback-monitor 20s \
--rollback-max-failure-ratio .2 \
redis:latest
# 建立服務並將目錄掛在至container中
docker service create --name mysql \
--publish 3306:3306 \
--mount type=bind,src=/data/mysql,dst=/var/lib/mysql \
--replicas 3 \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:5.6.36
在部署時,我們需要對升級操作的策略進行設定。
--update-delay
指定每一個task升級的延時時間。
預設情況下一次更新1個task,可以通過引數--update-parallelism
來設定同時開始更新的最大task個數。 預設情況下當一個執行更新任務的task的狀態成為RUNNING
時,才會繼續開始下一個task的更新,直到所有的task都成為RUNNING
狀態為止。
一旦更新操作過程中,有一個task返回FAILED
狀態,則更新會立即停止。可以在docker service create
或者docker service update
命令中使用--update-failure-action
引數,來控制更新失敗後的操作。
需要注意使用bind繫結宿主機目錄會帶來的風險
- 繫結的主機路徑必須存在於每個叢集節點上,否則會有問題;
- 排程程式可能會在任何時候重新安排執行服務容器,如果目標節點主機變得不健康或無法訪問;
- 主機繫結資料不可移植,當你繫結安裝時,不能保證你的應用程式開發方式與生產中的執行方式相同;
下面開始更新服務。manager根據UpdateConfig
對節點上的task進行更新。
docker service update --image redis:3.0.7 redis
排程更新的步驟如下:
- 停止第一個task。
- 更新第一個停止的task。
- 更新完成後將task啟動。
- 如果task返回
RUNNING
狀態,在等待task更新延時時間到達後,開始更新下一個task。 - 如果task返回
FAILED
狀態,則停止更新操作。
檢查更新後的redis
狀態,執行命令docker service inspect --pretty redis
;如果更新成功會有如下顯示結果:
docker service inspect --pretty redis
如果更新暫停,會有如下結果:
docker service inspect --pretty redis
用命令docker service update <SERVICE-ID>
重啟暫停的服務。例如:
docker service update redis
使用命令docker service ps <SERVICE-ID>
來檢視更新結果。
docker service ps redis
docker-stack.yml檔案編排
Docker stack 也是一個yaml檔案,和一份docker-compose.yml檔案差不多,指令也基本一致。但是與compose相比其不支援build、links和network_mode。Docker stack有一個新的指令deploy。
注:stack不支援的指令
Deploy是用來指定swarm服務部署和執行時的相關配置,並且只有使用docker stack deploy 部署swarm叢集時才會生效。如果使用docker-compose up 或者docker-compose run時,該選項會被忽略。要使用deploy選項,compose-file中version版本要在3或3+。
version: "3.4"
services:
demo-docker:
image: ejiyuan/demo-docker
ports:
- 8081:8081
environment:
- DF_NOTIFY_CREATE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/reconfigure
deploy:
mode: replicated
replicas: 2
endpoint_mode: vip
labels:
com.example.description: "This label will appear on the web service"
resources:
limits:
cpus: '0.50'
memory: 50M
reservations:
cpus: '0.25'
memory: 20M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
update_config:
parallelism: 1
delay: 10s
order: start-first
visualizer:
image: dockersamples/visualizer
ports:
- "9080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
portainer:
image: portainer/portainer
ports:
- "9000:9000"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
visualizer,portainer 為服務管理與監聽服務,這裡先不做詳細介紹
-
mode :global 全域性(每個群集節點只有一個容器)replicated 副本(指定容器的數量)。預設值:replicated。
-
replicas:副本模式下每個節點啟動副本的數量
-
endpoint_mode:指定swarm服務發現的模式
- vip - Docker為swarm叢集服務分配一個虛擬IP(VIP),作為客戶端到達叢集服務的“前端”。Docker 在客戶端和可用工作節點之間對服務的請求進行路由。而客戶端不用知道有多少節點參與服務或者是這些節點的IP/埠。(這是預設模式)
- dnsrr - DNS輪詢(DNSRR)服務發現不使用單個虛擬IP。 Docker為服務設定DNS條目,使得服務名稱的DNS查詢返回一個IP地址列表,並且客戶端直接連線到其中的一個。如果您想使用自己的負載平衡器,或者混合Windows和Linux應用程式,則DNS輪詢功能非常有用。
-
labels:指定服務的標籤。這些標籤僅在服務上設定,而不在服務的任何容器上設定
-
resources:設定服務資源分配
- limits:最大使用限制
- reservations:表示預留,即最小使用
- cpus: '0.50' 表示最大或預留50%
- memory: 20M:表示最大或預留20M
-
restart_policy:配置在容器退出時是否並如何重啟容器。取代docker-compose 中的 restart指令。
- condition :none、on-failure和any(預設any)
- delay :在重啟嘗試之間等待多久(預設0)
- max_attempts :嘗試重啟的次數(預設一直重啟,直到成功)
- window : 在確實一個重啟是否成功前需要等待的視窗時間
-
update_config :配置更新服務,用於無縫更新應用(rolling update)
- parallelism:同一時間升級的容器數量
- delay:容器升級間隔時間
- failure_action:升級失敗後的動作(continue、rollback和pause。預設pause)。
- monitor:更新完成後確實成功的時間(ns|us|ms|s|m|h)。(預設0s)
- max_failure_ratio:更新期間允許的失敗率
- order: 更新期間的操作順序。停止優先(舊任務在開始新任務之前停止)或者先啟動(首先啟動新任務,並且正在執行的任務短暫重疊)(預設停止優先)注意:只支援v3.4及更高版本。
docker stack相關命令
docker stack deploy:部署新的堆疊或更新現有堆疊
docker stack deploy [OPTIONS] STACK
引數
--bundle-file
:【實驗階段】分散式應用程式包檔案的路徑-c --compose-file
:Stack File 路徑--prune
:刪除不再被引用的服務--resolve-image
: 查詢 Registry 以解決映象摘要和支援的平臺可選值:always(預設)、changed、never--with-registry-auth
:向 Swarm 代理髮送 Registry 認證詳細資訊
私有倉庫需要攜帶"--with-registry-auth
"引數,否則提示: "latest could not be accessed on a registry to record
its digest."
需要先登入到阿里私有倉
sudo docker login [email protected] registry.cn-beijing.aliyuncs.com
內網地址
sudo docker login [email protected] registry-vpc.cn-beijing.aliyuncs.com
執行命令開始使用 docker-stack.yml 檔案部署服務堆,堆名為“test”
docker stack deploy -c docker-stack.yml test --with-registry-auth
docker stack ls:列出現有堆疊以及堆中的服務數量
docker stack ls [flags] # SERVICES 顯示堆中有多少個服務
#列出堆疊中的任務
docker stack ps [OPTIONS] STACK [flags]
對應 docke run為 上圖中,綠、藍、黃分別代表上面檔案中定義的三個服務
- 第一行,任務NAME “test_demo-docker.1”,"test"表示服務棧名稱,“_”後面的demo-docker是檔案中定義的服務名稱,“.1”表示第一個例項;
- 第四列 NODE表示所在的節點;
- 第五列:desired state 狀態running執行中;
- 下面一行 “_”表示為第一次重啟,跟部署配置檔案(docker-stack.yml)中的“restart_policy”,重啟策略有關,檔案中設定了 出錯重啟,最多重啟3次,所以3行的,“_”,第五列狀態顯示為 shutdown停止
- 第七列 顯示錯誤原因
- task:non-zero exit(137) 錯誤原因服務容器內容不足,上面部署配置檔案(docker-stack.yml)中設定了記憶體為20M,所以導致服務反覆重啟知道達到 max_attempts 限制, by:https://success.docker.com/article/what-causes-a-container-to-exit-with-code-137
docker stack services :列出堆疊中的服務
docker stack services [OPTIONS] STACK [flags]
- NAME 表示堆中的服務
- MODE 檔案中配置的啟動模式,這裡都是 replicated (副本模式)
- REPLICAS 表示正在執行的/啟動的副本數量
docker stack rm :刪除一個或多個堆疊
docker stack rm STACK [STACK...] [flags]
服務升級
docker service upadte 命令引數詳解
- --force 強制更新重啟服務,無論是否配置或映象改變都更新
- --image image:tag 制定更新的映象
- --with-registry-auth 向 Swarm 代理髮送 Registry 認證詳細資訊,私有倉庫需要攜帶該引數
更新映象:
docker service update --image ejiyuan/demo-docker:latest test_demo-docker
使用 portainer/portainer:latest映象,更新 portainer_portainer服務,此方法執行前,必須將 映象檔案上傳到遠端倉中,程式會自動拉取遠端倉中映象檔案與啟動服務的映象對比,如果沒有變化不會啟動更新,也不會重啟服務
即使設定了update_config.order: start-first,服務會先啟動在停止,但是tomcat 啟動 spring 專案需要一定時間,這段時間服務是不可用的,但是服務狀態是Runing的,所以,這裡啟用兩個副本,每次更新一個,等待一個服務啟動完成後,在執行另一個更新,主要目的是為了,無縫的升級系統,具體update_config.delay設定為多少可以參考tomcat的啟動時間
Tomcat started on port(s): 8081 (http) with context path ''
Started DemoDockerApplication in 17.292 seconds (JVM running for 18.745)
注意:如果有多個同名images,最後一次編譯的會被加tag :latest,push時 要加上
docker push registry.cn-beijing.aliyuncs.com/ejiyuan/demo-docker:latest
更新節點數目
docker service scale test_demo-docker=3
新增或者更新一個對外埠
docker service update -–publish-add 8090 test_demo-docker
更新節點
docker node update [OPTIONS] NODE [flags]
引數:
--availability
節點的可用性(有效/暫停/耗盡)--label-add
新增或更新節點標籤(key = value)--label-rm
刪除節點標籤(如果存在)--role
節點的作用(worker / manager)
NODE:節點名稱 可以使用 “docker info” 檢視得到
執行下面語句檢視節點詳情
docker node inspect atv61b72x9qa5dpbrew2n016g
該命令只會更改角色,部分許可權並沒有立即更新,因此執行服務操作時有可能提示該錯誤Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded
立即生效更新節點為manger 執行
docker node promote NODE [NODE...]
參考
http://dockone.io/article/2323 生產環境中使用Docker Swarm的一些建議
https://holynull.gitbooks.io/docker-swarm/content/kai-shi-shi-yong-swarm/geng-xin-hui-gun.html 滾動式更新
https://www.modb.pro/db/15196 Docker swarm(6)swarm網路配置