其他分享
首页 > 其他分享> > Kubernetes 部署 Jenkins + Kubernetes 的 CI CD

Kubernetes 部署 Jenkins + Kubernetes 的 CI CD

作者:互联网

Jenkins 与 Kubernetes 的 CI 与 CD & Git + Maven + Docker+Kubectl

参考:

http://www.mydlq.club/article/47/

https://plugins.jenkins.io/kubernetes/

一、Kubernetes 部署 Jenkins

1. 使用StorageClass+NFS创建pv

具体创建StorageClass+NFS方法详见下面链接或自行百度

https://www.cnblogs.com/yg0070/p/16440498.html

创建pvc

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  annotations:
    volume.beta.kubernetes.io/storage-provisioner: master-nfs-storage  # 与storageClass中metadata.name保持一致
  finalizers:
    - kubernetes.io/pvc-protection
  name: jenkins
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi    #存储空间大小
  storageClassName: master-nfs-storage   # 与storageClass中的provisioner一致

2. 创建ServiceAccount & ClusterRoleBinding

Kubernetes 集群一般情况下都默认开启了 RBAC 权限,所以需要创建一个角色和服务账户,设置角色拥有一定权限,然后将角色与 ServiceAccount 绑定,最后将 ServiceAccount 与 Jenkins 绑定,这样来赋予 Jenkins 一定的权限,使其能够执行一些需要权限才能进行的操作。这里为了方便,将 cluster-admin 绑定到 ServiceAccount 来保证 Jenkins 拥有足够的权限。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin       #ServiceAccount名
  namespace: jenkins        #指定namespace,一定要修改成你自己的namespace
  labels:
    name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: jenkins-admin
  labels:
    name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins-admin
    namespace: jenkins
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

3. 创建Service & Deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: jenkins
  name: jenkins
  namespace: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      serviceAccountName: jenkins-admin
      containers:
        - name: jenkins
          image: 'jenkins/jenkins:lts'
          securityContext:
            privileged: true  #拥有特权
            runAsUser: 0      #设置以ROOT用户运行容器
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
            - containerPort: 50000
              name: jnlp
              protocol: TCP
          resources:
            limits:
              cpu: '2'
              memory: 2Gi
            requests:
              cpu: '1'
              memory: 1Gi
          env:
            - name: LIMITS_MEMORY
              valueFrom:
                resourceFieldRef:
                  divisor: 1Mi
                  resource: limits.memory
            - name: JAVA_OPTS    #设置变量,指定时区和 jenkins slave 执行者设置
              value: >-
                -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
                -Dhudson.slaves.NodeProvisioner.initialDelay=0
                -Dhudson.slaves.NodeProvisioner.MARGIN=50
                -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
                -Duser.timezone=Asia/Shanghai
                -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai
            - name: JENKINS_OPTS    #设置路径前缀加上 Jenkins
              value: '--prefix=/jenkins'
            - name: JENKINS_JAVA_OPTIONS
              value: '-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai'
            - name: JAVA_ARGS
              value: >-
                -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
                -Dhudson.slaves.NodeProvisioner.initialDelay=0
                -Dhudson.slaves.NodeProvisioner.MARGIN=50
                -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
                -Duser.timezone=Asia/Shanghai
                -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai
            - name: JENKINS_JAVA_OPTIONS
              value: >-
                -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
                -Dhudson.slaves.NodeProvisioner.initialDelay=0
                -Dhudson.slaves.NodeProvisioner.MARGIN=50
                -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
                -Duser.timezone=Asia/Shanghai
          volumeMounts:
            - mountPath: /var/jenkins_home    #设置要挂在的目录
              name: data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: jenkins                #设置PVC

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: jenkins
  name: jenkins
  namespace: jenkins
spec:
  ports:
    - name: http
      nodePort: 32001      #NodePort方式暴露 Jenkins 端口
      port: 8080           #服务端口
      protocol: TCP
      targetPort: 8080
    - name: jnlp
      nodePort: 32002
      port: 50000         #代理端口
      protocol: TCP
      targetPort: 50000
  selector:
    app: jenkins
  type: NodePort

