1. 程式人生 > >基於 Kubernetes 實踐彈性的 CI/CD 系統

基於 Kubernetes 實踐彈性的 CI/CD 系統

ron 一切都 最重要的 等於 負載能力 是把 什麽 執行 實例

技術分享圖片
大家好,我是來自阿裏雲容器服務團隊的華相。首先簡單解釋一下何為 Kubernetes 來幫助大家理解。Kuberentes 是一個生產可用的容器編排系統。Kuberentes 一方面在集群中把所有 Node 資源做一個資源池,然後它調度的單元是 Pod,當然 Pod 裏面可以有多個容器。 就像一個人左手抓著 ECS 資源或計算資源,右手抓容器,然後把它們兩個匹配起來,這樣它就可以作為一個容器的編排系統。

而 Cloudnative 這個概念現在會經常被大家提起,很多人迷惑 Cloudnative 又與 Kuberentes 有什麽關聯?我們又該如何判斷一個應用是 Cloudnative 呢?我認為有以下三個判斷標準:

第一,它能夠給資源做池化;
第二,應用可以快速接入池的網絡。在 Kuberentes 裏面有一層自己的獨立網絡,然後只需要知道我要去訪問哪個服務名就可以,就是各種服務發現的一些功能,它可以通過 service mesh 去做一個快速地訪問;
第三是有故障轉移功能,如果一個池子裏面有一臺主機,或者某一個節點 down 掉了,然後整個應用就不可用了,這肯定不算是 Cloudnative 的應用。

比較這三點就可以發現 Kuberentes 做的非常好。首先我們看一個資源池的概念,Kuberentes 一個大的集群就是一個資源池,我們再也不用去關心說我這個應用要跑在哪臺主機上了,我只要把我們部署的 yaml 文件往 Kuberentes 上發布就可以了,它會自動做這些調度,並且它可以快速地接入我們整個應用的網絡,然後故障轉移也是自動。接下來我就來分享如何基於 Kuberentes 實現一個彈性的 CI/CD 系統。

CI/CD 的現狀
首先了解一下 CI/CD 的現狀。CI/CD 這個概念實際上已經提出很多年了,但是隨著技術地演進和新工具地不斷推出,它在整個流程和實現方式上逐漸豐富。而我們通常最早接觸 CI/CD 就是代碼提交,隨之觸發一個事件,然後在某個 CI/CD 系統上做自動構建。

下圖可以反映目前 CI/CD 的現狀:

技術分享圖片

另外還有 Gitlab CI,它主要特點是與 Gitlab 代碼管理工具結合地比較好。Jenkins 2.0 開始引入 pipeline as code 特性,pipeline as code 可以幫我們自動生成一個 jenkins file。

在 Jenkins 1.0 時,如果我們想要配置一條流水線,需要先登錄 Jenkins 建一個項目,然後在裏面寫一些 shell。這樣雖然能達到同樣的效果,但是它有一個最大的弊端,就是可復制性和遷移性不好。而且它與 Devops 有天然地割裂,比如一般是由運維人員來管理 Jenkins 系統,開發人員編寫代碼,但是這個代碼怎麽去構建,發布到哪裏,開發人員完全不知道。這就造成了開發和運維地割裂。但 pipeline as code 方式出現了之後,jenkins file 與代碼源碼可以放在同樣的倉庫裏面。

首先它有一個非常大的好處是發布的流程也可以納入版本管理,這樣對一個錯誤就可追溯。這是一個非常大地改動,但是實際上我們在與客戶溝通中發現,雖然很多人在 Jenkins 上都已經升到 2.0 系列了,但是它們的用法還是完全在 1.0 系列,很多用戶都沒有把 jenkins file 這種方式用起來。另外一個就是對容器地支持,大概 2016 年左右,那時對容器的支持是非常弱的,在容器裏面運行 Jenkins,同時構建的產物也是 Docker 會非常麻煩。

但是, Drone 對容器的支持力度就非常好。首先它完全用 Docker 方式來運行,就是說你的構建環境也在一個容器裏,你需要構建一個 Docker build 鏡像,然後在推送出去的時候,它也在容器裏面運行,然後它需要一個 privilege 權限。它這種方式有幾個特別好的地方,首先它不會對宿主機產生任何殘留,比如說你這個容器一銷毀,構建中產生的一些中間文件完全都跟著銷毀了,但是你如果用 Jenkins 的話,隨著用的時間越來越久會沈澱出很多臨時文件,它占的空間會越來越大。你需要定期做一些清理,而且清理過程中你又不能直接一鍵清空,所以這是很麻煩的過程。

