Jenkins 配置即程式碼(Configuration as Code)詳解

人艰不拆_zmc發表於2024-08-01

1、概述

  在《Centos7下安裝配置最新版本Jenkins(2.452.3)》這篇博文中講解了如何安裝Jenkins,雖然在安裝Jenkins時安裝了一些必備的推薦外掛,但在企業環境中使用Jenkins之前,我們仍需完成一系列手動配置工作,如配置 System Configuration、Security。

  • System Configuration是確保Jenkins正常執行的關鍵步驟,包括配置全域性環境變數、設定郵件伺服器、配置儲存和歸檔策略;指定JDK、Git、Maven等工具的路徑;配置構建執行的節點和代理等。
  • Security是保護Jenkins系統免受潛在威脅的核心任務,包括配置使用者認證和授權策略;配置跨站請求偽造保護;配置API Token;配置憑證等。

示例 1 配置 System Configuration——System(配置系統)頁面:

  如果你是一名 Jenkins 管理員,那麼你一定不會對上面這個頁面感到陌生,每次部署完一個新的Jenkins例項,在可以使用之前,往往都需要在上面頁面作出一些相應的配置(主目錄、執行器數量、Jenkins URL、系統管理員郵件地址、Resource Root URL等)。 該頁面除了包含 Jenkins 自身的一些基本配置資訊外,同時還包括了當前系統中所安裝的外掛的配置資訊。也就是說,當你的 Jenkins 安裝的外掛越多,該頁面的配置項就有可能會越多。

示例 2 配置 Security——Security(配置全域性安全性)頁面:

  Jenkins 管理員在這裡可以配置認證、授權、代理、跨域等內容。

  而 Jenkins 配置即程式碼(Configuration as Code) ,正是這樣一款能夠幫助我們從這些大量手動配置的工作中解放出來的 Jenkins 外掛。Jenkins Configuration as Code Plugin 允許使用者將System Configuration、Security等 Jenkins 的配置資訊寫入到一個 YAML 檔案中。這樣,可以將 Jenkins 的配置標準化,便於在團隊內部複用,易於傳播、便於快速搭建開箱即用的 Jenkins 服務。藉助這個外掛,我們幾乎不再需要透過人工在 UI 介面上點選的方式來配置 Jenkins 服務。而且,絕大部分其他外掛幾乎(甚至)不需要做任何調整,就可以與該外掛相容。本文將詳細解決如何在Jenkins使用 Jenkins Configuration as Code。

注意 1:Configuration as Code 外掛的主要作用是減少 Jenkins 管理員透過 UI 介面進行手動配置的工作。然而,並不能透過CasC管理的配置檔案安裝 Jenkins 外掛。因此,在使用 Configuration as Code 外掛之前,需要確保 Jenkins 例項已經安裝了配置檔案中涉及到的所有外掛。

2、Jenkins Configuration as Code 詳解

  Jenkins Configuration as Code,又名 JCasC,它允許我們將所有關於 Jenkins 的配置以 YAML 的格式寫入到配置檔案中去,並透過對裝有該外掛的 Jenkins 例項應用這些配置檔案,來實現一鍵式自動化配置 Jenkins 的目的。

  JCasC 為編寫 YAML 檔案提供一系列特定的 Key 值,這些 Key 值分別對應 Jenkins 中不同的配置項。透過為這些 Key 值賦值的方式來達到配置 Jenkins 的目的,下面是官方提供的一個示例配置檔案:

jenkins:
  systemMessage: "Jenkins configured automatically by Jenkins Configuration as Code plugin\n\n"
  securityRealm:
    ldap:
      configurations:
        - groupMembershipStrategy:
            fromUserRecord:
              attributeName: "memberOf"
          inhibitInferRootDN: false
          rootDN: "dc=acme,dc=org"
          server: "ldaps://ldap.acme.org:1636"
  nodes:
    - permanent:
        name: "static-agent"
        remoteFS: "/home/jenkins"
        launcher:
          jnlp:
            workDirSettings:
              disabled: true
              failIfWorkDirIsMissing: false
              internalDir: "remoting"
              workDirPath: "/tmp"
  slaveAgentPort: 50000
  agentProtocols:
    - "jnlp2"
