歡迎使用流水線指令-矩陣

  • 2020 年 2 月 14 日
  • 筆記

我經常發現自己需要在一堆不同的配置上執行相同的操作。到目前為止,意味着我需要在流水線上的同一階段製作多個副本。當我需要修改時,必須在整個流水線的多個地方做相同的修改。對於一個更大型的流水線來說,即便維護很少的配置也會變得困難。聲明式流水線1.5.0-beta1(可以從 Jenkins 實驗性更新中心獲取)添加了一個新的 matrix 部分,該部分能讓我一次指定一個階段列表,然後在多個配置上並行運行同一列表。讓我們來看一看!

單一配置流水線

開始我會使用一個帶有構建和測試階段的簡單流水線。我使用 echo 步驟作為構建和測試行為的佔位符。

Jenkinsfile

pipeline {  	agent none      stages {          stage('BuildAndTest') {              agent any              stages {                  stage('Build') {                      steps {                          echo 'Do Build'                      }                  }                  stage('Test') {                      steps {                          echo 'Do Test'                      }                  }              }          }      }  }

多平台與瀏覽器的流水線

我更喜歡在多系統以及瀏覽器結合的情況下執行我的構建和測試。新的 metrix 指令能讓我定義一個 axes 的集合。每個 axis 有一個 name 以及包含了一個或多個 values 的列表。當流水線運行的時候,Jenkins 會將這些託管過來並將每個「軸」上所有可能值的組合運行在我的階段內。一個「矩陣」上所有的元素都是並行運行的(只受限於可用的節點數量)。我的「矩陣」有兩個「軸」: PLATFORMBROWSERPLATFORM 有三個值 BROWSER 有四個值,所以我的階段會運行12個不同的組合。我已經修改了我的 echo 步驟用來使用每個元素中「軸」的值。

Jenkinsfile

pipeline {      agent none      stages {          stage('BuildAndTest') {              matrix {                  agent any                  axes {                      axis {                          name 'PLATFORM'                          values 'linux', 'windows', 'mac'                      }                      axis {                          name 'BROWSER'                          values 'firefox', 'chrome', 'safari', 'edge'                      }                  }                  stages {                      stage('Build') {                          steps {                              echo "Do Build for ${PLATFORM} - ${BROWSER}"                          }                      }                      stage('Test') {                          steps {                              echo "Do Test for ${PLATFORM} - ${BROWSER}"                          }                      }                  }              }          }      }  }

日誌輸出(部分內容)

...  [Pipeline] stage  [Pipeline] { (BuildAndTest)  [Pipeline] parallel  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'edge') (hide)  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'edge')  ...  Do Build for linux - safari  Do Build for linux - firefox  Do Build for windows - firefox  Do Test for linux - firefox  Do Build for mac - firefox  Do Build for linux - chrome  Do Test for windows - firefox  ...

排除無效的組合

現在我已經創建一個基本的「矩陣」了,我注意到我有一些無效的組合。Edge 瀏覽器只在 Windows 系統上運行以及沒有 Linux 版本的 Safari。我可以使用 exclude 命令去掉我的「矩陣」中無效的元素。每個 exclude 含有一個或多個帶有 namevaluesaxis 指令。一個 exclude 中的 axis 指令會生成一組組合(類似於生成「矩陣」中的元素)。「矩陣」中的元素匹配一個 exclude 中所有需要從「矩陣」中移出的值。如果我有不止一個 exclude 指令,每個都將分別評估來移除元素。當需要處理一個長的排除列表時,我可以使用 notValues 而不是 values 去指定「軸」中我們不想排除的值。是的,這有點雙重否定的意思,所以會有一點困惑。我只會在我真正想用的時候才會用它。下面的流水線示例,我排除了 linux, safari 的組合同樣我排除了除了 windows 之外的其他平台 和 edge 瀏覽器的組合。

本流水線使用兩個「軸」,但是沒有使用 axis 指令數量的限制。同樣,在這個流水線里每個 exclude 指定這兩個「軸」的值,但是這不是必須的。如果我們想只在「linux」元素中運行,我們需要使用以下的 exclude

exclude {      axis {          name 'PLATFORM'          notValues 'linux'      }  }
pipeline {      agent none      stages {          stage('BuildAndTest') {              matrix {                  agent any                  axes {                      axis {                          name 'PLATFORM'                          values 'linux', 'windows', 'mac'                      }                      axis {                          name 'BROWSER'                          values 'firefox', 'chrome', 'safari', 'edge'                      }                  }                  excludes {                      exclude {                          axis {                              name 'PLATFORM'                              values 'linux'                          }                          axis {                              name 'BROWSER'                              values 'safari'                          }                      }                      exclude {                          axis {                              name 'PLATFORM'                              notValues 'windows'                          }                          axis {                              name 'BROWSER'                              values 'edge'                          }                      }                  }                  stages {                      stage('Build') {                          steps {                              echo "Do Build for ${PLATFORM} - ${BROWSER}"                          }                      }                      stage('Test') {                          steps {                              echo "Do Test for ${PLATFORM} - ${BROWSER}"                          }                      }                  }              }          }      }  }

