K8S-DevOps基础和环境搭建

K8S-DevOps基础和环境搭建

在本章节开始前,需要了解的概念较多,在本文前半部分会将对应的概念、命令先进行解释。读者可以先选择阅读,或者先完成搭建部分的实验,当出现概念性的疑惑时,再回头翻阅。

DevOps

DevOps 是开发(Development)和运维(Operations)团队之间的协作文化与实践,旨在提高软件开发和发布的效率、质量以及可靠性。DevOps 强调通过自动化、持续集成、持续交付等方式,将开发和运维团队的工作流程更加紧密地结合在一起,消除传统上开发和运维之间的障碍

DevOps 关键要素:

1、协作文化:开发、运维和其他团队紧密合作,共同关注软件的整个生命周期。

2、自动化:自动化部署、测试、监控等环节,以减少人为错误和提高效率。

3、持续集成(CI):开发人员频繁将代码集成到共享代码库中,自动化构建和测试,减少集成问题。

4、持续交付(CD):自动将经过测试的代码部署到生产环境,确保软件可以随时发布到生产环境中。

5、监控和反馈:实时监控系统的性能和用户反馈,快速响应和迭代改进。

CI/CD

CI/CD 是 DevOps 中的核心实践,分别指 持续集成(Continuous Integration)和 持续交付/部署(Continuous Delivery/Continuous Deployment)。这两者帮助团队频繁而高效地发布软件,提高软件质量,并降低发布风险

持续集成(CI):持续集成是一种开发实践,指开发人员频繁地将代码提交到共享的代码仓库,并自动化构建和测试过程。目标是通过频繁集成代码,尽早发现和解决集成问题,确保主分支始终是可构建和可测试的。CI可以提早发现问题,减少集成冲突。增强代码的稳定性和质量。

持续交付(CD):持续交付指的是在持续集成的基础上,自动将通过所有测试的代码部署到一个类似生产环境的环境中,确保软件始终能够安全、快速地交付。目标是通过自动化流程,使软件在任何时刻都能以稳定的状态部署到生产环境。CD可以快速交付新功能或修复。减少手动干预,提高部署的频率和可靠性

持续部署(CD):持续部署是一种扩展的持续交付实践,指的是代码在经过所有自动化测试后,直接自动部署到生产环境,不需要人工审批。目标是通过完全自动化的发布流程,减少延迟,提高发布速度。CD可以实现“零等待”的交付模型。每次代码更新后都能迅速部署到生产环境。

JenKins

Jenkins 是一个开源的自动化服务器,广泛用于实现 持续集成(CI) 和 持续交付(CD)。它支持通过自动化脚本来构建、测试和部署软件。Jenkins 使开发团队能够频繁地集成代码,并确保代码质量和应用程序的可靠性,从而加速软件开发周期。

Jenkins 的核心功能:

1、自动化构建与测试: Jenkins 可以自动化地构建、测试和发布软件,减少手动操作,提高工作效率。每当代码被提交时,Jenkins 会自动触发构建和测试。

2、插件支持: Jenkins 提供了大量插件,能够与许多工具和服务集成,例如 Git、Maven、Docker、Kubernetes 等,帮助实现持续集成和持续交付的完整流水线。

3、支持分布式构建: Jenkins 支持通过 分布式构建 来加速构建过程。可以设置多个构建代理节点,分配构建任务,进一步提升性能。

4、易于扩展: 通过其插件架构,Jenkins 可以轻松扩展支持新的开发、构建、测试、部署工具。插件包括版本控制、代码分析、通知等功能。

5、流水线支持: Jenkins 允许使用 Jenkins Pipeline 进行复杂的构建和部署流程定义,支持将流水线视为代码进行版本控制和管理。Jenkins Pipeline 可以通过 Jenkinsfile 文件进行定义,实现灵活的自动化流程。

6、监控和通知: Jenkins 可以监控构建和测试的状态,并通过电子邮件、Slack、短信等方式发送通知。用户可以实时查看构建的成功或失败,及时采取措施。

Jenkins 工作流程:

1、源码管理: Jenkins 通过插件支持与版本控制系统(如 Git、SVN 等)集成。当开发人员将代码提交到版本控制系统时,Jenkins 会自动检测到变更。

2、触发构建: 每当代码被提交或发生其他指定事件(如定时触发),Jenkins 会启动一个新的构建任务,执行预设的构建脚本。

3、构建与测试: Jenkins 会执行指定的构建工具(如 Maven、Gradle、Ant 等)进行编译、打包和单元测试。如果代码通过了所有测试,Jenkins 会继续执行后续步骤,如发布和部署。

4、持续交付和部署: 在持续交付流水线中,Jenkins 可以自动将构建后的应用程序部署到开发、测试或生产环境,并进行自动化测试。

Jenkins 主要概念:

1、Job: Jenkins 的最基本执行单元。一个 Job 是一组构建任务的集合,可以通过配置不同的构建步骤来定义它的执行流程。

2、Pipeline: Jenkins Pipeline 是一个可扩展的自动化过程,能够实现复杂的构建、测试和部署任务。通过定义流水线,团队可以把构建和部署流程视为代码,进行版本控制和重用。

3、Executor: Executor 是 Jenkins 中用来执行构建的工作单元。每个 Jenkins 节点上可以有多个 executor,它们可以并行地执行多个构建任务。

4、Node: Node 是 Jenkins 的一个执行环境,可以是 Jenkins 主机或从机。Node 可以被视为执行 Job 的机器。

5、Workspace: Workspace 是 Jenkins 节点上的一个目录,用来存储构建过程中产生的文件。

Webhook

Webhook 是一种轻量级的、基于 HTTP 的回调机制,用于实现系统间的实时通信。当某个事件发生时,一个系统(发送方)会通过 HTTP 请求向另一个系统(接收方)发送数据或通知。这种机制非常适合在不同系统之间实现自动化和事件驱动的交互。

Webhook 的关键特性:

1、事件驱动: Webhook 基于事件触发。当发送方系统中发生特定事件(例如:用户提交表单、代码库有新提交等),它会立即向接收方发送请求。

2、实时性: 相比传统的轮询方式(客户端定期向服务端检查是否有更新),Webhook 是实时的,节省了系统资源并降低了延迟。

3、轻量且灵活: Webhook 使用 HTTP POST 请求发送数据,接收方只需提供一个可以接受 HTTP 请求的端点,便能轻松集成。

Webhook 的工作流程

1、注册 Webhook: 接收方需要在发送方系统中配置 Webhook(通常包括一个回调 URL 和需要监听的事件类型)。

2、事件触发: 当发送方系统中发生了配置的事件(例如:代码库有新的推送),它会通过 HTTP POST 请求向回调 URL 发送事件数据。

3、接收并处理: 接收方系统接收 Webhook 请求,解析数据并根据事件类型执行相应操作(例如:发送通知、触发其他自动化任务等)。

比如当有人向 GitHub 仓库提交代码(push 事件)时,GitHub 会触发 Webhook,通知你的服务器或 CI/CD 工具(如 Jenkins)启动构建流程

Maven

Maven 是一个基于 Java 的项目管理工具,主要用于构建、依赖管理和项目生命周期管理。

Maven的主要功能:

1、构建工具:Maven 可以自动化编译、打包、测试和部署过程,简化软件构建流程。

2、依赖管理:Maven 使用配置文件(pom.xml)来定义项目所需的库(依赖),并从中央仓库或自定义仓库下载这些依赖。

3、项目生命周期管理:Maven 定义了一套标准化的项目生命周期,包括清理(clean)、编译(compile)、测试(test)、打包(package)、安装(install)和部署(deploy)等。

4、多模块项目支持:Maven 支持在一个主项目下管理多个子模块,适合大型软件开发。

