基於 Kubernetes 的 CICD 基礎設施即代碼


在上一篇基於 Kubernetes 的基礎設施即代碼一文中,我概要地介紹了基於 Kubernetes 的 .NET Core 微服務和 CI/CD 動手實踐工作坊使用的基礎設施是如何使用代碼描述的,以及它的自動化執行過程。

如果要查看基於 Kubernetes 的基礎設施即代碼架構全圖,以及實現代碼,請回到文章基於 Kubernetes 的基礎設施即代碼

本文,我們深入探討其中 CI/CD 軟件部分的“基礎設施即代碼”的實現原理。

變量模板引擎

在工作坊中,由於所有與會者使用的都是同一個 Kubernetes 集群,因此我們需要一種方法來標識當前用戶。Kubernetes 的命名空間提供的邏輯隔離功能可以很輕松地實現這個效果。因此,我們要為每個工作坊與會者創建他的一批命名空間:

  • cicd-<suffix> 用於部署 CI/CD 軟件
  • dev-<suffix> 作為“開發環境”,部署微服務
  • stage-<suffix> 作為“預生產環境”,部署微服務

顯然,對於每一個與會者來說,這里的 suffix 會有所不同,因此它是一個變量。除了在啟動安裝 CI/CD 軟件時需要使用,這個變量還需要以某種形式保存到 Jenkins 上,因為當 Jenkins 運行部署任務時,它也需要知道目標命名空間的名字。

為了處理變量,我們自己發明了一個小型的“模板引擎”。其作用是,使用變量文件中指定的值,替換各個文件中的變量,輸出最終的內容。這個模板引擎以雙美元符號 $$ 作為變量起始字符。打開 cicd-infra/jenkins.yaml 並搜索 $$ 就可以發現其中大量地引用了各個變量。

模板引擎的實現位於 ./tmpl.sh 腳本文件,它能從指定的變量文件和環境變量讀入各個變量的值,並將待處理文件中的變量占位符替換為對應的值,最后向標准輸出(stdout)打印最終的文件內容。借助流水線指令 |,這些內容隨后被 kubectl apply -f - 命令讀取,用於安裝配置對應的 Kubernetes 資源。

在 kubelet 1.14 及以上的版本中,新增加了 kustomize 子命令。它提供更多編寫模板化、嵌套式 Yaml 文件的方法。在工作坊中,我們需要兼容支持低一些版本的 kubelet,就不得不借助這樣的模板引擎。

自動化安裝 Jenkins

打開 cicd-infra/jenkins.yaml 會發現接近 600 行,可以說不短了。其中包含如下幾個關鍵的 Kubernetes 資源:

  • ServiceAccount jenkins 是 Jenkins 本身,以及 Jenkins 用於生成容器鏡像並部署微服務時所用的 Pod 要使用的集群賬號
  • RoleBinding jenkins_edit 為上述集群賬號賦予相應權限
  • Service jenkins-jnlp 供 Jenkins 構建運行器(Slave)啟動期間連接 Jenkins 主機(Master)時用的集群 Service
  • Service jenkins 是供 Ingress 用於把 Jenkins Web 界面暴露給用戶用的集群 Service
  • Ingress jenkins-ingress 是負責把用戶請求轉發到集群 Service 的流量入口處理規則
  • ConfigMap jenkins-jobs 可掛載為 Jenkins 內置任務的配置
  • ConfigMap jenkins 一系列用於初始化 Jenkins 的配置
  • Deployment jenkins 用於部署 Jenkins Web 服務

這里需要重點介紹的是 configmap/jenkins,以及 deployment/jenkins。后者掛載前者,以文件的方式讀入內容並完成 Jenkins 的初始化配置工作。具體來說,deployment/jenkins 聲明了兩個容器,在這兩個容器上共享多個存儲卷,以實現共享文件的目的:

  1. 在 Jenkins 啟動之前運行的初始化容器 installer,它按照 plugins.txt 先將插件安裝到磁盤上,並為工作坊的所有微服務創建內置 Jenkins 任務
  2. 在 installer 運行完成之后才啟動的容器 jenkins,它就是 Jenkins Web 服務本身所在的容器

deployment/jenkins 的 Yaml 配置

從 deployment/jenkins 的 yaml 配置中,我們不難發現,installer 運行的具體過程位於腳本文件 /var/jenkins_config/apply_config.sh 中,它的內容是從 configmap/jenkins 掛載而來的。這個腳本中還將用到很多其他文件,比如安裝插件用的 plugins.txt,它們都是從這個 configmap/jenkins 掛載而來。為了加速 Jenkins 插件的安裝過程,我們在 installer 容器里使用 JENKINS_UCJENKINS_UC_DOWNLOAD 這兩個環境變量來讓它從國內的服務器源下載插件。

configmap/jenkins/plugins.txt 定義了工作坊中我們需要用到的插件列表:

  • git
  • dashboard-view
  • pipeline-stage-view
  • workflow-aggregator
  • kubernetes:1.20.0

其中的 kubernetes 插件讓我們的 Jenkins 可以與它所在的 Kubernetes 集群集成,從而實現幾乎能把任何容器鏡像作為構建運行器(Slave)節點來使用,並且這些節點將以獨立的 Pod 的方式“按需”在 Kubernetes 集群中運行,並自動連接到 Jenkins。這大大簡化了 Jenkins 的運行器節點的維護工作。如果進一步研讀 configmap/jenkins/config.xml 配置內容可以發現,我們的 Jenkins 將內置支持 dotnet 和 image-builder 兩種 Slave 節點。

