歡迎光臨
每天分享高質量文章

基於Pipeline的CI/CD在趣頭條的應用實踐

在今天的趣頭條,隨著業務需求的快速增長,部署與擴容的需求也越來越多,為更快的響應業務需求,業務容器化也隨之加速。本次分享主要介紹在此業務場景下,部署在ECS的服務如何進行容器化的快速接入,使用基於Pipeline的Jenkins實現流程控制及部署,並動態的適配多環境與多叢集。
CI/CD 作為業務自動化部署流水線的重要一環,在容器化快速及頻繁釋出的需求下,迎來了新的挑戰。傳統專案中,使用Shell或Pipeline指令碼,將編譯好的程式碼上傳至伺服器並啟動就算完成了,而在容器環境中,則涉及到更為複雜的流程。

流程概覽

在初期,CI究竟是使用基於Shell的自由風格任務還是基於Pipeline的任務,做個簡單對比來看看:

Shell Pipeline
缺點 單個任務流程不能並行處理任務無法分解至多個節點執行 初期有一定學習成本需要從頭編寫Groovy指令碼
優點 上手簡單對於運維無門檻有大量Shell指令碼可以復用 任務可以按階段分解至不同節點執行且可並行處理友好的視覺化流程圖

基於以上,最終還是選擇了已當前流行的Pipeline為主要任務型別,並據此重新設計了任務流程。看圖:
程式碼:
#! /usr/bin/env groovy

  def Controller(){
      Init.ExtraSettings()

      node(NODE_STAGE_INIT) {
          stage('Stage 1: Pre-Process') {
              // Fetch data from CMDB
              CMDB.FetchConfig()

              if (APP_LANG in APP_LANG_NO_COMPILE) {
                  NODE_STAGE_BUILD = "master"
              } else {
                  NODE_STAGE_BUILD =  APP_LANG
              }
          }
      }

      node(NODE_STAGE_GIT) {
          stage('Stage 2: Checkout & SonarQube') {
              Git.Controller()

              // SonarScanner
              Init.SonarAnalyzer()
          }
      }

      node(NODE_STAGE_BUILD) {
          stage('Stage 3: Build Project') {
              // Compile
              Compile.Controller()
          }
      }

      if ( BUILD_LEGACY == false ) {
          node(NODE_STAGE_DOCKER) {
              stage('Stage 4: Build Image') {
                  // Build & Push Docker image
                  Docker.Controller()
              }
          }

          node(NODE_STAGE_K8S) {
              stage('Stage 5: Deploy to k8s') {
                  k8s.Controller()
              }
          }

          next_stage_id = 6

      } else {
          // 相容釋出至非容器環境
          node(NODE_STAGE_BUILD) {
              stage('Stage 4: Deploy') {
                  Deploy.Legacy()
              }
          }

          next_stage_id = 5
      }

      node(NODE_STAGE_INIT) {
          stage("Stage $next_stage_id: Post-Process") {
              // Report
              Notice.Report()

              // Automatic Test
          }
      }
  }

  return this
- Code stages.groovy 
對每一個關鍵步驟都設定一個開關,便於除錯的同時也更大的增加靈活性。
// Triggers
SKIP_DOCKER   = false
SKIP_KUBE     = false
SKIP_COMPILE  = false
SKIP_GIT      = false
SKIP_TEST     = false
SKIP_SONAR    = true
SKIP_REPORT   = false 
可以在任務配置中透過定義變數來改變行為。


任務配置

每一個任務都可以透過變數進行定義,可接受的變數及其作用如下:
是不是非常複雜?看一眼實際任務配置:
任務的配置在開始構建時從CMDB獲取,但實際工作中總會有一些看似比較合理的需求需要能支援,因此可以在任務配置中定義變數來改寫CMDB的配置,如下圖:
現在,新建Jenkins任務就非常容易了,模組化的函式配合靈活的變數定義,可以輕鬆應對大部分需求。


Stage 1:Pre-Process

獲取專案配置
專案配置資訊直接寫在Jenkins任務配置裡簡單方便,但專案變多時配置維護就變得複雜了,因此需要集中統一管理配置,方便維護的同時,也能將配置許可權下放至專案開發者,配置變更也能更快速。
在每次構建的時候才去獲取配置,確保拿到最新配置。
拿到配置後,使用readJSON將配置解析為相關變數供後續流程使用。
許可權控制
Jenkins內建有多個許可權控制方式,但在多專案+多使用者下進行許可權控制時,情況就變複雜了,其實也不算特別複雜,就是需要勾太多框框,隨著專案與使用者增多,難度呈指數級上升。
因此,在設計流程時,就完全拋棄了Jenkins內建的那幾套,基於CMDB/GitLab進行許可權控制,一個很簡單的思路:
  • 如果你沒許可權訪問程式碼,那麼你可能也不能釋出我的專案

  • 許可權檢查在任務預處理階段進行,不透過則直接終止任務

  • 簡單方便易於實現(且又能少只鍋)


Stage 2:Checkout & SonarQube

Checkout
初始化,專案第一次構建自動進行初始化操作
檢查
  • 檢查Git Repo是否為空

  • 檢查是否選擇了版本

  • 檢查是否需要在指定目錄下進行Checkout

  • 檢查釋出環境與程式碼版本規則是否匹配

  • 最後,輸出程式碼相關資訊,方便事後追溯

