從Jenkins遷移到Jenkins X:一場持續交付之旅

這篇文章將介紹dailymotion(一家總部位於巴黎的視頻分享網站)從Jenkins遷移到Jenkins X的故事,包括我們遇到的問題以及我們如何解決它們。

背景

在dailymotion,我們信奉DevOps最佳實踐,並且重度使用了Kubernetes。我們的部分產品(並非全部)已經部署在Kubernetes上。在遷移我們的廣告技術平臺時,爲了趕時髦(作者你這麼直白的嗎?)我們希望完全採用“Kubernetes方式”或雲原生!這意味着我們需要重新定義我們的整個CI/CD管道,並使用按需分配的動態環境來替代永久性的靜態環境。我們的目標是爲我們的開發人員提供最好的支持、縮短產品上市時間並降低運營成本。

我們對新CI/CD平臺的初始要求是:

  • 如果可能的話,儘量避免從頭開始:我們的開發人員已經習慣使用Jenkins和聲明性管道,目前這些東西都還好。

  • 採用公有云基礎設施——谷歌雲平臺和Kubernetes集羣。

  • 與gitops兼容——因爲我們需要版本控制、評審和自動化。

CI/CD生態系統中有不解決方案,但只有一個符合我們的要求,也就是Jenkins X,它基於Jenkins和Kubernetes,原生支持預覽環境和gitops。

Jenkins X: Kubernetes上的Jenkins

Jenkins X 是一個高度集成化的CI/CD平臺,基於Jenkins和Kubernetes實現,旨在解決微服務體系架構下的雲原生應用的持續交付的問題,簡化整個雲原生應用的開發、運行和部署過程。

你猜的沒錯,Jenkins X 只能在Kubernetes集羣上運行

Jenkins X的搭建過程非常簡單,官方網站上已經提供了很好的文檔。由於我們已經在使用Google Kubernetes Engine(GKE),因此jx命令行工具可以自行創建所有的內容,包括Kubernetes集羣。在幾分鐘內就可以獲得一個完整的可運行系統真的讓人印象深刻。

Jenkins X提供了很多快速入門和模板,不過我們想重用現有代碼庫中的Jenkins管道。所以,我們決定另闢蹊徑,並對我們的聲明性管道進行重構,讓它們與Jenkins X兼容。

實際上,重構工作並不是只針對Jenkins X,而是爲了能夠使用Kubernetes插件在Kubernetes上運行Jenkins。

如果你習慣使用“經典”的Jenkins,並在裸機或虛擬機上運行靜態從節點,那麼這裏的主要變化是每個構建都將在自己的短存活期自定義pod上執行。你可以指定管道的每個步驟應該在哪個容器中執行。插件的源代碼中提供了一些管道示例

我們面臨的挑戰是如何定義容器的粒度,以及它們應該包含哪些工具:擁有足夠多的容器讓我們可以在不同的管道之間重用它們的鏡像,但又不至於太多,這樣容易維護——我們可不想要花太多時間重建容器鏡像。

在之前,我們在Docker容器中運行大部分管道步驟,當我們需要自定義步驟時,就在管道中進行即時構建。

這種方式較慢,但更容易維護,因爲所有內容都是在源代碼中定義的。例如,升級Go運行時可以在單個拉取請求中完成。因此,需要預先構建容器鏡像似乎是現有的設置中增加了更多的複雜性。它還具備一些優點:代碼庫之間的重複代碼更少、構建速度更快,並且沒有了因爲第三方構建平臺宕機而造成的構建錯誤。

在Kubernetes上構建鏡像

在Kubernetes集羣中構建容器鏡像是一件很有趣的事情。