閱讀 configmap/jenkins/apply_config.sh 可以看到,它使用了 Jenkins 支持的多種自動化配置功能:

  • 運行 /usr/local/bin/install-plugins.sh 腳本文件可以預先安裝指定的插件
  • 在 /var/jenkins_home/init.groovy.d 目錄中創建的 groovy 腳本將在 Jenkins 啟動后自動運行,我們這里用來向 Jenkins 中植入容器鏡像注冊表的登錄信息
  • 通過預先定義 /var/jenkins_home/config.xml 及其他 xml 文件可以定制 Jenkins 的各類全局系統設置
  • /var/jenkins_jobs 目錄下的子目錄將自動被視為內置任務自動被 Jenkins 加載

上面第一種自動化功能,是內置在 Jenkins 安裝包中的一個實用工具,它的源代碼位於 GitHub 上。第二種自動化功能是 Jenkins 的初始化腳本,它支持以 Groovy 語言為 Jenkins 開發自動運行的腳本鈎子。后面兩種自動化功能則是根據 Jenkins 的配置存儲機制而預先寫入配置來達到內置配置和任務的目的。

Jenkins 的自動化配置

deployment/jenkins 還讓這兩個容器共享 jenkins-home 和 plugin-dir 這兩個存儲卷,這樣就可以讓 jenkins 容器從 installer 容器繼承已經初始化完成的 Jenkins 配置和插件。這樣就確保 Jenkins 主容器運行起來時,就已經具備了已經下載好的插件,以及正確的全局配置。

自動化安裝 Gogs 和 Nexus

比起 Jenkins 自動化的過程,Gogs 和 Nexus 的自動化安裝就簡單得多了。雖然 Gogs 需要 Postgre 數據庫的支持,我們在工作坊環境中,還是為數據庫配置了 emptyDir 類型的臨時存儲。因此並不提供持久化存儲的支持。給 Nexus 提供的存儲也一樣用的是 emptyDir 臨時存儲。

值得一提的是這兩個軟件啟動后的初始化操作。在工作坊的自動化腳本中,分別對這兩個軟件執行了如下自動化初始化:

  • 在 Gogs 中自動創建賬號,從 GitHub 導入各個微服務的源代碼庫,並配置 WebHook
  • 修改 Nexus 的默認登錄信息為 admin/admin

這些過程,都是借助獨立的集群任務 cicd-installer 完成的。在該任務中,它首先讀入當前 Kubernetes 環境給定的 Service Account 憑據,配置好 kubectl 命令行工具。接着執行以下工作:

  1. 等待 gogs-postgresql 和 gogs 部署完成,調用 Gogs 的 RESTful API 接口,完成用戶注冊和代碼庫導入工作
  2. 等待 nexus 部署完成,調用 Nexus 的 Scripting API(腳本編程)接口,完成管理員密碼的修改

不難發現,雖然都是自動化配置,卻使用了不同的技術。比起 RESTful API 接口,Nexus 的腳本編程接口由於是直接注入腳本,似乎功能會更靈活和強大。不過,過於強大的功能也通常會帶來額外的安全風險。

總結

簡單總結一下,在上面的講解中,用到過的自動化技術有:

  1. 基於 Deployment 實現容器應用自動化部署(自動化部署 Jenkins、Gogs、Nexus 和 Sonarqube 等軟件)
  2. 借助 Pod 的初始化容器(initContainer)實現提前運行自動化任務(在 Jenkins 主容器啟動之前,在 initContainer 中安裝插件)
  3. 借助 Pod 多容器共享存儲卷來跨容器共享文件(在 initContainer 中安裝插件后,由 Jenkins 主容器直接使用)
  4. 借助 Pod 環境變量向應用注入預置的配置(為 Jenkins 指定插件下載源)
  5. 借助 ConfigMap 向應用中直接掛載預置的配置文件(為 Jenkins 預設配置)
  6. 借助 Pod 就緒探針和存活探針,配合 kubectl rollout status 跟蹤檢測應用部署狀態(等待 Gogs、Nexus 部署完成)
  7. 借助 Job 執行一次性任務(cicd-installer)
  8. 使用 Dockerfile 構建容器鏡像(Jenkins 上的自定義 Slave 節點)
  9. 調用應用准備好的腳本自動完成配置(使用 Jenkins 提供的 install-plubins.sh 安裝插件)
  10. 調用應用的 RESTful API 接口導入數據(為 Gogs 注冊用戶並自動導入代碼庫)
  11. 調用應用的 Script API 編程接口自動配置(向 Jenkins 和 Nexus 設置登錄憑據)
  12. 使用模板引擎替換變量引用

到目前,我們詳細地解讀了如何有機地結合使用各種自動化技術,讓工作坊的各個 CI/CD 軟件在 Kubernetes 上完成啟動之后,自動地完成各項自動化配置。由於 Kubernetes 部署 Yaml 文件以及各類自動化配置腳本都是文本文件,因此我們上一篇文章基於 Kubernetes 的基礎設施即代碼中關於“基礎設施即代碼”的兩個要求仍然成立。

最后,工作坊的自動化腳本還沒有提供存儲支持,在實際的項目中應該會有對應的需求;基本上,只要在你的 Kubernetes 集群中配置好集群的存儲類和自動存儲供給支持,要支持存儲並不困難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM