docker swarm是docker官方提供的一套容器編排系統。它的架構如下:
1. swarm節點
swarm是一系列節點的集合,而節點可以是一臺裸機或者一臺虛擬機。一個節點能扮演一個或者兩個角色,manager或者worker。
1.1 manager節點
Docker Swarm集群需要至少一個manager節點,節點之間使用Raft consensus protocol
進行協同工作。
通常,第一個啟用docker swarm的節點將成為leader,后來加入的都是follower。當前的leader如果掛掉,剩余的節點將重新選舉出一個新的leader。
每一個manager都有一個完整的當前集群狀態的副本,可以保證manager的高可用。
1.2 worker節點
worker節點是運行實際應用服務的容器所在的地方。理論上,一個manager節點也能同時成為worker節點,但在生產環境中,我們不建議這樣做。
worker節點之間,通過control plane
進行通信,這種通信使用gossip
協議,并且是異步的。
2. stacks, services, and tasks
集群中經常談到的stacks, services, tasks,他們之間的關系。
下面簡單解釋一下這三者的含義:
2.1 services
swarm service是一個抽象的概念,它只是一個對運行在swarm集群上的應用服務,所期望狀態的描述。它就像一個描述了下面物品的清單列表一樣:
- 服務名稱
- 使用哪個鏡像來創建容器
- 要運行多少個副本
- 服務的容器要連接到哪個網絡上
- 應該映射哪些端口
2.2 task
在Docker Swarm中,task是一個部署的最小單元,task與容器是一對一的關系。
2.3 stack
stack是描述一系列相關services的集合。我們通過在一個YAML文件中來定義一個stack。
3. 多主機網絡
在前面的章節中,我們學習過單主機網絡,那個時候所有的容器都運行在一個docker host上,他們之間的通信一般使用本地的bridge網絡即可。
在Swarm集群中,我們使用前面也簡單提到過的overlay network driver來讓位于不同主機間的容器進行通信。網絡模式如下圖:
當然你也可以使用第三方提供的其他網絡驅動,但對于swarm,還是推薦它自己的overlay network。
4. 實戰
4.1 創建一個單一節點的swarm
創建一個docker swarm非常簡單,現在假設我們只有一臺服務器(裸機或者虛擬機都行)。運行命令docker swarm init
,就可以創建一個swarm集群,并讓它成為manager leader:
[root@node2 ~]# docker swarm init
Swarm initialized: current node (pvj7k2urt8g5k1ucsao2rpiwu) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-2121w9nkk2qsyrgg477bz2jq26iojd5u1agdfa2b0bi8wxi3rk-00octanaf09agpe9v77nsm670 10.10.10.73:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
上面也告訴了你,如何添加一個新的manager或者worker。
查看swarm node信息:
[root@node2 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
pvj7k2urt8g5k1ucsao2rpiwu * node2.xxx.com Ready Active Leader 18.06.1-ce
[root@node2 ~]# docker node inspect pvj7k2urt8g5k1ucsao2rpiwu
4.2 使用virtualbox來創建一個swarm集群
上一章Docker Machine,我們講到過,可以使用docker machine結合virtualbox來創建多臺docker虛擬機。下面我們進行實踐:
之前,我們使用docker-machine create --driver virtualbox default
已經創建了一臺虛擬機。
[root@node2 ~]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default * virtualbox Running tcp://192.168.99.100:2376 v18.06.1-ce
通過上面方式創建的虛擬機,默認都將分配1G的內存。后面創建多臺虛擬機時,請確保你的實驗機器有足夠的內存。
1、創建5臺虛擬機
[root@node2 ~]# for NODE in `seq 1 5`; do docker-machine create --driver virtualbox "node-${NODE}"; done
整個過程需要一段時間,請耐心等待。創建完成后可以通過docker-machine ls
查看。
[root@node2 ~]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default * virtualbox Running tcp://192.168.99.100:2376 v18.06.1-ce
node-1 - virtualbox Running tcp://192.168.99.101:2376 v18.06.1-ce
node-2 - virtualbox Running tcp://192.168.99.102:2376 v18.06.1-ce
node-3 - virtualbox Running tcp://192.168.99.103:2376 v18.06.1-ce
node-4 - virtualbox Running tcp://192.168.99.104:2376 v18.06.1-ce
node-5 - virtualbox Running tcp://192.168.99.105:2376 v18.06.1-ce
2、創建swarm集群
按照下面的腳本創建swarm集群,當然你也可以用命令手動逐個創建:
#!/bin/bash
# 這個腳本用于在創建了5個docker虛擬機之后,創建swarm集群。
# 集群內配置如下:node-1為manager leader,node-2和node-3為manager follower
# node-4和node-5為worker
export IP=$(docker-machine ip node-1)
#ssh登錄到node-1,讓他成為swarm leader
docker-machine ssh node-1 docker swarm init --advertise-addr $IP
#獲取leader的token,方便后面其他主機加入
export JOIN_TOKEN=$(docker-machine ssh node-1 docker swarm join-token worker -q)
for NODE in `seq 2 5`
do
docker-machine ssh node-${NODE} docker swarm join --token ${JOIN_TOKEN} ${IP}:2377
done
#將node-2和node-3主機提權為manager
docker-machine ssh node-1 docker node promote node-2 node-3
完成后,可以在node-1上查看當前集群節點信息
[root@node2 swarm]# docker-machine ssh node-1 docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
zycyeyod3fctve54efs9oms55 * node-1 Ready Active Leader 18.06.1-ce
cwerk7kvtxybp7fgkicpgxe4y node-2 Ready Active Reachable 18.06.1-ce
l3dtf4l64z3bm5eb1sb4uuq2x node-3 Ready Active Reachable 18.06.1-ce
wg5ptfk4suctqvndjo81dqewq node-4 Ready Active 18.06.1-ce
wcog4ty7anqdixgm5n7ta8bxb node-5 Ready Active 18.06.1-ce
3、在集群上部署一個單service的stack
現在我們可以使用這5臺虛擬機組成的集群,來實踐一個案例了。
前面我們提到過,一個stack才是所有相關services的集合,組成一個完整的應用服務。可以通過yaml文本來定義一個stack:
version: "3.5"
services:
whoami:
image: training/whoami:latest
networks:
- test-net
ports:
- 81:8000
deploy:
replicas: 6
update_config:
parallelism: 2
delay: 10s
labels:
app: sample-app
enviroment: prod-south
networks:
test-net:
driver: overlay
解讀:
- 服務的名稱叫
whoami
。 - 使用的鏡像為
training/whoami:latest
。 - 使用了一個自定的網絡
test-net
。 - 將主機端口81映射至容器內的8000端口。
- 運行6個副本(也就是task,也對應6個容器)
- 升級策略:每2個task為一組進行升級,且當前組升級成功后,延遲10秒才開始升級下一組。
- 定義了兩個標簽。
實際上,還有很多其他的設置,我們這里沒有列出來,它們都有自己的默認值。
請注意,services里面各個service的名字在swarm中必須唯一。
現在,我們使用這個配置文件來部署我們的一個集群服務吧:
# 為了更直觀的查看過程,我們可以登錄到manager leader上進行部署
[root@node2 swarm]# docker-machine ssh node-1
# 在manager leader上創建上面的yaml文件
docker@node-1:~$ vi stack.yaml
docker@node-1:~$ docker stack deploy -c stack.yaml samle-stack
# 創建過程需要一定的時間,請耐心等待
docker@node-1:~$ docker stack ls
NAME SERVICES ORCHESTRATOR
samle-stack 1 Swarm
docker@node-1:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
mjx5rdvluyeb samle-stack_whoami replicated 6/6 training/whoami:latest *:81->8000/tcp
在docker stack deploy
執行完成后,我使用docker service ls
命令,剛開始在REPLICAS那一欄始終都是看到3/6,代表只啟動了3個task,但是實際應該啟動6個。過一段時候之后,我再次查看發現就是6個都啟動了。我當時猜測,有些node可能是拉取鏡像的時候超時了,導致沒有完全啟動,下面命令的輸出也印證了我的猜測。node1、2、5都自動創建了一個新的task。
docker@node-1:~$ docker service ps samle-stack_whoami
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
35gp71dh9sgl samle-stack_whoami.1 training/whoami:latest node-4 Running Running 36 minutes ago
b79jr4suvuc2 samle-stack_whoami.2 training/whoami:latest node-5 Running Running 29 minutes ago
noferwnjjr5s \_ samle-stack_whoami.2 training/whoami:latest node-5 Shutdown Rejected 29 minutes ago "No such image: training/whoam…"
wd59fklp6ayg samle-stack_whoami.3 training/whoami:latest node-1 Running Running 29 minutes ago
z0gxnngq5ipt \_ samle-stack_whoami.3 training/whoami:latest node-1 Shutdown Rejected 29 minutes ago "No such image: training/whoam…"
zcf9x5c61a6f samle-stack_whoami.4 training/whoami:latest node-2 Running Running 29 minutes ago
lb3plbt9b3c2 \_ samle-stack_whoami.4 training/whoami:latest node-2 Shutdown Rejected 29 minutes ago "No such image: training/whoam…"
xfxk4ngiggf3 samle-stack_whoami.5 training/whoami:latest node-3 Running Running 36 minutes ago
m647fsycr4rx samle-stack_whoami.6 training/whoami:latest node-4 Running Running 36 minutes ago
查看node-1節點上運行的容器
docker@node-1:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39c616331871 training/whoami:latest "/app/http" 33 minutes ago Up 33 minutes 8000/tcp samle-stack_whoami.3.wd59fklp6aygpua8289gt978l
查看服務的詳細信息
docker@node-1:~$ docker service inspect samle-stack_whoami
查看services的日志:
docker@node-1:~$ docker service logs samle-stack_whoami
samle-stack_whoami.4.zcf9x5c61a6f@node-2 | Listening on :8000
samle-stack_whoami.6.m647fsycr4rx@node-4 | Listening on :8000
samle-stack_whoami.1.35gp71dh9sgl@node-4 | Listening on :8000
samle-stack_whoami.2.b79jr4suvuc2@node-5 | Listening on :8000
samle-stack_whoami.5.xfxk4ngiggf3@node-3 | Listening on :8000
samle-stack_whoami.3.wd59fklp6ayg@node-1 | Listening on :8000
下面我們來看看集群的高可用,和狀態一致性(意思是集群始終會保持你所期待的樣子),通過上面的輸出,我們可以看到在node-4上是運行了2個容器的,我們手動的刪除一個容器看看:
docker@node-4:~$ docker container rm -f samle-stack_whoami.6.m647fsycr4rxk2epx542bx8n6
samle-stack_whoami.6.m647fsycr4rxk2epx542bx8n6
我們刪除的是編號為6的容器,samle-stack_whoami.6
以上命令顯示,在node-1上重新啟用了一個新的容器,名稱為:samle-stack_whoami.6。
除了像上面這樣,可能某個容器出現問題。但有些時候,也有可能出現某個節點故障,我們可以用如下命令模擬節點故障:
docker-machine stop node-2
相應的node-2上的task就都會自動轉移到其他主機上。
刪除一個stack或者service可以使用如下命令:
docker stack rm
,將會刪除整個stack以及service,還有主機上的task。
docker service rm
,會刪除service,還有主機上的task。
4、在集群上部署一個多service的stack
上面的示例在services中只有一個service,下面我們演示一個多service的。
stack配置文件為:
version: "3.5"
services:
web:
image: fundamentalsofdocker/ch08-web:1.0
networks:
- pets-net
ports:
- 3000:3000
deploy:
replicas: 3
db:
image: fundamentalsofdocker/ch08-db:1.0
networks:
- pets-net
volumes:
- pets-data:/var/lib/postgresql/data
volumes:
pets-data:
networks:
pets-net:
driver: overlay
實際在node-1上運行:
docker@node-1:~$ docker stack deploy -c pet_stack.yaml pets_web
Creating network pets_web_pets-net
Creating service pets_web_web
Creating service pets_web_db
docker@node-1:~$ docker stack ls
NAME SERVICES ORCHESTRATOR
pets_web 2 Swarm
docker@node-1:~$ docker stack ps pets_web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
t4sl5hmq4xbr pets_web_db.1 fundamentalsofdocker/ch08-db:1.0 node-4 Running Running 7 minutes ago
arqpt9p3bqws pets_web_web.1 fundamentalsofdocker/ch08-web:1.0 node-2 Running Running 7 minutes ago
k05n7x67kmig \_ pets_web_web.1 fundamentalsofdocker/ch08-web:1.0 node-2 Shutdown Rejected 7 minutes ago "No such image: fundamentalsof…"
sivxrv71npj1 pets_web_web.2 fundamentalsofdocker/ch08-web:1.0 node-3 Running Running 13 minutes ago
bw2h8z3yd3p0 pets_web_web.3 fundamentalsofdocker/ch08-web:1.0 node-1 Running Running 13 minutes ago
和上一小節區別不大,下面我們驗證一下web服務是否可用。
docker@node-1:~$ curl http://localhost:3000/pet
<html>
<head>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<h4>Cat Gif of the day</h4>
<img src="http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr06/15/9/anigif_enhanced-buzz-25158-1381844793-0.gif"" />
<p><small>Courtesy: <a >Buzzfeed</a></small></p>
<p>Delivered to you by container 8c6780b1b264<p>
</div>
</body>
4.3 swarm的路由網絡
對于上面最后一步的驗證過程,我們可以看到3個web的task分別運行在node1、2、3上,但為什么能夠在node-1節點上通過localhost來訪問web服務,并且得到從node-2上的容器返回的結果呢?這一切都歸功于swarm routing mesh
,它的工作原理如下圖:
對于每一個service,docker swarm都會自動的給它分配一個virtual IP,對外暴露的也是這個VIP,當有數據請求達到VIP時,它會進行負載均衡,將請求分配到某一個實際正在運行task的節點上。