最全教程,Jenkins 与 Kubernetes 持续集成 Springboot + Helm

7,666 阅读17分钟
环境介绍

一、Kubernetes 环境安装 Jenkins

详情请看 Kubernetes 中安装 Jenkins ,这里不过多叙述。

二、Jenkins 安装插件

为了方便集成 Maven、Kubernetes、配置文件等等,这里需要安装几个别的插件,这里插件可以在 系统管理—>插件管理—>可选插件 里面安装下面列出的插件。

  • ① Git 插件
  • ② Docker 插件
  • ③ Kubernetes
  • ④ Kubernetes Cli
  • ⑤ Config File Provider
  • ⑥ Pipeline Utility Steps

1、Git 插件

Jenkins 安装中默认安装 Git 插件,所以不需要单独安装。利用 git 工具可以将 github、gitlab 等等的地址下载源码。

2、Docker 插件

Jenkins 安装中默认安装 Docker 插件,所以不需要单独安装。利用 Docker 插件可以设置 Docker 环境,运行 Docker 命令,配置远程 Docker 仓库凭据等。

3、Kubernetes

Kubernetes 插件的目的是能够使用 Kubernetes 集群动态配置 Jenkins 代理(使用Kubernetes调度机制来优化负载),运行单个构建,等构建完成后删除该代理。这里我们需要用到这个插件来启动 Jenkins Slave 代理镜像,让代理执行 Jenkins 要执行的 Job。

4、Kubernetes Cli

Kubernetes Cli 插件作用是在执行 Jenkins Job 时候提供 kubectl 与 Kubernetes 集群交互环境。可以在 Pipeline 或自由式项目中允许执行 kubectl 相关命令。它的主要作用是提供 kubectl 运行环境,当然也可以提供 helm 运行环境。

5、Config File Provider

Config File Provider 插件作用就是提供在 Jenkins 中存储 properties、xml、json、settings.xml 等信息,可以在执行 Pipeline 过程中可以写入存储的配置。

例如,存入一个 Maven 全局 Settings.xml 文件,在执行 Pipeline Job 时候引入该 Settings.xml ,这样 Maven 编译用的就是该全局的 Settings.xml。

6、Pipeline Utility Steps

这是一个操作文件的插件,例如读写 json、yaml、pom.xml、Properties 等等。在这里主要用这个插件读取 pom.xml 文件的参数设置,获取变量,方便构建 Docker 镜像。

三、Jenkins 配置插件

1、Git 插件配置及使用

(1)、配置凭据:

如果是私有项目 Git 一般需要配置一个凭据用于验证,如果是公开项目,则无需任何配置。

凭据->系统->全局凭据->添加凭据

(2)、Pipeline 脚本中使用:

利用 Git 插件拉取源码,分别可以设置拉取的“分支”、“显示拉取日志”、“拉取的凭据”、“拉取的地址”,可以将上面设置的凭据ID设置到 credentialsId 参数上

参考:jenkins.io/doc/pipelin…

git branch: "master" ,changelog: true , credentialsId: "xxxx-xxxx-xxxx", url: "https://github.com/xxxxxx"

2、Docker 插件配置及使用

(1)、功能描述:

此插件将提供一下功能:

  • 记录FROM中使用的Docker镜像的跟踪
  • 记录在容器中运行的Docker镜像的跟踪
  • 在Docker容器中运行构建步骤
  • 设置Docker注册表端点,用于推送镜像验证
  • 设置Docker服务器端点,用于执行远程Docker API

(2)、Pipeline 脚本中使用:

安装 Jenkins 时候默认会安上此插件,这里主要是利用插件提供一个 docker 登录了的环境,以及执行一些 Docker 命令,具体请看参考,下面将写一个简单的执行例子来描述 Docker 镜像的构建过程。

参考:jenkins.io/doc/pipelin…

// 此方法是设置docker仓库地址,然后选择存了用户名、密码的凭据ID进行验证。注意,只有在此方法之中才生效。
docker.withRegistry("https://hub.docker.com/", "xxxxx-xxxx-xxxx-xxxx") {
    echo "构建镜像"
    def customImage = docker.build("hub.mydlq.club/myproject/springboot-helloworld:0.0.1")
    echo "推送镜像"
    customImage.push()
    echo "删除镜像"
    sh "docker rmi hub.mydlq.club/myproject/springboot-helloworld:0.0.1" 
}

3、Kubernetes 插件配置及使用

(1)、配置凭据:

配置连接 kubernetes 集群的凭据(Kubernetes ServiceAccount token),此凭据的账户权限最好设置较大点,避免出现未知问题。配置完成后,需要在后面的 Cloud 云配置中设置这个凭据。

(2)、云配置

系统管理—>系统设置—>

参考:github.com/jenkinsci/k…

这里是配置连接Kubernetes集群,启动 Jenkins Slave 代理的相关配置。

(3)、Template 模板配置

