我們?nèi)绾巫龅讲煌C(jī)將ZooKeeper遷移到Kubernetes
最近,我們在不停機(jī)的情況下將數(shù)百個(gè) ZooKeeper 實(shí)例遷移到了 Kubernetes。我們利用了強(qiáng)大的 Kubernetes 特性(例如端點(diǎn))簡化了遷移過程,那些想要跟我們一樣進(jìn)行 Zookeeper 遷移的人可以在這篇文章里找到答案。文章的末尾列出了進(jìn)行遷移所需的網(wǎng)絡(luò)條件。
1. 傳統(tǒng)的 ZooKeeper 遷移方法
ZooKeeper 是很多分布式系統(tǒng)的基礎(chǔ),它為這些系統(tǒng)提供了一個(gè)強(qiáng)大的平臺(tái),讓它們可以聚在一起形成集群。它提供了一種比較基礎(chǔ)的方法來形成集群:每個(gè)服務(wù)器實(shí)例都有一個(gè)配置文件,文件里列出了集群成員的主機(jī)名和數(shù)字 ID,所有的服務(wù)器都有相同的集群成員列表,如下所示:
- server.1=host1:2888:3888
- server.2=host2:2888:3888
- server.3=host3:2888:3888
每臺(tái)服務(wù)器都有一個(gè)叫作 myid 的文件,用來指明它在列表中對應(yīng)的是哪個(gè)數(shù)字 ID。
集群可以隨意添加和移除服務(wù)器,只要沒有違反這個(gè)關(guān)鍵規(guī)則:每臺(tái)服務(wù)器必須能夠與配置文件中列出的仲裁服務(wù)器通信。傳統(tǒng)的 ZooKeeper 服務(wù)器遷移步驟主要包括:
- 啟動(dòng)一臺(tái)新主機(jī),在服務(wù)器列表配置中加入“server.4=host:4…”;
- 更新已有主機(jī)上的配置文件,添加新的服務(wù)器條目,或刪除已退役的主機(jī);
- 滾動(dòng)重啟舊主機(jī)(3.4x 版本分支不提供動(dòng)態(tài)服務(wù)器配置功能);
- 更新客戶端的連接串。
這種方法的缺點(diǎn)是需要修改大量的配置文件并進(jìn)行滾動(dòng)重啟,這種方式可能無法進(jìn)行可靠的自動(dòng)化。在將 ZooKeeper 遷移到 Kubernetes 之前,我們也考慮過這種方法,但后來找到了一種更簡單的方法。這種方法更為安全,因?yàn)楦鶕?jù)我們的經(jīng)驗(yàn),每一次新的首領(lǐng)選舉都存在一個(gè)小風(fēng)險(xiǎn),就是有可能會(huì)讓依賴它們的系統(tǒng)崩潰。
2. 新的遷移方法
我們將已有的 ZooKeeper 服務(wù)器包裝成 Kubernetes 服務(wù),然后使用相同的 ZooKeeper ID 進(jìn)行從服務(wù)器到 Pod 的一對一替換。這只需要一次滾動(dòng)重啟就可以重新配置現(xiàn)有的 ZooKeeper 實(shí)例,然后逐一關(guān)閉服務(wù)器。不過,我們不打算深入討論如何為 ZooKeeper 配置 Kubernetes 拓?fù)洌膊淮蛩闵钊胗懻摰讓拥臓顟B(tài)就緒檢查機(jī)制,因?yàn)橛泻芏喾椒梢詫?shí)現(xiàn)這些操作。
我們將分五個(gè)步驟進(jìn)行遷移:
- 確保為 ZooKeeper 集群的遷移做好準(zhǔn)備;
- 在 Kubernetes 中創(chuàng)建 ClusterIP 服務(wù),將 Zookeeper 包裝成服務(wù);
- 修改 ZooKeeper 客戶端,讓它們連接到 ClusterIP 服務(wù);
- 配置 ZooKeeper 服務(wù)器實(shí)例,讓它們可以基于 ClusterIP 服務(wù)地址執(zhí)行點(diǎn)對點(diǎn)事務(wù);
- 通過 Kubernetes Pod 運(yùn)行 ZooKeeper 實(shí)例。
對于下面的每一個(gè)步驟,我們都將提供一個(gè)基礎(chǔ)設(shè)施拓?fù)潢P(guān)系圖。為了便于理解,這些圖只包含兩個(gè) ZooKeeper 實(shí)例(在現(xiàn)實(shí)當(dāng)中一般不會(huì)創(chuàng)建少于三個(gè)節(jié)點(diǎn)的集群)。
準(zhǔn)備好先決條件
我們從一個(gè)可運(yùn)行的 ZooKeeper 集群開始,確保主機(jī)上的服務(wù)能夠與 Kubernetes 集群通信。文末介紹了幾種方法。
圖 1:初始狀態(tài),一個(gè)包含兩個(gè)實(shí)例的 ZooKeeper 集群和一些客戶端
創(chuàng)建 ClusterIP 服務(wù)
為每個(gè) ZooKeeper 服務(wù)器創(chuàng)建一個(gè)具有匹配端點(diǎn)的 ClusterIP 服務(wù),可以讓客戶端端口(2181)和集群內(nèi)部端口(2888、3888)通過。完成之后,就可以通過這些服務(wù)主機(jī)名連接到 ZooKeeper 集群。Kubernetes ClusterIP 服務(wù)在這個(gè)時(shí)候很有用,因?yàn)樗鼈兲峁┝丝梢宰鳛楹蠖?Pod 負(fù)載均衡器的靜態(tài) IP 地址。我們用它們進(jìn)行從服務(wù)到 Pod 的一對一映射,相當(dāng)于為每個(gè) Pod 提供了一個(gè)靜態(tài)的 IP 地址。
圖 2:可以通過 ClusterIP 服務(wù)訪問我們的集群(ZooKeeper 仍然運(yùn)行在物理硬件上)
重新配置客戶端
在可以通過 Kubernetes ClusterIP 服務(wù)連接到 ZooKeeper 集群之后,接下來就可以重新配置客戶端了。如果你在 ZooKeeper 連接串中使用了 CNAME 記錄,那么請修改 DNS 記錄。如果客戶端在連接失敗時(shí)不會(huì)重新解析 DNS 條目,那么就重新啟動(dòng)客戶端。如果沒有使用 CNAME 記錄,那么就需要使用新的連接串,并重新啟動(dòng)客戶端。在這個(gè)時(shí)候,新舊連接串都可以使用。
圖 3:客戶端現(xiàn)在通過 ClusterIP 服務(wù)實(shí)例與 ZooKeeper 集群通信
重新配置 ZooKeeper 實(shí)例
接下來,我們將讓 ZooKeeper 服務(wù)器通過 ClusterIP 服務(wù)進(jìn)行點(diǎn)對點(diǎn)通信。為此,我們將結(jié)合 ClusterIP 服務(wù)的地址來修改配置文件。這里還需要配置 zk_quorum_listen_all_ips 標(biāo)志,如果沒有這個(gè),ZooKeeper 實(shí)例將無法成功綁定到主機(jī)接口上不存在的 IP 地址,因?yàn)樗且粋€(gè) Kube 服務(wù) IP。
- server.1=zk1-kube-svc-0:2888:3888
- server.2=zk2-kube-svc-1:2888:3888
- server.3=zk3-kube-svc-2:2888:3888
- zk_quorum_listen_all_ips: true
server.1=zk1-kube-svc-0:2888:3888server.2=zk2-kube-svc-1:2888:3888server.3=zk3-kube-svc-2:2888:3888zk_quorum_listen_all_ips: true
滾動(dòng)重新啟動(dòng)這些主機(jī),后面就可以開始準(zhǔn)備用 Pod 替換主機(jī)了。
圖 4:ZooKeeper 實(shí)例現(xiàn)在通過 ClusterIP 服務(wù)與其他實(shí)例通信
使用 Pod 替代 ZooKeeper 主機(jī)
我們將進(jìn)行以下這些步驟,每次操作一臺(tái)服務(wù)器:
- 選擇一臺(tái) ZooKeeper 服務(wù)器及其相應(yīng)的 ClusterIP 服務(wù);
- 關(guān)閉服務(wù)器上的 ZooKeeper 進(jìn)程;
- 使用與被關(guān)閉的 ZooKeeper 具有相同服務(wù)器列表配置和 myid 文件的 Pod;
- 等待,直到 Pod 中的 ZooKeeper 啟動(dòng),并與其他 ZooKeeper 節(jié)點(diǎn)的數(shù)據(jù)同步。
就這樣,ZooKeeper 集群現(xiàn)在運(yùn)行在 Kubernetes 中,并帶有之前所有的數(shù)據(jù)。
圖 5:經(jīng)過替換后的集群。ZK1 運(yùn)行在一個(gè) Pod 中,而 ZK2 不需要知道發(fā)生了什么
網(wǎng)絡(luò)先決條件
要順利完成這些步驟,需要確保一些網(wǎng)絡(luò)設(shè)置符合條件。你需要確保:
- 可以從所有需要連接到 ZooKeeper 的服務(wù)器重新路由 Kubernetes Pod 的 IP 地址;
- 所有連接到 ZooKeeper 的服務(wù)器必須能夠解析 Kubernetes 服務(wù)主機(jī)名;
- 所有需要連接到 ZooKeeper 的服務(wù)器必須運(yùn)行 kube-proxy,讓它們能夠訪問 ClusterIP 服務(wù)。
這些可以通過幾種方式來實(shí)現(xiàn)。我們使用了一個(gè)內(nèi)部網(wǎng)絡(luò)插件,類似于 Lyft 的插件:
https://github.com/aws/amazon-vpc-cni-k8s
或者 AWS 插件:
https://github.com/lyft/cni-ipvlan-vpc-k8s
可以直接將 AWS VPC IP 地址分配給 Pod,而不是使用虛擬疊加網(wǎng)絡(luò),所以可以從任意實(shí)例重新路由 Pod 的 IP。疊加網(wǎng)絡(luò)(如 flannel)也是可以的,只要所有的服務(wù)器都可以連接到疊加網(wǎng)絡(luò)。