Jenkins 配置即代码(Configuration as Code)详解

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.2 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.3 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.4 安全配置

复制代码
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.4 未分类配置

复制代码
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零配置)。**而且,绝大部分其他插件几乎(甚至)不需要做任何调整,就可以与该插件兼容。
  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