参数说明:

-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85

4.获取 Jenkins 生成的 Token

在安装 Jenkins 时候,它默认生成一段随机字符串在控制台日志中,用于安装时验证。这里需要获取它输出在控制台中的日志信息,来获取 Token 字符串。

查看jenkins pod 启动日志

$ kubectl log $(kubectl get pods -n mydlqcloud | awk '{print $1}' | grep jenkins) -n mydlqcloud

在日志中可以看到,默认给的token为:

*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

96b199612s12sd15s55dfs52dff55db8d

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************

5.启动 Jenkins 进行初始化

输入 Kubernetes 集群地址和上面设置的 Nodeport 方式的端口号 32001,访问Jenins UI 界面进行初始化。输入从日志中获取的token进入初始化页面。

安装插件

安装插件,选择 推荐安装 方式进行安装即可,后续再安装需要的插件。

设置用户名、密码

在这里输入一个用户名、密码,方便后续登录,如果不设置可能下次登录需要使用之前日志中默认的 Token 串来登录。

配置 Jenkins 地址

配置 Jenkins URL 地址,来告知 Jenkins 自己的 URL,在发送邮件、触发钩子等可能用到。

进入 Jenkins 界面

到此 Jenkins 初始化就配置完成,成功进入 Jenkins 界面。

二、Jenkins安装相关插件

Jenkins 中可以打开 系统管理->插件管理->可选插件 来安装下面的一些插件:

三、配置相关凭据

选择 凭据->系统->全局凭据->添加凭据 来新增 Git、Docker Hub、Kubernetes 等认证凭据。

1、添加 Git 认证凭据

配置的参数值:

2、添加 Kubernetes Token 凭据

配置的参数值:

3、添加 Docker 仓库认证凭据

配置的参数值:

四、Jenkins 配置 Kubernetes 插件

进入 系统管理->节点管理->云 中,点击 新增一个云 选项,来新建一个与 Kubernetes 的连接,然后按照下面各个配置项进行配置。

1、Kubernetes Plugin 基本配置

(1)、配置连接 Kubernetes 参数

配置 Kubernetes API 地址,然后再选择 Kubernetes Token 凭据。

注意: 如果你的 Jenkins 也是安装在 Kubernetes 环境中,那么可以直接使用 Kubernetes 集群内的 Kubernetes API 地址,如果 Jnekins 是在安装在正常物理机或者虚拟机环境中,那么使用集群外的 Kubernetes API 地址,两个地址如下:

然后点击连接测试,查看是否能成功连通 Kubernetes,如果返回结果 Successful 则代表连接成功,否则失败。

(2)、配置 Jenkins 地址

注意: 这里的 Jenkins 地址是供 Slave 节点连接 Jenkins Master 节点用的,所以这里需要配置 Jenkins Master 的 URL 地址。这里和上面一样,也是考虑 Jenkins 是部署在 Kubernetes 集群内还是集群外,两个地址如下:

如果 Jnekins 中配置了 /jenkins 前缀,则 URL 后面加上 /jenkins,否则不加,这个地址根据自己的 Jnekins 实际情况来判断。

2、Kubernetes 插件配置

详细配置见 https://plugins.jenkins.io/kubernetes/

(1)、配置 Pod 名称和标签列表

配置 Pod 模板的名称和标签列表名,Pod 模板名可用于子模板继承,标签列表可用于 Jenkins Job 中指定,使用此 Pod 模板来执行任务。

(2)、配置 Maven

配置Maven镜像

Maven 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/_/maven

创建 Maven 存储使用的 PV、PVC

Maven需要挂载存储,将中央仓库下载的 Jar 存储到共享目录

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: maven
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: master-nfs-storage
配置 Maven 挂载

在卷选项中,选择添加卷,选择 Persistent Volume Claim 按如下添加配置:

(3)、配置 Docker In Docker

配置 Docker In Docker 镜像

Docker-IN-Docker 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/_/docker

配置 Docker 挂载

