运维管理docker+k8sDevTestOps开发测试运维监控

二十二 DevOps实战

2022-06-13  本文已影响0人  負笈在线

(一) 自动化构建Java应用

1.创建Java测试用例

示例项目可以从https://gitee.com/dukuan/spring-boot-project.git找到该项目(也可以使用公司的Java项目也是一样的)。

接下来将该项目导入到自己的GitLab中。首先找到之前创建的Kubernetes组,然后点击New Project:



选择Import Project:



点击Repo by URL:

在Git repository URL输入示例地址,然后点击Create Project即可:



导入后,如下所示:

2.定义Jenkinsfile

接下来再GitLab的源代码中添加Jenkinsfile。首先点击代码首页的“+”号,然后点击New file:



在窗口中,添加如下内容:

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes-study'
      slaveConnectTimeout 1200
      workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false     
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
        - mountPath: "/root/.m2/"
          name: "cachedir"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "dockersock"
          readOnly: false
  restartPolicy: "Never"
  nodeSelector:
    build: "true"
  securityContext: {}
  volumes:
    - hostPath:
        path: "/var/run/docker.sock"
      name: "dockersock"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "localtime"
    - name: "cachedir"
      hostPath:
        path: "/opt/m2"
'''
    }   
}
  stages {
    stage('Pulling Code') {
      parallel {
        stage('Pulling Code by Jenkins') {
          when {
            expression {
              env.gitlabBranch == null
            }

          }
          steps {
            git(changelog: true, poll: true, url: 'git@CHANGE_HERE_FOR_YOUR_GITLAB_URL:root/spring-boot-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
              
            }

          }
        }

        stage('Pulling Code by trigger') {
          when {
            expression {
              env.gitlabBranch != null
            }

          }
          steps {
            git(url: 'git@CHANGE_HERE_FOR_YOUR_GITLAB_URL:root/spring-boot-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
            }

          }
        }

      }
    }

    stage('Building') {
      steps {
        container(name: 'build') {
            sh """ 
              curl repo.maven.apache.org
              mvn clean install -DskipTests
              ls target/*
            """
        }
      }
    }

    stage('Docker build for creating image') {
      environment {
        HARBOR_USER     = credentials('HARBOR_ACCOUNT')
    }
      steps {
        container(name: 'docker') {
          sh """
          echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
          docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
          docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
          docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
          """
        }
      }
    }

    stage('Deploying to K8s') {
      environment {
        MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
    }
      steps {
        container(name: 'kubectl'){
           sh """
           /usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
           """
        }
      }
    }

  }
  environment {
    COMMIT_ID = ""
    HARBOR_ADDRESS = "CHANGE_HERE_FOR_YOUR_HARBOR_URL"
    REGISTRY_DIR = "kubernetes"
    IMAGE_NAME = "spring-boot-project"
    NAMESPACE = "kubernetes"
    TAG = ""
  }
  parameters {
    gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
  }
}

3.Jenkinsfile详解

首先是顶层的Agent,定义的是Kubernetes的Pod作为Jenkins的Slave:

agent {
    # 定义使用Kubernetes作为agent
    kubernetes {
    # 选择的云为之前配置的名字
      cloud 'kubernetes-study'
      slaveConnectTimeout 1200
    # 将workspace改成hostPath,因为该Slave会固定节点创建,如果有存储可用,可以改成PVC的模式
      workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    # jnlp容器,和Jenkins主节点通信
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false  
    # build容器,包含执行构建的命令,比如Java的需要mvn构建,就可以用一个maven的镜像   
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
    # 使用Maven镜像,包含mvn工具。NodeJS可以用node的镜像
      image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
      imagePullPolicy: "IfNotPresent"
    # 容器的名字,流水线的stage可以直接使用该名字
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
        # Pod单独创建了一个缓存的volume,将其挂载到了maven插件的缓存目录,默认是/root/.m2
        - mountPath: "/root/.m2/"
          name: "cachedir"
          readOnly: false
    # 发版容器,因为最终是发版至Kubernetes的,所以需要有一个kubectl命令
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
    # 镜像的版本可以替换为其它的版本,也可以不进行替换,因为只执行set命令,所以版本是兼容的
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    # 用于生成镜像的容器,需要包含docker命令
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
        # 由于容器没有启动docker服务,所以将宿主机的docker经常挂载至容器即可
        - mountPath: "/var/run/docker.sock"
          name: "dockersock"
          readOnly: false
  restartPolicy: "Never"
# 固定节点部署
  nodeSelector:
    build: "true"
  securityContext: {}
  volumes:
    # Docker socket volume
    - hostPath:
        path: "/var/run/docker.sock"
      name: "dockersock"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "localtime"
    # 缓存目录
    - name: "cachedir"
      hostPath:
        path: "/opt/m2"
'''
    }