tool:
  git:
    installations:
      - name: git
        home: /usr/local/bin/git
credentials:
  system:
    domainCredentials:
      - credentials:
          - basicSSHUserPrivateKey:
              scope: SYSTEM
              id: ssh_with_passphrase_provided
              username: ssh_root
              passphrase: ${SSH_KEY_PASSWORD}
              description: "SSH passphrase with private key file. Private key provided"
              privateKeySource:
                directEntry:
                  privateKey: ${SSH_PRIVATE_KEY}

  該配置檔案中使用了 JCasC 提供的三個根配置元素 Key 值:jenkins、tool 和 credentials,分別對應 Jenkins 的基本配置項、全域性工具配置項,以及 Jenkins Credentials 相關的配置項。透過為這些根 Key 值所提供的子配置項 Key 設定適當的值,我們分別對 Jenkins 作出瞭如下配置:

  • jenkins
    • systemMessage Key 設定了 Jenkins 的 "System Message" 資訊。
    • securityRealm Key 設定了 LDAP 相關配置,並使其設定為 Jenkins 的認證方式。
    • nodes Key 建立一個名為 static-agent 的節點,並對其進行了適當對配置。
    • slaveAgentPort Key 設定了 Jenkins 主機與節點之間的通訊埠號以及通訊協議。
    • agentProtocols Key 設定 Jenkins 主機與節點之間對通訊協議。
  • tool
    • git 為 Jenkins 的全域性工具 Git 指定了預設執行路徑。
  • credentials
    • 建立一個 ID 為 ssh_with_passphrase_provided 的系統級的 SSH credential。

  除了上面示例中所使用到的三個根配置元素外, unclassified 是另一個非常常見的根配置元素,大部分針對於外掛的配置都被包含在了該根元素下。

  而除了這幾個根配置元素自身外,每個根配置元素下又提供了大量子配置 Key 值,並且根據安裝的外掛的不同,每個 Jenkins 例項所支援的這些子 Key 值也不盡相同。JCasC 提供的Documentation 頁面(Dashboard -> Manage Jenkins -> Configuration as Code -> 頁面最下面參考 -> 文件)列出了當前 Jenkins 例項中所支援的所有 Key 值資訊。

3、Jenkins安裝Configuration as Code Plugin

Jenkins外掛中心安裝Configuration as Code Plugin外掛,安裝完成後需要重啟Jenkins。

安裝成功後,可以看到System Configuration功能模組下多了Configuration as Code功能選項。

滑鼠點選Configuration as Code功能選項進入Configuration as Code功能頁面。功能比較簡單,這裡不再多說。這裡有2個快速編寫jenkins.yaml檔案的小技巧:

  • JCasC 外掛官方文件中提供了大量的 配置示例,這其中包含了幾乎所有關於 Jenkins 的配置以及大部分的外掛配置,參考這些配置示例可幫助我們快速編寫出自己的配置檔案來。
  • 另一中編寫 YAML 配置檔案的小技巧是,首先透過手動的方式在 Jenkin UI 介面做好所有配置,在透過該外掛頁的 “View Configuration” 獲取 JCasC 為我們自動生成出來的配置檔案作為參考,來編寫我們自己的配置檔案。

示例,當前的Jenkins環境僅安裝了一些外掛,並進行了基本的系統配置和安全配置。透過圖形化介面檢視和匯出配置可以生成一個 jenkins.yaml 檔案。下次安裝新的Jenkins時,只需簡單調整這個 jenkins.yaml 檔案即可,無需再次在圖形化介面手動配置這些引數。

除了可以在 UI 介面上操作 CasC 的相關功能,CLI 也有對應的支援。

$ jcli casc
Configuration as Code

Usage:
  jcli casc [command]

Available Commands:
  apply       從應用已有的配置
  export      匯出配置及程式碼的配置
  open        在瀏覽器中開啟配置及程式碼的頁面
  reload      重新載入配置及程式碼的配置
  schema      獲取配置及程式碼的結構

也可以透過Restful介面操作 CasC 的相關功能。

