基于 Jenkins、Gitlab、Harbor、Helm 和 Kubernetes 的完整 CICD案例

一. 练习项目

本次示例项目是一个完整的基于 Spring Boot、Spring Security、JWT、React 和 Ant Design 构建的一个开源的投票应用,项目地址:https://github.com/callicoder/spring-security-react-ant-design-polls-app。

我们将会在该项目的基础上添加部分代码,并实践 CI/CD 流程。

1. 服务端

本次测试就以部署服务端为例子,首先需要更改的是服务端配置,我们需要将数据库链接的配置更改成环境变量的形式,写死了的话就没办法进行定制了,修改服务端文件src/main/resources/application.properties,将下面的数据库配置部分修改成如下形式:

spring.datasource.url= jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:polling_app}?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username= ${DB_USER:root}
spring.datasource.password= ${DB_PASSWORD:root}

当环境变量中有上面的数据配置的时候,就会优先使用环境变量中的值,没有的时候就会用默认的值进行数据库配置。由于我们要将项目部署到 Kubernetes 集群中去,所以我们需要将服务端进行容器化,所以我们在项目根目录下面添加一个Dockerfile文件进行镜像构建:

FROM adoptopenjdk/maven-openjdk11 as BUILD

COPY src /usr/app/src
COPY pom.xml /usr/app

RUN mvn -f /usr/app/pom.xml clean package -Dmaven.test.skip=true

#FROM openjdk:8-jdk-alpine
FROM openjdk:11.0.10-jdk

MAINTAINER cnych <icnych@gmail.com>

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV TZ=Asia/Shanghai

RUN mkdir /app

WORKDIR /app

COPY --from=BUILD /usr/app/target/polls-0.0.1-SNAPSHOT.jar /app/polls.jar

EXPOSE 8080

ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar","/app/polls.jar"]

前面课程中我们就讲解过 Docker 的多阶段构建,这里我们定义了两个阶段,第一个阶段利用maven:3.6-alpine这个基础镜像将我们的项目进行打包,然后将该阶段打包生成的jar包文件复制到第二阶段进行最后的镜像打包,这样就可以很好的完成我们的 Docker 镜像的构建工作。

二. 配置jenkins

现在项目准备好了,接下来我们可以开始 Jenkins 的配置,还记得前面在 Pipeline 结合 Kubernetes 的课程中我们使用了一个kubernetes的 Jenkins 插件,但是之前使用的方式有一些不妥的地方,我们 Jenkins Pipeline 构建任务绑定到了一个固定的 Slave Pod 上面,这样就需要我们的 Slave Pod 中必须包含一系列构建所需要的依赖,比如 docker、maven、node、java 等等,这样就难免需要我们自己定义一个很庞大的 Slave 镜像。

现在我们可以直接在 Pipeline 中去自定义 Slave Pod 中所需要用到的容器模板,这样我们需要什么镜像只需要在 Slave Pod Template 中声明即可,完全不需要去定义一个庞大的 Slave 镜像了。

1.删除pod template

首先去掉 Jenkins 中 kubernetes 插件中的 Pod Template 的定义,Jenkins -> 系统管理 -> 系统设置 -> 云 -> Kubernetes区域,删除下方的Kubernetes Pod Template -> 保存

2.新建流水线任务

这里我们新建一个名为polling-app-server类型为流水线的任务

2.1 trigger

然后在这里需要勾选触发远程构建的触发器,其中令牌我们可以随便写一个字符串,然后记住下面的 URL,将 JENKINS_URL 替换成 Jenkins 的地址,我们这里的地址就是:

http://192.168.2.34:30002/job/polling-app-server/build?token=server321

trigger

2.2 pipeline scm

然后再下面流水线区域选择Pipeline script from SCM,意思就是从代码仓库中通过Jenkinsfile文件获取Pipeline script脚本定义,然后选择 SCM 来源为Git,在出现的列表中配置上仓库地址 http://192.168.2.35:88/root/polling-app-server.git

由于我们是在一个 Slave Pod 中去进行构建,所以如果使用 SSH 的方式去访问 Gitlab 代码仓库的话就需要频繁的去更新 SSH-KEY,所以我们这里采用直接使用用户名和密码的形式来方式:

2.3 gitlab auth

Credentials区域点击添加按钮添加我们访问 Gitlab 的用户名和密码:

gitlab auth

2.4 gitlab branch config

然后需要我们配置用于构建的分支,如果所有的分支我们都想要进行构建的话,只需要将Branch Specifier区域留空即可,一般情况下不同的环境对应的分支才需要构建,比如 master、develop、test 等,我们这里就只配置 master 和 develop 两个分支用户构建(实际我只配了master)

3. gitlab 配置webhook

然后前往 Gitlab 中配置项目polling-app-server Webhook,settings -> Integrations,填写上面得到的 trigger 地址:http://192.168.2.34:30002/job/polling-app-server/build?token=server321

保存后,可以直接点击Test -> Push Event测试是否可以正常访问 Webhook 地址,这里需要注意的是我们需要配置下 Jenkins 的安全配置,否则会报错403 No valid crumb was included in the request

解决方法:https://www.jianshu.com/p/00fcfa4a53b5

如果测试出现了Hook executed successfully: HTTP 201则证明 Webhook 配置成功了

4.创建测试jenkinsfile

配置成功后我们只需要往 Gitlab 仓库推送代码就会触发 Pipeline 构建了。接下来我们直接在服务端代码仓库根目录下面添加Jenkinsfile文件,用于描述流水线构建流程。首先定义最简单的流程,要注意这里和前面课程的不同之处,这里我们使用podTemplate来定义不同阶段使用的的容器,有哪些阶段呢?

Clone 代码 -> 代码静态分析 -> 单元测试 -> Maven 打包 -> Docker 镜像构建/推送 -> Helm 更新服务。

Clone 代码在默认的 Slave 容器中即可;静态分析和单元测试我们这里直接忽略,有需要这个阶段的同学自己添加上即可;Maven 打包肯定就需要 Maven 的容器了;Docker 镜像构建/推送是不是就需要 Docker 环境了呀;最后的 Helm 更新服务是不是就需要一个有 Helm 的容器环境了,所以我们这里就可以很简单的定义podTemplate了,如下定义:(添加一个kubectl工具用于测试)

def label = "slave-${UUID.randomUUID().toString()}"

podTemplate(label: label, serviceAccount: 'jenkins2', containers: [
  containerTemplate(name: 'maven', image: 'adoptopenjdk/maven-openjdk11', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true)
], volumes: [
  hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    def gitCommit = myRepo.GIT_COMMIT
    def gitBranch = myRepo.GIT_BRANCH

    stage('单元测试') {
      echo "测试阶段"
    }
    stage('代码编译打包') {
      container('maven') {
        echo "打码编译打包阶段"
      }
    }
    stage('构建 Docker 镜像') {
      container('docker') {
        echo "构建 Docker 镜像阶段"
      }
    }
    stage('运行 Kubectl') {
      container('kubectl') {
        echo "查看 K8S 集群 Pod 列表"
        sh "kubectl get pods"
      }
    }
    stage('运行 Helm') {
      container('helm') {
        echo "查看 Helm Release 列表"
        sh "helm list"
      }
    }
  }
}

注意:
1)kubectl get pods阶段,podTemplate中需要添加serviceAcount: 'jenkins2',否则会报权限错误,这个jenkins2在部署jenkins时已指定

2)helm list时会报权限错误,这个是因为之前创建jenkins这个sa时,没有添加list secret的权限。在clusterRole为jenkins2里添加上即可

kubectl edit clusterRole jenkins2
- apiGroups:
  - ""
  resources:
  - secrets
  verbs:
  - get
  - list  #加上list权限

上面这段groovy脚本比较简单,我们需要注意的是volumes区域的定义,

将容器中的/root/.m2目录挂载到宿主机上是为了给Maven构建添加缓存的,不然每次构建的时候都需要去重新下载依赖,这样就非常慢了;

挂载.kube目录是为了能够让kubectlhelm两个工具可以读取到 Kubernetes 集群的连接信息,不然我们是没办法访问到集群的;