然後插件的管理 Jenkins 還有一個特別讓人頭疼的地方,就是它的插件升級。首先你在 Jenkins 登錄進去,然後就做插件升級。如果說我想臨時在一個新的環境裏面,起一個 Jenkins 先測試一下或做一些調試可能每新建一個環境,都需要把這些插件升級一次。而且剛才我們說的在 Jenkins 裏面做的所有配置,也都需要重新配置一遍,這是一個非常繁瑣的一個過程。

但是 Drone 這個工具它有一個特別好的地方,就是所有的插件都是 Docker 容器,比如說你在 pipeline 裏用這個插件,你只要聲明用這個插件就可以了,你不用去自己管理把插件下載到哪裏,然後怎麽安裝,它這個一切都是全自動,只要說你網絡能把插件容器鏡像訪問到就可以了,這非常便利。

然後關於生態的構建,Jenkins 的最大的優勢就是它的插件非常多,就是你想用的各種東西都有,而且它基礎的底座非常好,你的插件可以實現的能力非常的強。比如說 pipeline 就是這種方式,它從 1.0 到 2.0 雖然有了,但是它完全是通過插件來實現的。但是現在 Jenkins 的發展又開始有點第二春的感覺。他開始對 Kuberentes 的支持力度明顯的加大了很多,首先從 JenkinsX 開始,它融合了一些 Kuberentes 生態相關的一些工具,比如 Harbor、Helm 它可以非常便利地在 Kuberentes 集群上來做一些構建,並且把一些做服務的固化的一些編排文件放到 Helm 裏面。

另外,現在它有一個新的子項目叫 config as code,也就是說他把所有 Jenkin 裏面做了一些配置,都可以輸出成一個 code 的形式,就是對整個 Jenkins 的遷移,或者說復制都是一個很便利的改進。

講了那麽多,實際上最後我們選擇的東西還是 Jenkins,因為最重要的是生態的構建,他們已經很好了。今天我們要講的在做一個彈性在 CI/CD 這個 Jenkins 上已經有這個插件了,但是在 Drone 的社區裏面,有人提這個事,但是現在還沒有看到。

CI/CD 工具的選擇
接下來,我們看一下 CI/CD 這些工具的選擇和他們的發展。首先最老牌的肯定是 Jenkins。實際上在容器技術興起之前,CI/CD 簡直約等於 Jenkins。但是在出現容器技術之後,很多新生 CI/CD 的工具也應運而生,比如說圖中 Drone 工具,它是一個完全基於容器來實現的 CI/CD 工具。它與容器地結合地非常好,並且它的構建過程是完全在容器中實現的。

技術分享圖片

第三個是 Gitlab CI,它主要特點是與 Gitlab 代碼管理工具結合地比較好。Jenkins 2.0 時開始引入 pipeline as code 特性,什麽叫 pipeline as code?pipeline as code 可以幫我們自動生成一個 jenkins file。在 Jenkins 1.0 時,如果我們想要配置一條流水線,需要先登錄 Jenkins,然後建一個項目,然後在裏面寫一些 shell。這樣雖然能達到同樣的效果,但是它有一個最大的弊端,就是可復制性和遷移性不好。而且它與 Devops 有天然地割裂,比如一般是運維人員來管理 Jenkins 這個系統。開發人員編寫代碼,但是這個代碼怎麽去構建,發布到哪裏,他是完全不知道的,是運維人員在Jenkins 裏面配的。這個就造成了開發和運維地割裂。但 pipeline as code 這種方式出現了之後,我們可以把 jenkins file 跟代碼源碼放在同樣的倉庫裏面。

首先它有一個非常大的好處就是我們發布的流程也可以納入版本管理,這樣對一個錯誤就可追溯。這是一個非常大地改動,但是實際上我們在與客戶溝通中發現,雖然很多人在 Jenkins 上都已經升到 2.0 系列了,但是它們的用法還是完全在 1.0 系列,很多用戶都沒有把 jenkins file 這種方式用起來。另外一個就是對容器地支持,大概 2016 年左右,那時對容器的支持是非常弱的,在容器裏面運行 Jenkins,同時構建的產物也是 Doker 會非常麻煩。