匯出配置 curl -X POST -u admin:112e74ac1ded9b9af4854e594405819df9 http://localhost:8080/configuration-as-code/export
檢視 Schema curl -X POST -u admin:112e74ac1ded9b9af4854e594405819df9 http://localhost:8080/configuration-as-code/schema
重新載入配置 curl -X POST -u admin:112e74ac1ded9b9af4854e594405819df9 http://localhost:8080/configuration-as-code/reload
從請求中應用配置 curl -X POST -u admin:112e74ac1ded9b9af4854e594405819df9 http://localhost:8080/configuration-as-code/apply 

4、啟動Jenkins時CasC配置檔案載入流程

另外,CasC只是配置支援同時載入多個配置檔案。如果我們將 Jenkins 的不同部分拆分成多個檔案,維護起來會很方便。

支援的策略:

  • ErrorOnConflictMergeStrategy(預設):該策略名稱為errorOnConflict;如果多個 YAML 檔案存在衝突,則會引發異常。
  • 覆蓋合併策略:該策略名稱是override;根據載入順序覆蓋配置檔案。

策略名稱的配置方式有兩種:

  • 設定環境CASC_MERGE_STRATEGY
  • 設定系統屬性casc.merge.strategy

5、Jenkins Configuration as Code 配置示例

jenkins:
  mode: EXCLUSIVE
  numExecutors: 0
  scmCheckoutRetryCount: 2
  disableRememberMe: true

  clouds:
    - kubernetes:
        name: "kubernetes"
        serverUrl: "https://kubernetes.default"
        skipTlsVerify: true
        namespace: "devops-system"
        credentialsId: "k8s-service-account"
        jenkinsUrl: "http://devops-jenkins.devops-system:80"
        jenkinsTunnel: "devops-jenkins-agent.devops-system:50000"
        containerCapStr: "10"
        connectTimeout: "60"
        readTimeout: "60"
        maxRequestsPerHostStr: "32"
        templates:
          - name: "base"
            namespace: "devops-system"
            label: "base"
            nodeUsageMode: "NORMAL"
            idleMinutes: 0
            containers:
            - name: "base"
              image: "builder-base:v3.2.2"
              command: "cat"
              args: ""
              ttyEnabled: true
              privileged: false
              resourceRequestCpu: "100m"
              resourceLimitCpu: "4000m"
              resourceRequestMemory: "100Mi"
              resourceLimitMemory: "8192Mi"
            - name: "jnlp"
              image: "jenkins/inbound-agent:4.10-2"
              args: "^${computer.jnlpmac} ^${computer.name}"
              resourceRequestCpu: "50m"
              resourceLimitCpu: "500m"
              resourceRequestMemory: "400Mi"
              resourceLimitMemory: "1536Mi"
            workspaceVolume:
              emptyDirWorkspaceVolume:
                memory: false
            volumes:
            - hostPathVolume:
                hostPath: "/var/run/docker.sock"
                mountPath: "/var/run/docker.sock"
            - hostPathVolume:
                hostPath: "/var/data/jenkins_sonar_cache"
                mountPath: "/root/.sonar/cache"
            yaml: |
              spec:
                affinity:
                  nodeAffinity:
                    preferredDuringSchedulingIgnoredDuringExecution:
                    - weight: 1
                      preference:
                        matchExpressions:
                        - key: node-role.kubernetes.io/worker
                          operator: In
                          values:
                          - ci
                tolerations:
                - key: "node.kubernetes.io/ci"
                  operator: "Exists"
                  effect: "NoSchedule"
                - key: "node.kubernetes.io/ci"
                  operator: "Exists"
                  effect: "PreferNoSchedule"
                containers:
                - name: "base"
                  resources:
                    requests:
                      ephemeral-storage: "1Gi"
                    limits:
                      ephemeral-storage: "10Gi"
                securityContext:
                  fsGroup: 1000

          - name: "nodejs"
            namespace: "devops-system"
            label: "nodejs"
            nodeUsageMode: "EXCLUSIVE"
            idleMinutes: 0
            containers:
            - name: "nodejs"
              image: "builder-nodejs:v3.2.0"
              command: "cat"
              args: ""
              ttyEnabled: true
              privileged: false
              resourceRequestCpu: "100m"
              resourceLimitCpu: "4000m"
              resourceRequestMemory: "100Mi"
              resourceLimitMemory: "8192Mi"
            - name: "jnlp"
              image: "jenkins/inbound-agent:4.10-2"
              args: "^${computer.jnlpmac} ^${computer.name}"
              resourceRequestCpu: "50m"
              resourceLimitCpu: "500m"
              resourceRequestMemory: "400Mi"
              resourceLimitMemory: "1536Mi"
            workspaceVolume:
              emptyDirWorkspaceVolume:
                memory: false
            volumes:
            - hostPathVolume:
                hostPath: "/var/run/docker.sock"
                mountPath: "/var/run/docker.sock"
            - hostPathVolume:
                hostPath: "/var/data/jenkins_nodejs_yarn_cache"
                mountPath: "/root/.yarn"
            - hostPathVolume:
                hostPath: "/var/data/jenkins_nodejs_npm_cache"
                mountPath: "/root/.npm"
            - hostPathVolume:
                hostPath: "/var/data/jenkins_sonar_cache"
                mountPath: "/root/.sonar/cache"
            yaml: |
              spec:
                affinity:
                  nodeAffinity:
                    preferredDuringSchedulingIgnoredDuringExecution:
                    - weight: 1
                      preference:
                        matchExpressions:
                        - key: node-role.kubernetes.io/worker
                          operator: In
                          values:
                          - ci
                tolerations:
                - key: "node.kubernetes.io/ci"
                  operator: "Exists"
                  effect: "NoSchedule"
                - key: "node.kubernetes.io/ci"
                  operator: "Exists"
                  effect: "PreferNoSchedule"
                containers:
                - name: "nodejs"
                  resources:
                    requests:
                      ephemeral-storage: "1Gi"
                    limits:
                      ephemeral-storage: "10Gi"
                securityContext:
                  fsGroup: 1000

         
  securityRealm:
    ldap:
      configurations:
      - displayNameAttributeName: "uid"
        mailAddressAttributeName: "mail"
        inhibitInferRootDN: false
        managerDN: "cn=admin,dc=zmc,dc=io"
        managerPasswordSecret: "admin"
        rootDN: "dc=zmc,dc=io"
        userSearchBase: "ou=Users"
        userSearch: "(&(objectClass=inetOrgPerson)(|(uid={0})(mail={0})))"
        groupSearchBase: "ou=Groups"
        groupSearchFilter: "(&(objectClass=posixGroup)(cn={0}))"
        server: "ldap://openldap.kubernetes-system.svc:389"
      disableMailAddressResolver: false
      disableRolePrefixing: true