Jenkins X提供了一組構建包,使用“Docker中的Docker”在容器內部構建鏡像。但隨着新容器運行時的出現,以及Kubernetes推出了Container Runtime Interface(CRI),我們想知道其他選擇是否可行。Kaniko是最成熟的解決方案,符合我們的需求。我們很激動,直到遇到以下2個問題。

  1. 第一個問題是阻塞性的:多階段構建不起作用。通過使用搜索引擎,我們很快發現我們並不是唯一受到這個問題影響的人,而且當時還沒有修復或解決方法。不過,Kaniko是用Go語言編寫的,而我們又是Go語言開發人員,所以爲什麼不看一下Kaniko的源代碼呢?事實證明,一旦我們找到了問題的根本原因,修復工作就非常簡單。Kaniko維護人員很快就合併了修復,一天後,修復的Kaniko鏡像就已經可用了。

  2. 第二個問題是我們無法使用相同的Kaniko容器構建兩個不同的鏡像。這是因爲Jenkins並沒有正確地使用Kaniko——因爲我們需要先啓動容器,然後再進行構建。這一次,我們在谷歌上找到了一個解決方法:聲明足夠多的Kaniko容器來構建鏡像,但我們不喜歡這個方法。所以我們又回到了源代碼,在找到了根本原因後,修復就很容易了。

我們測試了一些方案,想自己爲CI管道構建自定義的“工具”鏡像,最後,我們選擇使用單個代碼庫,每個分支使用一個鏡像,也即一個Dockerfile。因爲我們的代碼託管在Github上,並使用Jenkins Github插件來構建代碼庫,所以它可以構建所有的分支,並基於webhook觸發事件爲新分支創建新的作業,所以管理起來十分容易。每個分支都有自己的Jenkinsfile聲明性管道文件,使用Kaniko構建鏡像,並將構建好的鏡像推送到容器註冊表。Jenkins幫我們做了很多事情,所以可以快速地添加新鏡像或編輯現有的鏡像。

聲明所請求的資源

我們之前的Jenkins平臺存在的一個主要問題來自於靜態從屬節點或執行程序,以及有時候會在高峯時段出現的長構建隊列。Kubernetes上的Jenkins可以輕鬆地解決這個問題,特別是運行在支持集羣自動縮放器的Kubernetes集羣上時。集羣將根據當前的負載添加或移除節點。不過這是基於所請求的資源,而不是基於所使用資源的情況。

這意味着我們需要在構建pod模板中定義所請求的資源——比如CPU和內存。然後,Kubernetes調度程序將使用這些信息查找匹配的節點來運行pod——或者它可能決定創建一個新節點。這樣就不會出現長隊列了。

但是,我們需要謹慎定義所需資源的數量,並在更新管道時更新它們。因爲資源是在容器級別而不是pod級別定義的,所以處理起來會更加複雜。但我們不關心限制問題,我們只關心請求,所以我們只將對整個pod的資源請求分配給第一個容器(jnlp那個)——也就是默認的那個。

以下是Jenkinsfile的一個示例,以及我們是如何聲明所請求的資源的。

pipeline {
    agent {
        kubernetes {
            label 'xxx-builder'
            yaml """
kind: Pod
metadata:
  name: xxx-builder
spec:
  containers:
  - name: jnlp
    resources:
      requests:
        cpu: 4
        memory: 1G
  - name: go
    image: golang:1.11
    imagePullPolicy: Always
    command: [cat]
    tty: true
  - name: kaniko
    image: gcr.io/kaniko-project/executor:debug
    imagePullPolicy: Always
    command: [cat]
    tty: true
"""
        }
    }

    stages {
    }
}

Jenkins X的預覽環境

現在我們有了所有工具,可以爲我們的應用程序構建鏡像,我們已準備好進行下一步:部署到“預覽環境”!

通過重用現有工具(主要是Helm),Jenkins X可以輕鬆部署預覽環境,只要遵循一些約定,例如鏡像標籤的名稱。Helm是Kubernetes應用程序的包管理器。每個應用程序都被打包爲一個“chart”,然後可以使用helm命令行工具將其部署爲“release”。

可以使用jx命令行工具部署預覽環境,這個工具負責部署Helm的chart,併爲Github的拉取請求提供註釋。在我們的第一個POC中,我們使用了普通的HTTP,因此這種方式奏效了。但現在沒有人再用HTTP了,那我們使用加密的吧!

多虧了有cert-manager,在Kubernetes中創建攝入資源時可以自動獲取新域名的SSL證書。我們嘗試在設置中啓用tls-acme標誌——使用cert-manager進行綁定——但它不起作用。

於是我們閱讀了Jenkins X的源代碼——它也是使用Go開發的。稍後修改一下就好了,我們現在可以使用安全的預覽環境,其中包含了let’s encrypt提供的自動證書。