之后看一下Jenkinsfile最后的环境变量和parameters的配置:

# 定义一些全局的环境变量
environment {
    COMMIT_ID = ""
    HARBOR_ADDRESS = "CHANGE_HERE_FOR_YOUR_HARBOR_URL" # Harbor地址
    REGISTRY_DIR = "kubernetes" # Harbor的项目目录
    IMAGE_NAME = "spring-boot-project" # 镜像的名称
    NAMESPACE = "kubernetes" # 该应用在Kubernetes中的命名空间
    TAG = "" # 镜像的Tag,在此用BUILD_TAG+COMMIT_ID组成
  }
  parameters {
    # 之前讲过一些choice、input类型的参数,本次使用的是GitParameter插件
    # 该字段会在Jenkins页面生成一个选择分支的选项
    gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
  }

接下来是拉代码的stage,这个stage是一个并行的stage,因为考虑了该流水线是手动触发还是触发:

stage('Pulling Code') {
      parallel {
        stage('Pulling Code by Jenkins') {
          when {
            expression {
            # 假如env.gitlabBranch为空,则该流水线为手动触发,那么就会执行该stage,如果不为空则会执行同级的另外一个stage
              env.gitlabBranch == null
            }

          }
          steps {
           # 这里使用的是git插件拉取代码,BRANCH变量取自于前面介绍的parameters配置
           # git@xxxxxx:root/spring-boot-project.git代码地址
           # credentialsId: 'gitlab-key',之前创建的拉取代码的key
            git(changelog: true, poll: true, url: 'git@xxxxxx:root/spring-boot-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
            script {
            # 定义一些变量用于生成镜像的Tag
            # 获取最近一次提交的Commit ID
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
            # 赋值给TAG变量,后面的docker build可以取到该TAG的值
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
              
            }

          }
        }
        stage('Pulling Code by trigger') {
          when {
            expression {
            # 如果env.gitlabBranch不为空,说明该流水线是通过webhook触发,则此时执行该stage,上述的stage不再执行。此时BRANCH变量为空
              env.gitlabBranch != null
            }

          }
          steps {
           # 以下配置和上述一致,只是此时branch: env.gitlabBranch取的值为env.gitlabBranch
            git(url: 'git@xxxxxxxxxxx:root/spring-boot-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
            }

          }
        }

      }
    }

代码拉下来后,就可以执行构建命令,由于本次实验是Java示例,所以需要使用mvn命令进行构建:

stage('Building') {
      steps {
       # 使用Pod模板里面的build容器进行构建
        container(name: 'build') {
            sh """ 
            # 编译命令,需要根据自己项目的实际情况进行修改,可能会不一致
              mvn clean install -DskipTests
            # 构建完成后,一般情况下会在target目录下生成jar包
              ls target/*
            """
        }
      }
    }

生成编译产物后,需要根据该产物生成对应的镜像,此时可以使用Pod模板的docker容器:

stage('Docker build for creating image') {
   # 首先取出来HARBOR的账号密码 
environment {
        HARBOR_USER     = credentials('HARBOR_ACCOUNT')
    }
      steps {
        # 指定使用docker容器
        container(name: 'docker') {
          sh """
          # 执行build命令,Dockerfile会在下一小节创建,也是放在代码仓库,和Jenkinsfile同级
          docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
        # 登录Harbor,HARBOR_USER_USR和HARBOR_USER_PSW由上述environment生成
          docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
        # 将镜像推送至镜像仓库
          docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
          """
        }
      }
    }

最后一步就是将该镜像发版至Kubernetes集群中,此时使用的是包含kubectl命令的容器:

stage('Deploying to K8s') {
   # 获取连接Kubernetes集群证书  
environment {
        MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
    }
      steps {
       # 指定使用kubectl容器
        container(name: 'kubectl'){
           sh """
          # 直接set更改Deployment的镜像即可
           /usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
           """
        }
      }
    }
  }