Kubernetes 中 Pod 的容器是启动在各个节点上,每个节点就是一台宿主机,里面进行了很多 Docker 配置,所以我们这里将宿主机的 Docker 配置挂载进入 Docker 镜像。选择添加卷,选择 Host Path Volume 按如下添加配置:

① 路径 /usr/bin/docker:

② 路径 /var/run/docker.sock:

③ 路径 /etc/docker:

(4)、配置 Kubectl 镜像

Kubectl 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/r/bitnami/kubectl

(5)、配置 Pod 的原始 yaml

此项可省略,保存后jenkins会自动生成,这里记录生成后的yaml

apiVersion: "v1"
kind: "Pod"
metadata:
  annotations:
    buildUrl: "http://jenkins.jenkins:8080/jenkins/job/new-test/19/"
    runUrl: "job/new-test/19/"
  labels:
    jenkins: "slave"
    jenkins/label: "jnlp"
  name: "jnlp-agent"
spec:
  containers:
  - env:
    - name: "JENKINS_TUNNEL"
      value: "jenkins.jenkins:50000"
    - name: "JENKINS_AGENT_WORKDIR"
      value: "/home/jenkins/agent"
    - name: "JENKINS_URL"
      value: "http://jenkins.jenkins:8080/jenkins/"
    image: "jenkins/inbound-agent:4.3-4"
    name: "jnlp"
    resources:
      limits: {}
      requests:
        memory: "256Mi"
        cpu: "100m"
    volumeMounts:
    - mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
  nodeSelector:
    kubernetes.io/os: "linux"
  restartPolicy: "Never"
  volumes:
  - emptyDir:
      medium: ""
    name: "workspace-volume"

完整的配置如下

五、创建相关文件

之前安装了 Config File Provider 插件,该插件功能就是可以在 Jenkins 上存储一些配置文件,例如,我们经常使用到的 yaml、properties、Dockerfile、Maven 的 Settings.xml 等文件,都可以存储到 Jenkins 该插件中,也可以将文件存储到项目中从项目中读取文件。

打开 系统管理->Managed files ,在其中新增几个文件:

1、新增 Maven 配置文件

选择 Add a new Config—>Global Maven settings.xml 来新增一个 Maven 全局 Settings.xml 文件:

为了加快 jar 包的下载速度,这里将仓库地址指向 aliyun Maven 仓库地址。

<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <pluginGroups>
  </pluginGroups>

  <proxies>
  </proxies>

  <servers>
  </servers>
  
  <mirrors>
    <!--Aliyun Maven-->
    <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>
  
  <profiles>
  </profiles>

</settings>

2、新增 Dockerfile 文件

选择 Add a new Config—>Custom file 来新增一个 Dockerfile 文件:

java服务dockerfile

FROM java:8
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JVM_OPTS="-Xss256k -Duser.timezone=Asia/Shanghai -Djava.security.egd=file:/dev/./urandom"
ENV JAVA_OPTS=""
ENV APP_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JVM_OPTS $JAVA_OPTS -jar /app.jar $APP_OPTS" ]

vue服务dockerfile

FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN cnpm install
COPY . ./
RUN npm run build:prod

# production stage
FROM nginx:stable-alpine as production-stage
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build-stage /app/dist /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

3、新增 Kubernetes 部署文件

选择 Add a new Config—>Custom file 来新增一个 Kubernetes 部署文件

apiVersion: v1
kind: Service
metadata:
  name: #APP_NAME
  labels:
    app: #APP_NAME
spec:
  type: NodePort
  ports:
  - name: server          #服务端口
    port: 8080  
    targetPort: 8080
  - name: management      #监控及监控检查的端口 
    port: 8081
    targetPort: 8081
  selector:
    app: #APP_NAME
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: #APP_NAME
  labels:
    app: #APP_NAME