日誌輸出(部分內容)

...  [Pipeline] stage  [Pipeline] { (BuildAndTest)  [Pipeline] parallel  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge')  ...  Do Build for linux - firefox  ...

運行時控制元素行為

matrix 指令中同樣我可以添加「每個-元素」指令。這些相同的指令我可以添加到一個 stage 中讓我可以控制「矩陣」中每一個元素的行為。這些指令可以從它們的元素的「軸」中獲取值作為輸入,允許我自定義每一個元素的行為以匹配它的「軸」的值。在我的 Jenkins 服務器中我已經配置了各個節點並為各個節點配置了系統名稱的標籤(「linux-agent」,「windows-agent」,和「mac-agent」 )。為了在正確的操作系統上運行「矩陣」中的元素,我配置了 Groovy 字符模板為元素配置標籤。

matrix {      axes { ... }      excludes { ... }      agent {          label "${PLATFORM}-agent"      }      stages { ... }      // ...  }

有時我通過 Jenkins 的網頁手動運行流水線任務。當我這樣做時,我能夠只選擇一個運行的平台。 axisexclude 指令定義了一個組成「矩陣」的一組靜態的元素。這一組合的集合在運行開始之前就被創建出來,也早於任何的參數獲取。也就意味着我不能在任務已經開始後從「矩陣」上添加或者移除元素。另一方面,「每個-元素」指令,在運行時會被評估。我可以使用「每個-元素」 metrix 中的 when 指令來控制「矩陣」中哪個元素會被執行。我添加了一個帶有平台列表的 choice 字段,以及在 when 指令添加了判斷,這樣會確定是所有的平台都執行還是只執行我指定的平台的元素。

pipeline {      parameters {          choice(name: 'PLATFORM_FILTER', choices: ['all', 'linux', 'windows', 'mac'], description: 'Run on specific platform')      }      agent none      stages {          stage('BuildAndTest') {              matrix {                  agent {                      label "${PLATFORM}-agent"                  }                  when { anyOf {                      expression { params.PLATFORM_FILTER == 'all' }                      expression { params.PLATFORM_FILTER == env.PLATFORM }                  } }                  axes {                      axis {                          name 'PLATFORM'                          values 'linux', 'windows', 'mac'                      }                      axis {                          name 'BROWSER'                          values 'firefox', 'chrome', 'safari', 'edge'                      }                  }                  excludes {                      exclude {                          axis {                              name 'PLATFORM'                              values 'linux'                          }                          axis {                              name 'BROWSER'                              values 'safari'                          }                      }                      exclude {                          axis {                              name 'PLATFORM'                              notValues 'windows'                          }                          axis {                              name 'BROWSER'                              values 'edge'                          }                      }                  }                  stages {                      stage('Build') {                          steps {                              echo "Do Build for ${PLATFORM} - ${BROWSER}"                          }                      }                      stage('Test') {                          steps {                              echo "Do Test for ${PLATFORM} - ${BROWSER}"                          }                      }                  }              }          }      }  }

如果我從 Jenkins 的 UI 頁面上運行流水線設置 PLATFORM_FILTER 字段為 mac ,我會得到如下的輸出:

日誌輸出(部分內容 – PLATFORM_FILTER = 『mac』)

...  [Pipeline] stage  [Pipeline] { (BuildAndTest)  [Pipeline] parallel  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'firefox')  [Pipeline] { (Branch: Matrix - OS = 'linux', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'chrome')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'mac', BROWSER = 'safari')  [Pipeline] { (Branch: Matrix - OS = 'windows', BROWSER = 'edge')  ...  Stage "Matrix - OS = 'linux', BROWSER = 'chrome'" skipped due to when conditional  Stage "Matrix - OS = 'linux', BROWSER = 'firefox'" skipped due to when conditional  ...  Do Build for mac - firefox  Do Build for mac - chrome  Do Build for mac - safari  ...  Stage "Matrix - OS = 'windows', BROWSER = 'chrome'" skipped due to when conditional  Stage "Matrix - OS = 'windows', BROWSER = 'edge'" skipped due to when conditional  ...  Do Test for mac - safari  Do Test for mac - firefox  Do Test for mac - chrome

重要 在 DevOps World | Jenkins World 2019 「聲明式流水線2019:知識點,技巧,以及接下來的事情」中與我一起參與。我會回顧過去的一年有哪些加入到了流水線(包括「矩陣」)以及探討一些關於流水線下一步走向的想法。

結論

這篇博客裏面,我們已經看到了怎樣使用 matrix 指令來構成簡潔但又強大的聲明式流水線。同樣的一個不帶有 matrix 的流水線會容易一些,但會消耗更多的時間同樣也會更難理解和維護。

鏈接

  • Jenkins 實驗性更新中心
  • 使用 Jenkins 實驗性更新中心

譯者:s1mple_zj