SonarQube
SonarQube是一個開源的程式碼質量管理系統,支援25+種語言,可以透過使用外掛機制與Eclipse和Jira等其他外部工具整合,從而實現了對程式碼的質量的全面自動化分析和管理。
整合
SonarQube與Jenkins整合也非常簡單,構建時根據當前專案生成配置,再呼叫SonarScanner就行。
許可權控制
SoanrQube中預設情況下使用者可以看到所有專案,且可檢視原始碼,這肯定不是我們所期望的,因此也需要進行許可權控制,這個思路同上面Jenkins的許可權管理一致,依據程式碼庫許可權進行控制。


Stage 3:Build Project

這裡比較簡單,根據專案語言(APP_LANG),自動匹配相關節點進行構建。
  • PHP專案,如檢測到專案根目錄存在composer.json則呼叫compoer install

  • NodeJS專案,如未提供編譯命令(BUILD_COMMAND),則執行預設指令(npm i)

  • GoLang專案,如未提供編譯命令(BUILD_COMMAND),則執行預設指令(make)


Stage 4:Build Image

終於跟容器有點關係了!
是不是很簡單?
確實很簡單:
  • 查詢並模版檔案

  • 在模版檔案基礎上加入專案資訊,如

    專案程式碼

    為滿足稀奇古怪的需求而特設的指令

  • Build image

  • Push image

看到這裡,也許你會明白為何不用外掛非要不自量力的自己實現。


Stage 5:Deploy to Kubernetes

部署至Kubernetes相對簡單多了,替換deployment.yaml檔案, apply 一下就完了。
不信看這裡:
等等,註意CronJob函式,難免會有些專案需要利用CronJob完成一些定時任務。
需求多的時候,CronJob的管理相當麻煩為了(偷懶)減少不必要的麻煩,我們針對業務需求寫了個小函式(CronJob)自動化處理定時任務。
開發者在自己專案中提供一個檔案,命名為 cronjob.json,在專案釋出時,Pipeline檢測到這個檔案即進行處理,檔案格式大致如下:

[{"id":0
  "time""00 01 * * *"
  "name""test-task"
  "command""echo 'hello world'"
  "description""Hello world"
  "notify""user1@abc.com|user2@abc.com"
  "when""failure"
}] 

  • id為編號,從0開始,必填重要項

  • time為執行任務時間,標準Cron格式

  • name為計劃任務名稱,應與專案相關

  • command為要執行的命令

  • description為備註資訊

  • notify為接收通知者郵件地址,多個之間加 ‘|’, 企業微信傳送

  • when為傳送訊息的條件:

    always

    never

    failure

    success

  • 為避免任務失敗後迴圈執行,所有失敗的任務都會先傳送失敗訊息然後再以成功狀態結束。

如此,計劃任務管理在Kubernetes這部分基本無須人工幹預了。


Q&A;

Q:生成新的映象怎麼自動打新的tag?
A:我們映象Tag使用本次構建選定的Git版本,如分支名稱或者Tag。
Q:你們的Jenkins實體有多少,Jenkins實體是怎麼規劃的?
A:1個Master節點提供UI介面,幾個Agent分別對應不同語言版本和不同環境Kubernetes叢集,執行在容器中。規劃就是按語言或版本分節點,按叢集分節點(Agent)。
Q:Jenkins的許可權控制能否再細化一下?
A:我們這邊許可權實際上是在CMDB中完成的。構建時向CMDB發起查詢請求,傳遞當前專案名稱、選擇的環境、使用者名稱過去,CMDB判斷當前使用者是否有許可權構建選定的環境,允許則傳回專案配置資訊,否則傳回錯誤程式碼,這邊收到後報錯終止。
Q:SonarQube的許可權控制及效能當面?
A:許可權控制使用SonarQube提供的API,將專案跟GitLab中相應專案許可權匹配起來,GitLab中可以檢視這個專案程式碼,那麼SonarQube中就能看到這個專案結果和Code。
Q:Kubernetes的services.yaml檔案在哪裡管理?
A:deployment & service & configmap之類檔案都是提供Git進行版本控制,針對語言有模版,構建時進行替換。
Q: 你們的Pipeline觸發策略是什麼樣的?
A:人工觸發,因為有必須要人工選擇的Git版本。為防止誤釋出,預設沒有選定版本,不選則在預處理時報錯終止。
Q:構建及部署都在容器中?要構建的檔案或製品檔案怎麼存放與管理的?
A:Agent容器啟動時掛載了一個目錄,裡面有全套附屬檔案及Jenkins home目錄。build節點完成自己工作後,其它節點按需接手處理。

Kubernetes入門與進階實戰培訓

本次培訓包括:Docker介紹、Docker映象、網路、儲存、容器安全;Kubernetes架構、設計理念、常用物件、網路、儲存、網路隔離、服務發現與負載均衡;Kubernetes核心元件、Pod、外掛、微服務、雲原生、Kubernetes Operator、叢集災備等,點選瞭解具體培訓內容

7月13日正式上課,點選閱讀原文連結即可報名。
贊(0)

分享創造快樂