擁抱云原生,如何將開源項(xiàng)目用k8s部署?
k8s以及云原生相關(guān)概念近年來一直比較火熱,阿丸最近搞了個(gè)相關(guān)項(xiàng)目,小結(jié)一下。

本文將重點(diǎn)分享阿里開源項(xiàng)目otter適配k8s部署的改造過程,其中的改造過程和技巧應(yīng)該適用于將大多數(shù)開源項(xiàng)目改造到k8s進(jìn)行部署。
1.背景
otter是阿里開源的分布式數(shù)據(jù)庫(kù)同步系統(tǒng),基于數(shù)據(jù)庫(kù)增量日志解析,并準(zhǔn)實(shí)時(shí)同步到本機(jī)房或異地機(jī)房的mysql/oracle數(shù)據(jù)庫(kù)(相關(guān)內(nèi)容可以參考https://github.com/alibaba/otter,本文不做過多贅述)。
為了充分利用物理資源、快速擴(kuò)容同步節(jié)點(diǎn)、擁抱云原生,決定使用k8s部署otter。
otter的項(xiàng)目整體上自成一體,出于改造成本考慮,盡量在項(xiàng)目已有基礎(chǔ)上,做一些適配,不改動(dòng)源代碼。
本文將重點(diǎn)分享對(duì)于otter適配k8s部署的改造過程,有不當(dāng)之處,還請(qǐng)多多指教。
涉及到幾個(gè)核心內(nèi)容:
- otter基本架構(gòu)
- dockerfile編寫
- deployment編寫
- 啟動(dòng)腳本改造
- K8s中固定ip/port訪問
2.otter的基本架構(gòu)

典型管理系統(tǒng)架構(gòu),manager(web管理)+node(工作節(jié)點(diǎn))
- manager運(yùn)行時(shí)推送同步配置到node節(jié)點(diǎn)
- node節(jié)點(diǎn)將同步狀態(tài)反饋到manager上
- 基于zookeeper,解決分布式狀態(tài)調(diào)度的,允許多node節(jié)點(diǎn)之間協(xié)同工作
- 基于Canal開源產(chǎn)品,獲取數(shù)據(jù)庫(kù)增量日志數(shù)據(jù)。當(dāng)然,otter采用了canal的嵌入式模式,不是獨(dú)立的canal節(jié)點(diǎn)。
基于以上部署架構(gòu),我們只需要將otter-manager和otter-node部署到k8s上。
尤其是otter-node,需要利用k8s實(shí)現(xiàn)節(jié)點(diǎn)快速水平擴(kuò)展、計(jì)算性能彈性擴(kuò)縮容。
2.Dockerfile編寫
2.1 otter-manager的Dockerfile
otter-manager比較簡(jiǎn)單,包括幾個(gè)步驟:
- 使用一個(gè)centos的基礎(chǔ)鏡像
- 設(shè)置時(shí)區(qū)
- 創(chuàng)建目錄
- 拷貝下載好的manager.deployer-4.2.18到指定目錄
- 使用啟動(dòng)startup-new.sh腳本啟動(dòng)(這里對(duì)原本的啟動(dòng)腳本做了一些改造,后文進(jìn)行詳述)
具體如下所示:

2.2 otter-node的Dockerfile
otter-node稍微有所不同,根據(jù)官方文檔說明,需要安裝aria2來做文件傳輸。
注意注意,由于aria2安裝非常慢,因此,我們需要先安裝aria2作為一個(gè)新的基礎(chǔ)鏡像,然后在新的基礎(chǔ)鏡像上構(gòu)建otter-node鏡像,能大大提高后續(xù)鏡像構(gòu)建速度。
新的基礎(chǔ)鏡像如下,命名為 registry.xxx.com/xxx/otter-node-base:1.0。

然后在此基礎(chǔ)上構(gòu)建新的otter-node鏡像。

注意,otter-node的配置方式比較特殊,需要先在otter-admin上獲取一個(gè)nid,然后才能運(yùn)行一個(gè)otter-node。
所以,我們?cè)赿ockerfile中以ARG 聲明一個(gè)nid,然后在后續(xù)構(gòu)建鏡像的時(shí)候,通過 --docker-arg 傳入nid具體的值。
當(dāng)然,如果把nid看作一個(gè)配置文件的話,也可以用下文提到的ConfigMap的形式在Deployment中掛載進(jìn)去
3.Deployment編寫
什么是Deployment?
- k8s的一個(gè)Deployment控制器為 Pods 和 ReplicaSets 提供聲明式的更新能力。我們通過編寫Deployment描述期望的目標(biāo)狀態(tài),然后 Deployment 控制器更改Pods或者ReplicaSets的實(shí)際狀態(tài), 使其變?yōu)槠谕麪顟B(tài)。
具體關(guān)于Deployment的知識(shí)不展開說明,可以參考k8s官方文檔。
我們需要部署測(cè)試環(huán)境與生產(chǎn)環(huán)境兩套集群,而無論是otter-manager還是otter-node,都依賴于讀取 conf/otter.properties 作為配置。
因此,我們需要根據(jù)環(huán)境,修改不同的otter.properties。
那么,對(duì)于k8s部署來說,可以采用同一份鏡像,然后在不同環(huán)境(k8s的不同namespace)中將otter.properties作為ConfigMap寫入,最后通過volume的形式掛載到pod的指定路徑上。
這里對(duì)幾個(gè)名詞做簡(jiǎn)單介紹,詳細(xì)內(nèi)容可以參考k8s官方文檔。
- ConfigMap:一種 API 對(duì)象,用來將非機(jī)密性的數(shù)據(jù)保存到鍵值對(duì)中。使用時(shí),Pods可以將其用作環(huán)境變量、命令行參數(shù)或者存儲(chǔ)卷中的配置文件。(如果想存儲(chǔ)的數(shù)據(jù)是機(jī)密的,可以使用 Secret,而不是ConfigMap)
- Volume:卷的核心是包含一些數(shù)據(jù)的一個(gè)目錄,Pod 中的容器可以訪問該目錄。所采用的特定的卷類型將決定該目錄如何形成的、使用何種介質(zhì)保存數(shù)據(jù)以及目錄中存放 的內(nèi)容。使用卷時(shí), 在 .spec.volumes 字段中設(shè)置為 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中聲明卷在容器中的掛載位置。
所以,首先在指定環(huán)境(namespace中)創(chuàng)建configmap,以otter.properties作為key,以文件內(nèi)容作為value。
具體命令如下
kubectl create configmap otter-manager-dev-config --from-file=otter.properties=conf/otter-dev.properties -n otter-system
- 在namespace為otter-system中創(chuàng)建一個(gè)名字為otter-manager-dev-config的ConfigMap,其中,以otter.properties作為key,以文件內(nèi)容作為value。
產(chǎn)生的ConfigMap如下圖所示

