接下來通過實例演示一下如何使用 Swarm 來創(chuàng)建安全的集群。
實例中包含 3 個管理節(jié)點和 3 個工作節(jié)點,如下圖所示,可以根據(jù)需要自行調(diào)整管理節(jié)點和工作節(jié)點的數(shù)量、名稱和 IP。

每個節(jié)點都需要安裝 Docker,并且能夠與 Swarm 的其他節(jié)點通信。
如果配置有域名解析就更好了,這樣在命令的輸出中更容易識別出節(jié)點,也更有利于排除故障。
在網(wǎng)絡(luò)方面,需要在路由器和防火墻中開放如下端口。
? 2377/tcp:用于客戶端與 Swarm 進行安全通信。
? 7946/tcp 與 7946/udp:用于控制面 gossip 分發(fā)。
? 4789/udp:用于基于 VXLAN 的覆蓋網(wǎng)絡(luò)。
如果滿足以上前提,就可以著手開始搭建 Swarm 集群了。
搭建 Swarm 的過程有時也被稱為初始化 Swarm,大體流程包括初始化第一個管理節(jié)點 -> 加入額外的管理節(jié)點 -> 加入工作節(jié)點 -> 完成。
不包含在任何 Swarm 中的 Docker 節(jié)點,稱為運行于單引擎(Single-Engine)模式。一旦被加入 Swarm 集群,則切換為 Swarm 模式,如下圖所示。

在單引擎模式下的 Docker 主機上運行 docker swarm init會將其切換到 Swarm 模式,并創(chuàng)建一個新的 Swarm,將自身設(shè)置為 Swarm 的第一個管理節(jié)點。
更多的節(jié)點可以作為管理節(jié)點或工作節(jié)點加入進來。這一操作也會將新加入的節(jié)點切換為 Swarm 模式。
以下的步驟會將 mgr1 切換為 Swarm 模式,并初始化一個新的 Swarm。接下來將 wrk1、wrk2 和 wrk3 作為工作節(jié)點接入,自動將它們切換為Swarm模式。然后將 mgr2 和 mgr3 作為額外的管理節(jié)點接入,并同樣切換為 Swarm 模式。最終有 6 個節(jié)點切換到 Swarm 模式,并運行于同一個 Swarm 中。
⒈ 登錄到 mgr1 并初始化一個新的 Swarm
如果在 Windows 的 PowerShell 終端執(zhí)行如下命令的話,不要忘了將反斜杠替換為反引號。
$ docker swarm init \
--advertise-addr 10.0.0.1:2377 \
--listen-addr 10.0.0.1:2377
Swarm initialized: current node (d21lyz...c79qzkx) is now a manager.
將這條命令拆開分析如下。
docker swarm init會通知 Docker 來初始化一個新的 Swarm,并將自身設(shè)置為第一個管理節(jié)點。同時也會使該節(jié)點開啟 Swarm 模式。
--advertise-addr 指定其他節(jié)點用來連接到當前管理節(jié)點的 IP 和端口。這一屬性是可選的,當節(jié)點上有多個 IP 時,可以用于指定使用哪個IP。此外,還可以用于指定一個節(jié)點上沒有的 IP,比如一個負載均衡的 IP。
--listen-addr 指定用于承載 Swarm 流量的 IP 和端口。其設(shè)置通常與 --advertise-addr 相匹配,但是當節(jié)點上有多個 IP 的時候,可用于指定具體某個 IP。并且,如果 --advertise-addr 設(shè)置了一個遠程 IP 地址(如負載均衡的IP地址),該屬性也是需要設(shè)置的。建議執(zhí)行命令時總是使用這兩個屬性來指定具體 IP 和端口。
Swarm 模式下的操作默認運行于 2337 端口。雖然它是可配置的,但 2377/tcp 是用于客戶端與 Swarm 進行安全(HTTPS)通信的約定俗成的端口配置。
⒉ 列出 Swarm 中的節(jié)點。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
d21...qzkx * mgr1 Ready Active Leader
注意到 mgr1 是 Swarm 中唯一的節(jié)點,并且作為 Leader 列出。
⒊ 在 mgr1 上執(zhí)行 docker swarm join-token 命令
docker swarm join-token 命令用來獲取添加新的工作節(jié)點和管理節(jié)點到 Swarm 的命令和 Token。
$ docker swarm join-token worker
To add a manager to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-0uahebax...c87tu8dx2c \
10.0.0.1:2377
$ docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-0uahebax...ue4hv6ps3p \
10.0.0.1:2377
請注意,工作節(jié)點和管理節(jié)點的接入命令中使用的接入 Token(SWMTKN...)是不同的。因此,一個節(jié)點是作為工作節(jié)點還是管理節(jié)點接入,完全依賴于使用了哪個 Token。接入 Token 應(yīng)該被妥善保管,因為這是將一個節(jié)點加入 Swarm 的唯一所需!
⒋ 登錄到 wrk1,并使用包含工作節(jié)點接入 Token 的 docker swarm join 命令將其接入 Swarm。
$ docker swarm join \
--token SWMTKN-1-0uahebax...c87tu8dx2c \
10.0.0.1:2377 \
--advertise-addr 10.0.0.4:2377 \
--listen-addr 10.0.0.4:2377
This node joined a swarm as a worker.
--advertise-addr 與 --listen-addr 屬性是可選的。在網(wǎng)絡(luò)配置方面,請盡量明確指定相關(guān)參數(shù),這是一種好的實踐。
⒌ 在 wrk2 和 wrk3 上重復(fù)上一步驟來將這兩個節(jié)點作為工作節(jié)點加入 Swarm。
確保使用 --advertise-addr 與 --listen-addr 屬性來指定各自的 IP 地址。
⒍ 登錄到 mgr2,然后使用含有管理節(jié)點接入 Token 的 docker swarm join 命令,將該節(jié)點作為工作節(jié)點接入 Swarm。
$ docker swarm join \
--token SWMTKN-1-0uahebax...ue4hv6ps3p \
10.0.0.1:2377 \
--advertise-addr 10.0.0.2:2377 \
--listen-addr 10.0.0.1:2377
This node joined a swarm as a manager.
⒎ 在 mgr3 上重復(fù)以上步驟,記得在 --advertise-addr 與 --listen-addr 屬性中指定 mgr3 的 IP 地址。
⒏ 在任意一個管理節(jié)點上執(zhí)行 docker node ls 命令來列出 Swarm 節(jié)點。
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
0g4rl...babl8 * mgr2 Ready Active Reachable
2xlti...l0nyp mgr3 Ready Active Reachable
8yv0b...wmr67 wrk1 Ready Active
9mzwf...e4m4n wrk3 Ready Active
d21ly...9qzkx mgr1 Ready Active Leader
e62gf...l5wt6 wrk2 Ready Active
至此,已經(jīng)成功創(chuàng)建了 6 個節(jié)點的 Swarm,其中包含 3 個管理節(jié)點和 3 個工作節(jié)點。
在這個過程中,每個節(jié)點的 Docker 引擎都被切換到 Swarm 模式下。并且,Swarm 已經(jīng)自動啟用了 TLS 以策安全。
注意,mgr2 的 ID 列還顯示了一個星號(*),這個星號會告知用戶執(zhí)行docker node ls 命令所在的節(jié)點。本例中,命令是在 mgr2 節(jié)點執(zhí)行的。
每次將節(jié)點加入 Swarm 都指定 --advertise-addr 與 --listen-addr 屬性是痛苦的。然而,一旦 Swarm 中的網(wǎng)絡(luò)配置出現(xiàn)問題將會更加痛苦。況且,手動將節(jié)點加入 Swarm 也不是一種日常操作,所以在執(zhí)行該命令時額外指定這兩個屬性是值得的。
現(xiàn)在已經(jīng)有一個運行中的 Swarm 了,下面看一下如何進行高可用(HA)管理。
Swarm 的管理節(jié)點內(nèi)置有對 HA 的支持。這意味著,即使一個或多個節(jié)點發(fā)生故障,剩余管理節(jié)點也會繼續(xù)保證 Swarm 的運轉(zhuǎn)。
從技術(shù)上來說,Swarm 實現(xiàn)了一種主從方式的多管理節(jié)點的 HA。這意味著,即使你可能有多個管理節(jié)點,也總是僅有一個節(jié)點處于活動狀態(tài)。
通常處于活動狀態(tài)的管理節(jié)點被稱為“主節(jié)點”(leader),而主節(jié)點也是唯一一個會對 Swarm 發(fā)送控制命令的節(jié)點。也就是說,只有主節(jié)點才會變更配置,或發(fā)送任務(wù)到工作節(jié)點。如果一個備用(非活動)管理節(jié)點接收到了 Swarm 命令,則它會將其轉(zhuǎn)發(fā)給主節(jié)點。
這一過程如下圖所示。步驟 ① 指命令從一個遠程的 Docker 客戶端發(fā)送給一個管理節(jié)點;步驟 ② 指非主節(jié)點將命令轉(zhuǎn)發(fā)給主節(jié)點;步驟 ③ 指主節(jié)點對 Swarm 執(zhí)行命令。