这里配置 Jenkins Slave 在 kubernetes 集群中启动的 Pod 的配置,这里将设置四个镜像,分别是:

  • Jenkins Slave: 用于执行 Jenkins Job 命令。
  • Helm-Kubectl: 用于执行 Helm 命令。
  • Docker 用于编译、推送 Docker 镜像
  • Maven: 用于Maven编译、打包。

这里将这四个镜像融入到一个 Pod 之中,方便执行各种命令来完成持续部署交互过程。

Template 基本配置:

原始 Yaml 设置:

在 Pod 的原始 yaml 那栏中,填写下面的 yaml 文件内容进行配置,将会以下面的 yaml 配置作为 Jenkins Slave Pod 的基本配置,如果上面界面上配置了某些信息,会自动替换 yaml 中设置的值,相当于此 yaml 文件作为了一个默认(缺省)配置了。

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: jenkins-slave
spec:
  serviceAccountName: jenkins-admin
  securityContext:                  #容器安全设置
    runAsUser: 0                    #以ROOT用户运行容器
    privileged: true                #赋予特权执行容器
  containers:
  - name: jnlp                      #Jenkins Slave镜像
    image: registry.cn-shanghai.aliyuncs.com/mydlq/jenkins-jnlp-slave:3.27-1
    #设置工作目录
    workingDir: /home/jenkins/agent
    tty: true
  - name: docker                    #Docker镜像
    image: registry.cn-shanghai.aliyuncs.com/mydlq/docker:18.06.2-dind
    command: ['cat']
    tty: true
    volumeMounts:
    - name: docker
      mountPath: /usr/bin/docker
    - name: docker-sock
      mountPath: /var/run/docker.sock
    - name: docker-config
      mountPath: /etc/docker
  - name: maven                     #Maven镜像
    image: registry.cn-shanghai.aliyuncs.com/mydlq/maven:3.6.0-jdk8-alpine
    command:
    - cat
    tty: true
    volumeMounts:
    - name: maven-m2
      mountPath: /root/.m2
  - name: helm-kubectl              #Kubectl & Helm镜像
    image: registry.cn-shanghai.aliyuncs.com/mydlq/helm-kubectl:2.13.1
    command:
    - cat
    tty: true
  volumes:
  - name: docker                    #将宿主机 Docker 文件夹挂进容器,方便存储&拉取本地镜像
    hostPath: 
      path: /usr/bin/docker
  - name: docker-sock               #将宿主机 Docker.sock 挂进容器
    hostPath: 
      path: /var/run/docker.sock
  - name: docker-config             #将宿主机 Docker 配置挂在进入容器
    hostPath: 
      path: /etc/docker
  - name: maven-m2                  #Maven 本地仓库挂在到 NFS 共享存储,方便不同节点能同时访问与存储
    nfs: 
      server: 192.168.2.11
      path: "/nfs/data/maven-m2"
#  nodeSelector:
#    kubernetes.io/hostname: node-2-12

4、Kubernetes Cli 插件配置及使用

(1)、配置凭据:

配置连接 kubernetes 集群的凭据,这个凭据可以和上面 kubernetes 插件的凭据一致,都是用于连接 Kubernetes 集群

(2)、Pipeline 脚本中使用:

此插件主要功能就是提供执行 kubectl 的环境设置,在此插件方法中相当于有 kubectl、helm 等环境设置,然后用相关镜像就可以执行相关命令。

参考:jenkins.io/doc/pipelin…

// 提供 kubectl 执行的环境,其中得设置存储了 token 的凭据ID和 kubernetes api 地址
withKubeConfig([credentialsId: "xxxx-xxxx-xxxx-xxxx",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
    sh "kubectl get nodes"
}

5、Config File Provider 插件

(1)、配置 Maven settings.xml

在 Jenkins 安装时候安装了“config File Provider”插件,这个插件的作用就是提供在 Jenkins 中存储properties、xml、json、settings.xml 等信息,这里打开下面列表,配置一个全局的 Maven 的 settings.xml 文件。

系统管理—>Managed files—>Add a new Config—>Global Maven settings.xml

在里面添加一个全局的 setting.xml 设置,为了加快 jar 包的下载速度,这里将仓库地址指向 aliyun Maven 仓库地址。

<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>

(2)、Pipeline 脚本中使用:

参考:jenkins.io/doc/pipelin…

可以在 Pipeline 脚本中,用于生成上面设置的文件,用法如下:

// 生成 settings.xml 文件,这个方法第一个参数是引用文件ID,第二个是生成的文件名
configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]) {
    // 只有在方法里面该文件才存在
    echo "cat settings.xml"
}

6、Pipeline Utility Steps 插件

(1)、功能描述

此插件将提供一下功能:

  • 提取Zip文件
  • 创建Zip文件
  • 创建一个普通文件。
  • 生成一个Yaml文件。
  • 编写maven项目文件。
  • 在工作区中查找文件。
  • 读取 properties 文件参数。
  • 从工作区中的文件中读取JSON。
  • 读取 maven 项目的 pom.xml 文件
  • ……