spec:
  replicas: #APP_REPLICAS
  selector:
    matchLabels:
      app: #APP_NAME
  strategy:
    type: Recreate          #设置更新策略为删除策略,如需要灰度发布则去掉使用默认的
  template:
    metadata:
      labels:
        app: #APP_NAME
    spec:
      containers:
      - name: #APP_NAME
        image: #APP_IMAGE_NAME
        imagePullPolicy: Always
        ports:
        - containerPort: 8080   #服务端口
          name: server
        - containerPort: 8081   #监控及监控检查的端口 
          name: management
        env:
        - name: "update_uuid"
          value: "#APP_UUID"    #生成的随机值,放置执行kubectl apply时能够执行
        - name: SPRING_PROFILES_ACTIVE
          value: #SPRING_PROFILES_ACTIVE
        - name: REDIS_HOST
          value: #REDIS_HOST
        - name: REDIS_PORT
          value: "#REDIS_PORT"
        - name: REDIS_PW
          value: #REDIS_PW
        - name: DB_URL
          value: #DB_URL
        - name: DB_NAME
          value: #DB_NAME
        - name: DB_PASSWORD
          value: #DB_PASSWORD
        resources: 
          limits:
            cpu: 2000m
            memory: 1024Mi
          requests:
            cpu: 1000m
            memory: 512Mi
      imagePullSecrets:
        - name: #{K8S_PULLIMAGES_SECRET}

为了模板能够动态替换某些值,上面模板中设置了几个可替换的参数,用 #变量名称 来标记,后面我们在执行 Pipeline 时候将里面的 #xxx变量 标记替换掉,上面配置的变量有:

并且还有一点就是要注意,设置更新策略为 Recreate(删除再创建) 策略,否则后面的健康检查阶段将不能正常检查更新后的项目。

Kubernetes 默认为 RollingUpdate 策略,该策略为应用启动时,先将新实例启动,再删除旧的实例,就是因为这样,在后面健康检查阶段,健康检查 URL 地址还是未更新前的旧实例的 URL 地址,会导致健康检查不准确,所以必须改为 Recreate 策略,先删除旧实例,再创建新实例。

五、如何写流水线脚本和使用插件

具体配置参考官网文档:https://plugins.jenkins.io/kubernetes/

1、脚本中设置全局超时时间

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

timeout(time: 60, unit: 'SECONDS') {
    // 脚本
}

2、脚本中使用 Git 插件

Git 插件方法使用格式,及其部分参数:

git changelog: true,
    url: "http://gitlab.xxxx/xxx.git"
    branch: "master",
    credentialsId: "xxxx-xxxx-xxxx-xxxx",

3、脚本中使用 Kubernetes 插件

Kubernetes 插件中存在 PodTemplate 方法,在执行脚本时候,会自动在 Kubernetes 中创建 Pod Template 配置的 Slave Pod,在其中执行 podTemplate 代码块中的脚本。

def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        print "在 Slave Pod 中执行任务"  
    }
}

podTemplate 方法参数简介:

4、脚本中使用 Docker 镜像

在之前配置了 Kubernetes 插件的 Pod Template 配置中,配置了几个容器,每个容器中都有特定的功能的环境,例如:

既然每个容器都能提供特定的环境,那么再执行执行 Pipleline 脚本时候,就可以在不同的镜像中使用不同的环境的命令:

container('maven') {  
    sh "mvn install
}
container('docker') {  
    sh "docker build -t xxxxx:1.0.0 .
}
container('kubectl') {  
    sh "kubectl apply -f xxxx.yaml"
}

5、脚本中引入 Jenkins 中预先存储的文件

在之前的 系统设置->File Manager 中,存储了很多文件,例如:

在使用 Pipleline 脚本时候,我们需要将这些文件文本提取出来,创建在执行任务的流程中,创建这些文件可以使用 Config File Provider 插件提供的 configFileProvider 方法,如下所示:

configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
    sh "cat settings.xml"
}
configFileProvider([configFile(fileId: "global-dockerfile-file", targetLocation: "Dockerfile")]){
    sh "cat Dockerfile"
}
configFileProvider([configFile(fileId: "global-kubernetes-deployment", targetLocation: "deployment.yaml")]){
    sh "cat deployment.yaml"
}

6、脚本创建文件

在使用 Groovy 写 Pipleline 脚本时候,经常要将变量的文本生成文件,方便在执行流水线过程中操作文本文件使用,如何将文件转换为文件,可以使用 Pipeline Utility Steps 插件的 writeFile 方法,如下:

writeFile encoding: 'UTF-8', file: './test.txt', text: "写入文件的文本内容"

7、脚本中使用 Http Rrequest 插件

脚本中可以使用 HttpRequest 来对某一地址进行请求,这里简单使用 Get 请求地址,复杂的可以查看 Jenkins 插件的官网查看使用示例。

下面是使用 Http Request 的 Get 请求示例:

result = httpRequest "http:www.baidu.com"

if ("${result.status}" == "200") {
    print "Http 请求成功"
} 

8、脚本中使用 Kubernetes Cli 插件

在之前说过,在 kubectl 镜像中能够使用 kubectl 命令,不过由于执行 Kubectl 命令一般需要在镜像的 $HOME/.kube/ 目录中存在连接 Kubernetes APIconfig 文件,使其 kubectl 命令有明确请求 kubernetes API 的地址和用户权限,不过将 config 文件挂入镜像内部是一件比较繁琐的事情。

好在 Jenkins 提供的 Kubectl Cli 插件,只要在其中配置连接 Kubernetes 的 Token 凭据,就能够在 Kubectl Cli 提供的 withKubeConfig 方法,拥有类似存在 config 一样的功能,在 kubectl 镜像中的 withKubeConfig 方法块内执行 kubectl 就可以操作配置的 Kubectl Cli 的凭据的 K8S 集群。

container('kubectl') {
    withKubeConfig([credentialsId: "Kubernetes Token 凭据 ID",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
        sh "kubectl get nodes"
    }
}

9、脚本中操作字符串替换值

在使用 Groovy 语法写 Pipleline 脚本时候,我们经常要替换先前设置好的一些文本的值,这里我们简单示例一下,如何替换字符串。

// 测试的字符串
sourceStr = "这是要替换的值:#value1,这是要替换的值:#value2"
// 替换#value1与#value2连个值
afterStr = sourceStr.replaceAll("#value1","AAA").replaceAll("#value2","BBB")
// 输出替换后的字符串
print "${afterStr}"

10、脚本中读取 pom.xml 参数

在执行 Java 项目的流水线时,我们经常要动态获取项目中的属性,很多属性都配置在项目的 pom.xml 中,还好 Pipeline Utility Steps 插件提供能够读取 pom.xml 的方法,示例如下:

stage('读取pom.xml参数阶段'){
    // 读取 Pom.xml 参数
    pom = readMavenPom file: './pom.xml'
    // 输出读取的参数
    print "${pom.artifactId}"
    print = "${pom.version}"
}

11、脚本中使用 Docker 插件构建与推送镜像

在流水线脚本中,我们一般不直接使用 Docker 命令,而是使用 Docker 插件提供的 docker.withRegistry("") 方法来构建与推送镜像,并且还能在方法中配置登录凭据信息,来让仓库验证权限,这点是非常方便的。使用示例如下:

docker.withRegistry("http://xxxx Docker 仓库地址", "Docker 仓库凭据 ID") {
        // 构建 Docker 镜像
        def customImage = docker.build("${dockerImageName}")
        // 推送 Docker 镜像
        customImage.push()
    }

六、在 Jenkins 创建模板任务

创建一个 Pipeline Job 来充当各个 Jenkins Job 的模板,方便后续创建 Job 时,直接复制模板项目,然后修改配置就能使用。所以这里我们创建一个模板 Pipeline Job,在 Job 配置中需要添加一些参数和环境变量,方便我们动态替换一些值。

1、创建 Pipeline 任务

2、配置项目构建基本参数

配置同一时间一个 Job 只能构建一个,不允许多个并发构建。另外需要设置项目构建后,包的保留时间,以防止包过多且大占用大量空间(一个包很肯能占 10MB~200MB 大小)导致储不足。

3、配置变量

在 Job 配置的 参数化构建过程 中,添加下面参数:

配置GIT变量

Git 项目地址变量

Git 分支变量

Git 凭据变量

配置 Maven 变量

Maven 构建命令变量

配置 Docker 变量

Docker 项目地址变量

Docker 仓库项目组变量

Docker 仓库认证凭据变量

Docker Dockerfile 文件 ID 变量

配置 Kubernetes 变量

Kubernetes 认证凭据变量

Kubernetes Namespace 变量

Kubernetes 应用实例副本数

Kubernetes 应用部署 yaml 文件ID

配置 HTTP 变量

HTTP 健康检查端口

HTTP 健康检查地址

HTTP 健康检查次数

HTTP 健康检查时间间隔

配置项目参数

全部采用String类型创建

服务端口号

服务环境
Redis地址
Redis端口
Redis密码
数据库地址及端口
数据库名
数据库密码
k8s中维护的拉取镜像的secret凭证

七、创建 Pipeline 脚本

接下将使用 Groovy 语法创建一个为 SpringBoot 项目准备的 CI/CD 的脚本式的流水线脚本。其中,脚本中包含多个阶段,分别为 Git 拉取镜像,Maven 编译 Java 项目,Docker 构建与推送镜像,Kubectl 部署应用到 Kubernetes 中,最后使用 Http 请求进行健康检查,成功的话发送钉钉消息,下面是各个阶段脚本及其介绍。

1、脚本中使用 Kubernetes 插件及设置超时时间

使用 Kubernetes 插件执行任务,并设置超时时间为 10 分钟,脚本如下:

// 设置超时时间 600 SECONDS,方法块内的方法执行超时,任务就标记为失败
timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            print "在 Slave Pod 中执行任务"  
        }
    }
}