unclassified:
  gitLabServers:
    servers:
    - name: "https://gitlab.com"
      serverUrl: "https://gitlab.com"

以上jenkins.yaml配置檔案是基於CASC外掛的,用於在Jenkins例項啟動時自動載入配置。它配置了Jenkins的各個方面,包括執行模式、Kubernetes叢集的連線和Pod模板、安全設定等。以下是對每個部分的解釋:

5.1 Jenkins主配置

jenkins:
  mode: EXCLUSIVE
  numExecutors: 0
  scmCheckoutRetryCount: 2
  disableRememberMe: true
  • mode: EXCLUSIVE: Jenkins的操作模式,設定為“EXCLUSIVE”表示Jenkins將只在設定的節點上執行作業,而不是在主節點上。
  • numExecutors: 0: 主節點上的執行器數量設定為0,表示不在主節點上執行任何作業。
  • scmCheckoutRetryCount: 2: SCM(原始碼管理)檢出重試次數,預設為2次。
  • disableRememberMe: true: 禁用“記住我”功能,提高安全性。

5.1.1 Kubernetes Cloud配置

clouds:
    - kubernetes:
        name: "kubernetes"
        serverUrl: "https://kubernetes.default"
        skipTlsVerify: true
        namespace: "devops-system"
        credentialsId: "k8s-service-account"
        jenkinsUrl: "http://devops-jenkins.devops-system:80"
        jenkinsTunnel: "devops-jenkins-agent.devops-system:50000"
        containerCapStr: "10"
        connectTimeout: "60"
        readTimeout: "60"
        maxRequestsPerHostStr: "32"
  • name: Kubernetes雲的名稱。
  • serverUrl: Kubernetes API伺服器的URL。
  • skipTlsVerify: 是否跳過TLS驗證,設定為true表示跳過。
  • namespace: 用於執行Jenkins agent的名稱空間。
  • credentialsId: Kubernetes憑據的ID。
  • jenkinsUrl: Jenkins例項的URL。
  • jenkinsTunnel: Jenkins和Kubernetes agent之間的隧道地址。
  • containerCapStr: 最大容器數量。
  • connectTimeout 和 readTimeout: 連線和讀取超時時間。
  • maxRequestsPerHostStr: 每個主機的最大請求數。