預覽環境的另一個問題與環境的清理有關。我們爲每個拉取請求創建了一個預覽環境,在合併或關閉拉取請求時需要刪除相應的環境。這是由Jenkins X設置的Kubernetes作業負責處理的,它會刪除預覽環境使用的命名空間。問題是這些作業並不會刪除Helm的release——因此,如果你運行helm list,仍然會看到舊的預覽環境列表。

對於這個問題,我們決定改變使用Helm部署預覽環境的方式。我們決定使用helmTemplate功能標誌,只將Helm作爲模板渲染引擎,並使用kubectl來處理生成的資源。這樣,臨時的預覽環境就不會“污染”Helm release列表。

將gitops應用於Jenkins X

在初始POC的某個時候,我們對設置和管道非常滿意,並準備將POC平臺轉變爲可投入生產的平臺。第一步是安裝SAML插件進行Okta集成——允許內部用戶登錄。它運作得很好,但幾天後,我發現Okta集成已經不在了。我在忙其他的一些事情,所以只是問了同事一下他是否做了一些更改,然後繼續做其他事情。幾天後再次發生這種情況,我開始調查原因。我注意到Jenkins pod最近重啓過。但我們有一個持久的存儲,而且作業也在,所以是時候仔細看看了!

事實證明,用於安裝Jenkins的Helm chart有一個啓動腳本通過Kubernetes configmap重置了Jenkins配置。當然,我們無法像管理在VM中運行的Jenkins那樣來管理在Kubernetes中運行的Jenkins!

我們沒有手動編輯configmap,而是退後一步從大局看待這個問題。configmap是由jenkins-x-platform管理的,因此通過升級平臺來重置我們的自定義更改。我們需要將“定製”內容保存在一個安全的地方,並對變化進行跟蹤。

我們可以使用Jenkins X的方式,並使用一個chart來安裝和配置所有內容,但這種方法有一些缺點:它不支持“加密”——我們的git代碼庫中包含了一些敏感的信息——並且它“隱藏”了所有子chart。因此,如果我們列出所有已安裝的Helm版本,只會看到其中一個。但是還有其他一些基於Helm的工具,它們更適合gitops。Helmfile就是其中之一,它通過helm-secrets插件sops原生支持加密。

遷移

從Jenkins遷移到Jenkins X以及如何使用2個構建系統處理代碼庫也是我們整個旅程的一個很有趣的部分。

首先,我們搭建新Jenkins來構建“jenkinsx”分支,同時更新了舊Jenkins配置,用來構建除“jenkinsx”分支之外的所有內容。我們計劃在“jenkinsx”分支上構建新管道,並將其合併。

對於初始POC,這樣做沒有問題,但當我們開始使用預覽環境時,不得不創建新的拉取請求,並且由於分支的限制,那些拉取請求不是基於新的Jenkins構建的。因此,我們選擇在兩個Jenkins實例上構建所有內容,只是在Jenkins上使用Jenkinsfile文件名和在新Jenkins上使用Jenkinsxfile文件名。遷移之後,我們將會更新這個配置,並重命名文件。這樣做是值得的,因爲它讓我們能夠在兩個系統之間平穩過渡,並且每個項目都可以自行遷移,不會影響到其他項目。

我們的目的地

那麼,Jenkins X是否適合所有人?老實說,我不這麼認爲。並非所有功能和支持的平臺——git託管平臺或Kubernetes託管平臺——都足夠穩定。但是,如果你有足夠的時間進行深挖,並選擇了適合自己用例的功能和平臺,就可以改善你的管道。這將縮短髮布週期,降低成本,如果你對測試也非常認真,那麼對你的軟件質量也應當充滿信心。

我們的旅程還沒有結束,因爲我們的目標仍在繼續:Jenkins X仍然處於開發階段,而且它本身正在走向Serverless,目前正在使用Knative build。它的目標是雲原生Jenkins。

我們的旅程也在繼續,因爲我們不希望它就這樣結束。我們目前的完成的一些事情並不是我們的最終目的地,它只是我們不斷演化的一個步驟。這就是我們喜歡Jenkins X的原因:與我們一樣,它遵循了相同的模式。你也可以開始你自己的旅程~

英文原文:https://medium.com/dailymotion/from-jenkins-to-jenkins-x-604b6cde0ce3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章