2、脚本中 Git 拉取项目阶段

接下来接着往整体的脚本中添加 Git 模块,其中需要引用上面配置的变量,将变量填入脚本中的方法,如下:

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('get_commit_msg') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
            		// 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
        }
    }
}

变量介绍:

3、脚本中 Maven 编译项目阶段

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('get_commit_msg') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
        }
    }
}

变量介绍:

4、脚本中读取 pom.xml 参数阶段

这里使用 Pipeline Utility StepsreadMavenPom 方法读取项目的 pom.xml 文件,并设置 appNameappVersion 两个全局参数。

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
        }
    }
}

变量介绍:

5、脚本中 Docker 镜像构建与推送模块

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
        }
    }
}

变量介绍:

6、Kubernetes 模块

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
            stage('Kubernetes 阶段'){
                container('kubectl') {
                    // 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 读取 Kubernetes 部署文件
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                        .replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
                                        .replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
                                        .replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
                                        .replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
                                        .replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
                                        .replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
                                        .replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
                                        .replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
                                        .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                        .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                        .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                                        .replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
                        // 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 输出新创建的部署 yaml 文件内容
                            sh "cat deploy.yaml"
                            // 执行 Kuberctl 命令进行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
        }
    }
}

变量介绍:

7、HTTP 健康检查模块

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
            stage('Kubernetes 阶段'){
                container('kubectl') {
                    // 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 读取 Kubernetes 部署文件
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                        .replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
                                        .replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
                                        .replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
                                        .replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
                                        .replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
                                        .replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
                                        .replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
                                        .replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
                                        .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                        .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                        .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                                        .replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
                        // 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 输出新创建的部署 yaml 文件内容
                            sh "cat deploy.yaml"
                            // 执行 Kuberctl 命令进行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
            stage('健康检查阶段'){
                // 设置检测延迟时间 10s,10s 后再开始检测
                sleep 10
                // 健康检查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 循环使用 httpRequest 请求,检测服务是否启动
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 输出请求信息和请求次数
                        print "访问服务:${appName} \n" +
                            "访问地址:${httpRequestUrl} \n" +
                            "访问次数:${n}"
                        // 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判断是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 请求成功,流水线结束"
                            break
                        }
                    }catch(Exception e){
                        print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
                        // 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                        }
                    }
                }
            }
        }
    }
}

变量介绍:

8、发送钉钉通知

创建钉钉机器人

在需要推送钉钉消息的群中创建钉钉机器人,设置机器人名字及安全设置(jenkins中会用到)