但是, Drone 對容器的支持力度就非常好。首先它完全用 Doker 方式來運行,就是說你的構建環境也在一個容器裏,你需要構建一個 Doker build 鏡像,然後在推送出去的時候,它也在容器裏面運行,然後它需要一個 privilege 權限。它這種方式有幾個特別好的地方,首先它不會對宿主機產生任何殘留,比如說你這個容器一銷毀,構建中產生的一些中間文件完全都跟著銷毀了,但是你如果用 Jenkins 的話,隨著用的時間越來越久會沈澱出很多臨時文件,它占的空間會越來越大。你需要定期做一些清理,而且清理過程中你又不能直接一鍵清空,所以這是很麻煩的過程。然後插件的管理 Jenkins 還有一個特別讓人頭疼的地方,就是它的插件升級。首先你在 Jenkins 登錄進去,然後就做插件升級。如果說我想臨時在一個新的環境裏面,起一個 Jenkins 先測試一下或做一些調試可能每新建一個環境,都需要把這些插件升級一次。而且剛才我們說的在 Jenkins 裏面做的所有配置,也都需要重新配置一遍,這是一個非常繁瑣的一個過程。

但是 Drone 這個工具它有一個特別好的地方,就是所有的插件都是 Doker 容器,比如說你在 pipeline 裏用這個插件,你只要聲明用這個插件就可以了,你不用去自己管理把插件下載到哪裏,然後怎麽安裝,它這個一切都是全自動,只要說你網絡能把插件容器鏡像訪問到就可以了,這非常便利。

然後關於生態的構建,Jenkins 的最大的優勢就是它的插件非常多,就是你想用的各種東西都有,而且它基礎的底座非常好,你的插件可以實現的能力非常的強。比如說 pipeline 就是這種方式,它從 1.0 到 2.0 雖然有了,但是它完全是通過插件來實現的。但是現在 Jenkins 的發展又開始有點第二春的感覺。他開始對 Kuberentes 的支持力度明顯的加大了很多,首先從 JenkinsX 開始,它融合了一些 Kuberentes 生態相關的一些工具,比如 Harbor、Helm 它可以非常便利地在 Kuberentes 集群上來做一些構建,並且把一些做服務的固化的一些編排文件放到 Helm 裏面。

另外,現在它有一個新的子項目叫 config as code,也就是說他把所有 Jenkin 裏面做了一些配置,都可以輸出成一個 code 的形式,就是對整個 Jenkins 的遷移,或者說復制都是一個很便利的改進。

講了那麽多,實際上最後我們選擇的東西還是 Jenkins,因為最重要的是生態的構建,他們已經很好了。今天我們要講的在做一個彈性在 CI/CD 這個 Jenkins 上已經有這個插件了,但是在 Drone 的社區裏面,有人提這個事,但是現在還沒有看到。

CI/CD的系統業務場景
然後我們看一下 CI/CD 它的系統的業務場景,它有一個比較典型的場景與特點,首先它面向開發人員,這是比較少見的,因為開發人員一般都比較挑剔一點。所以你要是這個系統做的不夠穩健了,或者說響應時間比較長一點的話,會被經常吐槽。

技術分享圖片

然後就是有時效性要求,因為我們代碼寫完之後,往上提交,我們都不希望在這個代碼的構建中一直排隊,我們希望馬上就開始進行構建,並且資源足夠豐富。另外一個就是它的資源占用的波峰波谷是非常明顯的。就因為開發人員不可能時時刻刻都在提交代碼,有的人可能一天提交幾次,有的人會提交很多次。

因為我之前看過有一個分享,有一個人畫了一條反映自家公司構建任務的曲線。他們公司大概是每天下午三、四點的時候代碼提交量最高,其他時間都比較平緩。這說明他們公司三、四點的時候,程序員提交代碼開始劃水了。然後隨著 CI/CD 資源地需求越來越高,構建集群是一個必須要做的一件事情。就是提高負載能力,縮短任務的排隊時間。當然真正的集群有一個讓人覺得很不太好的地方,就是它的 Master 實際上只有一個,當然這個也可以通過插件來做改進。

容器可以給我們 CI/CD 系統來註入新的能力,就是環境隔離的能力。我們可以通過 Kubernetes 來為 CI/CD 系統註入更多的能力,然後矛盾點就出現了。開發人員總是希望 CI/CD 系統能夠快速地響應代碼提交的一個事件,但是每個公司資源都不可能是無限的。因為就像上面提到的,如果每天下午三、四點的時候是一個代碼提交的高峰,這個時候可能需要 30 或 40 臺機器才能滿足構建的任務。但是我不可能每天就開著 30 或 40 臺機器在這裏,就為了每天下午三、四點,可能會構建一、兩個小時。

Kubernetes 可以為 jenkins 註入新的能力,讓 CI/CD 系統實現彈性的能力。我們期望的目標是什麽呢?有構建任務的時候,可以自動為我們資源增加新的機器也好,或增加新的運計算能力也好,反正就是當我需要的時候,可以幫我自動地做一個資源擴張,但是同時也在我不需要的時候,可以自動把這些資源釋放掉。

