Docker Swarm 是 Docker 官方項目之一,提供 Docker 容器集羣服務,是 Docker 官方對容器雲生態進行支持的核心方案。使用它,用戶能夠將多個 Docker 主機封裝爲單個大型的虛擬 Docker 主機,快速打造一套容器雲平臺。node
Docker 1.12 Swarm mode 已經內嵌入 Docker 引擎,成爲了 docker 子命令 docker swarm
。mysql
Swarm mode 內置 kv 存儲功能,提供了衆多的新特性,好比:具備容錯能力的去中心化設計、內置服務發現、負載均衡、路由網格、動態伸縮、滾動更新、安全傳輸等。linux
docker 中和 swarm 相關的命令有:nginx
docker swarm
dokcer node
docker service
docker stack
docker secret
Swarm 中一臺主機就是一個節點(node
),節點分爲管理( manager
)和工做(worker
)節點。git
管理節點用於 Swarm 集羣的管理。一個 Swarm 集羣能夠有多個管理節點,但只有一個管理節點能夠成爲 leader
,leader
經過 raft
協議實現。github
管理節點有內置 Raft 數據庫,它們用來存放配置等數據。並且它們之間的通訊都是加密的。web
工做節點是任務執行節點,管理節點將服務 (service) 下發至工做節點執行。管理節點默認也做爲工做節點。也能夠經過讓服務只運行在管理節點上,管理節點和工做節點只是它們的權限不一樣,工做節點就沒有管理節點那麼多權限,好比在工做節點上不能查看集羣中的容器。redis
有了 swarm 咱們就不用本身一個個的建立容器了,好比咱們有 3 個主機,咱們告訴 swarm 咱們要建立 5 個 nginx 容器,swarm 會本身幫咱們部署到不一樣主機上,好比那個主機部署一個那個部署兩個。sql
$ docker swarm init
# 咱們執行這個命令,來啓用 swarm,咱們當前主機做爲管理節點
# 它還建立了 swarm 根證書,還建立了 join token,用來讓其餘節點加入這個 Swarm
# 還建立了 raft 數據庫。
Swarm initialized: current node (86pi89uue3q0eqiqpomna3ejy) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5aco0o1iqxbgjludw6m6rhl9qug60ylm8lj938hnyfy2cyt8d7-7jq47t76cohvtah61sab7rwcf 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
複製代碼
咱們能夠直接複製上面那條輸出的命令,讓其餘節點做爲 worker
加入這個 swarm
。docker
如我咱們想讓加入的節點做爲 manager
加入這個 swarm
能夠執行:
$ docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5aco0o1iqxbgjludw6m6rhl9qug60ylm8lj938hnyfy2cyt8d7-ecmkdz7x0eswu3uyn0vij72dy 192.168.65.3:2377
複製代碼
$ docker node ls
# docker node 命令能夠用來管理咱們的節點,ls 表示列出集羣全部節點
複製代碼
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
# docker service 命令用來取代 docker run 命令
# 由於在 swarm 中咱們不關心容器的個個配置信息,並且也不會去其餘節點本身手動建立容器
# 咱們只須要拋出一個 任務(task) 由 Swarm 來協調
複製代碼
任務 (Task
)是 Swarm
中的最小的調度單位,目前來講就是一個單一的容器。
服務 (Services
) 是指一組任務的集合,服務定義了任務的屬性。服務有兩種模式:
replicated services
按照必定規則在各個工做節點上運行指定個數的任務。global services
強制在每一個 node 上都運行一個且最多一個容器。$ docker service create alpine ping baidu.com
# 建立一個容器,讓它來 ping 百度
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 1/1 alpine:latest
# 咱們發現 MODE 是 replicated(默認)
# REPLICAS 是 1/1 右邊是要運行,左邊是實際運行的數量
$ docker service ps nostalgic_zhukovsky
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
snpjphe14ztv nostalgic_zhukovsky.1 alpine:latest linuxkit-00155d01020f Running Running 4 minutes ago
# 查看這個 service 的任務(容器)
複製代碼
$ docker service update --replicas 3 nostalgic_zhukovsky
# docker service update 用來更新一個 service
# 這裏讓它運行 3 個任務
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 3/3 alpine:latest
$ docker service scale -d nostalgic_zhukovsky=10
# 咱們還能夠經過 scale 來擴大縮小咱們的服務
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yjoejehnu4yx nostalgic_zhukovsky replicated 7/10 alpine:latest
複製代碼
$ docker service scale nostalgic_zhukovsky=2
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
584829ddf89f alpine:latest "ping baidu.com" 56 seconds ago Up 49 seconds
8678f634f14b alpine:latest "ping baidu.com" 18 minutes ago Up 18 minutes
$ docker rm -f 8678f634f14b
$ docker service ps yjoejehnu4yx
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
snpjphe14ztv nostalgic_zhukovsky.1 alpine:latest linuxkit-00155d01020f Running Running 17 minutes ago
vk0ih9ds1kdm nostalgic_zhukovsky.2 alpine:latest linuxkit-00155d01020f Running Running 9 seconds ago
m4ylibww9l87 \_ nostalgic_zhukovsky.2 alpine:latest linuxkit-00155d01020f Shutdown Failed 15 seconds ago "task: non-zero exit (137)"
# 咱們發現咱們強制關閉一個容器,swarm 自動幫咱們新建了一個。
複製代碼
不像咱們本身啓動容器,使用 docker swarm 咱們只要說出本身任務就能夠了,swarm 會本身有沒有完成任務,比上面發現要運行兩個,卻發現一個被咱們刪了,它就會再建立一個容器。
建立 Swarm 集羣,咱們要建立多個 node,就須要多臺主機或虛擬機。
咱們能夠去 play with docker 網站來建立多個節點,這個網站是免費的,可是 4 個小時後就會銷燬你建立的全部虛擬機。
咱們還可使用 docker machine 幫咱們快速的在本地建立多臺 docker 虛擬機。
再或者去雲服務商那裏買幾臺雲主機,使用 這個腳本 快速安裝 docker。
Docker Machine 是 Docker 官方編排(Orchestration)項目之一,負責在多種平臺上快速安裝 Docker 環境。它使用 Go 語言實現。
對於 windos 和 mac 安裝 docker 的時候就自帶了 docker machine。
對於 linux 可使用以下命令安裝。
$ sudo curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine
$ sudo chmod +x /usr/local/bin/docker-machine
複製代碼
$ docker-machine create \
--engine-registry-mirror https://dockerhub.azk8s.cn \
-d virtualbox \
node1
# 建立一個驅動是 virtualbox 類型的 docker 主機
# --engine-registry-mirror 用來指定鏡像加速器
# 驅動除了是 virtualbox 還能夠是 macOS 的 xhyve 驅動
# 它比 virtualbox 運行效率要高
複製代碼
若是是在 Windows 10 上執行上面那個命令就會報錯,由於 Windows 10 安裝 Docker for Windows 以後不能再安裝 VirtualBox,也就不能使用 virtualbox 驅動來建立 Docker Machine。
咱們可使用 hyperv
驅動,並且必須事先在 Hyper-V 管理器中新建一個 外部虛擬交換機 執行下面的命令時,使用 --hyperv-virtual-switch=MY_SWITCH
指定虛擬交換機名稱。
$ docker-machine create \
--engine-registry-mirror https://dockerhub.azk8s.cn \
--hyperv-virtual-switch PVS \
-d hyperv \
node1
# 而後咱們以管理員身份打開終端執行這條命令
# --hyperv-virtual-switch 後面是剛剛取的虛擬交換機名稱
# 咱們重複上面那個命令,分別建立 node1 node2 node3 3個節點
複製代碼
建立好 3 個 docker 主機後,咱們登陸上去建立 swarm 集羣。
$ docker-machine ssh node1
# 登陸到 node1
$ docker swarm init
# docker swarm join --token SWMTKN-1-1ve56x9y7784s0enqvhghnls4rbjazwxovz5me1cmqub9jlgwa-83msyrvzwpgrk46fsifmht8nj 192.168.1.168:2377
複製代碼
但後去 node2 和 node3 執行上面輸出的那條命令,而後再登陸到 node1。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
2y3ih8gtvit225155x5u33690 * node1 Ready Active Leader 18.09.7
q0ag3cuma8sqiszjlou2ttc3t node2 Ready Active 18.09.7
cx4yap7apqnii58xmmxs8mdo7 node3 Ready Active 18.09.7
複製代碼
能夠看到這個 Swarm 中已經有 3 個節點了,其中 node1 是管理節點,剩下兩個是工做節點。
worker 節點沒有管理集羣,爲了方便咱們能夠複製 manager 的 token 讓另外兩個節點也成爲 manager。
或者在 node1 上執行:
$ docker node update --role manager node2
$ docker node update --role manager node3
複製代碼
在使用多節點以前,咱們先在本地執行一下,以下命令:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
rkfr0w8kpqvi ingress overlay swarm
複製代碼
咱們能夠看到一個驅動類型是 overlay
類型的網絡,那是剛纔 swarm 幫咱們建立的。
要使用服務發現,須要相互通訊的 service 必須屬於同一個 overlay
網絡。自動建立的 ingress
沒有提供服務發現,必須建立本身的 overlay 網絡。
它是 Swarm 範圍橋接網絡,容器能夠跨主機互相訪問,就像它們在一臺主機上同樣。經過 overlay 網絡,主機與容器、容器與容器之間能夠相互訪問。
它只在一個 Swarm 內部,這樣就不會搞亂主機網絡配置。
如今咱們使用 Swarm 來建立一個 drupal 網站(Drupal 是自由開源內容管理系統,用PHP語言寫成)。
登陸到 node1 首先建立一個本身 overlay
網絡
$ docker network create -d overlay drupal
複製代碼
而後建立一個 postgres
service,用來存儲數據。
$ docker service create --name psql --network drupal -e POSTGRES_PASSWORD=mypass postgres
# --network 加入到咱們剛剛建立的那個網絡
# -e 添加環境變量,用來指定 postgres 密碼
複製代碼
而後就是 drupal service。
$ docker service create --name drupal --network drupal -p 80:80 drupal
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
4c1rgorpz5hp drupal replicated 1/1 drupal:latest *:80->80/tcp
wyd2y3roqwh3 psql replicated 1/1 postgres:latest
$ docker service ps psql
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
v05c9pi98i4a psql.1 postgres:latest node2 Running Running 10 minutes ago
$ docker service ps drupal
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
qz4443imlexv drupal.1 drupal:latest node3 Running Running 6 minutes ago
# 能夠看到 postgres 運行在 node2,drupal 運行在 node3
複製代碼
咱們打開瀏覽器,輸入上面 node1 的 ip 地址。可使用 docker-machine ls
或 docker-machine ip node1
查看。
打開事後就能夠看到 drupal 頁面,快速填寫一下配置。
咱們能夠輸入一下 node2 和 node3 的 ip 到瀏覽器。
咱們發現一樣能夠訪問到 drupal 網站,雖然它只運行在 node2 上。
swarm 向外暴露端口,全部節點都參與進入 routing mesh 中。每一個節點都能接受暴露端口鏈接,即便 node 中沒有運行這個 service。routing mesh 會將請求路由到運行這個 service 上活躍的容器中,它在全部節點上作負載均衡。
當內部容器對容器通訊時,它們使用虛擬 IP (VIP)通訊,它是 Swarm 虛擬網絡中的私有 IP,它會讓請求分佈到全部服務任務中,好比咱們有 10 個 worker 容器,咱們無需作負載均衡,swarm 已經幫你作了。
當外部流量訪問監聽的端口時,全部 node 都會監聽該端口流量,而後它會將該流量負載均衡的路由到合適的容器。若是是不一樣 node 那麼就會經過虛擬網絡路由到容器,若是是同一個 node 上,那麼就會直接路由到容器的端口。
能夠看到每一個節點中都會有一個 load balancer。swarm 的負載均衡會把你的請求路由到一個任意節點的可用的容器上。routing mesh 在 swarm 節點的全部 IP 上監聽 published 端口。
若是在一個服務器端口上運行一個網站,咱們就會失去 swarm 的負載均衡,由於它是 TCP 層面的負載均衡而不是 DNS 層面的。這時候咱們就須要一個外部負載均衡,可使用 nginx 或 HAProxy。
滾動升級是一次只升級一部分副本,不一次性所有升級,它下降了應用更新的風險,若是某個副本更新失敗,整個更新將暫停,其餘副本則能夠繼續提供服務。在更新的過程當中,老是有副本在運行的,也保證了業務的連續性。
如今咱們把 nginx:1.16
版本升級到 nginx:1.17
。
$ docker service create --name web --replicas=3 nginx:1.16
# --replicas=3 啓用 3 個副本
複製代碼
$ docker service update --image nginx:1.17 web
# 升級到 1.17
# swarm 會中止一個容器,更新它,若是失敗就會暫停整個更新過程
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
PORTS
nqch5ivpmmcd web.1 nginx:1.17 linuxkit-00155d010229 Running Running 57 seconds ago
uclfd0iev0sk \_ web.1 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown 58 seconds ago
ku2aomr1br3d web.2 nginx:1.17 linuxkit-00155d010229 Running Running about a minute ago
fpqp9mdfez9r \_ web.2 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown about a minute ago
ww6hxdsutlog web.3 nginx:1.17 linuxkit-00155d010229 Running Running about a minute ago
hvy2g68j9ah4 \_ web.3 nginx:1.16 linuxkit-00155d010229 Shutdown Shutdown about a minute ago
複製代碼
Swarm 還有回滾功能,能夠經過 --rollback
快速恢復到更新以前的狀態。
$ docker service update --rollback web
# 也能夠寫成 docker serivice rollback web
複製代碼
--rollback
只能回滾到上一次執行 docker service update
以前的狀態,並不能無限制地回滾。
Swarm 會自動幫咱們把 Service 分配到合適的 node 上,性能高的 node 分配的 Service 就越多。除了 Swarm 自動分配,咱們也能夠手動分配。
咱們能夠給 node 設置 label,而後讓 Service 運行在指定 label 的 node 上。
$ docker node update --label-add key=value node1
$ docker node inspect node1 --pretty
...
Labels:
- key = value
...
複製代碼
而後建立 Service
$ docker service create \
--constraint node.labels.key==value \
--replicas 3 \
--name web \
--p 80:80 \
nginx
# --constraint 限制將 service 部署到指定的 node
$ docker service inspect web --pretty
...
Placement:Contraints: [node.labels.key==value]
...
複製代碼
咱們還能夠更新 Service constraint,讓它更換 node。
$ docker service update --constraint-rm node.labels.env==test web
$ docker service update --constraint-add node.labels.env==prod web
複製代碼
若是咱們想 swarm 強制更新可使用 --force
$ docker service update --force web
複製代碼
咱們常常要向容器傳遞敏感信息,好比上面傳遞的 POSTGRES_PASSWORD=mypass
環境變量。密碼是以明文的形式寫在命令中,潛在的巨大的安全風險。
咱們能夠經過 Secret 安全地管理 Swarm 集羣中密碼、密鑰證書等敏感數據,並容許在多個 Docker 容器實例之間共享訪問指定的敏感數據。它最大支持 500KB 的字符串或二進制內容。
Secret
會被加密的保存在管理節點的硬盤上,被加密傳輸。只有被容許的容器才能查看 Secret
,在容器中它只會被存在內存中,能夠在 /run/secrets/<secret_name | secret_alias>
訪問到。
Secret
能夠經過兩種方式建立,一種是文件另外一種是 stdin
建立。
$ docker secret create psql_user psql_user.txt
$ echo 'mypass' | docker secret create psql_pass -
複製代碼
$ docker service create --name psql --secret psql_user --secret psql_pass \
-e POSTGRES_PASSWORD_FILE=/run/secrets/psql_pass \
-e POSTGRES_USER_FILE=/run/secrets/psql_user postgres
複製代碼
--secret
用來指定 Service 能使用那個 secret
。
若是咱們如今刪除 Service 的 secret
,可使用 --secret-rm
$ docker service update --secret-rm psql_pass psql
# 刪除 Service 的 secret。
# 容器會自動重建,由於 Service 是容器的一部分。
複製代碼
Swarm 除了能夠幫咱們管理敏感數據,還能夠幫咱們管理配置文件。config
與 secret
命令的使用方法徹底一致。
$ docker config create redis.conf redis.conf
$ docker service create \
--name redis \
# --config source=redis.conf,target=/etc/redis.conf \
--config redis.conf \
-p 6379:6380 \
redis:latest \
redis-server /redis.conf
複製代碼
咱們每次啓動一個 service 的時候都要寫一堆命令行命令很是的笨拙冗長。
咱們能夠用 Stack 來簡化這個操做。Stack 是 Swarm 調用的抽象,和 docker-compose
同樣它也接收 docker compose
文件,用來定義 Services, Networks 和 Volumes 等。
咱們使用 docker stack deploy
而不是 docker service create
,Stack 會幫咱們管理這些對象。
咱們能夠直接用 docker-compose.yml
文件,可是不能 build
鏡像,Swarm 只接收構建好的鏡像,新加了一個 deploy
字段。當使用 docker-compose
執行這個文件時,會忽略 deploy
字段。docker stack
中會忽略 build
字段,因此咱們能夠開發和發佈都使用一個 docker-compose.yml
文件。
version: '3.7'
services:
db:
image: mysql:5.7
networks:
- wordpress
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets: # 指定 secret
- db_root_password
- db_password
deploy: # 部署
update_config: # 更新規則
parallelism: 2 # 一次兩個
delay: 10s # 更新延遲 10s,給 app 一個啓動時間
restart_policy: # 重啓規則
condition: on-failure
placement:
constraints: [node.role == manager] # 只部署在 manager 節點
wordpress:
depends_on:
- db
image: wordpress
networks:
- wordpress
ports:
- '80:80'
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
deploy:
replicas: 2
labels: [APP=WORDPRESS] # 添加自定義 label
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
visualizer: # 可視化頁面,打開瀏覽器 8080 端口能夠看見效果
image: dockersamples/visualizer:stable
networks:
- wordpress
ports:
- "8080:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
volumes:
db-data:
networks:
wordpress:
secrets:
db_password:
file: db_password.txt
db_root_password:
file: db_root_password.txt
複製代碼
而後咱們就能夠部署這個 stack
$ docker stack deploy -c docker_compose.yml wordpress
# -c 指定配置文件
# deploy 也能夠換成 up
$ docker stack ls
NAME SERVICES ORCHESTRATOR
wordpress 2 Swarm
$ docker stack ps wordpress
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
qk67so5btzln wordpress_wordpress.1 wordpress:latest linuxkit-00155d01022b Running Running 2 minutes ago
vmnk1gb7zl05 wordpress_db.1 mysql:latest linuxkit-00155d01022b Running Running 2 minutes ago
s5x09e5q8fr9 wordpress_wordpress.2 wordpress:latest linuxkit-00155d01022b Running Running 2 minutes ago
$ docker stack services wordpress
ID NAME MODE REPLICAS IMAGE PORTS
kuro9uavayjq wordpress_wordpress replicated 2/2 wordpress:latest *:80->80/tcp
l667za415j6s wordpress_db replicated 1/1 mysql:latest
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
xnunueoy9t5m wordpress_wordpress overlay swarm
$ docker secret ls
ID NAME DRIVER CREATED UPDATED
6wxq7esrrikmiq9rnhnl1uuzk wordpress_db_password 5 minutes ago 6 minutes ago
scvzglyfk4cjxfco1nytoajjz wordpress_db_root_password 5 minutes ago 6 minutes ago
複製代碼
經過上面能夠看到 stack 自動幫咱們建立 secret, network 等,咱們只須要一個命令就能夠了,若是咱們更新這個配置文件,咱們能夠再執行下面這個命令更新。
$ docker stack deploy -c docker-compose.yml wordpress
複製代碼
若是要中止這個 stack
,能夠執行 rm
命令
$ docker stack rm wordpress
複製代碼
咱們能夠只是用一個 yaml
文件完成本地和生產環境開發,可是當咱們的應用變得複雜的時候,一個配置文件可能沒那麼好用,這時候咱們就可使用多配置文件。
好比咱們能夠新建以下 yaml
文件
docker-compose.yml
docker-compose.override.yml
docker-compose.test.yml
docker-compose.prod.yml
複製代碼
docker-compose.yml
做爲其餘配置文件的基礎,它會合併到其餘配置文件上。
docker-compose.override.yml
當執行 docker-compose up
的時候 docker-compose
會自動將 docker-compose.yml
和名爲 docker-compose.override.yml
合併成爲一個文件執行。
docker-compose.test.yml
用於 CI 環境,咱們能夠執行
$ docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d
# 基本文件在前面
複製代碼
docker-compose.prod.yml
生產環境文件,咱們可使用 docker-compose
將它和 docker-compose.yml
合併成一個文件再交給 docker stack
$ docker-compose -f docker-compose.yml -f docker-compose.prod.yml config > out.yml
複製代碼