栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

基于Jenkins和Kubernetes流水线实现应用的自动发版

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于Jenkins和Kubernetes流水线实现应用的自动发版

本文我们将基于Jenkins和Kubernetes流水线过程实现自动化构建Java应用,即把流水线的过程转换为pipeline语法,实现应用的自动发版。

一、 创建Java测试用例

本书为读者准备了一个简单的Java测试用例,用于读者的学习,可以从 https://github.com/andanyoung/springboot-hello.git 找到该项目(也可以使用公司的Java项目)。

接下来将该项目导入自己的GitLab中。首先找到之前创建的Kubernetes组,然后单击New project,如图1所示。

图1 创建测试项目

选择Import project,如图2所示。

图2 导入项目

单击Repo by URL

图3 导入Java测试项目

在Git repository URL中输入示例地址,然后单击Create project,如图4所示。

图4 创建Project

导入后,结果如图5所示。

图5 查看导入的项目

二、定义Jenkinsfile

Jenkinsfile定义流水线的步骤包括拉取代码、执行构建、生成镜像、更新Kubernetes资源等。本书将Jenkinsfile放置于项目源代码一并管理,也可以单独放置于一个Git仓库进行管理。

接下来在GitLab的源代码中添加Jenkinsfile。首先单击代码首页的“+”,然后单击New file,如图6所示。

图6 添加文件

在窗口中添加如下内容(完整内容见本书代码):

pipeline {

 ...

 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比较长,接下来我们分别解析一下。

三、 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:20.10.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 = "10.0.0.204" # 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 M Y K U B E C O N F I G s e t i m a g e d e p l o y − l a p p = MY_KUBECONFIG set image deploy -l app= MYK​UBECONFIGsetimagedeploy−lapp={IMAGE_NAME} I M A G E N A M E = {IMAGE_NAME}= IMAGEN​AME={HARBOR_ADDRESS}/ R E G I S T R Y D I R / {REGISTRY_DIR} / REGISTRYD​IR/{IMAGE_NAME}{TAG} -n $NAMESPACE。由于本书示例的Deployment名称、Label、容器名称均和项目名称一致,因此就统一了IMAGE_NAME,如果读者不是按照此规范命名的,就需要进行更改,可以参考本书5.2节Deployment部分的kubectl set命令。

四、 定义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
五、 定义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

 ...

    path: /

    pathType: ImplementationSpecific

需要注意的是,在实际使用时,Java一般是后台应用,不需要使用Ingress暴露出去,只需要配置Service即可。如果需要被集群外部的用户直接访问,需要配置Ingress,通过域名发布。

创建该资源:

# 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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945870.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号