技術分享圖片

我們期望的目標就是這樣,Kuberentes 就可以為 Jenkins 來做這樣的能力。Kuberentes 作為一個容器編排的系統,它所能提供的能力,它可以快速地彈出一些新的實例,並且把它們自動調度到空閑的機器上,做一個資源池,在資源池裏面做一個調度,並且他執行完任務之後,它可以做一個回收。而且如果把這 Jenkins Master 也部署在 Kuberentes 之上,它可以對 Master 做一個故障轉移,就是說如果我們系統可以容忍的話,Master 就算掛了,我可以快速把它調到另外一臺機器上,這個響應時間不會很長。

Kuberentes-plugin
這裏面也是用一個插件來實現,這個插件名字比較直接叫 Kuberentes-plugin,它這個插件所能提供的能力就是說,他直接管理一個 Kuberentes 集群,它在 Jenkins 裏面安裝之後,它可以監聽 Jenkins 的構建任務。有構建任務,在等待資源的時候,它就可以向 Kuberenetes 去申請一個新的資源,申請一個新的 Pod 去做自動地構建完之後,它就會自動的清理。

技術分享圖片

先簡單介紹一下它的能力,因為這個插件安裝完之後,它對 pipeline 的語法也有一個改造,一會我們來看一下實例。但是就算到了這一步,還是不行的。首先,Kuberentes 的集群規劃還是一個問題。比說我有個集群有 30 個節點,真正的 master 部署在這上面,然後裝了那些插件,做了一個管理之後,我們可以發現來了一個新的任務,它就起一個新的 Pod,把這個構建任務給執行制定完。

執行完之後,這 Pod 自動銷毀不占集群的資源。 平時我們可以在這集群上做一些別的任務,但這個終究還是有一點不好,就是我們這個集群到底規劃多大,並且這個集群我們平時不做構建任務的時候,可以在上面做一些別的任務。但是如果正在做任務,突然來了一些構建任務,它可能會出現資源的沖突問題。

Kubernetes Autoscaler
總的來說,還是有一些不完美的地方,那麽我們可以利用 Kuberentes 一些比較沒那麽常見的特性,來解決我們剛才說的這個問題。這兩個東西一個是叫 Autoscaler,一個叫 Virtual node。我們先看一下 Autoscaler,Autoscaler 是一個 Kubernetes 官方的一個組件。在 Kuberentes 的 group 下面支持三種能力:

Cluster Autoscaler,可以對集群節點做自動伸縮;
Vertical Pod Autoscaler,對集群的 Pod 豎直方向的資源伸縮。因為 Kuberentes 本身裏面就帶著 HPA 可以做水平方向的 Pod 伸縮、節點數量的伸縮;這個特性還不是生產可用的特性;
Addone Resizer,是 Kuberentes 上那些 addone 比如說 Ingress Controler、 DNS 可以根據 Node 的數量來對資源的分配做調整。
Cluster autoscaler
我要講的是 Cluster autoscaler,是對集群 node 節點數量做一個擴縮容。 首先我們看一下,這個是在阿裏雲我們容器服務上所實現的 Autoscaler 的一個方式。我們看一下這個圖,這個是 HPA 和 Autoscler 做結合使用的一個場景。

技術分享圖片

HPA 監聽監控的事件時發現資源使用率上升到一定程度了之後,HPA 會自動通知 workload,來彈出一個新的 Pod,彈出一個新的 Pod,可能這時候集群資源就已經不夠了,所以這個 Pod 可能就會 pending 在這裏。它就會觸發 Autoscaler 的事件,Autoscaler 就會根據我們之前配置好的 ESS 這個模板來去彈出來一臺新的 Node,然後自動地把 Node 加入到我們集群裏面。 它是利用了 ESS 定制的模板功能,並且它可以支持多種的 Node 實例的類型,可以支持普通實例,支持 gpu,還有搶占式實例。

Virtual node
然後第二種是 Virtual node,Virtual node 實現方式是基於微軟開源的 Virtual Kubelet 這個項目。它做了一個虛擬的 Kubelet,然後向 Kubernetes 集群裏面去註冊上面。但如果不太好理解的話,可以想象一下 MySQL proxy ,然後他把自己偽裝成一個MySQL server,然後後端可能管理著非常多的 MySQL server,並且它可以幫你自動的做一些 SQL 查詢的路由或者拼接。

技術分享圖片

Virtual kubelet 做的也是類似的工作,就是說它本身向著 Kubernetes 註冊說我是一個節點,但實際上它後端管理的可能是整個公有雲上的非常多的資源,他可能對接公有雲的一些 ECI 或者說對接的這些 VPC,這是一個它的大體的示意圖。