5.1.2 Pod模板

配置了多個Pod模板,用於不同的構建環境。每個模板都包含具體的容器配置、資源請求和限制、儲存卷以及親和性等。示例中配置了base、nodejs,如需其他容器模板的話,按需新增即可。

    templates:
          - name: "base"
            namespace: "devops-system"
            label: "base"
            nodeUsageMode: "NORMAL"
            idleMinutes: 0
                ......
                securityContext:
                  fsGroup: 1000
  • name: Pod模板的名稱。
  • namespace: 執行Pod的名稱空間。
  • label: Pod的標籤,用於在Jenkins作業中指定節點。
  • nodeUsageMode: 節點使用模式。
  • containers: 包含在Pod中的容器配置。
  • workspaceVolume: Jenkins工作空間的卷配置。
  • volumes: 掛載在Pod中的卷。

5.1.3 安全配置

securityRealm:
    ldap:
      configurations:
      - displayNameAttributeName: "uid"
        mailAddressAttributeName: "mail"
        inhibitInferRootDN: false
        managerDN: "cn=admin,dc=zmc,dc=io"
        managerPasswordSecret: "admin"
        rootDN: "dc=zmc,dc=io"
        userSearchBase: "ou=Users"
        userSearch: "(&(objectClass=inetOrgPerson)(|(uid={0})(mail={0})))"
        groupSearchBase: "ou=Groups"
        groupSearchFilter: "(&(objectClass=posixGroup)(cn={0}))"
        server: "ldap://openldap.kubernetes-system.svc:389"
      disableMailAddressResolver: false
      disableRolePrefixing: true
  • ldap: 配置LDAP作為安全域。
  • configurations: LDAP伺服器配置,包括DN、密碼、搜尋基準等。
  • server: LDAP伺服器地址。

5.2 未分類配置

unclassified:
  gitLabServers:
    servers:
    - name: "https://gitlab.com"
      serverUrl: "https://gitlab.com"
  • gitLabServers: GitLab伺服器配置。

6、總結

  Jenkins 配置即程式碼(Configuration as Code) 能夠幫助我們從大量手動配置Jenkins的工作中解放出來。Jenkins Configuration as Code Plugin 允許使用者將System Configuration、Security等 Jenkins 的配置資訊寫入到一個 YAML 檔案中。這樣,可以將 Jenkins 的配置標準化,便於在團隊內部複用,易於傳播、便於快速搭建開箱即用的 Jenkins 服務。藉助這個外掛,我們幾乎不再需要透過人工在 UI 介面上點選的方式來配置 Jenkins 服務(實現Jenkins零配置)。而且,絕大部分其他外掛幾乎(甚至)不需要做任何調整,就可以與該外掛相容。

  對於CASC管理的配置檔案,除了這幾個根配置元素自身外,每個根配置元素下又提供了大量子配置 Key 值,並且根據安裝的外掛的不同,每個 Jenkins 例項所支援的這些子 Key 值也不盡相同。JCasC 提供的Documentation 頁面(Dashboard -> Manage Jenkins -> Configuration as Code -> 頁面最下面參考 -> 文件)列出了當前 Jenkins 例項中所支援的所有 Key 值資訊(如果不清楚配置放到哪個配置元素下,一定要看當前Jenkins例項的Documentation 頁面)。

  Jenkins 的外掛成千上萬,Jenkins 的配置也同樣千變萬化,關於更多如何編寫 JCasC 配置檔案,請參考官方示例

主要參考:https://github.com/jenkinsci/configuration-as-code-plugin

主要參考:https://www.jenkins-zh.cn/tutorial/management/plugin/configuration-as-code/

主要參考:https://www.jianshu.com/p/cc459cb06dd7

相關文章