歡迎光臨
每天分享高質量文章

荔枝運維平臺容器化實踐

荔枝微服務化行程較早,目前已有上千個服務模塊,先前的運維平臺漸漸無法滿足微服務架構下運維管理的需求,於是決定從2018年開始重構運維平臺,結合容器化技術,讓開發人員盡可能無感知遷移的同時享受容器化帶來的諸多好處。本次分享將主要為大家介紹我們專案發佈系統重構過程中,技術選型的考慮以及實踐過程中遇到的一些問題和解決方案。
背景

 

荔枝後端微服務化行程較早,目前已有上千個服務模塊,絕大多數是Java。得益於良好的規範,舊的運維平臺實現了一套簡單的自動化部署流程:
  1. 對接Jenkins進行編譯打包和版本標記

  2. 把指定版本的jar包,配置檔案,啟動腳本一起發佈到指定的機器上裸機運行

  3. 通過對Java行程的管理來完成重啟,關閉應用等運維操作

但是隨著開發人員,專案數量,請求量的增加,舊的運維平臺逐漸暴露出以下一些問題:
  1. Java實體部署所需資源沒有清晰的統計和系統層面的隔離,僅僅依賴於啟動腳本中的JVM引數來進行記憶體的約束,新增實體或新上專案時往往需要運維人員靠“感覺”指定部署的機器,沒有有效地分配機器資源,專案之間資源爭用會導致性能問題。

  2. 雖然大多數應用依賴的環境只有JDK 7和JDK 8,但一些JDK的小版本差異,以及一些自研發Java agent的使用,使得簡單地指定JAVA_HOME目錄的方式很難有效地管理運行環境。

  3. 開發人員的服務器權限需要回收。一個服務器上可能運行多個不同部門的專案,相關開發人員誤操作可能會導致其他專案被影響。

上述問題雖然也可以通過一些技術和規範約束來解決,但天生就是為瞭解決環境依賴和資源管理的容器技術是當下最合適的方案。

 

技術選型

 

核心組件方面,Docker和Kubernetes是當下最成熟的開源容器技術。我們對強隔離沒有太多的需求,所以沒有使用KVM等虛擬機方案,直接在裸機上部署Kubernetes。
分佈式儲存方面,容器化的專案大多是無狀態的雲原生應用,沒有分佈式儲存的需求。極少數專案需要分佈式儲存的場合,我們會把已有的MFS集群掛載到宿主機,由宿主機掛載到容器里提供簡單的分佈式儲存。
容器本地資料捲方面,使用Docker預設的OverlayFS 2。我們服務器操作系統主要是CentOS,DeviceMapper在生產環境必須使用direct-lvm樣式,該樣式需要獨立資料設備,對已有的SA自動化管理有一些影響。而OverlayFS 2在Linux內核4.17以上已經比較穩定,也不需要太多複雜的配置,開箱即用。
日誌收集方面,我們已有一套基於ELK的收集方案,對應用日誌也有一定約束(必須將日誌打印到指定目錄下)。傳統的基於控制台輸出的Docker日誌方案需要修改應用的日誌輸出配置,並且海量的控制台日誌輸出也會影響dockerd的性能,所以我們通過掛載日誌資料盤的方式即可解決問題。
監控方面,原有的監控設施是Zabbix,但在Kubernetes監控設施上Zabbix的方案顯然沒有親兒子Prometheus成熟和開箱即用。所以在Kubernetes的監控方面,我們以Prometheus+Granfana為核心,使用kube-state-metrics採集Kubernetes運行資料。相比於Heapster,kube-state-metrics是Kubernetes生態的一部分,從Kubernetes的資源角度去採集資料,維度更多,信息更全面。
最後是比較重要的Kubernetes網絡方面,我們使用了比較新的網絡方案kube-router。kube-router是基於三層Routing和BGP的路由方案,其優點如下:
  • 比Flannel等在資料包上再封裝一層通信協議(常見是VXLAN)的網絡實現性能上更優秀。

  • 比同樣是基於BGP和三層路由的Calico來說更輕量簡單,易於部署。

  • Macvlan技術會使宿主機網絡和Pod網絡隔離,不太符合我們的需求。

  • 在開啟Service Proxy樣式後可以取代預設組件kube-proxy,Service Proxy的實現是IPVS,在性能上和負載均衡策略上靈活度更高(在Kubernetes 1.8後kube-proxy也有IPVS的實現支持,但到現在還是實驗性質)。