技術分享圖片

在阿裏雲上他們對接的是阿裏雲的 ECI 做一個彈性的容器實例,它響應時間就非常快,因為它不需要把 Node 去加入到集群裏面,它是大概能夠到一分鐘一百個 Pod 左右這種性能。而且我們可以在 Pod 上聲明這種資源的使用情況,這是一個非常快的響應速度時間。

然後剛才說我們利用這兩種方式,就可以對我們 CI/CD 彈性的系統做出新的改造,我們不用非常早規劃好我們集群的規模,我們可以讓集群規模在需要的時候自動的做一些伸縮的動作。但是你做了這些動作之後,我們做了這些把真正的放在容器裏面的這些動作之後,引入了一些新的門檻:docker-outside-of-docker 和 docker in docker。

技術分享圖片

我們在 Docker 中運行 Jenkins 時,通常有兩種方式,一個是把宿主機的 docker.sock 掛載到容器裏面,讓 Jenkins 通過這個文件來和本機的 docker daemon 做一個通信,然後它在這裏面做 docker build 構建鏡像,或者把這些鏡像 push 到遠程的倉庫裏面,所以它中間產生的所有鏡像都會堆積在本機上,這是一個問題。

在一些 serverless 的那些場景上使用,它就會有一些限制,因為 serverless 本身就不允許把 socket 文件給掛在進去。另外一個就是 docker in docker 這種方式,它的優點就在於在容器裏面它啟動一個新的 Docker daemon,它所有的中間產物、構建產物隨著容器都一起銷毀,但是它有問題,它就是需要 privilege 的權限。

很多時候我們是盡量不要用它。另外一個就是說你做 docker build 的時候能在宿主機上做的時候,它如果有已經有鏡像了,它會直接就使用這個鏡像,但是你用 docker in docker 這種方式來使用的,它每次都會重新拉進項,拉鏡像也是需要一定時間,這個取決於我們各個使用場景來判斷。

新的構建工具——Kaniko
這時又引入了一個谷歌開源的新工具——Kaniko。它做的東西是 docker in docker 的方式。它有一個非常大的好處就是不依賴 Docker,而且所以它不需要 privilege 權限就可以在容器裏面用用戶態的模式,來完全構建 docker image。用戶態執行 Dockerfile 的命令,它把這個鏡像完全構建出來。

10

這算是一個比較期望的彈性的 CI/CD 系統。然後這個時候就是說從真正的節點到底層的計算資源全部是彈性擴縮的,而且滿足交付的需求,可以非常精細化地管理我們的資源。

Demo 演示
然後我們可以看一下 Demo 演示:
https://github.com/hymian/webdemo 這裏是我準備好的一個例子,重點在這個 Jenkinsfile 文件,裏面定義了agent 的 pod template,包含兩個容器,一個用來做 golang 的 build,一個用來做 image 的 build。

然後我們現在構建它。開始構建了,剛開始的,因為是現在我們在這環境裏面只有一個,只有一個 master,所以他就是沒有不會有構建節點。大家可以看到,它現在新啟動了一個 Pod,這個 Pod 是作為節點加進來的,但是因為我在這個 Pod 模板裏面定義了一個 label,所以它沒有這個節點,所以它 Pod 狀態是 pending 的。所以我們在構建日誌裏面顯示的這個是 agent 節點是離線的。

但是我們在這個集群裏面定義了一個彈性伸縮的一個東西,當沒有節點的時候,它會自動做一個新節點分配加入,可以看到有一個節點正在加入,這個我就可以稍等一下。就是說這段時間可能會有個一分鐘兩分鐘的時間。

這個是異常,是因為這個節點正在向集群加入,所以它顯示是異常,這是我們從命令行看一下,好,已經是四個節點了,加了一個節點,這時候我們看 Pod,這時候在 agent 正在創建,這時候大家可能有一個小的細節,大家可以看一下,就是 0/3 是顯示 Pod,它有三個容器,但是我剛才在這個裏面定義的,它實際上是 Pod 裏面只有兩個容器,這就是我們剛才 PPT 上寫的一個地方。

JNLP 那個容器,是 plugin 自動註入的一個容器,它通過這個容器實時的向 master 匯報構建的一個中間的狀態,我把它的日誌給發送出去。這個是 agent 的節點在初始化的一個過程一個事情這時候 slave節點已經在運行了。我這邊已經輸出完了,構建完成。我的分享內容就這些,謝謝大家。

基於 Kubernetes 實踐彈性的 CI/CD 系統