(2)、Pipeline 脚本中使用:

这里主要是用此插件读取 pom.xml 的项目有关的参数,用于 docker 编译镜像时使用。另一个功能是在脚本进行时候用于生成文件,例如 yaml 文件、helm 证书等。

参考:jenkins.io/doc/pipelin…

// 读取 pom.xml 文件
pom = readMavenPom file: "./pom.xml"  
echo "${pom.artifactId}:${pom.version}"

四、测试插件

为了保证插件配置正确且值执行,在 kubernetes 环境下启动 Jenkins 代理执行任务,这里将进行测试。

1、创建流水线任务

创建一个名为 “k8s-test” 的任务,类型选择“流水线”。

2、配置流水线任务

(1)、常规配置

  • 为了安全,禁止并发构建。
  • 为了提升效率,这里设置流水线效率,持久保存设置覆盖。

(2)、流水线脚本

这里写一个简单的脚本,将 Kubernetes 插件提供的 Pipeline 的方法引入,如下:

// 代理名称,填写系统设置中设置的 Cloud 中 Template 模板的 label
def label = "jnlp-agent"    

// 调用Kubernetes提供的方法
podTemplate(label: label,cloud: 'kubernetes' ){
    // 在代理节点上运行脚本
    node (label) {
        echo "测试 kubernetes 中 jenkins slave 代理!~"
    }
}

(3)、运行流水线任务

回到任务界面,点击立即构造来执行任务。

3、查看流水线日志

然后点击执行历史栏中点击,查看控制台输出的日志信息。

五、部署前准备

1、配置文件存放位置比较

以下仅是个人看法,有更好的方式,希望告知。

这里涉及到一个问题,在 Jenkins 中,我们的 Jenkinsfile 脚本存放在哪比较方便,这里本人想到三种:

  • 1、新建 Git 项目,专门存放不同的 jenkinsfile 脚本,Jenkins 创建任务时候指定脚本存放的 Git 地址;
  • 2、放到各个项目中,当在执行 Jenkins 任务时候读取 Git项目,从中检测 jenkinsfile 脚本从而执行;
  • 3、每个脚本都放置到 Jenkins 每个任务的配置中,每次都执行配置中设置的脚本;

比较三者:

  • 第1种方式方便统一管理,一改动git上的配置,jenkins 任务的流水线脚本都会跟着变化;
  • 第2种方式可以针对每个项目单独设置,更灵活,就是不方便统一管理,维护需要各个项目组;
  • 第3种方式需要每次都新建项目时候在配置中设置脚本,比较费力不方便维护,不太推荐;

2、设置配置文件到项目中

这里需要将将一些配置文件存入项目源码中,用于在执行流水线中读取对应的配置参数,比如:

  • SpringBoot源码: 用于测试的 helloworld 的SpringBoot项目。
  • Dockerfile: 用于 Docker 编译镜像的文件,比如打包的基础镜像等等。
  • values.yaml: 用于 Helm 启动的chart的配置文件,里面设置了一些chart的配置信息,告知该如何启动应用程序。

项目 Github 地址:github.com/my-dlq/spri…

(1)、Dockerfile

FROM registry.cn-shanghai.aliyuncs.com/mydlq/openjdk:8u201-jdk-alpine3.9
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS="-Xmx512M -Xms256M -Xss256k -Duser.timezone=Asia/Shanghai"
ENV APP_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]

(2)、values.yaml

kind: Deployment
image:
  pullPolicy: "Always"
replicas: 1
resources:
  limits:
    memory: 512Mi
    cpu: 1000m
  requests:
    memory: 256Mi
    cpu: 500m

#***java && app 环境变量设置
env: 
  - name: "JAVA_OPTS"
    value: "-Xmx512M -Xms355M -Xss256k -Duser.timezone=Asia/Shanghai"
  - name: "APP_OPTS"
    value: "" 

envFrom:
  #- configMapRef:
  #  name: env-config

service:
  type: NodePort                     #Service type设置 (可以设置为ClusterIP、NodePort、None)
  labels: 
    svcEndpoints: actuator
  annotations: {}
  ports: 
    - name: server
      port: 8080
      targetPort: 8080
      protocol: TCP
      nodePort: 30080 
    - name: management
      port: 8081
      targetPort: 8081
      protocol: TCP
      nodePort: 30081

3、测试运行环境是否可用

这里先写一个简单的脚本,用于测试各个环境是否都能用,例如slave镜像的git命令能否执行、maven镜像的mvn命令是否能用等等。

这里可以用 container(‘docker’) 方式,来引用 kubernetes 插件中设置的容器,利用各个容器不同的客户端功能,来执行对应的命令。

将之前创建的任务配置中的 pipeline 脚本改成下面:

def label = "jnlp-agent"

podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "1、开始拉取代码"
            sh "git version"
        }
        stage('Maven阶段'){
            container('maven') {
                echo "2、开始Maven编译、推送到本地库"
                sh "mvn -version"
            }
        }
        stage('Docker阶段'){
            container('docker') {
                echo "3、开始读取Maven pom变量,并执行Docker编译、推送、删除"
                sh "docker version"
            }
        }
         stage('Helm阶段'){
            container('helm-kubectl') {
                echo "4、开始检测Kubectl环境,测试执行Helm部署,与执行部署"
                sh "helm version"
            }
        }
    }
}

jenkins slave 容器中默认集成 git 客户端,该整体流水线执行就在 Jenkins slave 容器中,任务默认在 Jenkins Slave 执行,所以不需要设置容器名称。

然后执行查看日志,日志内容如下:

Running on jnlp-agent-g7qk5 in /home/jenkins/workspace/k8s-test
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Git阶段)
[Pipeline] echo
1、开始拉取代码
[Pipeline] sh
+ git version
git version 2.11.0
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Maven阶段)
[Pipeline] container
[Pipeline] {
[Pipeline] echo
2、开始Maven编译、推送到本地库
[Pipeline] sh
+ mvn -version
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T18:41:47Z)
Maven home: /usr/share/maven
Java version: 1.8.0_201, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-1.8-openjdk/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-957.1.3.el7.x86_64", arch: "amd64", family: "unix"
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Docker阶段)
[Pipeline] container
[Pipeline] {
[Pipeline] echo
3、开始读取Maven pom变量,并执行Docker编译、推送、删除
[Pipeline] sh
+ docker version
Client:
 Version:           18.06.2-ce
 API version:       1.38
 Go version:        go1.10.4
 Git commit:        6d37f41
 Built:             Sun Feb 10 03:43:40 2019
 OS/Arch:           linux/amd64
 Experimental:      false
Server: Docker Engine - Community
 Engine:
  Version:          18.09.3
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       774a1f4
  Built:            Thu Feb 28 06:02:24 2019
  OS/Arch:          linux/amd64
  Experimental:     false
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Helm阶段)
[Pipeline] container
[Pipeline] {
[Pipeline] echo
4、开始检测Kubectl环境,测试执行Helm部署,与执行部署
[Pipeline] sh
+ helm version
Client: &version.Version{SemVer:"v2.13.1", GitCommit:"618447cbf203d147601b4b9bd7f8c37a5d39fbb4", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.13.1", GitCommit:"79d07943b03aea2b76c12644b4b54733bc5958d6", GitTreeState:"clean"}
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

最后看见执行状态为 SUCCESS 则证明环境可用,否则有问题,请检测问题所在。

六、开始写 Pipeline 脚本

这里进行分阶段性的脚本编写,然后一步步测试,最后合并在一起。这里新建一个名称为 k8s-pipeline 的任务,然后在配置项脚本框汇总输入 Pipleline 脚本。

1、Git 拉取

这里拉取本人 Github 上的一个简单的 SpringBoot Demo 项目进行实践。

Groovy脚本

def label = "jnlp-agent"

podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "Git 阶段"
            git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"
        }
    }
}

查看执行日志

......
Running on jnlp-agent-dhr1h in /home/jenkins/workspace/k8s-pipeline
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Git阶段)
[Pipeline] echo
1、开始拉取代码
[Pipeline] sh
+ git clone https://github.com/my-dlq/springboot-helloworld.git
Cloning into 'springboot-helloworld'...
[Pipeline] sh
+ ls -l
total 0
drwxr-xr-x 4 root root 79 Apr 28 07:00 springboot-helloworld
[Pipeline] }
......
Finished: SUCCESS

可以通过控制台输出的日志看到,已经拉取成功。继续进行下一步,Maven 阶段。

2、Maven 编译

这里将进行 Maven 编译,将 Java 源码编译成一个 Jar 项目,方便后续打包进入 Docker 镜像。( Maven 中也可以进行单元测试,由于某些原因,这里不进行阐述,可以自己执行测试命令进行测试 )

Groovy脚本

def label = "jnlp-agent"

podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "Git 阶段"
            git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"
        }
        stage('Maven阶段'){
            container('maven') {
                //这里引用上面设置的全局的 settings.xml 文件,根据其ID将其引入并创建该文件
                configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){
                    sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"
                }
            }
        }
    }
}

查看执行日志

......
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ springboot-helloword ---
[INFO] 
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ springboot-helloword ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ springboot-helloword ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/jenkins/workspace/k8s-pipeline/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ springboot-helloword ---
[INFO] Not copying test resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ springboot-helloword ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ springboot-helloword ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ springboot-helloword ---
[INFO] Building jar: /home/jenkins/workspace/k8s-pipeline/target/springboot-helloword-0.0.1.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.1.4.RELEASE:repackage (repackage) @ springboot-helloword ---
[INFO] Replacing main artifact with repackaged archive
[INFO] 
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ springboot-helloword ---
[INFO] Installing /home/jenkins/workspace/k8s-pipeline/target/springboot-helloword-0.0.1.jar to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.jar
[INFO] Installing /home/jenkins/workspace/k8s-pipeline/pom.xml to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.989 s
[INFO] Finished at: 2019-04-28T09:38:03Z
[INFO] ------------------------------------------------------------------------
......
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