最后,將這個(gè)ConfigMap在Deployment中用volume進(jìn)行引用,然后通過volumeMounts掛載到指定目錄,Deployment具體如下所示。

這里需要特別注意volumeMounts的路徑覆蓋問題,需要在volumeMounts中配置subPath為具體文件名。
4.啟動(dòng)腳本改造
Otter包括兩個(gè)部分,管理控制臺(tái)manager和工作運(yùn)行節(jié)點(diǎn)node,正常情況下都是用各自的啟動(dòng)腳本startup.sh啟動(dòng)的。
為了適配k8s,我們需要對(duì)啟動(dòng)腳本做改造,本文以otter-manager的啟動(dòng)腳本為例,otter-node也是類似。
將啟動(dòng)腳本startup.sh改造為 startup-moon.sh,重點(diǎn)解決兩個(gè)問題
- 前臺(tái)進(jìn)程保持運(yùn)行
- jvm參數(shù)自定義改造
4.1 前臺(tái)進(jìn)程保持運(yùn)行
由于容器中用entrypoint啟動(dòng)的進(jìn)程為1號(hào)進(jìn)程,一旦1號(hào)進(jìn)程執(zhí)行結(jié)束,容器就會(huì)退出了。
而原本的startup.sh中,用java啟動(dòng)后,使用 “&” 將java進(jìn)程轉(zhuǎn)換為后臺(tái)進(jìn)程,所以startup.sh作為1號(hào)進(jìn)程會(huì)很快執(zhí)行結(jié)束,容器就會(huì)自動(dòng)退出了。
所以我們需要將1號(hào)進(jìn)程保持住,不要退出。
這里考慮了兩個(gè)方案:
- Startup.sh腳本中增加一個(gè)前臺(tái)進(jìn)程進(jìn)行保持,比如 tail -f /dev/null 命令
- 將“&”去掉,讓java啟動(dòng)后就作為前臺(tái)進(jìn)程一直保持
后來考慮了一下,還是選擇了方案二。主要原因是為了利用pod自動(dòng)重啟的特性。
如果Java進(jìn)程意外退出了,那么方案二就能使得1號(hào)進(jìn)程也結(jié)束,然后pod就能自動(dòng)重啟了。而方案一的話,由于startup.sh腳本仍然在執(zhí)行tail,所以即使java進(jìn)程退出,1號(hào)進(jìn)程也不會(huì)結(jié)束。
具體修改如下:

最終pod中的進(jìn)程如圖所示

- 1號(hào)進(jìn)程是啟動(dòng)腳本
- 1號(hào)進(jìn)程的子進(jìn)程是otter的java應(yīng)用進(jìn)程(前臺(tái)進(jìn)程)
4.2 虛擬機(jī)大小自定義配置
由于otter項(xiàng)目中,將jvm的啟動(dòng)參數(shù)配置在了start.sh中,不方便進(jìn)行手動(dòng)配置。
因此,將start.sh的配置jvm參數(shù)的邏輯注釋掉,采用自己配置的環(huán)境變量JAVA_OPTIONS進(jìn)行注入。

這個(gè)環(huán)境變量的注入方式也比較簡(jiǎn)單,就是在Deployment中的env配置的(藍(lán)色框部分),方便以后手動(dòng)修改jvm參數(shù)大小而不用修改鏡像。

5.k8s上固定IP/Port訪問
otter-node的部署中,有個(gè)比較特殊的地方。
不同于普通的微服務(wù)的無狀態(tài)擴(kuò)展,otter-node的部署必須指定nid、ip、port,這種設(shè)計(jì)據(jù)說是為解決單機(jī)部署多實(shí)例而設(shè)計(jì)的,允許單機(jī)多node指定不同的端口(具體可以參考官方wiki,https://github.com/alibaba/otter/wiki/Node_Quickstart,這里不展開說明)。
還是直接看看如何在k8s上進(jìn)行適配吧。
這里采用了k8s的NodePort進(jìn)行處理。
NodePort 服務(wù)是引導(dǎo)外部流量到你的服務(wù)的最原始方式。NodePort,正如這個(gè)名字所示,在所有節(jié)點(diǎn)(虛擬機(jī))上開放一個(gè)特定端口,任何發(fā)送到該端口的流量都被轉(zhuǎn)發(fā)到對(duì)應(yīng)服務(wù)。如下圖所示。

在上面的配置中,可以使用IP1:3000 或者 IP2:3000 或者 IP3:3000 訪問service。
當(dāng)然,為了保證不綁定特定KVM的IP,我們?cè)谇懊鎾煲粋€(gè)SLB服務(wù),通過訪問SLB的 虛擬IP:PORT 的形式訪問。
對(duì)于otter部署來說,otter-manager需要兩組 IP:PORT、每個(gè)node需要三組 IP:PORT。
注意,由于otter部署中,每個(gè)node需要暴露的port都是不同的,所以每次新增一個(gè)otter-node,都需要新增三組 IP:PORT。
我們以otter-node為例,來看下NodePort類型的Service的yml文件吧。

- kind為service
- type為NodePort
- 配置了三組端口。port/targetport都是應(yīng)用暴露端口,而nodePort是對(duì)外訪問端口。
6.總結(jié)
經(jīng)過這樣的改造,我們就能用k8s的部署otter-manager和otter-node了,并且能夠快速擴(kuò)容節(jié)點(diǎn)、彈性使用機(jī)器資源。
我們回顧一下其中的關(guān)鍵問題和技巧:
- Dockerfile編寫中,可以把環(huán)境相關(guān)依賴打成一個(gè)新的基礎(chǔ)鏡像,提高后續(xù)應(yīng)用鏡像的構(gòu)建速度。
- Dockerfile中,可以通過ARG定義一些構(gòu)建過程中的變量,進(jìn)行替換。
- 對(duì)于不同環(huán)境的配置文件,可以在不同環(huán)境(k8s的namespace)下配置不同的ConfigMap,然后在Deployment文件中通過volumeMounts的方式掛載進(jìn)去。
- 對(duì)于后臺(tái)進(jìn)程,需要改造為前臺(tái)進(jìn)程使得pod能夠保持
- 對(duì)于一些特定的環(huán)境變量,可以在Deployment中通過env進(jìn)行傳入。
其他開源項(xiàng)目如果有需要上k8s的,這些技巧應(yīng)該都能用上。