注意:本次发版的命令为/usr/local/bin/kubectl --kubeconfig MY_KUBECONFIG set image deploy -l app={IMAGE_NAME} {IMAGE_NAME}={HARBOR_ADDRESS}/{REGISTRY_DIR}/{IMAGE_NAME}:{TAG} -nNAMESPACE。由于本示例的Deployment名称、Label、容器名称均和项目名称一致,所以就统一了IMAGE_NAME,如果示例不是按照此规范命名,需要进行更改。

4.定义Dockerfile

在执行流水线过程时,需要将代码的编译产物做成镜像。Dockerfile主要写的是如何生成公司业务的镜像。而本次示例是Java项目,只需要把Jar包放在有Jre环境的镜像中,然后启动该Jar包即可:

#  基础镜像可以按需修改,可以更改为公司自有镜像
FROM registry.cn-beijing.aliyuncs.com/dotbalo/jre:8u211-data
# jar包名称改成实际的名称,本示例为spring-cloud-eureka-0.0.1-SNAPSHOT.jar
COPY target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar ./
# 启动Jar包
CMD java -jar spring-cloud-eureka-0.0.1-SNAPSHOT.jar

5.定义Kubernetes资源

本示例在GitLab创建的Group为kubernetes,可以将其认为是一个项目,同一个项目可以部署至Kubernetes集群中同一个Namespace中,本示例为kubernetes命名空间。由于使用的是私有仓库,因此也需要先配置拉取私有仓库镜像的密钥:

# kubectl create ns kubernetes
namespace/kubernetes created
# kubectl create secret docker-registry harborkey --docker-server=CHANGE_HERE_FOR_YOUR_HARBOR_ADDRESS --docker-username=admin --docker-password=Harbor12345 --docker-email=mail@kubeasy.com -n kubernetes
secret/harborkey created

配置该应用的Deployment(部分内容),需要注意文件的加粗部分:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: spring-boot-project # Deployment标签,和流水线的set -l一致
  name: spring-boot-project  # Deployment名称
  namespace: kubernetes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-boot-project # Pod的标签
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: spring-boot-project # Pod的标签
    spec:
      ...
        - name: LANG
          value: C.UTF-8
        image: nginx # 此处使用的nginx作为原始的镜像,通过Jenkins构建并发版后,变成Java应用的镜像
        imagePullPolicy: IfNotPresent
        lifecycle: {}
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8761 # 端口号和健康检查按照实际情况进行修改
          timeoutSeconds: 2
        name: spring-boot-project # 容器的名称,需要和流水线set命令的容器名称一致
        ports:
        - containerPort: 8761 # 端口号按照实际情况进行修改
          name: web
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8761 # 端口号和健康检查按照实际情况进行修改
          timeoutSeconds: 2
        resources: # 资源请求按照实际情况修改
          limits:
            cpu: 994m
            memory: 1170Mi
          requests:
            cpu: 10m
            memory: 55Mi
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: harborkey # Harbor仓库密钥,需要和上述创建的Secret一致
      restartPolicy: Always
      securityContext: {}
      serviceAccountName: default

定义该应用的Service和Ingress:

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: spring-boot-project
  name: spring-boot-project
  namespace: kubernetes