获取webhook,并复制webhook

配置Jenkins

选择 系统管理—>系统配置—>钉钉 来配置钉钉机器人:

点击测试,可以看是否连通,发送钉钉机器人信息成功,则配置成功

脚本中调用dingtalk

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    // 获取提交的说明
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {  
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
            stage('Kubernetes 阶段'){
                container('kubectl') {
                    // 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 读取 Kubernetes 部署文件
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                        .replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
                                        .replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
                                        .replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
                                        .replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
                                        .replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
                                        .replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
                                        .replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
                                        .replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
                                        .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                        .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                        .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                                        .replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
                        // 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 输出新创建的部署 yaml 文件内容
                            sh "cat deploy.yaml"
                            // 执行 Kuberctl 命令进行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
            stage('健康检查阶段'){
                // 设置检测延迟时间 10s,10s 后再开始检测
                sleep 10
                // 健康检查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 循环使用 httpRequest 请求,检测服务是否启动
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 输出请求信息和请求次数
                        print "访问服务:${appName} \n" +
                            "访问地址:${httpRequestUrl} \n" +
                            "访问次数:${n}"
                        // 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判断是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 请求成功,流水线结束"
                            // 发送钉钉通知
                            dingtalk (
                                robot: "jenkins-ding-msg",	//jenkins中设置的钉钉参数中的id
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新成功",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#00CD00 >更新成功</font>',
                                    "- 更新内容:${env.GIT_COMMIT_MSG}",
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                            break
                        }
                    }catch(Exception e){
                        print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
                        // 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                            dingtalk (
                                robot: "jenkins-ding-msg",	//jenkins中设置的钉钉参数中的id
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新失败",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#EE0000 >更新失败</font>',
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                        }
                    }
                }
            }
        }
    }
}

8、完整脚本

使用在jenkins中配置的jnlp-agent

def label = "jnlp-agent"
timeout(time: 900, unit: 'SECONDS') {
    podTemplate(label: label, cloud: 'kubernetes'){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
                print "maven构建完成"
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("https://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
            stage('Kubernetes 阶段'){
                print "重启k8s中的服务"
                container('kubectl') {
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 读取 Kubernetes 部署文件
                        deploy = readFile encoding: "UTF-8", file: "./deployment.yaml"
                        deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                        .replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
                                        .replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
                                        .replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
                                        .replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
                                        .replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
                                        .replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
                                        .replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
                                        .replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
                                        .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                        .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                        .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                                        .replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
                        // 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
                        writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                        // 输出新创建的部署 yaml 文件内容
                        sh "cat deploy.yaml"
                        // 执行 Kuberctl 命令进行部署操作
                        sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        print "重启完了"
                    }
                }
            }
            stage('健康检查阶段'){
                // 设置检测延迟时间 10s,10s 后再开始检测
                sleep 10
                // 健康检查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 循环使用 httpRequest 请求,检测服务是否启动
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 输出请求信息和请求次数
                        print "访问服务:${appName} \n" +
                            "访问地址:${httpRequestUrl} \n" +
                            "访问次数:${n}"
                        // 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判断是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 请求成功,流水线结束"
                            dingtalk (
                                robot: "shawanyi-test",
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新成功",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#00CD00 >更新成功</font>',
                                    "- 更新内容:${env.GIT_COMMIT_MSG}",
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                            break
                        }
                    }catch(Exception e){
                        print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
                        // 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                            dingtalk (
                                robot: "shawanyi-test",
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新失败",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#EE0000 >更新失败</font>',
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                        }
                    }
                }
            }
        }
    }
}

直接在groovy脚本中指定镜像参数

