HPE CMS團隊推進NFV容器化的探索之路之Clearwater On Kuberntes_Kubernetes中文社群
這一節吳治輝老師
繼續和我們分享
HPE CMS團隊
在Clearwater On Kuberntes
一波三折的改造探索之旅
Clearwater On Kuberntes改造の緣起
首先,我們來看看為什麼Clearwater on Kubernetes的方案更為重要,這是因為Kubernetes作為當前最有影響力的容器化微服務架構平臺,很多公司已經用它來實現自己的彈性PAAS平臺,所以相對於Clearwater on Docker來說,Clearwater On Kubernetes顯得更為重要;此外,Clearwater on Docker的部署方案,目前不好解決彈性擴容問題,因為
改造從哪裡開始
由於官方沒有給出Clearwater On Kuberntes的資料,所以我們只能從官方給出的Clearwater On Docker的資料去研究,一路上遇到不少“坑”,這個過程中遇到的一些疑問也得到了Clearwater專案志願者們的解答,最終得以順利實現Clearwater在Kuberntes叢集上的部署,在此感謝他們。我們首先仔細研究了IMS通訊的流程以及Clearwater的架構圖,初步瞭解了Clearwater各個元件的作用,元件之間大致的通訊關係等基礎問題,然後按照Clearwater的容器化指南,開始了我們的Clearwater On Kuberntes的改造探索之旅。
首先,我們針對Clearwater元件分別定義了10個Service,如下表所示:
上述Kubernetes Service與Pod定義並不難,根據官方給出的Clearwater On Docker資料即可完成,下面我們以最複雜的Bono服務為例,給出它的Pod與Service定義檔案以及重要引數的介紹。首先是Pod定義:
apiVersion: v1
kind: Pod
metadata:
name: clearwater-bono
labels:
app: clearwater-bono
spec:
containers:
– name: clearwater-bono
image: 10.34.40.11:1179/clearwater-bono
env:
– name: PUBLIC_IP
value: 15.116.146.11
imagePullPolicy: IfNotPresent
ports:
– containerPort: 22
– containerPort: 3478
– containerPort: 5060
– containerPort: 5062
– containerPort: 5060
protocol: UDP
– containerPort: 5062
protocol: UDP
restartPolicy: Always
Bono容器程序需要一個PUBLIC_IP的環境變數,可以理解為叢集的“公網IP”,在沒有外部負載均衡器的情況下,這裡可以填寫Kubernetes叢集中任意Node節點的IP地址。
接下來是是Bono對應的Service的定義檔案:
apiVersion: v1
kind: Service
metadata:
name: bono
spec:
type: NodePort
ports:
– name: 5060t
port: 5060
protocol: TCP
nodePort: 5060
– name: 5060u
port: 5060
protocol: UDP
nodePort: 5060
– name: 5062t
port: 5062
protocol: TCP
nodePort: 30002
– name: 5062u
port: 5062
protocol: UDP
nodePort: 5062
– name: 3478t
port: 3478
protocol: TCP
nodePort: 3478
– name: 3478u
port: 3478
protocol: UDP
nodePort: 3478
selector:
app: clearwater-bono
由於Bono需要暴露SIP接入與STUN服務的的埠,供客戶端連線,所以Bono的5060,5062以及3478等埠都採用NodePort方式繫結到Node上,從而SIP客戶端可以與叢集中的任意Node建立通訊連線。更好的方式是外部有負載均衡器,BONO服務的埠對映到公網上,供外部使用者使用,下面是建議的部署示意圖:
發現Clearwater的一個深坑
隨後,我們用kubectrl命令將定義好的YAML檔案部署到Kubernetes叢集上。注意,必須是或更高的版本,並且Docker的映象儲存不能採用OverlayFS,否則由於OverlayFS的一個Socket相關的Bug,supervisord無法正常執行,導致Clearwater的一些元件啟動失敗。釋出到Kubernetes叢集上以後,觀察到Pod的狀態都正常,我們成功登入到Ellis的網頁(8080埠),但很快我們發現,無法建立SIP賬號,瀏覽器頁面報錯,隨後我們進入到Ellis的Pod容器中進行錯誤排查,發現了問題所在:
[email protected]:/#more/var/log/ellis/ellis_20161017T170000Z.txt
17-10-2016 17:35:11.121 UTC INFO main.py:113: Ellis process starting up
17-10-2016 17:35:11.195 UTC ERROR homestead.py:68: Failed to ping Homestead at http://homestead:8889/ping. Have you configured your HOMESTEAD_URL?
Ellis元件無法Ping通homestead這個主機名(Kubernetes Service名稱), 而我們在Pod容易裡執行下面的命令,卻能返回結果:
curl http://homestead:8889/ping
後來我們排查才發現,這是Clearwater的一個深坑。
Clearwater的設計中大量採用了DNS來實現負載均衡機制,與常規做法不同,Clearwater內部自己向DNS Name Server發起DNS查詢指令以達到精確控制的負載均衡效果,下面是Bono元件查詢Sprout服務的DNS記錄失敗時的日誌截圖:
Bug是什麼
為什麼在Pod容器內可以解析(Ping)的Kubernetes域名卻無法被Clearwater解析?這背後隱藏著Clearwater的一個Bug。我們知道,Linux的DNS查詢會用到/etc/resolv.conf檔案,該檔案是DNS域名解析的配置檔案,它的格式很簡單,每行以一個關鍵字開頭,後接配置引數,resolv.conf的關鍵字主要有四個,分別是:
nameserver #定義DNS伺服器的IP地址
domain #定義本地域名
search #定義域名的搜尋列表
sortlist #對返回的域名進行排序
其中,domain和search作用相同,不能共存,當我們查詢一個不帶域名的主機名時,如果resolv.conf中配置了domain(或search),則DNS查詢的時候,需要帶上domain(或search)中定義的域名,以Kuberntes Pod中的容器為例,resolv.conf檔案內容如下:
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 169.169.0.100
options ndots:5
因此,當DNS客戶端要查詢sprout的DNS記錄時,其實是向SkyDNS Server發出如下的完整查詢指令:nslookup sprout.default.svc.cluster.local ,但是,Clearwater的DNS查詢沒有遵循上面的規則,會忽略主機的域名,直接向SkyDNS發出的指令是nslookup sprout,由於SkyDNS上是沒有這個主機記錄的,所以自然就查詢不到了,找到問題原因以後就好辦了,我們找到Clearwater配置檔案/etc/clearwater/shared_config的控制指令碼/etc/init.d/clearwater-auto-config-docker,修改相關的元件名,補充了完整的域名記錄,最後生成的/etc/clearwater/shared_config內容如下:
# Deployment definitions
home_domain=example.com
sprout_hostname=sprout.default.svc.cluster.local
hs_hostname=homestead.default.svc.cluster.local:8888
hs_provisioning_hostname=homestead.default.svc.cluster.local:8889
xdms_hostname=homer.default.svc.cluster.local:7888
ralf_hostname=ralf.default.svc.cluster.local:10888
chronos_hostname=chronos.default.svc.cluster.local
cassandra_hostname=cassandra.default.svc.cluster.local
# Email server configuration
smtp_smarthost=127.0.0.1
smtp_username=username
smtp_password=password
[email protected]
# I-CSCF/S-CSCF configuration
upstream_hostname=scscf.sprout.default.svc.cluster.local
一次又一次華麗麗的失敗
重新打包映象併發布以後,終於可以在Ellis介面上生成SIP使用者賬號了,我們下載X-Lite客戶端並配置好SIP賬號後,卻發現無法登陸成功,報錯SIP路由找不到。而Bono與Sprout元件的日誌都正常,後來我們研究Bono的啟動指令碼,修改日誌輸出級別為DEBUG,此時可以看到完整的SIP報文資訊,從這些資訊中我們得知原因,是因為SIP路由過程中無法連線
scscf.sprout.default.svc.cluster.local,後來我們再去看Docker方式部署Clearwater的命令,發現sprout元件用Docker network-alias的方式注入了3個DNS名稱:
sudo docker run -d –net=clearwater_nw –network-alias=icscf.sprout –network-alias=scscf.sprout –name sprout -p 22 clearwater/sprout
這三個DNS名稱分別是sprout、icscf.sprout以及 scscf.sprout,這與Clearwater的設計有關,因為sprout元件用子域名icscf.sprout承載ICSCF,用子域名scscf.sprout 承載SCSCF服務。如果上述任何一個DNS名字不存在或者設定不對(如設定為一樣的DNS名稱),則SIP客戶端登入Clearwater的時候報錯,常見的兩個錯誤是“路由不存在”以及“Too many hops error”,下面是Clearwater的問題郵件列表,可以看出很多嘗試在Kubernetes上部署Clearwater的人都遇到這個問題:
我們的最初想法是為sprout這個元件定義三個Kubernetes Service,名字分別是:sprout、icscf.sprout以及 scscf.sprout,並且都指向同一個Pod,但很快表明這個思路行不通,因為在Kubernetes中,Service的名字是不能帶“.”的,其中一個原因是Kubernetes Service的名稱是SkyDNS的一部分,而“.”恰好是子域名的分割符號。解決這個問題有兩個辦法,第一個辦法,第一個辦法是修改Clearwater裡的相關配置引數,採用類似 icscf_sprout這樣的DNS名稱而不是子域名來提供I_CSCF服務的定址;第二個辦法是修改Kubernetes的原始碼,使得Service名稱支援“.”。後一個做法很快被否決了,因為Kubernetes發展太快,非官方的原始碼修改並不靠譜。所以我們採用了第一種做法,首先建立名字為scscf_sprout與icscf_sprout的兩個Kubernetes Service,他們與sprout服務共享同一個後端Pod例項,然後再去修改/etc/init.d/clearwater-auto-config-docker指令碼中的upstream_hostname引數upstream_hostname,最後重新打包映象並測試,發現還是通不過。後來我們研究發現,在Clearwater其他元件的啟動指令碼程式中也有一些固定寫死的引數,前後花費了近一週的時間,一一修改查詢和修改這些引數後重新打包映象並進行測試,但最終還是華麗麗的失敗了!後來我們查看了Bono與Srpout的原始碼,才發現原始碼中有一些icscf.sprout(scscf.sprout)相關的硬編碼,意味著僅僅修改配置檔案的方式是行不通的!無奈之下我們在Clearwater的github上發了一個Issue,他們也承認Clearwater目前的確有些設計並不能很好的相容Kubernetes,也隨即發起了一個新的分支,嘗試解決Clearwater在Kubernetes上的執行問題,但截至當前,這個新的分支除了給出了幾個Kuberntes YAML檔案,還沒有其他動靜。
終於峰迴路轉
我們繼續研究可能的解決方案,後來我們設想了一個新方案:繞過Kuberntes,用手工方式在Kubernetes的SkyDNS裡插入icscf.sprout與scscf.sprout這兩條DNS記錄,他們都指向sprout服務的Cluster IP,這樣一來這個棘手的問題可能就得以解決。
於是我們開始研究SkyDNS的機制,模仿Kubernetes Service產生的DNS記錄,用命令列在Etcd裡執行手工插入Key/Value鍵值對。為了防止手工插入的DNS記錄在SkyDNS服務重啟後消失,我們把SkyDNS裡面用的Etcd地址改為Kubernetes叢集所用的獨立的Etcd地址。一開始我們插入的DNS記錄完全模仿Sprout Service的DNS記錄,假如Sprout的ClusterIP是30.0.28.133,則插入的icscf.sprout的DNS記錄如下所示:
Key:/skydns/local/cluster/svc/default/sprout/icscf/29512e34
Value: {“host”:”30.0.28.133″,”priority”:10,”weight”:10,”ttl”:30,”targetstrip”:0}
接下來的打包測試結果表明這種方式是可行的,我們的SIP客戶端終於連線到了Clearwater上。但觀察以後,我們發現一個奇怪的問題:過一段時間後,就登陸失敗了。後來排查原因,才發現SkyDNS會週期性同步Kubernetes Service的資訊,對於不是Kubernetes Service的DNS記錄,他會在同步的過程中刪除掉,同步過程影響到/skydns/local/cluster/svc/目錄下的所有路徑,因此我們後來用了/skydns/local/cluster的路徑來定義sprout的子域名,從而繞過了這個問題。
經歷了超過3周的反覆摸索過程,最終我們打包的Clearwater映象終於成功釋出在Kubernetes平臺上,併成功實現了即時通訊、VIOP以及視訊通話的演示,以下2張圖片給出了Clearwater On Kubernetes的相關細節資訊:
下圖是用X-Lite客戶端成功進行視訊電話測試的截圖:
我們成功了
為了讓Clearwater叢集能正常執行,我們需要注意相關元件的啟動順序,要先啟動Etcd、然後memcached、隨後Cassandra等,如果順序不對,也導致Clearwater不能正確執行。此外,需要注意的另外一點是Clearwater的容器採用了supervisord的方式來管理多個程序服務,因此可能出現的問題是容器看起來是正常的,但Clearwater的程序卻啟動失敗的問題。因此我們需要登入到容器內部,檢視supervisord控制的各個Clearwater程序的當前執行狀態來判斷系統是否正常,如下所示:
supervisorctl status
clearwater-group:homestead BACKOFF Exited too quickly (process log may have details)
如果發現系統不能正常工作,我們還需要深入分析Clearwater各個元件的日誌來排查錯誤。
接下來,我們來看看Clearwater on Kubernetes的彈性伸縮問題,由於Kubernetes的微服務架構模型,所以我們只要將Clearwater的相關Service的Pod副本數增加,即可實現線上動態擴容,如下圖所示,我們用Kubernetes命令列工具將SIP互動中壓力比較大的Bono節點的Pod副本從1個擴充套件為2個:
下圖是我們建議的Clearwater叢集中各個元件對應的Pod副本數:
最後,我們也成功在HPE NFV-Director上實現了Clearwater On Kubernetes的新案例,下面是HPE NFV-Director的整體架構圖以及Clearwater應用的編排介面(部分):
結語
Clearwater這種複雜的、重量級的IMS平臺成功遷移到Kubernetes平臺上,並且能正常穩定執行,這個事實充分說明Kubernetes這種先進的基於容器的微服務架構基礎平臺不僅網際網路應用,也適合傳統的、密切依賴網路的電信系統的改造升級。一旦舊系統成功改造遷移到Kubernetes平臺上, Kubernetes平臺強大的彈性伸縮能力和運維高度自動化的優點將會帶來可觀的收益,包括提高系統的吞吐量和高峰時期業務的承載能力、縮短新業務的上述市場時間、並在很大程度上降低電信運營商的綜合運營成本。