spec:
  ports: # 端口按照实际情况进行修改
  - name: web
    port: 8761
    protocol: TCP
    targetPort: 8761
  selector:
    app: spring-boot-project
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: null
  name: spring-boot-project
  namespace: kubernetes
spec:
  rules:
  - host: spring-boot-project.test.com
    http:
      paths:
      - backend:
          service:
            name: spring-boot-project
            port: 
              number: 8761
        path: /
        pathType: ImplementationSpecific

完整文件:

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: spring-boot-project
  name: spring-boot-project
  namespace: kubernetes
spec:
  ports:
  - name: web
    port: 8761
    protocol: TCP
    targetPort: 8761
  selector:
    app: spring-boot-project
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: null
  name: spring-boot-project
  namespace: kubernetes
spec:
  rules:
  - host: spring-boot-project.test.com
    http:
      paths:
      - backend:
          service:
            name: spring-boot-project
            port: 
              number: 8761
        path: /
        pathType: ImplementationSpecific
status:
  loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: spring-boot-project
  name: spring-boot-project
  namespace: kubernetes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-boot-project
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: spring-boot-project
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - spring-boot-project
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - env:
        - name: TZ
          value: Asia/Shanghai
        - name: LANG
          value: C.UTF-8
        image: nginx
        imagePullPolicy: IfNotPresent
        lifecycle: {}
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8761
          timeoutSeconds: 2
        name: spring-boot-project
        ports:
        - containerPort: 8761
          name: web
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8761
          timeoutSeconds: 2
        resources:
          limits:
            cpu: 994m
            memory: 1170Mi
          requests:
            cpu: 10m
            memory: 55Mi
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: harborkey
      restartPolicy: Always
      securityContext: {}
      serviceAccountName: default

创建该资源:

# kubectl create -f spring-boot-project.yaml 
service/spring-boot-project created
ingress.networking.k8s.io/spring-boot-project created
deployment.apps/spring-boot-project created

提示:由于在Harbor安装小节已经配置了kubernetes的项目目录,在此不再重复创建,如果需要将镜像推送至其它目录,按照同样的步骤创建即可。

6.创建Jenkins任务(Job)

单击首页的创建任务(Job)选项(或者是New Item)并配置任务信息,如图所示:



输入的Job的名称(一般和GitLab的仓库名字一致,便于区分),类型为Pipeline,最后点击OK即可:



在新页面,点击Pipeline,输入Git仓库地址、选择之前配置GitLab Key、分支为master,点击Save即可:

创建完成后,点击Build Now(由于Jenkins参数由Jenkinsfile生成,所以第一次执行流水线会失败):



第一次构建结束后,可以看到Build Now变成了Build with Parameters。点击Build with Parameters后,可以读取到Git仓库的分支,之后可以选择分支进行手动构建(后面的章节会介绍自动触发):

选择分支,之后点击Build,然后点击Build History的进度条即可看到构建日志:

构建日志上部分为创建Pod的日志,可以看到Pod为Agent指定的Pod:

也可以点击Blue Ocean更加直观的流程:



如果是Java应用,可以看到BUILD SUCCESS说明mvn编译已经通过:



编译结束后,可以看到制作镜像的日志:

之后镜像被推送至Harbor:



最后为发版至Kubernetes:

Finished:SUCCESS说明该流水线正常结束。接下来可以查看Deployment的镜像:
# kubectl get deploy -n kubernetes spring-boot-project -oyaml | grep "image:"
        image: xxxxxxxxxxxxx.com/kubernetes/spring-boot-project:jenkins-spring-boot-project-4-959387c
# kubectl get po -n kubernetes
NAME                                   READY   STATUS    RESTARTS   AGE
spring-boot-project-869589667d-2zqjm   1/1     Running   0          6m53s

如果配置了域名,可以通过域名访问(测试域名需要配置hosts):


(二) 自动化构建Vue/H5前端应用

