陳 博,周亦敏
(上海理工大學 光電信息與計算機工程學院,上海 200093)
隨著互聯網規模的不斷擴大,越來越多的公司逐漸將業務的重點放在了互聯網業務上,如何合理的開發和管理海量的互聯網業務成了當前的熱點.云計算的概念是2006年8月份由Google 的CEO 在搜索引擎大會上提出,旨在為個人或組織提供虛擬化計算資源,使得公司可以將自己的互聯網業務托管于云服務商,而不用構建自己的基礎設施[1].
因為云的便利和業務量的上升,越來越多的公司開始采用微服務的架構,如B 站推出了自己的微服務框架Kratos,阿里巴巴的Spring Cloud Alibaba 等.因為在早期傳統的單體架構應用中,往往各個應用之間具有高耦合度、擴展能力弱.微服務的提出很好地解決了這一痛點[2].相比較傳統的架構,微服務架構能夠更好地幫助企業將新的功能點快速的迭代插入到現有的生產環境中去,它減少了開發的復雜性以及部署的復雜性.同時,架構本身也降低了資源的消耗[3].表1是傳統單體架構與微服務架構的對比.微服務因為其拆分的原則,往往一個業務會被拆分成多個微服務,無論是開發或是部署都是非常的繁瑣.Kubernetes 是一款分布式容器編排引擎[4],它能夠很好的管理各個服務,可以自動實現容器伸縮,方便運維人員對容器的管理,是一個得到生產實踐證明的容器編排管理系統.本文就將基于Kubernetes,研究在其上構建持續集成持續部署的自動化流水線平臺的最佳實踐.

表1 單體架構與微服務架構的對比
在研究基于Kubernetes 的CI/CD 平臺之前,我們有必要先了解下它的整體架構及運作方式,以便于我們更好的針對其架構特點設計出更符合其特性的CI/CD流水線.Kubernetes(簡稱k8s)是Google 使用go 語言開發的一個自動化容器操作的開源平臺.使用Kubernetes可以:
(1)自動化彈性構建容器;
(2)自動管理容器;
(3)提供容器之間的負載均衡;
(4)方便對容器版本回滾更新;
(5)易于擴容.
Kubernetes 的集群主要由若干個Master 節點和Node 節點構成.Master 節點在其上運行相應的Master組件和Node 組件,Node 節點運行Node 組件,圖1是Kubernetes 集群的架構圖.
Master 組件是集群的管理控制中心[4],如下是Master組件:
(1)Kube-Apiserver[5]:提供Restful 風格的API 接口,通過它我們可以對k8s 的資源對象進行增刪改查,同樣apiserver 也是k8s 集群的數據總線和數據中心.
(2)Kube-Scheduler:負責分配調度Pod 到集群內的節點上,它監聽Kube-Apiserver,查詢還未分配Node的Pod,然后根據調度策略為這些Pod 分配節點.
(3)ETCD:是一個高可用的分布式鍵值數據庫,Kubernetes 集群使用其作為它的數據后端.
(4)Kube-Controller-Manager:集群內部的管理控制中心,它會及時發現并執行自動化修復流程,確保集群始終處于預期的工作狀態.