5、插件扩展:Maven 的功能通过插件实现,例如编译插件、测试插件、打包插件等,可以根据需求扩展功能。

JNLP (Java Network Launch Protocol)

JNLP(Java Network Launch Protocol,Java 网络启动协议)是一种用于启动 Java 应用程序的协议,它通过 XML 格式的文件来描述如何下载、启动和运行 Java 应用程序。JNLP 允许用户从网络上启动 Java Web Start 应用程序,而无需在本地机器上进行复杂的安装。

JNLP 协议的工作原理:

JNLP 通过 .jnlp 文件将应用程序的元数据传递给 Java Web Start(JWS),然后 JWS 根据文件中指定的信息下载所需的 JAR 文件、类和资源,并启动 Java 应用程序。该协议支持应用程序的自动更新、缓存和跨平台运行。

1、JNLP 文件:用户点击一个包含 JNLP 文件的链接(通常是 .jnlp 后缀的文件)。文件是一个 XML 格式的文档,描述了应用程序的相关信息,包括主类、JAR 文件、应用程序依赖的资源和属性等。

2、Java Web Start 启动:用户的计算机上安装了 Java 运行时环境(JRE),并且启用了 Java Web Start。浏览器会启动 Java Web Start 客户端,该客户端读取 JNLP 文件,并解析出应用程序的配置。

3、下载资源:Java Web Start 会自动下载 JNLP 文件中列出的 JAR 文件及其他资源。如果资源已经存在且没有更新,则直接从本地缓存加载。

4、运行应用程序:下载和缓存的 JAR 文件和资源被加载到 Java 虚拟机(JVM)中,应用程序启动并运行。

5、自动更新:如果 JNLP 文件中指定了更新策略,Java Web Start 会在每次启动时检查是否有新版本的应用程序可供下载,并自动更新本地缓存。

流水线

声明式流水线

在声明式流水线语法中,流水线过程定义在 Pipeline{}中,Pipeline 块定义了整个流水线中完成的所有工作

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                //
            }
        }
        stage('Test') {
            steps {
                //
            }
        }
        stage('Deploy') {
            steps {
                //
            }
        }
    }
}
参数 解析
agent any 流水线可以在任何可用的 Jenkins Agent 节点上运行,也就是执行流水线过程的位置,也可以指定到具体的节点
stage 定义流水线的执行过程(相当于一个阶段),比如上文所示的 Build、Test、Deploy,但是这个名字是根据实际情况进行定义的,并非固定的名字
steps 执行某阶段具体的步骤