本节介绍自动化构建Vue/H5应用,其构建方式和自动化构建Java基本相同,重点是更改Deployment、Jenkinsfile和Dockerfile即可。

前端应用测试项目地址:https://gitee.com/dukuan/vue-project-new.git,可以参考Java小节的方式,导入前端项目到GitLab中,当然也可以使用公司自己的项目。

1.定义Jenkinsfile

完整文件:

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes-study'
      slaveConnectTimeout 1200
      workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/node:lts"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
        - mountPath: "/root/.m2/"
          name: "cachedir"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "dockersock"
          readOnly: false
  restartPolicy: "Never"
  nodeSelector:
    build: "true"
  securityContext: {}
  volumes:
    - hostPath:
        path: "/var/run/docker.sock"
      name: "dockersock"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "localtime"
    - name: "cachedir"
      hostPath:
        path: "/opt/m2"
'''
    }
}
  stages {
    stage('Pulling Code') {
      parallel {
        stage('Pulling Code by Jenkins') {
          when {
            expression {
              env.gitlabBranch == null
            }

          }
          steps {
            git(changelog: true, poll: true, url: 'git@10.103.236.251:kubernetes/vue-project-new-new.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"

            }

          }
        }

        stage('Pulling Code by trigger') {
          when {
            expression {
              env.gitlabBranch != null
            }

          }
          steps {
            git(url: 'git@10.103.236.251:kubernetes/vue-project-new.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
            }

          }
        }

      }
    }

    stage('Building') {
      steps {
        container(name: 'build') {
            sh """
              npm install --registry=https://registry.npm.taobao.org
              npm run build
            """
        }
      }
    }

    stage('Docker build for creating image') {
      environment {
        HARBOR_USER     = credentials('HARBOR_ACCOUNT')
    }
      steps {
        container(name: 'docker') {
          sh """
          echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
          docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
          docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
          docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
          """
        }
      }
    }

    stage('Deploying to K8s') {
      environment {
        MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
    }
      steps {
        container(name: 'kubectl'){
           sh """
           /usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
           """
        }
      }
    }

  }
  environment {
    COMMIT_ID = ""
    HARBOR_ADDRESS = "10.103.236.204"
    REGISTRY_DIR = "kubernetes"
    IMAGE_NAME = "vue-project-new"
    NAMESPACE = "kubernetes"
    TAG = ""
  }
  parameters {
    gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
  }
}

Jenkinsfile和Java项目并无太大区别,需要更改的位置如下:

pipeline {
  agent {
    kubernetes {
   ... # 此处代码有省略
      # 构建容器改为具有NodeJS环境的镜像,版本需要和公司项目一致
      image: "registry.cn-beijing.aliyuncs.com/citools/node:lts"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
        - mountPath: "/root/.m2/" # NodeJS的缓存目录为node_modules,此处可以不配置,因为workspace采用的是hostPath,该目录会被缓存到创建Pod节点的/opt/目录
          name: "cachedir"
          readOnly: false
   ... # 此处代码有省略
           # 此处的地址需要改为自己的Git地址
            git(changelog: true, poll: true, url: 'git@xxxxxxxxxx:kubernetes/vue-project-new.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
            ...

        }

        stage('Pulling Code by trigger') {
          when {
            expression {
              env.gitlabBranch != null
            }

          }
          steps {
            # 此处的地址需要改为自己的Git地址
            git(url: 'git@xxxxxxxx:kubernetes/vue-project-new.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
            ...


    stage('Building') {
      steps {
        container(name: 'build') {
            sh """ 
              # 此处为NodeJS/Vue项目的构建命令,根据实际情况进行修改
              npm install --registry=https://registry.npm.taobao.org
              npm run build
            """
        }
      }
    }

   ...
    REGISTRY_DIR = "kubernetes"
    IMAGE_NAME = "vue-project-new"  # 名字为vue-project-new
    NAMESPACE = "kubernetes"
    TAG = ""
  ...

2.定义Dockerfile

前端应用构建后一般会在dist文件下产生html文件,只需要拷贝到nginx的根目录下即可:

FROM registry.cn-beijing.aliyuncs.com/dotbalo/nginx:1.15.12
COPY dist/* /usr/share/nginx/html/

3.定义Kubernetes资源

对于Kubernetes的资源也是类似的,只需要更改资源名称和端口号即可(加粗部分):

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: vue-project-new
  name: vue-project-new
  namespace: kubernetes
spec:
  ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: vue-project-new
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: null
  name: vue-project-new
  namespace: kubernetes
spec:
  rules:
  - host: vue-project-new.test.com
    http:
      paths:
      - backend:
          service:
            name: vue-project-new
            port: 
              number: 80
        path: /
        pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: vue-project-new
  name: vue-project-new
  namespace: kubernetes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vue-project-new
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: vue-project-new
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - vue-project-new
              topologyKey: kubernetes.io/hostname
            weight: 100
...
        lifecycle: {}
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 80
          timeoutSeconds: 2
        name: vue-project-new
        ports:
        - containerPort: 80
          name: web
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 80
          timeoutSeconds: 2
  ...

完整文件:

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: vue-project-new
  name: vue-project-new
  namespace: kubernetes
spec:
  ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: vue-project-new
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: null
  name: vue-project-new
  namespace: kubernetes
spec:
  rules:
  - host: vue-project-new.test.com
    http:
      paths:
      - backend:
          service:
            name: vue-project-new
            port: 
              number: 80
        path: /
        pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: vue-project-new
  name: vue-project-new
  namespace: kubernetes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vue-project-new
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: vue-project-new
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - vue-project-new
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - env:
        - name: TZ
          value: Asia/Shanghai
        - name: LANG
          value: C.UTF-8
        image: nginx
        imagePullPolicy: IfNotPresent
        lifecycle: {}
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 80
          timeoutSeconds: 2
        name: vue-project-new
        ports:
        - containerPort: 80
          name: web
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 80
          timeoutSeconds: 2
        resources:
          limits:
            cpu: 994m
            memory: 1170Mi
          requests:
            cpu: 10m
            memory: 55Mi
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: harborkey
      restartPolicy: Always
      securityContext: {}
      serviceAccountName: default

创建该资源:

# kubectl create -f vue-project-new.yaml 
service/vue-project-new created
ingress.networking.k8s.io/vue-project-new created
deployment.apps/vue-project-new created

4.创建Jenkins Job

创建Jenkins Job和之前并无太大区别。首先创建Pipeline类型的Job,名称为vue-project-new:



同样在Pipeline栏,写上地址、Key和分支:



同样的,第一次构建也会失败,第二次可以选择分支构建:

当出现SUCCESS时,表示构建结束:



之后可以在浏览器访问该域名:

(三) 自动化构建Golang项目

上述演示了Java和前端应用的自动化,接下来演示一下对于Golang的自动化构建,本次示例的代码地址:https://gitee.com/dukuan/go-project.git

1.定义Jenkinsfile

本次示例的Jenkinsfile和之前的也无太大区别,需要的更改的位置是构建容器的镜像、缓存目录、Git地址和项目名称:

pipeline {
  agent {
    kubernetes {
      ...
      # 构建镜像改为golang,需要根据实际情况更改版本
      image: "registry.cn-beijing.aliyuncs.com/citools/golang:1.15"
    ...
       # 缓存目录为/go/pkg/,执行go build时下载的依赖包会缓存在该目录
        - mountPath: "/go/pkg/"
          name: "cachedir"
          readOnly: false
  ... # 缓存目录保存的位置
    - name: "cachedir"
      hostPath:
        path: "/opt/gopkg"
'''
    }   
}
...

    # 构建命令
    stage('Building') {
      steps {
        container(name: 'build') {
            sh """ 
              export GO111MODULE=on
              go env -w GOPROXY=https://goproxy.cn,direct
              go build
            """
        }
      }
    }

    ... 