當然kube-router也存在一些不足:
  • 專案比較新,現在最新的還是v0.2.5,使用過程=踩坑。

  • 節點間網絡必須二層可達,不像Calico提供了IPIP的解決方案。

  • 依賴於iptables,網絡要求高的場景Netfilter本身會成為瓶頸。

  • 對於Pod IP的分配,Pod之間網絡的ACL實現較為簡單,無法應付安全要求高的場景。

基於三層路由的CNI解決方案:

 

業務落地實踐

 

搭好Kubernetes只是一個開始,我們這次重構有個很重要的標的是盡可能讓業務開發方無感知無修改地把專案遷移到Kubernetes上,並且要保證實體部署和容器部署同時並行過度。
理想的專案應該有Dockerfile宣告自己的運行環境,有Jenkinsfile解決編譯打包,有對應的Deployment和Service來告訴Kubernetes如何部署,但現實很骨幹,我們有上千個專案,對應上千個Jenkins編譯打包專案,逐一地修改顯然不太現實。自動化運維的前提是標準化,好在專案規範比較嚴謹,符合了標準化這個充分條件。
重新設計後的部署流程如下圖所示:

構建方面,專案統一使用同一個Dockerfile模板,通過變更基礎鏡像來解決一些不同環境專案(比如需要使用JDK 7)的問題。基於Kubernetes Job和dind技術,我們開發了一個構建worker來實現從Jenkins拉取編譯後的應用包並打包成鏡像的流程,這樣Jenkins打出來的應用可以同時用在實體部署和容器部署上。在運維後臺上即可完成版本的構建:

部署方面,專案的部署配置分成兩方面。資源配置一般不經常修改,所以僅僅只是在運維平臺上修改記錄。經常變更的版本變更和實體數變更則與部署操作系結。將Kubernetes複雜的物件封裝成擴展成原有專案物件的資源配置引數,執行部署時,根據專案資源配置,版本和實體數生成對應的Deployment和Service,呼叫Kubernetes API部署到指定的Kubernetes集群上。如果專案有在運維平臺上使用靜態配置檔案,則使用ConfigMap儲存並掛載到應用Pod里。

在運維平臺上提供Pod串列展示,預發環境debug應用,灰度發佈,狀態監控和webshell,方便開發觀察應用運行情況,除錯和日誌查看,同時也避免開發SSH到生產環境服務器上,回收了服務器權限。

在應用從實體部署遷移到容器部署的過程中主要遇到以下幾個問題:
  • Kubernetes集群內的Pod和集群外業務的通信問題。為了風險可控,實體部署和容器部署之間將會存在很長一段時間的並行階段,應用方主要使用Dubbo做微服務治理,Kubernetes集群內的Pod和集群外業務的通信就成為問題了。kube-router是基於三層Routing實現,所以通過上層路由器指定Pod IP段的靜態路由,或對接BGP動態交換路由表來解決問題。

  • JVM堆記憶體配置問題導致OOMKill的問題。因為JVM的記憶體不止有Xmx配置的堆記憶體,還有Metaspace或PermSize,以及某些如Netty等框架還有堆外記憶體,把Xmx的配置等同於容器記憶體配置幾乎是一定會出現OOMKiil,所以必須放寬容器記憶體限制。以我們的經驗來說,容器記憶體比Xmx多20%左右一般可以解決問題,但也有部分例外,需要額外配置。

  • Pod啟動失敗難以排查的問題。有一些Pod一啟動就失敗,輸出的日誌難以分析問題,我們構建和部署的描述檔案都是運維平臺動態生成的,很難使用傳統docker run標的鏡像的方式進行除錯,所以我們在運維平臺上提供了debug容器的功能,新建一個和原有deployment一樣的debug部署,僅去掉健康檢查相關的引數和修改command引數使pod運行起來,業務開發方即可通過webshell控制台進入Pod除錯應用。

 