仔細觀察上圖會發(fā)現(xiàn),管理節(jié)點或者是 Leader 或者是 Follower。這是 Raft 的術(shù)語,因為 Swarm 使用了 Raft 共識算法的一種具體實現(xiàn)來支持管理節(jié)點的HA。
關(guān)于 HA,有以下兩條最佳實踐原則。
? 部署奇數(shù)個管理節(jié)點。
? 不要部署太多管理節(jié)點(建議 3 個或 5 個)。
部署奇數(shù)個管理節(jié)點有利于減少腦裂(Split-Brain)情況的出現(xiàn)機會。假如有 4 個管理節(jié)點,當網(wǎng)絡(luò)發(fā)生分區(qū)時,可能會在每個分區(qū)有兩個管理節(jié)點。這種情況被稱為腦裂。
每個分區(qū)都知道曾經(jīng)有 4 個節(jié)點,但是當前網(wǎng)絡(luò)中僅有兩個節(jié)點,糟糕的是,每個分區(qū)都無法知道其余兩個節(jié)點是否運行,也無從得知本分區(qū)是否掌握大多數(shù)(Quorum)。
雖然在腦裂情況下集群依然在運行,但是已經(jīng)無法變更配置,或增加和管理應(yīng)用負載了。不過,如果部署有 3 個或 5 個管理節(jié)點,并且也發(fā)生了網(wǎng)絡(luò)分區(qū),就不會出現(xiàn)每個分區(qū)擁有同樣數(shù)量的管理節(jié)點的情況。
這意味著掌握多數(shù)管理節(jié)點的分區(qū)能夠繼續(xù)對集群進行管理。下圖中右側(cè)的例子,闡釋了這種情況,左側(cè)的分區(qū)知道自己掌握了多數(shù)的管理節(jié)點。

對于所有的共識算法來說,更多的參與節(jié)點就意味著需要花費更多的時間來達成共識。這就像決定去哪吃飯,只有3個人的時候總是比有33個人的時候能更快確定。
考慮到這一點,最佳的實踐原則是部署 3 個或 5 個節(jié)點用于 HA。7 個節(jié)點可以工作,但是通常認為 3 個或 5 個是更優(yōu)的選擇。當然絕對不要多于 7 個,因為需要花費更長的時間來達成共識。
關(guān)于管理節(jié)點的 HA 再補充一點。顯然將管理節(jié)點分布到不同的可用域(Availability Zone)中是一種不錯的實踐方式,但是一定要確保它們之間的網(wǎng)絡(luò)連接是可靠的,否則由于底層網(wǎng)絡(luò)分區(qū)導致的問題將是令人痛苦的。
Swarm 集群內(nèi)置有繁多的安全機制,并提供了開箱即用的合理的默認配置——如 CA 設(shè)置、接入 Token、公用 TLS、加密集群存儲、加密網(wǎng)絡(luò)、加密節(jié)點 ID 等。
盡管內(nèi)置有如此多的原生安全機制,重啟一個舊的管理節(jié)點或進行備份恢復(fù)仍有可能對集群造成影響。
一個舊的管理節(jié)點重新接入 Swarm 會自動解密并獲得 Raft 數(shù)據(jù)庫中長時間序列的訪問權(quán),這會帶來安全隱患。
進行備份恢復(fù)可能會抹掉最新的 Swarm 配置。
為了規(guī)避以上問題,Docker 提供了自動鎖機制來鎖定 Swarm,這會強制要求重啟的管理節(jié)點在提供一個集群解鎖碼之后才有權(quán)從新接入集群。
通過在執(zhí)行 docker swarm init 命令來創(chuàng)建一個新的 Swarm 集群時傳入 --autolock 參數(shù)可以直接啟用鎖。
然而,前面已經(jīng)搭建了一個 Swarm 集群,這時也可以使用 docker swarm update 命令來啟用鎖。
在某個 Swarm 管理節(jié)點上運行如下命令。
$ docker swarm update --autolock=true
Swarm updated.
To unlock a swarm manager after it restarts, run the
`docker swarm unlock`command and provide the following key:
SWMKEY-1-5+ICW2kRxPxZrVyBDWzBkzZdSd0Yc7Cl2o4Uuf9NPU4
Please remember to store this key in a password manager, since without
it you will not be able to restart the manager.
請確保將解鎖碼妥善保管在安全的地方!
重啟某一個管理節(jié)點,以便觀察其是否能夠自動重新接入集群。
$ service docker restart
嘗試列出Swarm中的節(jié)點。
$ docker node ls
Error response from daemon: Swarm is encrypted and needs to be unlocked
before it can be used.
盡管 Docker 服務(wù)已經(jīng)重啟,該管理節(jié)點仍然未被允許重新接入集群。
為了進一步驗證,可以到其他管理節(jié)點執(zhí)行 docker node ls 命令,會發(fā)現(xiàn)重啟的管理節(jié)點會顯示 down 以及 unreachable。
執(zhí)行 docker swarm unlock 命令來為重啟的管理節(jié)點解鎖 Swarm。該命令需要在重啟的節(jié)點上執(zhí)行,同時需要提供解鎖碼。
$ docker swarm unlock
Please enter unlock key: <enter your key>
該節(jié)點將被允許重新接入 Swarm,并且再次執(zhí)行 docker node ls 命令會顯示 ready 和 reachable。
至此,Swarm 集群已經(jīng)搭建起來,相信大家已經(jīng)對主節(jié)點和管理節(jié)點 HA 有了一定了解。