完整文件:

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes-study'
      slaveConnectTimeout 1200
      workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/golang:1.15"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
        - mountPath: "/go/pkg/"
          name: "cachedir"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "localtime"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "dockersock"
          readOnly: false
  restartPolicy: "Never"
  nodeSelector:
    build: "true"
  securityContext: {}
  volumes:
    - hostPath:
        path: "/var/run/docker.sock"
      name: "dockersock"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "localtime"
    - name: "cachedir"
      hostPath:
        path: "/opt/gopkg"
'''
    }
}
  stages {
    stage('Pulling Code') {
      parallel {
        stage('Pulling Code by Jenkins') {
          when {
            expression {
              env.gitlabBranch == null
            }

          }
          steps {
            git(changelog: true, poll: true, url: 'git@10.103.236.251:kubernetes/go-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"

            }

          }
        }

        stage('Pulling Code by trigger') {
          when {
            expression {
              env.gitlabBranch != null
            }

          }
          steps {
            git(url: 'git@10.103.236.251:kubernetes/go-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
            script {
              COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
              TAG = BUILD_TAG + '-' + COMMIT_ID
              println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
            }

          }
        }

      }
    }

    stage('Building') {
      steps {
        container(name: 'build') {
            sh """
              export GO111MODULE=on
              go env -w GOPROXY=https://goproxy.cn,direct
              go build
            """
        }
      }
    }

    stage('Docker build for creating image') {
      environment {
        HARBOR_USER     = credentials('HARBOR_ACCOUNT')
    }
      steps {
        container(name: 'docker') {
          sh """
          pwd
          ls 
          echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
          docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
          docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
          docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
          """
        }
      }
    }

    stage('Deploying to K8s') {
      environment {
        MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
    }
      steps {
        container(name: 'kubectl'){
           sh """
           /usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
           """
        }
      }
    }

  }
  environment {
    COMMIT_ID = ""
    HARBOR_ADDRESS = "10.103.236.203"
    REGISTRY_DIR = "kubernetes"
    IMAGE_NAME = "go-project"
    NAMESPACE = "kubernetes"
    TAG = ""
  }
  parameters {
    gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
  }
}

2.定义Dockerfile

和之前不一样的地方是,Golang编译后生成的是一个二进制文件,可以直接执行,所以底层镜像设置为alpine或者其它的小镜像即可:

FROM registry.cn-beijing.aliyuncs.com/dotbalo/alpine-glibc:alpine-3.9

COPY conf/  ./conf # 如果定义了单独的配置文件,可能需要拷贝到镜像中
COPY ./go-project ./ # 包名按照实际情况进行修改

ENTRYPOINT [ "./go-project"] # 启动该应用

完整文件:

FROM registry.cn-beijing.aliyuncs.com/dotbalo/alpine-glibc:alpine-3.9
 # 如果定义了单独的配置文件,可能需要拷贝到镜像中
COPY conf/  ./conf
 # 包名按照实际情况进行修改
COPY ./go-project ./

ENTRYPOINT [ "./go-project"] # 启动该应用

3.定义Kubernetes资源

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: go-project
  name: go-project
  namespace: kubernetes
spec:
  ports:
  - name: web
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: go-project
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: null
  name: go-project
  namespace: kubernetes
spec:
  rules:
  - host: go-project.test.com
    http:
      paths:
      - backend:
          service:
            name: go-project
            port: 
              number: 8080
        path: /
        pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: go-project
  name: go-project
  namespace: kubernetes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-project
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: go-project
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - go-project
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - env:
        - name: TZ
          value: Asia/Shanghai
        - name: LANG
          value: C.UTF-8
        image: nginx
        imagePullPolicy: IfNotPresent
        lifecycle: {}
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8080
          timeoutSeconds: 2
        name: go-project
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 8080
          timeoutSeconds: 2
        resources:
          limits:
            cpu: 994m
            memory: 1170Mi
          requests:
            cpu: 10m
            memory: 55Mi
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: harborkey
      restartPolicy: Always
      securityContext: {}
      serviceAccountName: default

4.创建Jenkins Job

直接创建即可:

# kubectl create -f go-project.yaml 
service/go-project created
ingress.networking.k8s.io/go-project created
deployment.apps/go-project created

创建Jenkins Job和上述小节也并无区别,在此不再重复演示。创建完成后,可以自行构建,看到如下信息表示创建成功:



之后即可访问该应用(如果是后端项目可以无需创建Ingress暴露服务):



之后在创建Pod的节点,可以看到缓存目录:
[root@k8s-node01 gopkg]# cd /opt/gopkg/
[root@k8s-node01 gopkg]# ls
mod  sumdb

(四) 自动触发构建

之前的构建都是采用手动选择分支进行构建的,实际使用时,项目可能有很多,如果都是手动触发可能比较消耗人力。所以推荐可以按需配置自动触发,即提交代码后自动触发Jenkins进行构建任务。

本次用Java项目进行演示。首先找到Java项目的Job,点击Configure:


之后选择Build Triggers,勾选Build when a change…,记录webhook URL:


之后点击Advanced:



选择Allow all branches,如果不想任何分支都可以触发该流水线,可以选择Filter进行条件匹配。之后点击Generate生成Secret token,最后点击Save即可。

接下来配置GitLab,首先点击Menu--->Admin:



选择Settings--->Network,之后允许访问外部的请求:


保存后,找到Java项目,点击Settings--->WebHooks:
![](https://img.haomeiwen.com/i20896689/ae752c2ed879c8c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
在新页面输入Webhook地址和token:


确认无误后,点击Add webhook:

之后下方会添加一个新的Project Hooks,可以点击Test进行Push测试:

点击后,即可在Jenkins页面看到任务被触发:

也可以通过Blue Ocean看到是自动触发的stage被执行:

以上就是通过GitLab的事件触发Jenkins任务,在实际使用时,此功能非常常用,一般会用于开发、测试等环境,省去了手动构建的过程。而在UAT和生产环境,一般不需要再次构建,而是选择其它环境产生的镜像进行发版,接下来看一下如何进行不构建进行发版。

(五) 一次构建多次部署

创建一个新的Job,名字为go-project-uat,类型Pipeline:



点击页面的This Project is parameterized(参数化构建):



选择参数类型为Image Tag Parameter(需要安装Image Tag插件),之后定义Name为变量的名称,Iamge Name为Harbor的目录和镜像名称:

点击Advance,输入仓库的地址,注意如果配置了证书,需要配置https:



之后可以先点击Save,然后测试能否获取到进行Tag。保存后点击Build with Parameters:

之后点击Configure,添加Pipeline脚本:
pipeline {
     agent {
    kubernetes {
      cloud 'kubernetes-study'
      slaveConnectTimeout 1200
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    # 只需要配置jnlp和kubectl镜像即可
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
      name: jnlp
      imagePullPolicy: IfNotPresent
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
  restartPolicy: "Never"
'''
    }   
}

   stages {
      stage('Deploy') {
        environment {
            MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
        }
         steps {
         container(name: 'kubectl'){
            sh """
               echo ${IMAGE_TAG} # 该变量即为前台选择的镜像
               kubectl --kubeconfig=${MY_KUBECONFIG} set image deployment -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${IMAGE_TAG} -n ${NAMESPACE}
               kubectl --kubeconfig=${MY_KUBECONFIG} get po  -l app=${IMAGE_NAME} -n ${NAMESPACE} -w
            """
         }
        }
      }
   }
   environment {
    HARBOR_ADDRESS = "HARBOR_ADDRESS"
    NAMESPACE = "kubernetes"
    IMAGE_NAME = "go-project"
    TAG = ""
  }
}

保存后,选择一个镜像,点击Build:



即可看到是直接将镜像版本更新至Kubernetes,并无构建过程,可以省下很多时间。该流水线也可以选择之前的版本进行回滚操作。

上一篇下一篇

猜你喜欢

热点阅读