一个以声明式流水线的语法编写的 Jenkinsfile 文件如下

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'make'
            }
        }
        stage('Test') {
            steps {
                sh 'make check'
                junit 'reports/**/*.xml'
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}
参数 解析
sh 执行一个 shell 命令
junit Jenkins 内置的 JUnit 插件解析测试报告
脚本化流水线

在脚本化流水线语法中,会有一个或多个 Node(节点)块在整个流水线中执行核心工作,比如:

node {
    stage('Build') {
        //
    }
    stage('Test') {
        //
    }
    stage('Deploy') {
        //
    }
}
参数 解析
node 在任何可用的代理上执行流水线或它的任何阶段,也可以指定到具体的节点
stage 和声明式的含义一致,定义流水线的阶段。Stage 块在脚本化流水线语法中是可选的,然而在脚本化流水线中实现 stage 块,可以清楚地在 Jenkins UI 界面中显示每个stage 的任务子集

声明式 Pipeline 语法

声明式流水线必须包含在一个 Pipeline 块中,比如以下是一个 Pipeline 块的格式:

pipeline {
    /* insert Declarative Pipeline here */
}

在声明式流水线中有效的基本语句和表达式遵循与 Groovy 的语法同样的规则,但有以下例外:

1、流水线顶层必须是一个 block,即 pipeline{};

2、分隔符可以不需要分号,但是每条语句都必须在自己的行上;

3、块只能由 Sections、Directives、Steps 或 assignment statements 组成;

4、属性引用语句被当做是无参数的方法调用,比如 input 会被当做 input()

1、Sections

声明式流水线中的 Sections 不是一个关键字或指令,而是包含一个或多个 Agent、Stages、post、Directives 和 Steps 的代码区域块

Agent

Agent 表示整个流水线或特定阶段中的步骤和命令执行的位置,该部分必须在 pipeline 块的顶层被定义,也可以在 stage 中再次定义,但是 stage 级别是可选的

any:在任何可用的代理上执行流水线,配置语法

pipeline {
    agent any
}

none:表示该 Pipeline 脚本没有全局的 agent 配置。当顶层的 agent 配置为 none 时,每个 stage 部分都需要包含它自己的 agent。配置语法:

pipeline {
    agent none
    stages {
        stage('Stage For Build') {
            agent any
        }
    }
}

label:选择某个具体的节点执行 Pipeline 命令,例如:agent { label 'my-defined-label' }。配置语法:

pipeline {
    agent none
    stages {
        stage('Stage For Build') {
            agent { label 'my-slave-label' }
        }
    }
}

node:和 label 配置类似,只不过是可以添加一些额外的配置,比如 customWorkspace

dockerfile:使用从源码中包含的 Dockerfile 所构建的容器执行流水线或 stage。此时对应的 agent 写法如下:

agent {
    dockerfile {
        filename 'Dockerfile.build'
        dir 'build'
        label 'my-defined-label'
        additionalBuildArgs '--build-arg version=1.0.2'
    }
}
参数 解析
dockerfile 表示将使用一个 Dockerfile 来构建和运行 Pipeline 的环境
filename 指定使用的 Dockerfile 文件名为 Dockerfile.build。默认情况下,Jenkins 使用 Dockerfile,如果文件名不同需要通过此字段指定
dir 指定 Dockerfile 文件所在的目录为 build。Jenkins 会在 build 目录下查找 Dockerfile.build 文件
additionalBuildArgs 为 Docker 构建过程添加额外的参数。这里通过 --build-arg 传递了构建时参数 version=1.0.2,可以在 Dockerfile 中通过 ARG version 使用

docker:相当于 dockerfile,可以直接使用 docker 字段指定外部镜像即可,可以省去构建的时间。比如使用 maven 镜像进行打包,同时可以指定 args:

agent {
    docker {
        image 'maven:3-alpine'
        label 'my-defined-label'
        args '-v /tmp:/tmp'
    }
}

kubernetes:Jenkins 也支持使用 Kubernetes 创建 Slave,也就是常说的动态 Slave。配置示例如下:

agent {
    kubernetes {
        label podlabel
        yaml """
        kind: Pod
        metadata:
          name: jenkins-agent
        spec:
          containers:
            - name: kaniko
              image: gcr.io/kaniko-project/executor:debug
              imagePullPolicy: Always
              command:
                - /busybox/cat
              tty: true
              volumeMounts:
                - name: aws-secret
                  mountPath: /root/.aws/
                - name: docker-registry-config
                  mountPath: /kaniko/.docker
          restartPolicy: Never
          volumes:
            - name: aws-secret
              secret:
                secretName: aws-secret
            - name: docker-registry-config
              configMap:
                name: docker-registry-config
        """
    }
}
配置示例

有一个 Java 项目,需要用 mvn 命令进行编译,此时可以使用 maven 的镜像作为 agent。配置如下:

pipeline {
    agent { 
        docker 'maven:3-alpine' 
    }
    stages {
        stage('Example Build') {
            steps {
                sh 'mvn -B clean verify'
            }
        }
    }
}

在流水线顶层将 agent 定义为 none,那么此时 stage 部分就需要必须包含它自己的 agent 部分。在 stage('Example Build')部分使用 maven:3-alpine 执行该阶段步骤,在stage('Example Test')部分使用 openjdk:8-jre 执行该阶段步骤。此时 Pipeline 如下:

pipeline {
    agent none
    stages {
        stage('Example Build') {
            agent { docker 'maven:3-alpine' }
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' }
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}

上述的示例也可以用基于 Kubernetes 的 agent 实现。比如定义具有三个容器的 Pod,分别为 jnlp(负责和 Jenkins Master 通信)、build(负责执行构建命令)、kubectl(负责执行 Kubernetes相关命令),在 steps 中可以通过 containers 字段,选择在某个容器执行命令:

pipeline {
    agent {
        kubernetes {
            cloud 'kubernetes-default'
            slaveConnectTimeout 1200
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: jnlp
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      imagePullPolicy: IfNotPresent
      args: ['$(JENKINS_SECRET)', '$(JENKINS_NAME)']

    - name: build
      image: 'registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3'
      imagePullPolicy: IfNotPresent
      command: ['cat']
      tty: true

    - name: kubectl
      image: 'registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17'
      imagePullPolicy: IfNotPresent
      command: ['cat']
      tty: true
'''
        }
    }
    stages {
        stage('Building') {
            steps {
                container(name: 'build') {
                    sh 'mvn clean install'
                }
            }
        }
        stage('Deploy') {
            steps {
                container(name: 'kubectl') {
                    sh 'kubectl get node'
                }
            }
        }
    }
}
Post

Post 一般用于流水线结束后的进一步处理,比如错误通知等。Post 可以针对流水线不同的结果做出不同的处理,就像开发程序的错误处理,比如 Python 语言的 try catch。Post 可以定义在Pipeline 或 stage 中,目前支持以下条件

参数 解析
always 无论 Pipeline 或 stage 的完成状态如何,都允许运行该 post 中定义的指令
changed 只有当前 Pipeline 或 stage 的完成状态与它之前的运行不同时,才允许在该post 部分运行该步骤
fixed 当本次 Pipeline 或 stage 成功,且上一次构建是失败或不稳定时,允许运行该post 中定义的指令
regression 当本次 Pipeline 或 stage 的状态为失败、不稳定或终止,且上一次构建的状态为成功时,允许运行该 post 中定义的指令
failure 只有当前 Pipeline 或 stage 的完成状态为失败(failure),才允许在 post 部分运行该步骤,通常这时在 Web 界面中显示为红色
success 当前状态为成功(success),执行 post 步骤,通常在 Web 界面中显示为蓝色或绿色
unstable 当前状态为不稳定(unstable),执行 post 步骤,通常由于测试失败或代码违规等造成,在 Web 界面中显示为黄色
aborted 当前状态为终止(aborted),执行该 post 步骤,通常由于流水线被手动终止触发,这时在 Web 界面中显示为灰色
unsuccessful 当前状态不是 success 时,执行该 post 步骤
cleanup 无论 pipeline 或 stage 的完成状态如何,都允许运行该 post 中定义的指令。和 always 的区别在于,cleanup 更专注于在流水线完成后执行清理工作,适合停止资源、清理临时文件等

一般情况下 post 部分放在流水线的底部,比如本实例,无论 stage 的完成状态如何,都会输出一条 I will always say Hello again!信息(也可以把 post 写在 stage位置)

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo "Hello World"
            }
        }
    }
    post {
        always {
            echo 'I will always say Hello again!'
        }
    }
}
stages {
    stage('Test') {
        steps {
            sh 'EXECUTE_TEST_COMMAND'
        }
        post {
            failure {
                echo "Pipeline Testing failure..."
            }
        }
    }
}
Stages

Stages 包含一个或多个 stage 指令,同时可以在 stage 中的 steps 块中定义真正执行的指令。比如创建一个流水线,stages 包含一个名为 Example 的 stage,该 stage 执行 echo 'Hello World'命令输出 Hello World 字符串

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo "Hello World ${env.BUILD_ID}"
            }
        }
    }
}
Steps

Steps 部分在给定的 stage 指令中执行的一个或多个步骤,比如在 steps 定义执行一条 shell 命令,或者是使用sh字段执行多条指令

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                sh """
                    echo 'Execute building...'
                    mvn clean install
                """
            }
        }
    }
}
2、Directives

Directives 可用于一些执行 stage 时的条件判断或预处理一些数据,和 Sections 一致,Directives不是一个关键字或指令,而是包含了 environment、options、parameters、triggers、stage、tools、input、when 等配置

Environment

Environment 主要用于在流水线中配置的一些环境变量,根据配置的位置决定环境变量的作用域。可以定义在 pipeline 中作为全局变量,也可以配置在 stage 中作为该 stage 的环境变量。该指令支持一个特殊的方法 credentials(),该方法可用于在 Jenkins 环境中通过标识符访问预定义的凭证。对于类型为 Secret Text 的凭证,credentials()可以将该 Secret 中的文本内容赋值给环境变量。对于类型为标准的账号密码型的凭证,指定的环境变量为 username 和 password,并且也会定义两个额外的环境变量,分别为 MYVARNAME_USR 和 MYVARNAME_PSW。

假如需要定义个变量名为 CC 的全局变量和一个名为 AN_ACCESS_KEY 的局部变量,并且用 credentials 读取一个 Secret 文本,可以通过以下方式定义:(注意:凭据需要在Jenkins的UI界面进行配置,此处只是讲解语法)

pipeline {
    agent any
    environment { 
        // Pipeline 中定义,属于全局变量
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment { 
                // 定义在 stage 中,属于局部变量
                AN_ACCESS_KEY = credentials('my-prefined-secret-text')
            }
            steps {
                sh 'printenv'
            }
        }
    }
}
Options

Jenkins 流水线支持很多内置指令,比如 retry 可以对失败的步骤进行重复执行 n 次,可以根据不同的指令实现不同的效果。比较常用的指令如下

参数 解析
buildDiscarder 保 留 多 少 个 流 水 线 的 构 建 记 录 。 比 如 : options{ buildDiscarder(logRotator(numToKeepStr: '1')) }
disableConcurrentBuilds 禁止流水线并行执行,防止并行流水线同时访问共享资源导致流水线失败。比如:options { disableConcurrentBuilds() }
disableResume 如 果 控 制 器 重 启 , 禁 止 流 水 线 自 动 恢 复 。 比 如 : options{ disableResume() }
newContainerPerStage agent 为 docker 或 dockerfile 时,每个阶段将在同一个节点的新 容 器 中 运 行 , 而 不 是 所 有 的 阶 段 都 在 同 一 个 容 器 中 运 行 。 比 如 : options{ newContainerPerStage () }
quietPeriod 流水线静默期,也就是触发流水线后等待一会在执行。比如:options{ quietPeriod(30) }
retry 流水线失败后重试次数。比如:options { retry(3) }
timeout 设置流水线的超时时间,超过流水线时间,job 会自动终止。比如:options{ timeout(time: 1, unit: 'HOURS') }
timestamps 为控制台输出时间戳。比如:options { timestamps() }

配置示例如下,只需要添加 options 字段即可

pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS')  // 设置管道超时时间为1小时
        timestamps()  // 在输出中添加时间戳
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'  // 输出 Hello World
            }
        }
    }
}

Option 除了写在 Pipeline 顶层,还可以写在 stage 中,但是写在 stage 中的 option 仅支持 retry、timeout、timestamps,或者是和 stage 相关的声明式选项,比如 skipDefaultCheckout。处于 stage级别的 options 写法如下

pipeline {
    agent any
    stages {
        stage('Example') {
            options {
                timeout(time: 1, unit: 'HOURS')  // 设置该阶段超时为 1 小时
            }
            steps {
                echo 'Hello World'  // 打印 Hello World
            }
        }
    }
}
Parameters

Parameters 提供了一个用户在触发流水线时应该提供的参数列表,这些用户指定参数的值可以通过 params 对象提供给流水线的 step(步骤)

目前支持的参数类型如下

参数 解析
string 字符串类型的参数,例如:parameters { string(name: 'DEPLOY_ENV', defaultValue:'staging', description: '') },表示定义一个名为 DEPLOY_ENV 的字符型变量,默认值为staging
text 文本型参数,一般用于定义多行文本内容的变量。例如 parameters { text(name:'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '') },表示定义一个名为 DEPLOY_TEXT 的变量,默认值是'One\nTwo\nThree\n'
booleanParam 布尔型参数,例如: parameters { booleanParam(name: 'DEBUG_BUILD',defaultValue: true, description: '') }
choice 选择型参数,一般用于给定几个可选的值,然后选择其中一个进行赋值,例如:parameters { choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '') },表<示定义一个名为 CHOICES 的变量,可选的值为 one、two、three
password 密码型变量,一般用于定义敏感型变量,在 Jenkins 控制台会输出为*。例如:parameters { password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Asecret password') },表示定义一个名为 PASSWORD 的变量,其默认值为 SECRET

Parameters 用法如下:

pipeline {
    agent any
    parameters {
        string(
            name: 'PERSON', 
            defaultValue: 'Mr Jenkins', 
            description: 'Who should I say hello to?'
        )
        text(
            name: 'BIOGRAPHY', 
            defaultValue: '', 
            description: 'Enter some information about the person'
        )
        booleanParam(
            name: 'TOGGLE', 
            defaultValue: true, 
            description: 'Toggle this value'
        )
        choice(
            name: 'CHOICE', 
            choices: ['One', 'Two', 'Three'], 
            description: 'Pick something'
        )
        password(
            name: 'PASSWORD', 
            defaultValue: 'SECRET', 
            description: 'Enter a password'
        )
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
                echo "Biography: ${params.BIOGRAPHY}"
                echo "Toggle: ${params.TOGGLE}"
                echo "Choice: ${params.CHOICE}"
                echo "Password: ${params.PASSWORD}"
            }
        }
    }
}
Triggers

在 Pipeline 中可以用 triggers 实现自动触发流水线执行任务,可以通过 Webhook、Cron、pollSCM 和 upstream 等方式触发流水线。

假如某个流水线构建的时间比较长,或者某个流水线需要定期在某个时间段执行构建,可以使用 cron 配置触发器,比如周一到周五每隔四个小时执行一次:(注意:H 的意思不是 HOURS 的意思,而是 Hash 的缩写。主要为了解决多个流水线在同一时间同时运行带来的系统负载压力。)

pipeline {
    agent any
    triggers {
        cron('H */4 * * 1-5')  // 设置定时触发器:工作日(周一到周五)每 4 小时触发一次
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'  // 输出 Hello World
            }
        }
    }
}

使用 cron 字段可以定期执行流水线,如果代码更新想要重新触发流水线,可以使用 pollSCM字段:

pipeline {
    agent any
    triggers {
        pollSCM('H */4 * * 1-5')  // 每工作日每 4 小时检查 SCM 是否有变化
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'  // 输出 Hello World
            }
        }
    }
}

Upstream 可以根据上游 job 的执行结果决定是否触发该流水线。比如当 job1 和 job2 执行成功时触发该流水线:(目前支持的状态有 SUCCESS、UNSTABLE、FAILURE、NOT_BUILT、ABORTED 等)

pipeline {
    agent any
    triggers {
        upstream(
            upstreamProjects: 'job1,job2',  // 监听 job1 和 job2 的执行
            threshold: hudson.model.Result.SUCCESS  // 当 job1 或 job2 成功时触发
        )
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'  // 输出 Hello World
            }
        }
    }
}
Input

Input 字段可以实现在流水线中进行交互式操作,比如选择要部署的环境、是否继续执行某个阶段等。

配置 Input 支持以下选项:

参数 解析
message 必选,需要用户进行 input 的提示信息,比如:“是否发布到生产环境?”
id 可选,input 的标识符,默认为 stage 的名称
ok 可选,确认按钮的显示信息,比如:“确定”、“允许”
submitter 可选,允许提交 input 操作的用户或组的名称,如果为空,任何登录用户均可提交 input
parameters 提供一个参数列表供 input 使用

假如需要配置一个提示消息为“还继续么”、确认按钮为“继续”、提供一个 PERSON 的变量的参数,并且只能由登录用户为 alice 和 bob 提交的 input 流水线

pipeline {
    agent any
    stages {
        stage('Example') {
            input {
                message "还继续么?"  // 提示信息:是否继续
                ok "继续"  // 确认按钮的文字
                submitter "alice,bob"  // 仅允许 alice 和 bob 提交输入
                parameters {
                    string(
                        name: 'PERSON', 
                        defaultValue: 'Mr Jenkins', 
                        description: 'Who should I say hello to?'
                    )  // 输入的参数,字符串类型
                }
            }
            steps {
                echo "Hello, ${params.PERSON}, nice to meet you."  // 输出打招呼信息
            }
        }
    }
}
When

When 指令允许流水线根据给定的条件决定是否应该执行该 stage,when 指令必须包含至少一个条件。如果 when 包含多个条件,所有的子条件必须都返回 True,stage 才能执行。

When 也可以结合 not、allOf、anyOf 语法达到更灵活的条件匹配。

目前比较常用的内置条件如下:

参数 解析
branch 当正在构建的分支与给定的分支匹配时,执行这个 stage,例如:when { branch'master' }。注意,branch 只适用于多分支流水线
changelog 匹 配 提 交 的 changeLog 决 定 是 否 构 建 , 例 如 : when { changelog'.*^\[DEPENDENCY\] .+$' }
environment 当指定的环境变量和给定的变量匹配时,执行这个 stage,例如:when{ environment name: 'DEPLOY_TO', value: 'production' }
equals 当期望值和实际值相同时,执行这个 stage,例如:when { equals expected: 2,actual: currentBuild.number }
expression 当指定的 Groovy 表达式评估为 True,执行这个 stage,例如:when{ expression { return params.DEBUG_BUILD } }
tag 如果 TAG_NAME 的值和给定的条件匹配,执行这个 stage,例如:when { tag"release-*" }
not 当嵌套条件出现错误时,执行这个 stage,必须包含一个条件,例如:when { not{ branch 'master' } }
allOf 当所有的嵌套条件都正确时,执行这个 stage,必须包含至少一个条件,例如:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
anyOf 当至少有一个嵌套条件为 True 时,执行这个 stage,例如:when { anyOf { branch'master'; branch 'staging' } }

当分支为 production 时,执行 Example Deploy 步骤:

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'  // 仅在当前分支为 production 时执行
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

可以同时配置多个条件,比如分支是 production,而且 DEPLOY_TO 变量的值为 production时,才执行 Example Deploy

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                allOf {
                    branch 'production'   // 当前分支必须是 production
                    environment name: 'DEPLOY_TO', value: 'production' // 环境变量 DEPLOY_TO 必须为 production
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

可以使用 anyOf 进行匹配其中一个条件即可,比如分支为 production,DEPLOY_TO 为production 或 staging 时执行 Deploy

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'  // 当前分支必须是 production
                anyOf {              // 满足以下任意一个环境变量条件
                    environment name: 'DEPLOY_TO', value: 'production'
                    environment name: 'DEPLOY_TO', value: 'staging'
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

可以使用 expression 进行正则匹配,比如当 BRANCH_NAME 为 production 或 staging,并且 DEPLOY_TO 为 production 或 staging 时才会执行 Example Deploy

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                expression { env.BRANCH_NAME ==~ /(production|staging)/ } // 当前分支为 production 或 staging
                anyOf { 
                    environment name: 'DEPLOY_TO', value: 'production'   // DEPLOY_TO 为 production
                    environment name: 'DEPLOY_TO', value: 'staging'     // DEPLOY_TO 为 staging
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

默认情况下,如果定义了某个 stage 的 agent,在进入该 stage 的 agent 后,该 stage 的 when条件才会被评估,但是可以通过一些选项更改此选项。比如在进入 stage 的 agent 前评估 when,可以使用 beforeAgent,当 when 为 true 时才进行该 stage

目前支持的前置条件如下:(beforeOptions 优先级大于 beforeInput 大于 beforeAgent)

参数 解析
beforeAgent 如果 beforeAgent 为 true,则会先评估 when 条件。在 when 条件为 true时,才会进入该 stage
beforeInput 如果 beforeInput 为 true,则会先评估 when 条件。在 when 条件为 true时,才会进入到 input 阶段
beforeOptions 如果 beforeInput 为 true,则会先评估 when 条件。在 when 条件为 true时,才会进入到 options 阶段
pipeline {
    agent none
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                beforeInput true            // 在用户输入之前评估条件
                branch 'production'         // 当前分支必须为 production
            }
            input {
                message "Deploy to production?" // 提示信息
                id "simple-input"               // 输入阶段的唯一标识符
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}
pipeline {
    agent none
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                beforeOptions true            // 在选择 options 之前进行条件评估
                branch 'testing'              // 当前分支必须为 testing
            }
            options {
                lock label: 'testing-deploy-envs', quantity: 1, variable: 'deployEnv'
                // 使用锁定资源,确保只有一个构建可以访问该资源
            }
            steps {
                echo "Deploying to ${deployEnv}"
            }
        }
    }
}
3、Parallel

在声明式流水线中可以使用 Parallel 字段,即可很方便的实现并发构建,比如对分支 A、B、C 进行并行处理

pipeline {
    agent any
    stages {
        stage('Non-Parallel Stage') {
            steps {
                echo 'This stage will be executed first.'
            }
        }
        stage('Parallel Stage') {
            when {
                branch 'master'  // 仅在 master 分支上执行该阶段
            }
            failFast true  // 在任何并行阶段失败时立即终止其他并行任务
            parallel {
                stage('Branch A') {
                    agent {
                        label "for-branch-a"  // 指定执行该阶段的节点标签
                    }
                    steps {
                        echo "On Branch A"
                    }
                }
                stage('Branch B') {
                    agent {
                        label "for-branch-b"  // 指定执行该阶段的节点标签
                    }
                    steps {
                        echo "On Branch B"
                    }
                }
                stage('Branch C') {
                    agent {
                        label "for-branch-c"  // 指定执行该阶段的节点标签
                    }
                    stages {
                        stage('Nested 1') {
                            steps {
                                echo "In stage Nested 1 within Branch C"
                            }
                        }
                        stage('Nested 2') {
                            steps {
                                echo "In stage Nested 2 within Branch C"
                            }
                        }
                    }
                }
            }
        }
    }
}

设置 failFast 为 true 表示并行流水线中任意一个 stage 出现错误,其它 stage 也会立即终止。也可以通过 options 配置在全局

pipeline {
    agent any
    options {
        failFast true  // 设置全局 failFast,确保在任何并行阶段失败时立即终止其他并行任务
    }
    stages {
        stage('Non-Parallel Stage') {
            steps {
                echo 'This stage will be executed first.'
            }
        }
        stage('Parallel Stage') {
            when {
                branch 'master'  // 仅在 master 分支上执行该阶段
            }
            parallel {
                stage('Branch A') {
                    agent {
                        label "for-branch-a"  // 指定执行该阶段的节点标签
                    }
                    steps {
                        echo "On Branch A"
                    }
                }
                stage('Branch B') {
                    agent {
                        label "for-branch-b"  // 指定执行该阶段的节点标签
                    }
                    steps {
                        echo "On Branch B"
                    }
                }
                stage('Branch C') {
                    agent {
                        label "for-branch-c"  // 指定执行该阶段的节点标签
                    }
                    stages {
                        stage('Nested 1') {
                            steps {
                                echo "In stage Nested 1 within Branch C"
                            }
                        }
                        stage('Nested 2') {
                            steps {
                                echo "In stage Nested 2 within Branch C"
                            }
                        }
                    }
                }
            }
        }
    }
}

Jenkinsfile 的使用

流水线支持两种语法,即声明式和脚本式,这两种语法都支持构建持续交付流水线。并且都可以用来在 Web UI 或 Jenkinsfile 中定义流水线,不过通常将 Jenkinsfile 放置于代码仓库中(当然也可以放在单独的代码仓库中进行管理)。

创建一个 Jenkinsfile 并将其放置于代码仓库中,方便对流水线上的代码进行复查/迭代;对管道进行审计跟踪;流水线真正的源代码能够被项目的多个成员查看和编辑。

环境变量
静态变量

Jenkins 有许多内置变量可以直接在 Jenkinsfile 中使用,可以通过 JENKINS_URL/pipeline-syntax/globals#env 获取完整列表。目前比较常用的环境变量如下:

参数 解析
BUILD_ID 当前构建的 ID,与 Jenkins 版本 1.597+中的 BUILD_NUMBER 完全相同
BUILD_TAG 用来标识构建的版本号,格式为:jenkins-${JOB_NAME}-${BUILD_NUMBER},可以对产物进行命名,比如生产的 jar 包名字、镜像的 TAG 等
BUILD_URL 本次构建的完整 URL,比如:http://buildserver/jenkins/job/MyJobName/17/
JOB_NAME 本次构建的项目名称
NODE_NAME 当前构建节点的名称
JENKINS_URL Jenkins 完整的 URL,需要在 System Configuration 设置
WORKSPACE 执行构建的工作目录

上述变量会保存在一个 Map 中,可以使用 env.BUILD_ID 或 env.JENKINS_URL 引用某个内置变量

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
            }
        }
    }
}

除了上述默认的环境变量,也可以手动配置一些环境变量(注意:例子中有两个环境变量,一个 CC 值为 clang,另一个是 DEBUG_FLAGS 值为-g。但是两者定义的位置不一样,CC 位于顶层,适用于整个流水线,而 DEBUG_FLAGS 位于 stage 中,只适用于当前 stage。)

pipeline {
    agent any
    environment {
        CC = 'clang'  // 全局环境变量,定义了编译器为 clang
    }
    stages {
        stage('Example') {
            environment {
                DEBUG_FLAGS = '-g'  // 局部环境变量,仅在该阶段内有效
            }
            steps {
                sh 'printenv'  // 打印所有环境变量,包括全局和局部的
            }
        }
    }
}
动态变量

动态变量是根据某个指令的结果进行动态赋值,变量的值根据指令的执行结果而不同

pipeline {
    agent any
    environment {
        // 使用 returnStdout
        CC = """${sh(
            returnStdout: true,
            script: 'echo "clang"'
        )}"""
        // 使用 returnStatus
        EXIT_STATUS = """${sh(
            returnStatus: true,
            script: 'exit 1'
        )}"""
    }
    stages {
        stage('Example') {
            environment {
                DEBUG_FLAGS = '-g'  // 局部环境变量
            }
            steps {
                sh 'printenv'  // 打印所有环境变量
            }
        }
    }
}
参数 解析
returnStdout 将命令的执行结果赋值给变量,比如上述的命令返回的是 clang,此时 CC的值为“clang ”。注意后面多了一个空格,可以用.trim()将其删除
returnStatus 将命令的执行状态赋值给变量,比如上述命令的执行状态为 1,此时EXIT_STATUS 的值为 1
凭证管理

Jenkins 的声明式流水线语法有一个 credentials()函数,它支持 secret text(加密文本)、username和 password(用户名和密码)以及 secret file(加密文件)等。接下来看一下一些常用的凭证处理方法

加密文本

将两个 Secret 文本凭证分配给单独的环境变量来访问 Amazon Web 服务,需要 提前创建这两个文件的 credentials(实践的章节会有演示),Jenkinsfile 文件的内容如下:(如果在 steps 中使用 echo $AWS_ACCESS_KEY_ID,此时返回的是****,加密内容不 会被显示出来)

pipeline {
    agent {
        // Define agent details here
        // 这里可以配置使用的 Jenkins 节点或者代理
    }
    environment {
        // 使用 credentials 方法从 Jenkins 的凭证管理中获取 AWS 密钥
        AWS_ACCESS_KEY_ID = credentials('jenkins-aws-secret-key-id')
        AWS_SECRET_ACCESS_KEY = credentials('jenkins-aws-secret-access-key')
    }
    stages {
        stage('Example stage 1') {
            steps {
                // 在这里可以编写步骤,执行您在 stage 1 中需要的操作
            }
        }
        stage('Example stage 2') {
            steps {
                // 在这里可以编写步骤,执行您在 stage 2 中需要的操作
            }
        }
    }
}
用户名密码

演示 credentials 账号密码的使用,比如使用一个公用账户访问 Bitbucket、GitLab、Harbor 等。假设已经配置完成了用户名密码形式的 credentials,凭证 ID 为 jenkins-bitbucket-common-creds

可以用以下方式设置凭证环境变量(BITBUCKET_COMMON_CREDS 名称可以自定义)

environment {
    BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
}

上述的配置会自动生成 3 个环境变量

BITBUCKET_COMMON_CREDS : 包 含 一 个 以 冒 号 分 隔 的 用 户 名 和 密 码 , 格 式 为username:password
BITBUCKET_COMMON_CREDS_USR:仅包含用户名的附加变量
BITBUCKET_COMMON_CREDS_PSW:仅包含密码的附加变量

调用用户名密码的 Jenkinsfile 如下(此时环境变量的凭证仅作用于 stage 1,也可以配置在顶层对全局生效)

pipeline {
    agent {
        // Define agent details here
        // 在这里定义代理的详细信息
    }
    stages {
        stage('Example stage 1') {
            environment {
                // 在这个阶段设置环境变量 BITBUCKET_COMMON_CREDS,获取 Jenkins 存储的凭证
                BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
            }
            steps {
                // 在此阶段执行的步骤
            }
        }
        stage('Example stage 2') {
            steps {
                // 在第二阶段执行的步骤
            }
        }
    }
}
加密文件

需要加密保存的文件,也可以使用 credential,比如链接到 Kubernetes 集群的 kubeconfig 文件等。

假如已经配置好了一个 kubeconfig 文件,此时可以在 Pipeline 中引用该文件:

pipeline {
    agent {
        // Define agent details here
        // 在此定义代理的详细信息,例如指定要使用的节点
    }
    environment {
        // 获取 Jenkins 中存储的 kubeconfig 文件,并将其作为环境变量
        MY_KUBECONFIG = credentials('my-kubeconfig')
    }
    stages {
        stage('Example stage 1') {
            steps {
                // 使用 kubectl 命令与 Kubernetes 集群交互
                sh("kubectl --kubeconfig $MY_KUBECONFIG get pods")
            }
        }
    }
}

参数处理

声明式流水线支持很多开箱即用的参数,可以让流水线接收不同的参数以达到不同的构建效果,在 Directives 小节讲解的参数均可用在流水线中。

在 Jenkinsfile 中指定的 parameters 会在 Jenkins Web UI 自动生成对应的参数列表,此时可以在 Jenkins 页面点击 Build With Parameters 来指定参数的值,这些参数可以通过 params 变量被成员访问。

假设在 Jenkinsfile 中配置了名为 Greeting 的字符串参数,可以通${params.Greeting}访问该参数

pipeline {
    agent any
    parameters {
        // 定义一个字符串类型的参数 "Greeting"
        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    }
    stages {
        stage('Example') {
            steps {
                // 使用参数 "Greeting" 来打印问候语
                echo "${params.Greeting} World!"
            }
        }
    }
}

使用多个代理

流水线允许在 Jenkins 环境中使用多个代理,这有助于更高级的用例,例如跨多个平台执行构建、测试等。

比如,在 Linux 和 Windows 系统的不同 agent 上进行测试

pipeline {
    agent none // 全局代理设为 none,意味着没有默认的执行环境,具体每个阶段会指定代理
    stages {
        // 构建阶段
        stage('Build') {
            agent any // 使用任意可用的代理节点
            steps {
                // 从源代码管理(SCM)检出代码
                checkout scm
                // 执行构建命令
                sh 'make'
                // 使用 stash 保存构建生成的文件,方便在其他阶段使用
                stash includes: '**/target/*.jar', name: 'app'
            }
        }

        // Linux 测试阶段
        stage('Test on Linux') {
            agent {
                label 'linux' // 指定使用 label 为 'linux' 的节点执行
            }
            steps {
                // 获取之前存储的构建工件
                unstash 'app'
                // 在 Linux 上执行测试命令
                sh 'make check'
            }
            post {
                // 测试完成后始终执行的操作:生成 JUnit 测试报告
                always {
                    junit '**/target/*.xml'
                }
            }
        }

        // Windows 测试阶段
        stage('Test on Windows') {
            agent {
                label 'windows' // 指定使用 label 为 'windows' 的节点执行
            }
            steps {
                // 获取之前存储的构建工件
                unstash 'app'
                // 在 Windows 上执行测试命令
                bat 'make check'
            }
            post {
                // 测试完成后始终执行的操作:生成 JUnit 测试报告
                always {
                    junit '**/target/*.xml'
                }
            }
        }
    }
}

DevOps 平台建设

在 Kubernetes 中进行 CICD 的过程,一般的步骤如下

1、在 GitLab 中创建对应的项目

2、配置 Jenkins 集成 Kubernetes 集群,后期 Jenkins 的 Slave 将为在 Kubernetes 中动态创建的 Slave

3、Jenkins 创建对应的任务(Job),集成该项目的 Git 地址和 Kubernetes 集群

4、开发者将代码提交到 GitLab

5、如有配置钩子,推送(Push)代码会自动触发 Jenkins 构建,如没有配置钩子,需要手动构建

6、Jenkins 控制 Kubernetes(使用的是 Kubernetes 插件)创建 Jenkins Slave(Pod 形式)

7、Jenkins Slave 根据流水线(Pipeline)定义的步骤执行构建

8、通过 Dockerfile 生成镜像

9、将镜像提送(Push)到私有 Harbor(或者其它的镜像仓库)

10、Jenkins 再次控制 Kubernetes 进行最新的镜像部署

11、流水线结束删除 Jenkins Slave。

由于实验所需资源较多,编者调整了机器的配置,详情参见下表(正常应该把Jenkins、Harbor、Gitlab拆分,不然会有端口冲突的问题,但是机器资源不足,此处将Jenkins和Harbor进行合并)

角色 IP地址 资源
master-01 192.168.132.169/24 2C4G
master-02 192.168.132.170/24 2C3G
master-03 192.168.132.171/24 2C3G
node-01 192.168.132.172/24 2C4G
node-02 192.168.132.173/24 2C4G
Jenkins/Harbor 192.168.132.168/24 2C4G
Gitlab 192.168.132.167/24 2C4G
1、Jenkins安装

本次实验的Jenkins使用Docker的方式进行安装,需要安装Docker(在前面的章节已经介绍过,此处不再重复介绍)

创建 Jenkins 的数据目录,防止容器重启后数据丢失(持久化)

[root@harbor ~]# mkdir /data/jenkins_home
[root@harbor ~]# chmod -R 777 /data/jenkins_home/

启动 Jenkins,配置持久化目录和映射的端口,将宿主机上的8088端口映射到容器内部的8080端口,若镜像无法拉取,请参考前面章节使用Github进行同步(这样做的原因是harbor也需要8080端口,所以调整了Jenkins的配置)。 8080 端口为 Jenkins Web 界面的端口,50000 是 jnlp 使用的端口,后期 Jenkins Slave 需要使用 50000 端口和 Jenkins 主节点通信

[root@harbor harbor]# docker run -d --name=jenkins --restart=always -p 8088:8080 -p 50000:50000 -v /data/jenkins_home:/var/jenkins_home docker.io/jenkins/jenkins:2.489-rhel-ubi9-jdk21
f7885d84206eddc4efdf630249fe78adaf76e06177f68ffd9a6482a5b5e93ee8

通过浏览器访问http://节点IP:8088端口,出现配置界面,提示需要密码

Jenkins入门

由于容器内部的配置目录会映射到宿主机的/data/jenkins_home/目录,所以直接查看宿主机上的密码文件,输入密码后,单击继续按钮

[root@harbor harbor]# cat /data/jenkins_home/secrets/initialAdminPassword
f71e8bd109544f38830118640b25c80d

出现插件安装的界面,此处可以先跳过,因为网络原因插件的安装大概率会失败,所以读者可以单击选择插件来安装按钮,然后依次单击无→安装按钮即可跳过

新手入门

选择插件来安装

随后提示创建管理员用户,如果读者只是实验环境,可以单击下方的使用admin账户继续,如果是生产环境,建议创建管理员用户

创建管理员

出现实例配置界面,使用默认配置或者单击下方的现在不要,延后根据生产环境进行配置,随后单击保存并完成

实例配置

界面提示Jenkins已就绪,单击开始使用Jenkins按钮

就绪

进入到Jenkins主界面,依次单击左侧导航栏Manage Jenkins→Plugins→Advanced settings,配置国内的插件源,随后单击Submit按钮

国内插件源

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

单击左侧导航栏Available Plugins按钮,即可查看到可以安装的插件。在上方搜索需要安装的插件,单击勾选,插件列表如下

查询插件

Git
Git Parameter
Git Pipeline for Blue Ocean
GitLab
Credentials
Credentials Binding
Blue Ocean
Blue Ocean Pipeline Editor
Blue Ocean Core JS
Pipeline SCM API for Blue Ocean
Dashboard for Blue Ocean
Build With Parameters
List Git Branches Parameter
Pipeline
Pipeline: Declarative
Kubernetes
Kubernetes CLI
Kubernetes Credentials
Image Tag Parameter
Active Choices

全部插件选择完成后,单击Install右方的小箭头,选择Install after restart,随后自动切换到下载进度界面,插件下载完成后会提示Downloaded Successfully . Will be activated duting the next boot

插件下载

读者可能会出现等待很久,但是还是有某几个插件处于Pending状态,此时勾选 Restart Jenkins when installation is complete and no jobs are running 复选项,重启Jenkins即可。重启后,Jenkins安装完成。

restart Jenkins

重启后,需要重新登录,可以观察到左侧导航栏出现了打开 Bule Ocean按钮,单击该按钮,验证界面是否能正常打开

blue ocean

2、Gitlab安装

Gitlab的安装并不复杂,但是使用和维护比较困难,需要读者自行学习。其次搭建Gitlab的机器最小要求2CPU和4Gib内存,小于该内存要求,很大可能会出现报错。

本次搭建使用官方教材内YUM的方式搭建,并且使用最新版的17.6.2(注意,最好单独使用一台机器部署,否则安装Git命令时会出现依赖包不匹配的问题)

配置极狐GitLab 软件源镜像

[root@gitlab ~]# curl -L get.gitlab.cn | bash
...省略部分输出...
==> Detected OS centos

==> Add yum repo file to /etc/yum.repos.d/gitlab-jh.repo

[gitlab-jh]
name=JiHu GitLab
baseurl=https://packages.gitlab.cn/repository/el/$releasever/
gpgcheck=1
gpgkey=https://packages.gitlab.cn/repository/raw/gpg/public.gpg.key
priority=1
enabled=1

==> Generate yum cache for gitlab-jh

==> Successfully added gitlab-jh repo. To install JiHu GitLab, run "sudo yum/dnf install gitlab-jh".

如果读者希望直接使用rpm包下载,可以通过以下地址获取

https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/

传递访问的域名或主机IP参数(用于访问Gitlab UI界面),传递Root密码参数(默认情况下,Linux 软件包安装会自动为初始管理员用户账号 (root) 生成密码,并将其存储到/etc/gitlab/initial_root_password 至少 24 小时。出于安全原因,24 小时后,此文件会被第一次 gitlab-ctl reconfigure 自动删除)

[root@gitlab ~]# GITLAB_ROOT_PASSWORD="Caijunxian_99" EXTERNAL_URL="http://192.168.132.167" yum install gitlab-jh -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
Resolving Dependencies
--> Running transaction check
---> Package gitlab-jh.x86_64 0:17.6.2-jh.0.el7 will be installed
--> Processing Dependency: policycoreutils-python for package: gitlab-jh-17.6.2-jh.0.el7.x86_64
--> Running transaction check
---> Package policycoreutils-python.x86_64 0:2.5-34.el7 will be installed
--> Processing Dependency: setools-libs >= 3.3.8-4 for package: policycoreutils-python-2.5-34.el7.x86_64
...省略部分输出...

安装完成后,需要修改Gitlab的配置文件/etc/gitlab/gitlab.rb,关闭 GitLab 自带的 Prometheus 插件

[root@gitlab ~]# vim /etc/gitlab/gitlab.rb
prometheus['enable'] = false

更改完成后需要重新加载配置文件

[root@gitlab ~]# gitlab-ctl reconfigure
[2024-12-12T14:10:12-05:00] INFO: Started Cinc Zero at chefzero://localhost:1 with repository at /opt/gitlab/embedded (One version per cookbook)
Cinc Client, version 18.3.0
Patents: https://www.chef.io/patents
Infra Phase starting
[2024-12-12T14:10:12-05:00] INFO: *** Cinc Client 18.3.0 ***
[2024-12-12T14:10:12-05:00] INFO: Platform: x86_64-linux
[2024-12-12T14:10:12-05:00] INFO: Cinc-client pid: 14844
...省略部分输出...

通过浏览器访问 GitLab,账号 root,如果在安装时没有传递密码参数,那么默认密码在/etc/gitlab/initial_root_password

gitlab登录

请读者注意,如果出现报错(一直无法打开Gitlab界面,提示502),请按照如下方法进行排查!

1、先通过tail命令和status命令查看组件是否有报错的日志输出

gitlab-ctl status
gitlab-ctl tail puma

2、作者发现日志有如下报错,并且puma组件有端口被占用的日志提示

/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket: connect: connection refused

3、通过查询issues和查询组件对应监听的端口,发现Gitlab需要访问8080端口和80端口,所以根本原因是端口冲突,但是如果读者并不是此问题,需要检查组件运行状态是否正常,内存是否充足,是否进行了reconfigure

4、如果已经有其他服务占用了80端口或者8080端口怎么解决呢,可以按照如下配置进行修改,其他组件监听的端口需要修改也是类似方式

此字段控制Gitlab的UI访问地址

[root@gitlab ~]# vim /etc/gitlab/gitlab.rb
external_url 'http://192.168.132.167:8888'

此字段控制puma的监听端口和地址

puma['enable'] = true
puma['worker_timeout'] = 60
puma['worker_processes'] = 2
puma['listen'] = '127.0.0.1'
puma['port'] = 8989

修改上述配置后,重新reconfigure,即可成功登录

登录后,对Gitlab功能进行测试,依次单击左侧导航栏Groups→New Group→Create group,创建一个组,名称为caijx,可见级别设为私有,随后单击Create group按钮,创建组

创建组

在该组下面创建Project,创建完后自动跳转到组界面,依次单击New project→Create blank project,project名为test-project,其余配置保持默认,随后单击Create project按钮创建

创建project

切换到Jenkins服务器上,生成密钥(注意:如果之前已经生成,使用之前的公钥即可)

[root@harbor ~]# ssh-keygen -t rsa -C "jxcai@coremail.cn"
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:QFEe1/MkKnNW99H0hxhXITIWCOkd7awYJz60LCqXyB0 jxcai@coremail.cn
The key's randomart image is:
+---[RSA 2048]----+
|      o+= +*o..+=|
|     . o =..B++oo|
|      o o +o.*..+|
|       *oo+o  . o|
|      + S=.      |
|   E . * .       |
|. o + . .        |
| + =             |
|  o              |
+----[SHA256]-----+
[root@harbor ~]# cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6beKHm6lL3oCTJk0BDaGuLUkv8m7zdU5ItBJLp26qWAemB5ougmGzmrdXybVU6bXWwEY8Hs5wUId+8Yok1BOOEmXLj6L2EsTZTZbA8P4sKLb9g9gcruItMjPuJh40JeIyiqcil5paxcvlGg13wQ6RM3KvAAz5kqQGEIqeCR3CJucJ13HxPaTQDjG91TTjyVoODyTlAhSrlBjvJd6St2oJNlslSTZ+huks+Swh6rpzdGyFlFHHuUsvkDbO9p3PRXcK0SxyZV05HfocVkvEC8oAb/lXtjOE2wqwXX54KoRvyW7vN+sy+9hs3QNJcL47iyYqVAro5H44e3AEpLJOxFE7 jxcai@coremail.cn

在Gitlab界面上,鼠标移动到界面左侧,此时会弹出导航栏,依次单击SSH Keys→Add new key,将刚才的公钥粘贴到Key文本框内,此时会自动识别到公钥的信息,单击Add key按钮,添加密钥

ssh key

添加完成后,切换到Jenkins服务器,安装git命令,模拟代码拉取是否正常

[root@harbor ~]# yum -y install git
[root@harbor ~]# git clone git@192.168.132.167:caijx/test-project.git
Cloning into 'test-project'...
The authenticity of host '192.168.132.167 (192.168.132.167)' can't be established.
ECDSA key fingerprint is SHA256:3jDHkD+/2lQF89uZBEBLWtHSQLcv34cdY/oRhyo507I.
ECDSA key fingerprint is MD5:ca:ec:2f:b0:c6:65:d9:50:11:f6:b1:38:84:64:f1:c4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.132.167' (ECDSA) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.
[root@harbor ~]# ls
test-project
[root@harbor ~]# ls test-project/
README.md

写入文件,切换到目录内,随后提交

[root@harbor ~]# echo "# Frist Commit For DevOps" > /root/test-project/first.md

配置全局的用户名和邮箱

[root@harbor test-project]# git config --global user.email "jxcai@coremail.cn"
[root@harbor test-project]# git config --global user.name "jxcai"
[root@harbor test-project]# git config --global --list
user.email=jxcai@coremail.cn
user.name=jxcai

将文件推送到暂存区,提交已被跟踪的文件的更改,并添加一条提交信息

[root@harbor test-project]# git add .
[root@harbor test-project]# git status
# On branch main
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   first.md
#
[root@harbor test-project]# git commit -am "first commit"
[main a09ea5c] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 first.md
[root@harbor test-project]# git status
# On branch main
# Your branch is ahead of 'origin/main' by 1 commit.
#   (use "git push" to publish your local commits)
#

将本地的 main 分支提交到远程仓库的 main 分支

[root@harbor test-project]# git push origin main
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 290 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@192.168.132.167:caijx/test-project.git
   16791d4..a09ea5c  main -> main

成功提交后,即可在Gitlab看到对应的文件和修改历史,至此测试完成,Gitlab搭建完成

提交

3、Harbor安装

请读者按照下面的链接完成部署,此处不再重复介绍安装过程

https://blog.caijxlinux.work/214/

由于Harbor的证书是自签,需要在docker和containerd内添加insecure-registry的配置

docker添加daemon.json文件,并写入如下配置

[root@master-01 ~]# cat /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "insecure-registries": ["192.168.132.168"]
}

containerd修改config.toml文件

[root@master-01 ~]# vim /etc/docker/daemon.json
      [plugins."io.containerd.grpc.v1.cri".registry.configs]
        [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.132.168".tls]
          insecure_skip_verify = true
        [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.132.168".auth]
          username = "admin"
          password = "Harbor12345"

      [plugins."io.containerd.grpc.v1.cri".registry.headers]

      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.132.168"]
          endpoint = ["http://192.168.132.168]

重启docker和containerd

[root@master-01 docker]# systemctl daemon-reload
[root@master-01 docker]# systemctl restart docker
[root@master-01 ~]# systemctl restart containerd.service

验证docker拉取私有仓库镜像

[root@master-01 ~]# docker login 192.168.132.168
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[root@master-01 ~]# docker  pull 192.168.132.168/test/centos:v1
v1: Pulling from test/centos
a1d0c7532777: Pull complete
1e4971f24402: Pull complete
2e0fca24b945: Pull complete
c29db190b9af: Pull complete
Digest: sha256:8e5a011af2073fd2afee3f4767153a5d9035c9216fcc2bddea1fb80d4886b15e
Status: Downloaded newer image for 192.168.132.168/test/centos:v1
192.168.132.168/test/centos:v1

验证containerd拉取私有仓库镜像

[root@master-01 ~]# crictl pull 192.168.132.168/test/centos:v1
Image is up to date for sha256:6e0ddaa831000f5fbd14b0ceafdfc5b06132d93cd02ce5aa595efc210dc3ca99

但是使用ctr命令拉取依然会报错

ctr: failed to resolve reference "192.168.132.168/test/centos:v1": failed to do request: Head "https://192.168.132.168:443/v2/test/centos/manifests/v1": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.132.168 because it doesn't contain any IP SANs

可以看到是因为证书的问题,看来skip参数没有生效,而且作者现在这种配置方式在未来可能被弃用,建议读者使用注册表的方式进行修改。此处指定 -k 参数跳过证书校验,或者指定CA证书文件

[root@master-01 ~]# ctr images pull --user admin:Harbor12345 -k 192.168.132.168/test/centos:v1
WARN[0000] DEPRECATION: The `mirrors` property of `[plugins."io.containerd.grpc.v1.cri".registry]` is deprecated since containerd v1.5 and will be removed in containerd v2.0. Use `config_path` instead.
WARN[0000] DEPRECATION: The `configs` property of `[plugins."io.containerd.grpc.v1.cri".registry]` is deprecated since containerd v1.5 and will be removed in containerd v2.0. Use `config_path` instead.
192.168.132.168/test/centos:v1:                                                   resolved       |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:8e5a011af2073fd2afee3f4767153a5d9035c9216fcc2bddea1fb80d4886b15e: done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:c29db190b9af56c52610fb32881c63ff5e652ec47fc6598626461f4771441f9b:    done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:6e0ddaa831000f5fbd14b0ceafdfc5b06132d93cd02ce5aa595efc210dc3ca99:   done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:1e4971f244026d790c0589324e2f78b83af3f0e98acc5c9fa4d01012ec7b2b9c:    done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:2e0fca24b9455a97e0746691a29eec28a487e13872850b6d49cea7be44787fe6:    done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 0.2 s                                                                    total:   0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:8e5a011af2073fd2afee3f4767153a5d9035c9216fcc2bddea1fb80d4886b15e...
done: 11.703020213s
[root@master-01 ~]# ctr images pull --user admin:Harbor12345 --tlscacert ca.crt 192.168.132.168/test/centos:v1

本章节的内容太多,读者先按照此章节的步骤将Devops环境先部署好,再浏览下个章节的内容