圖1 Kubernetes 集群的架構圖
Node 組件在每個節點上運行,維護運行的Pod 并提供Kubernetes 運行時環境.
(1)Kubelet:是主要的節點代理,它監測已分配給其節點的Pod.
(2)Kube-Proxy:在每個節點上運行網絡代理,并反映每個節點上Kubernetes API 中定義的服務.
在該分布式系統中,各個服務運行在node 節點上,由master 節點自動管理.所以考量將CI/CD 服務放置集群中運行,方便自動化運維,鏡像倉庫因需要頻繁讀寫操作,可放置于集群外進行管理,減少對Kubernetes集群的壓力.Kubernetes 使用docker 來進行容器的管理和云上的自動運維,減少了相應的成本,也不會再生產相應的環境沖突了,總而言之是一種非常便利的工具[6].
CI 即持續集成是指開發將代碼提交到GIT 服務器上,會觸發一次集成服務器的相關功能,比如編譯、測試、輸出結果等,往往一天內會有多次的集成,確保新增代碼能與原先代碼正確集成,這樣有利于及時檢查代碼的缺陷.CD 即持續部署是指通過自動化部署的手段將軟件功能頻繁的進行交付,加快了代碼的上線速度.
往往持續集成持續部署是相繼進行的CI 的常用工具有Jenkins、Circle CI、Codeship 等,Jenkins 因其開源和完善的社區豐富的插件被廣大公司所采用,因此本文選型CI 工具為Jenkins.
在持續集成中,版本控制是不可或缺的一部分.若部署時,代碼發生災難性缺陷,通過使用版本控制可及時回溯至上個正常的代碼版本.在常用的版本控制中,GIT 含有的是分布式代碼庫與文件快照的設計思想,相對于傳統CVS、SVN 等集中式、文件差異式版本控制工具是一種挑戰與顛覆[7].所以本文采用GIT 作為版本控制器,在實驗中將把代碼托管給Github.
在持續集成中,測試是必不可少的部分,若未經過測試直接集成于生產環境,會有重大的隱患.在構建過程中考慮從倉庫下拉代碼完畢后執行開發寫完的單元測試用例,并生成XML 格式的測試報告給Jenkins 識別.測試成功則繼續后續的構建步驟,若失敗則退出構建.
Jenkins 集群為Master 和Agent 節點,在Kubernetes中,所有服務均為容器化構建并由Kubernetes 管理,所以采用容器化搭建Jenkins Master 和Agent 服務,并托管與Kubernetes 中.當用戶觸發一次CI 時,Jenkins Master 節點會向Agent 節點派送相應的CI 任務.考慮到在空閑時,Agent 節點并無用處,故采用動態構建Agent節點,在有CI 任務時由Jenkins Master 向Kubernetes Apiserver 發出構建請求,可由Jenkins 中Kubernetes 該插件完成.
我們將CI/CD 的步驟可分為如下幾步[4],流程如圖2所示.
(1)開發將代碼提交至GIT 倉庫.
(2)代碼發生變化后觸發GIT HOOK,Jenkins Master 節點請求Kubernetes Apiserver 生成新的Jenkins Agent Pod.
(3)Jenkins Slave Pod 生成后開始構建Jenkins Master 節點下發的任務,從GIT 倉庫中拉取代碼.
(4)代碼拉取成功后,開始執行單元測試,單元測試成功則繼續執行,失敗則退出構建任務.
(5)CI 服務器根據預先定義的Pipeline 文件,將代碼進行編譯.
(6)編譯完成后,打包成鏡像將鏡像推送至鏡像倉庫,并打上最新標簽.
(7)CI 調用Kubernetes Cli,將預先設定好的Deployment 中的鏡像更改為剛剛構建已推送至私用倉庫的鏡像,從而完成部署.

圖2 構建流程
實驗環境為3 臺已組成Kubernetes 集群的虛擬機.系統為Ubuntu19.04 并對外暴露集群IP 為192.168.11.31.已將第一版本的代碼,使用Flask 框架編寫的后端打包部署至該集群lab 的命名空間里,并通過NodePort 的方式對外暴露服務,端口為30010,模擬生產環境的后端接口.此時我們使用curl 192.168.11.31:30010 命令,可返回預先設置好的字符串“This is first version code!”
(1)修改代碼,設置返回字符串為“This is second version code!”
(2)提交代碼并推送至GIT 倉庫.
(3)發現Jenkins Slave Pod 已經自動生成并開始構建任務.
(4)稍等片刻,Jenkins Slave Pod 顯示終止中,查看Jenkins console output,發現已成功完成構建任務.
(5)再次嘗試curl 192.168.11.31:30010,返回的字符串是“This is second version code!”,即服務端代碼已成功更新.
實驗測試過程如圖3所示.

圖3 實驗測試
本文通過實踐可以發現,比起傳統的需要人力去打包并手動部署到服務器上從而需要大量的人力物力,該CI/CD 流水線僅需開發人員上傳代碼后即能自動打包部署至相應的環境中,展現了符合期望的基于Kubernetes 的CI/CD 自動化流水線,可大大提升開發到交付的效率,并且可實現高并發彈性構自動化建流水線.對于擁有多個在Kubernetes 集群中的服務、需要快速迭代頻繁修改代碼并部署的項目具有省時省力且不容易出錯的優勢.