未來

 

  1. 開發經常需要使用的一些除錯工具比如Vim,Arthas之類的,現在我們是打包到基礎鏡像中提供,但這樣不僅增加了鏡像的體積,而且需要重新打包新的鏡像。目前看到比較好的解決方案是起一個除錯用的容器並加到指定Pod的namespace中,但還需二次開發集成到webshell中。

  2. 跨機房Kubernetes集群調度。當現有資源無法滿足峰值需求時,借助公有雲來擴展系統是比較好的選擇,我們希望借助Kubernetes多集群調度功能做到快速擴容到公有雲上。

  3. 峰值流量的自動擴容和縮容,Kubernetes提供的HPA策略較為簡單,我們希望能從更多的維度來計算擴容和縮容的數量,做到精準的控制。

Q&A;

 

Q:容器的Pod網絡和外部網絡全部打通嗎,如何實現的?
A:因為kube-router是基於三層路由,所以只要在頂層交換上指定Pod IP的靜態路由即可,比如宿主機是192.168.0.1,該宿主機上的pod ip range是10.0.0.1/24,那隻要在交換機或需要訪問Pod的外部主機上添加路由10.0.0.1/24 via 192.168.0.1 …即可。
Q:你們如何去保證io的隔離?
A:目前網絡和硬碟的io沒有做隔離,暫時還沒有這方面的剛需。kube-router對網絡IO這方面控制比較弱。硬碟IO方面Docker支持IOPS控制,但Kubernetes我還不太清楚是否支持。
Q:Job和dind如何配合去實現打包鏡像的呢?
A:首先是dind技術,通過掛載宿主機的docker client和docker sock,可以實現在容器內呼叫宿主機的Docker來做一些事情,這裡我們主要就用於build。Kubernetes的Job則是用於執行這個構建worker的方式,利用Kubernetes的Job來調度構建任務,充分利用測試集群的空閑資源。
Q:你們Kubernetes裡面 統一配置是用的ConfigMap還是集成了第三方工具,例如Disconf。你們在Kubernetes中,APM用的是什麼呢?Pinpoint還是Sky還是Jeager?還是其他?
A:過去的專案配置檔案是放運維平臺上的,所以只要ConfigMap掛進去就可以了。後來新的專案開始採用攜程的Apollo,Kubernetes上就只要通過ENV把Apollo的一些相關敏感信息傳給Pod即可。APM方面因為我們是Java棧所以使用的skywalking,也是開篇提到的Java agent技術。
Q:Macvlan和IPvlan性能非常好,幾乎沒有損耗,但預設都是容器和宿主機網絡隔離的,但是也有解決方案,你們這邊是沒有考慮還是使用了一些解決方案發現有問題又放棄的?如果是後者,有什麼問題讓你們選擇放棄?
A:Macvlan之類的方式需要交換機層面上做一些配置打通VLAN,並且性能上並不會比基於三層的解決方案要高非常多,權衡之下我們還是選擇比較易用的基於三層的方案,甚至為了易用而選擇了更為激進的kube-router。
Q:容器的多行日誌收集如何解決?或者是,很多業務日誌需要背景關係關係,但是ELK只能查詢到單條,這種情況怎麼處理呢?
A:容器多行日誌的問題只存在於標準輸出里,我們應用的日誌是輸出到指定目錄下的,Filebeat有一些通用的多行日誌解決方案。因為日誌是存放在ES里的,所以可以通過調ES接口拿到某個Pod一個時間段里的日誌,在UI上把它展示出來即可。
Q:請問用的儲存是什麼?如何集成的?儲存架構是怎樣的?有考慮過Ceph嗎?
A:只有極少部分專案需要接分佈式儲存,並且對儲存的管理,IOPS限制等沒有硬性要求,所以我們把已有的MFS集群掛載到宿主機,再掛到容器里來實現。
Q:Jenkins的Slave是用Pod Template創建的嗎?Slave是Job共享還是需要時自動創建?
A:Jenkins還是傳統的master-slave單機部署樣式,因為版本比較舊連Kubernetes Slave都不支持,所以我們只是呼叫了Jenkins的API來完成這個部署的過程。
Q:請問鏡像大小是否有做優化?生產中有用alpine之類的base鏡像嗎?
A:暫時沒有,我們鏡像的大小大約在100-300M之間。而且比起鏡像大小的優化,運行環境的穩定和除錯的便利更為重要。鏡像有分層的策略,即使是很大的鏡像,只要每次版本部署時更新的是最底層的鏡像就不會導致每次都要拉取完整鏡像。

已同步到看一看
赞(0)

分享創造快樂