3、Docker 编译

Groovy脚本

def label = "jnlp-agent"

podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "Git 阶段"
            git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"
        }
        stage('Maven阶段'){
            echo "Maven 阶段"
            container('maven') {
                //这里引用上面设置的全局的 settings.xml 文件,根据其ID将其引入并创建该文件
                configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){
                    sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"
                }
            }
        }
        stage('Docker阶段'){
            echo "Docker 阶段"
            container('docker') {
                // 读取pom参数
                echo "读取 pom.xml 参数"
                pom = readMavenPom file: './pom.xml'
                // 设置镜像仓库地址
                hub = "registry.cn-shanghai.aliyuncs.com"
                // 设置仓库项目名
                project_name = "mydlq"
                echo "编译 Docker 镜像"
                docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {
                    echo "构建镜像"
                    // 设置推送到aliyun仓库的mydlq项目下,并用pom里面设置的项目名与版本号打标签
                    def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")
                    echo "推送镜像"
                    customImage.push()
                    echo "删除镜像"
                    sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}" 
                }
            }
        }
    }
}

查看执行日志

编译 Docker 镜像
[Pipeline] withEnv
[Pipeline] {
[Pipeline] withDockerRegistry
Executing shell script inside container [docker] of pod [jnlp-agent-v6f1f]
Executing command: "docker" "login" "-u" "3******7@qq.com" "-p" ******** "http://registry.cn-shanghai.aliyuncs.com"  /home/jenkins/workspace/k8s-pipeline2@tmp/b52e213b-a730-4120-b004-decd8e16b246/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[Pipeline] {
[Pipeline] echo
构建镜像
[Pipeline] sh
+ docker build -t registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1 .
Sending build context to Docker daemon  18.37MB

Step 1/7 : FROM registry.cn-shanghai.aliyuncs.com/mydlq/openjdk:8u201-jdk-alpine3.9
 ---> 3675b9f543c5
Step 2/7 : VOLUME /tmp
 ---> Running in 7fc4af80e6ce
Removing intermediate container 7fc4af80e6ce
 ---> 4e4224d3b50b
Step 3/7 : ADD target/*.jar app.jar
 ---> 0c24118d522f
Step 4/7 : RUN sh -c 'touch /app.jar'
 ---> Running in 8836cb91e1ca
Removing intermediate container 8836cb91e1ca
 ---> 389e604851b6
Step 5/7 : ENV JAVA_OPTS="-Xmx512M -Xms256M -Xss256k -Duser.timezone=Asia/Shanghai"
 ---> Running in 5126902b1e6b
Removing intermediate container 5126902b1e6b
 ---> 055ad2b9c49d
Step 6/7 : ENV APP_OPTS=""
 ---> Running in cf8ea4b61eea
Removing intermediate container cf8ea4b61eea
 ---> 07dd4fdda44a
Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
 ---> Running in 93c4d0d859e1
Removing intermediate container 93c4d0d859e1
 ---> d29e092f2c17
Successfully built d29e092f2c17
Successfully tagged registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1
[Pipeline] dockerFingerprintFrom
[Pipeline] echo
推送镜像
[Pipeline] sh
+ docker tag registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1 registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1
[Pipeline] sh
+ docker push registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1
The push refers to repository [registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword]
8d45ad1172aa: Preparing
aba126d6a94c: Preparing
a464c54f93a9: Mounted from mydlq/openjdk
dee6aef5c2b6: Mounted from mydlq/openjdk
aba126d6a94c: Pushed
8d45ad1172aa: Pushed
0.0.1: digest: sha256:2c661931a3c08a1cad1562ec4936c68f06b4b3ffcec5de14c390ae793cf5b53b size: 1371
[Pipeline] echo
删除镜像
[Pipeline] sh
+ docker rmi registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1
Untagged: registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1
Untagged: registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword@sha256:2c661931a3c08a1cad1562ec4936c68f06b4b3ffcec5de14c390ae793cf5b53b
Deleted: sha256:d29e092f2c175a3662af0175e62462238583330ba7d46b84d89134056ba14027
Deleted: sha256:07dd4fdda44a12d7749e5e7f60b1943c83e6d0a3da2e4f279ce4d53f3b04f27e
......
Finished: SUCCESS

4、Helm 启动应用

创建Helm执行方法

这里提前创建好执行 helm 的方法,将其简单封装一下用于执行流水线时候,调用此方法,执行对应的 Helm 操作。

  • 方法名:helmDeploy()
  • 可配参数
参数描述
- init:是否为执行 helm 初始化
- url:初始化 chart 仓库地址
- dry:是否为尝试部署
- name:部署的应用 Release 名
- namespace:应用启动到哪个Namespace
- image:镜像名
- tag:镜像标签
- template:选用的chart模板
// 执行Helm的方法
def helmDeploy(Map args) {
    // Helm 初始化
    if(args.init){
        sh "helm init --client-only --stable-repo-url ${args.url}"
    } 
    // Helm 尝试部署
    else if (args.dry_run) {
        println "尝试 Helm 部署,验证是否能正常部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} -f values.yaml --set ${args.repository},${args.tag} stable/${args.template} --dry-run --debug"
    } 
    // Helm 正式部署
    else {
        println "正式 Helm 部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} -f values.yaml --set ${args.repository},${args.tag} stable/${args.template}"
    }
}

// 方法调用
stage() {
    echo "Helm 初始化 http://chart.mydlq.club"
    helmDeploy(init: true ,url: "Helm 仓库地址");
    echo "Helm 尝试执行部署"
    helmDeploy(init: false ,dry: true ,name: "应用名" ,namespace: "应用启动的Namespace" ,image: "镜像名",tag: "镜像标签" ,template: "选用的chart模板")
    echo "Helm 正式执行部署"
    helmDeploy(init: false ,dry: false ,name: "应用名" ,namespace: "应用启动的Namespace" ,image: "镜像名",tag: "镜像标签" ,template: "选用的chart模板")
}

完整Groovy脚本

def label = "jnlp-agent"

// 执行Helm的方法
def helmDeploy(Map args) {
    if(args.init){
        println "Helm 初始化"
        sh "helm init --client-only --stable-repo-url ${args.url}"
    } else if (args.dry_run) {
        println "尝试 Helm 部署,验证是否能正常部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template} --dry-run --debug"
    } else {
        println "正式 Helm 部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template}"
    }
}

// jenkins slave 执行流水线任务
podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "Git 阶段"
            git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"
        }
        stage('Maven阶段'){
            echo "Maven 阶段"
            container('maven') {
                //这里引用上面设置的全局的 settings.xml 文件,根据其ID将其引入并创建该文件
                configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){
                    sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"
                }
            }
        }
        stage('Docker阶段'){
            echo "Docker 阶段"
            container('docker') {
                // 读取pom参数
                echo "读取 pom.xml 参数"
                pom = readMavenPom file: './pom.xml'
                // 设置镜像仓库地址
                hub = "registry.cn-shanghai.aliyuncs.com"
                // 设置仓库项目名
                project_name = "mydlq"
                echo "编译 Docker 镜像"
                docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {
                    echo "构建镜像"
                    // 设置推送到aliyun仓库的mydlq项目下,并用pom里面设置的项目名与版本号打标签
                    def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")
                    echo "推送镜像"
                    customImage.push()
                    echo "删除镜像"
                    sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}" 
                }
            }
        }
        stage('Helm阶段'){
            container('helm-kubectl') {
                withKubeConfig([credentialsId: "8510eda6-e1c7-4535-81af-17626b9575f7",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                    // 设置参数
                    image = "image.repository=${hub}/${project_name}/${pom.artifactId}"
                    tag = "image.tag=${pom.version}"
                    template = "spring-boot"
                    repo_url = "http://chart.mydlq.club"
                    app_name = "${pom.artifactId}"
                    // 检测是否存在yaml文件
                    def values = ""
                    if (fileExists('values.yaml')) {
                        values = "-f values.yaml"
                    }
                    // 执行 Helm 方法
                    echo "Helm 初始化"
                    helmDeploy(init: true ,url: "${repo_url}");
                    echo "Helm 执行部署测试"
                    helmDeploy(init: false ,dry_run: true ,name: "${app_name}" ,namespace: "mydlqcloud" ,image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")
                    echo "Helm 执行正式部署"
                    helmDeploy(init: false ,dry_run: false ,name: "${app_name}" ,namespace: "mydlqcloud",image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")
                }
            }
        }
    }
}

查看执行日志

.....
Executing shell script inside container [helm-kubectl] of pod [jnlp-agent-r3c8h]
Executing command: "kubectl" "config" "set-cluster" "k8s" "--server=https://kubernetes.default.svc.cluster.local" "--insecure-skip-tls-verify=true" 
exit
Cluster "k8s" set.
Executing shell script inside container [helm-kubectl] of pod [jnlp-agent-r3c8h]
Executing command: "kubectl" "config" "set-credentials" "cluster-admin" ******** 
Switched to context "k8s".
[Pipeline] {
[Pipeline] fileExists
[Pipeline] echo
Helm 初始化
[Pipeline] echo
Helm 初始化
[Pipeline] sh
+ helm init --client-only --stable-repo-url http://chart.mydlq.club
Creating /root/.helm/repository/repositories.yaml 
Adding stable repo with URL: http://chart.mydlq.club
Adding local repo with URL: http://127.0.0.1:8879/charts 
$HELM_HOME has been configured at /root/.helm.
Not installing Tiller due to 'client-only' flag having been set
Happy Helming!
[Pipeline] echo
Helm 执行部署测试
[Pipeline] echo
尝试 Helm 部署,验证是否能正常部署
[Pipeline] sh
+ helm upgrade --install springboot-helloworld --namespace mydlqcloud -f values.yaml --set 'image.repository=registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloworld,image.tag=0.0.1' stable/spring-boot --dry-run --debug
[debug] Created tunnel using local port: '37001'

[debug] SERVER: "127.0.0.1:37001"

[debug] Fetched stable/spring-boot to /root/.helm/cache/archive/spring-boot-1.0.4.tgz

Release "springboot-helloworld" does not exist. Installing it now.
[debug] CHART PATH: /root/.helm/cache/archive/spring-boot-1.0.4.tgz
[Pipeline] echo
Helm 执行正式部署
[Pipeline] echo
正式 Helm 部署
[Pipeline] sh
+ helm upgrade --install springboot-helloworld --namespace mydlqcloud -f values.yaml --set 'image.repository=registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloworld,image.tag=0.0.1' stable/spring-boot
Release "springboot-helloworld" does not exist. Installing it now.
NAME:   springboot-helloworld
LAST DEPLOYED: Mon Apr 29 07:31:39 2019
NAMESPACE: mydlqcloud
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME                                    READY  STATUS             RESTARTS  AGE
springboot-helloworld-7cd66cf74d-vfjr6  0/1    ContainerCreating  0         0s

==> v1/Service
NAME                   TYPE      CLUSTER-IP   EXTERNAL-IP  PORT(S)                        AGE
springboot-helloworld  NodePort  10.10.87.61  <none>       8080:30080/TCP,8081:30081/TCP  0s

==> v1beta1/Deployment
NAME                   READY  UP-TO-DATE  AVAILABLE  AGE
springboot-helloworld  0/1    1           0          0s
......
Finished: SUCCESS

5、测试接口

上面的 Helm步骤执行完成后,就可以进行简单测试了,其中此项目引用的chart是一个简单的 SpringBoot 项目,其中用 NodePort 方式暴露了两个端口,30080 & 30081,分别对应8080、8081俩个端口,切提供了一个 Hello World 接口为“/hello”,所以我们这里访问一下这个接口地址:

http://192.168.2.11:30080/hello

七、完善 Pipeline 脚本

1、设置超时时间

设置任务超时时间,如果在规定时间内任务没有完成,则进行失败操作。

格式:

timeout(time: 20, unit: 'SECONDS') {
    // 流水线代码
}

例子:

设置超时时间为 60s 来让 Jenkins Slave 节点执行任务。

def label = "jnlp-agent"
timeout(time: 60, unit: 'SECONDS') {
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                echo "Git 阶段"
            }
            stage('Maven阶段'){
                echo "Maven 阶段"
            }
            stage('Docker阶段'){
                echo "Docker 阶段"
            }
            stage('Helm阶段'){
                echo "Helm 阶段"
            }
        }
    }
}

2、设置邮箱通知

(1)、设置邮箱开启 POP3/SMTP/IMAP 设置

这里用的是 163 邮箱

(2)、安装 Email Extension Template 插件

这里安装插件“Email Extension Template”用于设置邮件模板。

(3)、配置系统默认邮件参数

系统管理->系统设置:

配置“Jenkins Location”和“Extended E-mail Notification”,其中系统管理员邮件地址一定要和“User Name”值一致。

Jenkins Location 设置:
参数名称描述
- Jenkins URL:Jenkins 地址,用于发送邮件时写入内容之中
- 系统管理员邮件地址:邮件服务器账户

Extended E-mail Notification 设置:
参数名称描述
- SMTP server:smtp 邮箱服务的地址
- Default user E-mail suffix:邮件服务器后缀
- User Name:邮件服务器账户
- Password:邮件服务器 SMTP 授权码
- Default Content Type:设置邮件文本格式
- Enable Debug Mode:启用 Debug 模式

(4)、创建流水线项目

创建一个流水线项目,用于写 Pipeline 脚本测试邮件发送,并配置 Pipeline 脚本,这里写一个简单的 Pipeline 脚本,调用 emailext 方法执行发送邮件。

脚本内容:

def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        stage('Git阶段'){
            echo "Git 阶段"
        }
        stage('Maven阶段'){
            echo "Maven 阶段"
        }
        stage('Docker阶段'){
            echo "Docker 阶段"
        }
        stage('Helm阶段'){
            echo "Helm 阶段"
        }
        stage('email'){
            echo "测试发送邮件"
            emailext(subject: '任务执行失败',to: '324******47@qq.com',body: '''测试邮件内容...''')
        }
    }
}

(5)、运行项目查看日志

查看日志,看是否执行发送操作以及运行状况。

Started by user admin
Running in Durability level: PERFORMANCE_OPTIMIZED
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/email-test
[Pipeline] {
[Pipeline] stage
[Pipeline] { (email)
[Pipeline] echo
测试发送邮件
[Pipeline] emailext
Sending email to: 32*****47@qq.com
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

(6)、查看邮件

查看邮件,可以看到已经收到设置邮箱发来的邮件信息。

3、判断成功失败来发送邮件

(1)、流水线过程判断成功失败

这里加 try、catch、finally 进行流水线,党执行 finally 时候,进行判断此任务执行到此是否成功构建,如果成功,则发送成功邮件通知。如果失败,则发送失败邮件通知。

try{
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                echo "Git 阶段"
            }
            stage('Maven阶段'){
                echo "Maven 阶段"
            }
            stage('Docker阶段'){
                echo "Docker 阶段"
            }
            stage('Helm阶段'){
                echo "Helm 阶段"
            }
        }
    }
}catch(Exception e) {
    currentBuild.result = "FAILURE"
}finally {
    // 获取执行状态
    def currResult = currentBuild.result ?: 'SUCCESS' 
    // 判断执行任务状态,根据不同状态发送邮件
    stage('email'){
        if (currResult == 'SUCCESS') {
            echo "发送成功邮件"
            emailext(subject: '任务执行成功',to: '32*****7@qq.com',body: '''任务已经成功构建完成...''')
        }else {
            echo "发送失败邮件"
            emailext(subject: '任务执行失败',to: '32*****7@qq.com',body: '''任务执行失败构建失败...''')
        }
    }
}

(2)、测试成功失败执行时发送邮件

成功:

让其正常成功跑完流程后发送邮件。

失败:

模拟故意执行错误发送邮件。

4、将脚本放入到项目中

将脚本放入项目之中,方便后续调用时直接设置项目所在的Git地址即可。

八、完整代码

完整代码如下:

// 执行Helm的方法
def helmDeploy(Map args) {
    if(args.init){
        println "Helm 初始化"
        sh "helm init --client-only --stable-repo-url ${args.url}"
    } else if (args.dry_run) {
        println "尝试 Helm 部署,验证是否能正常部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template} --dry-run --debug"
    } else {
        println "正式 Helm 部署"
        sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template}"
    }
}

// jenkins slave 执行流水线任务
timeout(time: 600, unit: 'SECONDS') {
    try{
        def label = "jnlp-agent"
        podTemplate(label: label,cloud: 'kubernetes' ){
            node (label) {
                stage('Git阶段'){
                    echo "Git 阶段"
                    git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"
                }
                stage('Maven阶段'){
                    echo "Maven 阶段"
                    container('maven') {
                        //这里引用上面设置的全局的 settings.xml 文件,根据其ID将其引入并创建该文件
                        configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){
                            sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"
                        }
                    }
                }
                stage('Docker阶段'){
                    echo "Docker 阶段"
                    container('docker') {
                        // 读取pom参数
                        echo "读取 pom.xml 参数"
                        pom = readMavenPom file: './pom.xml'
                        // 设置镜像仓库地址
                        hub = "registry.cn-shanghai.aliyuncs.com"
                        // 设置仓库项目名
                        project_name = "mydlq"
                        echo "编译 Docker 镜像"
                        docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {
                            echo "构建镜像"
                            // 设置推送到aliyun仓库的mydlq项目下,并用pom里面设置的项目名与版本号打标签
                            def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")
                            echo "推送镜像"
                            customImage.push()
                            echo "删除镜像"
                            sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}" 
                        }
                    }
                }
                stage('Helm阶段'){
                    container('helm-kubectl') {
                        withKubeConfig([credentialsId: "8510eda6-e1c7-4535-81af-17626b9575f7",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                            // 设置参数
                            image = "image.repository=${hub}/${project_name}/${pom.artifactId}"
                            tag = "image.tag=${pom.version}"
                            template = "spring-boot"
                            repo_url = "http://chart.mydlq.club"
                            app_name = "${pom.artifactId}"
                            // 检测是否存在yaml文件
                            def values = ""
                            if (fileExists('values.yaml')) {
                                values = "-f values.yaml"
                            }
                            // 执行 Helm 方法
                            echo "Helm 初始化"
                            helmDeploy(init: true ,url: "${repo_url}");
                            echo "Helm 执行部署测试"
                            helmDeploy(init: false ,dry_run: true ,name: "${app_name}" ,namespace: "mydlqcloud" ,image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")
                            echo "Helm 执行正式部署"
                            helmDeploy(init: false ,dry_run: false ,name: "${app_name}" ,namespace: "mydlqcloud",image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")
                        }
                    }
                }
            }
        }
    }catch(Exception e) {
        currentBuild.result = "FAILURE"
    }finally {
        // 获取执行状态
        def currResult = currentBuild.result ?: 'SUCCESS' 
        // 判断执行任务状态,根据不同状态发送邮件
        stage('email'){
            if (currResult == 'SUCCESS') {
                echo "发送成功邮件"
                emailext(subject: '任务执行成功',to: '32******7@qq.com',body: '''任务已经成功构建完成...''')
            }else {
                echo "发送失败邮件"
                emailext(subject: '任务执行失败',to: '32******7@qq.com',body: '''任务执行失败构建失败...''')
            }
        }
    }
}