最后挂载/var/run/docker.sock文件是为了能够让我们的docker这个容器获取到Docker Daemon的信息的,因为docker这个镜像里面只有客户端的二进制文件,我们需要使用宿主机的Docker Daemon来构建镜像,当然我们也需要在运行 Slave Pod 的节点上拥有访问集群的文件,然后在每个Stage阶段使用特定需要的容器来进行任务的描述即可,所以这几个volumes都是非常重要的

volumes: [
  hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]

另外一个值得注意的就是label标签的定义,我们这里使用 UUID 生成一个随机的字符串,这样可以让 Slave Pod 每次的名称都不一样,而且这样就不会被固定在一个 Pod 上面了,以后有多个构建任务的时候就不会存在等待的情况了,这和我们之前的课程中讲到的固定在一个 label 标签上有所不同。

正常情况就可以看到我们的流水线任务polling-app-server已经被触发构建了,然后回到我们的 Kubernetes 集群中可以看到多了一个 slave 开头的 Pod,里面有5个容器,就是我们上面 podTemplate 中定义的4个容器,加上一个默认的 jenkins slave 容器,同样的,构建任务完成后,这个 Pod 也会被自动销毁掉:

5.完善jenkinsfile

第一个阶段:单元测试,我们可以在这个阶段是运行一些单元测试或者静态代码分析的脚本,我们这里直接忽略。

第二个阶段:代码编译打包,我们可以看到我们是在一个maven的容器中来执行的,所以我们只需要在该容器中获取到代码,然后在代码目录下面执行 maven 打包命令即可,如下所示:

    stage('代码编译打包') {
      try{
        container('maven') {
          echo "打码编译打包阶段"
          sh "mvn clean package -Dmaven.test.skip=true"
        }
      } catch(exc){
        println "构建失败 - ${currentBuild.fullDisplayName}"
        throw(exc)
      }
    }

这里下载maven插件组件会很慢,可以修改下插件源为国内,需要更改default.json

default.json的具体路径是:{jenkins的工作路径}/updates/default.json

sed -i 's#updates.jenkins.io/download/plugins#mirrors.tuna.tsinghua.edu.cn/jenkins/plugins#g' default.json 
sed -i 's#www.google.com#www.baidu.com#g' default.json

解决没有合法证书而不能查看Https网址的问题

需要将~/.jenkins/hudson.model.UpdateCenter.xml中的https改为http

第三个阶段:构建 Docker 镜像,要构建 Docker 镜像,就需要提供镜像的名称和 tag,要推送到 Harbor 仓库,就需要提供登录的用户名和密码,所以我们这里使用到了withCredentials方法,在里面可以提供一个credentialsIdharbor的认证信息,如下

    stage('构建 Docker 镜像') {  
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
      credentialsId: 'harbor',
      usernameVariable: 'harborUser',
      passwordVariable: 'harborPassword']]) {
        container('docker') {
          echo "构建 Docker 镜像阶段"
          sh """
            docker login ${dockerRegistryUrl} -u ${harborUser} -p ${harborPassword}
            docker build -t ${image}:${imageTag} .
            docker push ${image}:${imageTag}
          """
        }
      }
    }

其中 image, imageTag, dockerRegistryUrl 我们可以在上面node(label){}中定义成全局变量:

..
node(label) {
    def myRepo = checkout scm
    def gitCommit = myRepo.GIT_COMMIT
    def gitBranch = myRepo.GIT_BRANCH
    
    def dockerRegistryUrl = "192.168.2.35"
    def imageEndpoint = "course/polling-app-server"
    def image = "${dockerRegistryUrl}/${imageEndpoint}"
    def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
    ...

harbor 的用户名和密码信息则需要通过凭据来进行添加,进入 jenkins 首页 -> 左侧菜单凭据 -> 添加凭据,选择用户名和密码类型的,其中 ID 一定要和上面的credentialsId的值保持一致: 这个之前已经做过了。

第四个阶段:运行 kubectl 工具,其实在我们当前使用的流水线中是用不到 kubectl 工具的,那么为什么我们这里要使用呢?这还不是因为我们暂时还没有去写应用的 Helm Chart 包吗?所以我们先去用原始的 YAML 文件来编写应用部署的资源清单文件,这也是我们写出 Chart 包前提,因为只有知道了应用如何部署才可能知道 Chart 包如何编写,所以我们先编写应用部署资源清单

首先当然就是 Deployment 控制器了,如下所示:(k8s.yaml)

需要事先创建命令空间course

apiVersion: apps/v1
kind: Deployment
metadata:
  name: polling-server
  namespace: course
spec:
  replicas: 1
  selector:
    matchLabels:
      app: polling-server
  template:
    metadata:
      labels:
        app: polling-server
    spec:
      restartPolicy: Always
      imagePullSecrets:
        - name: myreg
      containers:
      - image: <IMAGE>:<IMAGE_TAG>
        name: polling-server
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: api
        env:
        - name: DB_HOST
          value: mysql
        - name: DB_PORT
          value: "3306"
        - name: DB_NAME
          value: polling_app
        - name: DB_USER
          value: polling
        - name: DB_PASSWORD
          value: polling321

---

kind: Service
apiVersion: v1
metadata:
  name: polling-server
  namespace: course
spec:
  selector:
    app: polling-server
  type:  ClusterIP
  ports:
  - name: api-port
    port: 8080
    targetPort:  api

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: course
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      restartPolicy: Always
      containers:
      - name: mysql
        image: mysql:5.7
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3306
          name: dbport
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: rootPassW0rd
        - name: MYSQL_DATABASE
          value: polling_app
        - name: MYSQL_USER
          value: polling
        - name: MYSQL_PASSWORD
          value: polling321
        volumeMounts:
        - name: db
          mountPath: /var/lib/mysql
      volumes:
      - name: db
        hostPath:
          path: /var/lib/mysql

---
kind: Service
apiVersion: v1
metadata:
  name: mysql
  namespace: course
spec:
  selector:
    app: mysql
  type:  ClusterIP
  ports:
  - name: dbport
    port: 3306
    targetPort: dbport

可以看到我们上面的 YAML 文件中添加使用的镜像是用标签代替的:<IMAGE>:<IMAGE_TAG>,这是因为我们的镜像地址是动态的,下依赖我们在上一个阶段打包出来的镜像地址的,所以我们这里用标签代替,然后将标签替换成真正的值即可,

另外为了保证应用的稳定性,我们还在应用中添加了健康检查,所以需要在代码中创建了一个健康检查的 Controller:(src/main/java/com/example/polls/controller/StatusController.java)

package com.example.polls.controller;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/_status/healthz")
public class StatusController {

    @GetMapping
    public String healthCheck() {
        return "UP";
    }

}

最后就是环境变量了,还记得前面我们更改了资源文件中数据库的配置吗?(src/main/resources/application.properties)因为要尽量通用,我们在部署应用的时候很有可能已经有一个外部的数据库服务了,所以这个时候通过环境变量传入进来即可。另外由于我们这里使用的是私有镜像仓库,所以需要在集群中提前创建一个harbor对应的 Secret 对象:

kubectl create secret docker-registry myreg --docker-server=192.168.2.35 --docker-username=admin --docker-password=Harbor12345 --docker-email=1816635208@qq.com --namespace course

在代码根目录下面创建一个 manifests 的目录,用来存放上面的资源清单文件,正常来说是不是我们只需要在镜像构建成功后,将上面的 k8s.yaml 文件中的镜像标签替换掉就 OK,所以这一步的动作如下:

stage('运行 Kubectl') {
  container('kubectl') {
    echo "查看 K8S 集群 Pod 列表"
    sh "kubectl get pods"
    sh """
      sed -i "s#<IMAGE>#${image}#" manifests/k8s.yaml
      sed -i "s/<IMAGE_TAG>/${imageTag}/" manifests/k8s.yaml
      kubectl apply -f k8s.yaml
    """
  }
}

可能是版本或者系统原因,在我的环境中sed命令最后的/也需要加上,否则报错。这里因为image中有"/", 使用"#”来代替"/"

第五阶段:运行 Helm 工具,就是直接使用 Helm 来部署应用了,现在有了上面的基本的资源对象了,要创建 Chart 模板就相对容易了,Chart 模板仓库地址:https://github.com/cnych/polling-helm,我们可以根据values.yaml文件来进行自定义安装,模板中我们定义了可以指定使用外部数据库服务或者内部独立的数据库服务,具体的我们可以去看模板中的定义。首先我们可以先使用这个模板在集群中来测试下。首先在集群中 Clone 上面的 Chart 模板:

git clone https://github.com/cnych/polling-helm.git

然后我们使用内部的数据库服务,新建一个 custom.yaml 文件来覆盖 values.yaml 文件中的值:

persistence:
  enabled: true
  persistentVolumeClaim:
    database:
      storageClass: "database"

database:
  type: internal
  internal:
    database: "polling"
    # 数据库用户
    username: "polling"
    # 数据库用户密码
    password: "polling321"

可以看到我们这里使用了一个名为database的 StorgeClass 对象,所以还得创建先创建这个资源对象:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: database
provisioner: fuseim.pri/ifs

然后我们就可以在 Chart 根目录下面安装应用,执行下面的命令:

helm upgrade --install polling -f custom.yaml . --namespace course

再执行之前最好把原先k8s.yaml生成的资源删掉

安装完成后,正常情况如下

6.使用helm实现全部部署

这样我们就完成了使用 Helm Chart 安装应用的过程,但是现在我们使用的包还是直接使用的 git 仓库中的,平常我们正常安装的时候都是使用的 Chart 仓库中的包,所以我们需要将该 Chart 包上传到一个仓库中去。

比较幸运的是我们的 Harbor 也是支持 Helm Chart 包的。我们可以选择手动通过 Harbor 的 Dashboard 将 Chart 包进行上传,也可以通过使用Helm Push插件:

官网下载helm-push_0.9.0_linux_amd64.tar.gz,其它版本太新了不太会用
https://github.com/chartmuseum/helm-push/tags
https://github.com/chartmuseum/helm-push/releases/download/v0.9.0/helm-push_0.9.0_linux_amd64.tar.gz

helm env 查看plugin路径,我这里是HELM_PLUGINS="/root/.local/share/helm/plugins"
在此路径下新建helm-push文件夹,并将安装包拷贝到该文件夹下解压即可

mkdir /root/.local/share/helm/plugins/helm-push
cp helm-push_0.9.0_linux_amd64.tar.gz /root/.local/share/helm/plugins/helm-push
tar zxvf /root/.local/share/helm/plugins/helm-push/helm-push_0.9.0_linux_amd64.tar.gz

首先将 Harbor 提供的仓库添加到 helm repo 中,由于是私有仓库,所以在添加的时候我们需要添加用户名和密码:

helm repo add course http://192.168.2.35/chartrepo/course --username=admin --password=Harbor12345
其中chartrepo是固定写法

将之前下载的polling-helm包上传到Harbor仓库的course项目中

helm push polling-helm course

到这里 Helm 相关的工作就准备好了。那么我们如何在 Jenkins Pipeline 中去使用 Helm 呢?我们可以回顾下,我们平时的一个 CI/CD 的流程:开发代码 -> 提交代码 -> 触发镜像构建 -> 修改镜像tag -> 推送到镜像仓库中去 -> 然后更改 YAML 文件镜像版本 -> 使用 kubectl 工具更新应用。

现在我们是不是直接使用 Helm 了,就不需要去手动更改 YAML 文件了,也不需要使用 kubectl 工具来更新应用了,而是只需要去覆盖下 helm 中的镜像版本,直接 upgrade 是不是就可以达到应用更新的结果了。我们可以去查看下 chart 包的 values.yaml 文件中关于 api 服务的定义:

api:
  image:
    repository: cnych/polling-api
    tag: 0.0.7
    pullPolicy: IfNotPresent

我们是不是只需要将上面关于 api 服务使用的镜像用我们这里 Jenkins 构建后的替换掉就可以了,这样我们更改上面的最后运行 Helm的阶段如下:

stage('运行 Helm') {
  container('helm') {
    echo "更新 polling 应用"
    sh """
      helm upgrade --install polling polling --set persistence.persistentVolumeClaim.database.storageClass=database --set database.type=internal --set database.internal.database=polling --set database.internal.username=polling --set database.internal.password=polling321 --set api.image.repository=${image} --set api.image.tag=${imageTag} --set imagePullSecrets[0].name=myreg --namespace course
    """
  }
}

当然我们可以将需要更改的值都放入一个 YAML 之中来进行修改,我们这里通过--set来覆盖对应的值,这样整个 API 服务的完整 Jenkinsfile 文件如下所示:

def label = "slave-${UUID.randomUUID().toString()}"


def helmLint(String chartDir) {
    println "校验 chart 模板"
    sh "helm lint ${chartDir}"
}

//def helmInit() {
//  println "初始化 helm client"
//  sh "helm init --client-only --stable-repo-url https://mirror.azure.cn/kubernetes/charts/"
//}

def helmRepo(Map args) {
  println "添加 course repo"
  sh "helm repo add --username ${args.username} --password ${args.password} course http://192.168.2.35/chartrepo/course"

  println "更新 repo"
  sh "helm repo update"

  println "获取 Chart 包"
  sh """
    helm fetch course/polling
    tar -xzvf polling-0.1.0.tgz
    """
}

def helmDeploy(Map args) {
    //helmInit()
    helmRepo(args)

    if (args.dry_run) {
        println "Debug 应用"
        sh "helm upgrade --dry-run --debug --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
    } else {
        println "部署应用"
        sh "helm upgrade --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
        echo "应用 ${args.name} 部署成功. 可以使用 helm status ${args.name} 查看应用状态"
    }
}


podTemplate(label: label, serviceAccount: 'jenkins2', containers: [
  containerTemplate(name: 'maven', image: 'adoptopenjdk/maven-openjdk11', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
  //containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true)
], volumes: [
  hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    def gitCommit = myRepo.GIT_COMMIT
    def gitBranch = myRepo.GIT_BRANCH
    
    def dockerRegistryUrl = "192.168.2.35"
    def imageEndpoint = "course/polling-app-server"
    def image = "${dockerRegistryUrl}/${imageEndpoint}"
    def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()

    stage('单元测试') {
      echo "测试阶段"
    }
    stage('代码编译打包') {
      try{
        container('maven') {
          echo "打码编译打包阶段"
          sh "mvn clean package -Dmaven.test.skip=true"
        }
      } catch(exc){
        println "构建失败 - ${currentBuild.fullDisplayName}"
        throw(exc)
      }
    }

    stage('构建 Docker 镜像') {  
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
      credentialsId: 'harbor',
      usernameVariable: 'harborUser',
      passwordVariable: 'harborPassword']]) {
        container('docker') {
          echo "构建 Docker 镜像阶段"
          sh """
            docker login ${dockerRegistryUrl} -u ${harborUser} -p ${harborPassword}
            docker build -t ${image}:${imageTag} .
            docker push ${image}:${imageTag}
          """
        }
      }
    }

    //stage('运行 Kubectl') {
    //  container('kubectl') {
    //    echo "查看 K8S 集群 Pod 列表"
    //    sh "kubectl get pods"

    //    sh """
    //      sed -i "s#<IMAGE>#${image}#" manifests/k8s.yaml
    //      sed -i "s/<IMAGE_TAG>/${imageTag}/" manifests/k8s.yaml
    //      kubectl apply -f manifests/k8s.yaml
    //    """
    //  }
    //}

    stage('运行 Helm') {
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
        credentialsId: 'harbor',
        usernameVariable: 'harborUser',
        passwordVariable: 'harborPassword']]) {
          container('helm') {
            //echo "查看 Helm Release 列表"
            //sh "helm list"
            echo "开始helm部署"
            helmDeploy(
                dry_run     : false,
                name        : "polling",
                chartDir    : "polling",
                namespace   : "course",
                tag         : "${imageTag}",
                image       : "${image}",
                username    : "${harborUser}",
                password    : "${harborPassword}"
            )
            echo "helm 部署成功"
          }
        }
    }        
  }
}

posted @ 2023-09-19 16:59  坚强的小蚂蚁  阅读(133)  评论(0编辑  收藏  举报