// 使用默认的jnlp
def label = "jnlp"
timeout(time: 900, unit: 'SECONDS') {
    podTemplate(label: label, cloud: 'kubernetes', 
    // 手动指定需要用的镜像
    containers: [
        containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
        containerTemplate(name: 'docker', image: 'docker:stable', ttyEnabled: true, command: 'cat'),
        containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.18.20', ttyEnabled: true, command: 'cat')
    ],
    // 挂载卷
    volumes: [
            persistentVolumeClaim(claimName: 'maven', mountPath: '/root/.m2'),
            hostPathVolume(hostPath: '/usr/bin/docker', mountPath: '/usr/bin/docker'),
            hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock'),
            hostPathVolume(hostPath: '/etc/docker', mountPath: '/etc/docker'),
        ],
    ){
        node (label) {
            stage('Git阶段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('获取git提交信息') {
                script {
                    MAX_MSG_LEN = 100
                    def changeString = ""
                    echo "Gathering SCM changes"
                    def changeLogSets = currentBuild.changeSets
                    for (int i = 0; i < changeLogSets.size(); i++) {
                        def entries = changeLogSets[i].items
                        for (int j = 0; j < entries.length; j++) {
                            def entry = entries[j]
                            truncated_msg = entry.msg.take(MAX_MSG_LEN)
                            changeString += "--${truncated_msg}  [${entry.author}]\n"
                        }
                    }

                    if (!changeString) {
                        changeString = " - 无"
                    }
                    env.GIT_COMMIT_MSG = changeString
                }
            }
            stage('Maven阶段'){
                print "开始构建maven"
                container('maven') {
                    // 创建 Maven 需要的 Settings.xml 文件
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
                        sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
                print "maven构建完成"
            }
            stage('读取pom.xml参数阶段'){
                // 读取 Pom.xml 参数
                pom = readMavenPom file: './pom.xml'
                // 设置 appName 和 appVersion 两个全局参数
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker阶段'){
                print "开始构建docker镜像"
                container('docker') {
                    // 创建 Dockerfile 文件,但只能在方法块内使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 设置 Docker 镜像名称
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
                        docker.withRegistry("https://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            print "docker镜像构建完成"
                            customImage.push()
                            print "docker镜像push完成"
                            sh "docker rmi ${dockerImageName}"
                            print "镜像已删除"
                        }
                    }
                }
            }
            stage('Kubernetes 阶段'){
                print "重启k8s中的服务"
                container('kubectl') {
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 读取 Kubernetes 部署文件
                        deploy = readFile encoding: "UTF-8", file: "./deployment.yaml"
                        deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                        .replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
                                        .replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
                                        .replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
                                        .replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
                                        .replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
                                        .replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
                                        .replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
                                        .replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
                                        .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                        .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                        .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                                        .replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
                        // 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
                        writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                        // 输出新创建的部署 yaml 文件内容
                        sh "cat deploy.yaml"
                        // 执行 Kuberctl 命令进行部署操作
                        sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        print "重启完了"
                    }
                }
            }
            stage('健康检查阶段'){
                // 设置检测延迟时间 10s,10s 后再开始检测
                sleep 10
                // 健康检查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 循环使用 httpRequest 请求,检测服务是否启动
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 输出请求信息和请求次数
                        print "访问服务:${appName} \n" +
                            "访问地址:${httpRequestUrl} \n" +
                            "访问次数:${n}"
                        // 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判断是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 请求成功,流水线结束"
                            dingtalk (
                                robot: "shawanyi-test",
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新成功",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#00CD00 >更新成功</font>',
                                    "- 更新内容:${env.GIT_COMMIT_MSG}",
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                            break
                        }
                    }catch(Exception e){
                        print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
                        // 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                            dingtalk (
                                robot: "shawanyi-test",
                                type:'ACTION_CARD',
                                atAll: false,
                                title: "${appName}更新失败",
                                messageUrl: 'xxxx',
                                text: [
                                    "### [${appName}](${env.JOB_URL}) ",
                                    '---',
                                    "- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
                                    '- 状态:<font color=#EE0000 >更新失败</font>',
                                    "- 持续时间:${currentBuild.durationString}".split("and counting")[0],
                                ]
                            )
                        }
                    }
                }
            }
        }
    }
}

标签:xml,CI,Kubernetes,URL,params,Jenkins,Docker,docker
来源: https://www.cnblogs.com/yg0070/p/16441188.html