在 dailymotion,咱們信奉 DevOps 最佳實踐,而且重度使用了 Kubernetes。咱們的部分產品(並不是所有)已經部署在 Kubernetes 上。在遷移咱們的廣告技術平臺時,爲了趕時髦(做者你這麼直白的嗎?)咱們但願徹底採用「Kubernetes 方式」或雲原生!這意味着咱們須要從新定義咱們的整個 CI/CD 管道,並使用按需分配的動態環境來替代永久性的靜態環境。咱們的目標是爲咱們的開發人員提供最好的支持、縮短產品上市時間並下降運營成本。git
咱們對新 CI/CD 平臺的初始要求是:github
若是可能的話,儘可能避免從頭開始:咱們的開發人員已經習慣使用 Jenkins 和聲明性管道,目前這些東西都還好。golang
採用公有云基礎設施——谷歌雲平臺和 Kubernetes 集羣。web
與 gitops 兼容——由於咱們須要版本控制、評審和自動化。安全
CI/CD 生態系統中有不解決方案,但只有一個符合咱們的要求,也就是 Jenkins X,它基於 Jenkins 和 Kubernetes,原生支持預覽環境和 gitops。架構
Jenkins X 是一個高度集成化的 CI/CD 平臺,基於 Jenkins 和 Kubernetes 實現,旨在解決微服務體系架構下的雲原生應用的持續交付的問題,簡化整個雲原生應用的開發、運行和部署過程。less
你猜的沒錯,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 集羣中構建容器鏡像是一件頗有趣的事情。
Jenkins X 提供了一組構建包,使用「Docker 中的 Docker」在容器內部構建鏡像。但隨着新容器運行時的出現,以及 Kubernetes 推出了 Container Runtime Interface(CRI),咱們想知道其餘選擇是否可行。 Kaniko 是最成熟的解決方案,符合咱們的需求。咱們很激動,直到遇到如下 2 個問題。
第一個問題是阻塞性的:多階段構建不起做用。經過使用搜索引擎,咱們很快發現咱們並非惟一受到這個問題影響的人,並且當時尚未修復或解決方法。不過,Kaniko 是用 Go 語言編寫的,而咱們又是 Go 語言開發人員,因此爲何不看一下 Kaniko 的源代碼呢?事實證實,一旦咱們找到了問題的根本緣由,修復工做就很是簡單。Kaniko 維護人員很快就合併了修復,一天後,修復的 Kaniko 鏡像就已經可用了。
第二個問題是咱們沒法使用相同的 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 { } }
如今咱們有了全部工具,能夠爲咱們的應用程序構建鏡像,咱們已準備好進行下一步:部署到「預覽環境」!
經過重用現有工具(主要是 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 列表。
在初始 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 的緣由:與咱們同樣,它遵循了相同的模式。你也能夠開始你本身的旅程~