Jenkins CI自动发布SprintBoot项目到Kubernetes集群

一. 环境准备

本文示例如何使用 Jenkins 自动编译打包, 构建 Docker 镜像, 提交到本地 Docker Registry 镜像服务, 并通过 Helm 发布到 K8S 集群.

整体的流程图如下:
整理流程

本文涉及到的环境:

  • 服务端 ttg12:

    • OS: Ubuntu Server 18.04
    • IP: 192.168.31.12
    • 应用:

      • Docker v19.03.6
      • Docker Registry v2.6.1
      • K8S kubectl v1.18.5.
      • Jenkins Docker v2.235.1-lts
  • 本地工作电脑

    • OS: MacOS Catalina 10.15.5
    • IP: 192.168.31.22
    • 应用:

      • K8S kubectl 工具, 连接到 k8s master

二. 安装和配置相关应用

2.1 Docker 准备

ttg12 上安装好 docker, 并配置好国内镜像.

ttg12 上通过 docker 安装本地 Docker Registry (v2.6.1). 参考私有化部署极简 Docker Registry, 并将自签名证书复制到 /etc/docker/certs.d/:/ca.crt.

2.2 K8S 准备

准备一个 K8S 集群或者单节点 K8S, 在 ttg12 上安装好 kubectl 工具, 并配置好 /etc/.kube/config.

ttg12 上安装好 helm 3 客户端. Linux 下 Helm 3 的安装很简单, 直接下载二进制文件下来, 解压, 移动到/usr/local/bin/目录下即可.

# 这里以 helm v3.3.0 为例
curl https://get.helm.sh/helm-v3.3.0-rc.1-linux-amd64.tar.gz > /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz
tar zxf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz -C /tmp/helm-v3.3.0-rc.1-linux-amd64/helm `/usr/local/bin/helm`
sudo cp /tmp/helm-v3.3.0-rc.1-linux-amd64/helm /usr/local/bin/helm
sudo rm -rf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz /tmp/helm-v3.3.0-rc.1-linux-amd64/helm

2.3 安装 Jenkins

本文使用 Docker 方式安装 Jenkins. 具体步骤参考本文另外一篇博文 Docker安装Jenkins并支持Maven,Docker,Helm.

启动容器后, 登录 Jenkins, 安装插件 Docker plugin and Docker Pipeline. 否则会报异常:

Obtained Jenkinsfile from git [https://gitee.com/facelessdemos/SpringBootDemo.git](https://gitee.com/facelessdemos/SpringBootDemo.git)
Running in Durability level: MAX\_SURVIVABILITY
org.codehaus.groovy.control.MultipleCompilationErrorsExcept.ion: startup failed:
WorkflowScript: 34: Invalid agent type "docker" specified. Must be one of \[any, label, none\] @ line 34, column 17.
                   docker {
                   ^

参考 Gitee 官方文档, 安装 Gitee 插件.

三. 代码准备

在项目根目录下新建 Dockerfile, Jenkinsfile 文件 以及 helm 目录.
image.png

3.1 Dockerfile

FROM frolvlad/alpine-java:jdk8-slim
#在build镜像时可以通过 --build-args profile=xxx 进行修改
ARG profile
ENV SPRING_PROFILES_ACTIVE=${profile}
#项目的端口
EXPOSE 8080
WORKDIR /mnt

#修改时区
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
    && apk add --no-cache tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata \
    && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache

COPY ./web/target/web-1.0-SNAPSHOT.jar ./app.jar
ENTRYPOINT ["java", "-jar", "/mnt/app.jar"]

3.2 Jenkinsfile

image_tag = "1.0.0-snapshot"  //定一个全局变量,存储Docker镜像的tag(版本)
pipeline {
    agent any
    environment {
        //GIT_REPO = "${env.gitlabSourceRepoName}"  //从Jenkins Gitlab插件中获取Git项目的名称
        GIT_REPO = "springbootdemo"
        GIT_BRANCH = "${env.gitlabTargetBranch}"  //项目的分支
        GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim()  //commit id或tag名称
        KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config')  //开发测试环境的kube凭证
        KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生产环境的kube凭证

        DOCKER_REGISTRY = "registry.faceless.com:5443" //Docker仓库地址
        DOCKER_NAMESPACE = "facelessdemo"  //命名空间
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker镜像地址

        INGRESS_HOST_DEV = "dev.springbootdemo.cn"    //开发环境的域名
        INGRESS_HOST_TEST = "test.springbootdemo.cn"  //测试环境的域名
        INGRESS_HOST_PROD = "prod.springbootdemo.cn"  //生产环境的域名

        K8S_NAMESPACE = "demo-dev"
    }
    parameters {
        string(name: 'ingress_path', defaultValue: '/', description: '服务上下文路径')
        string(name: 'replica_count', defaultValue: '2', description: '容器副本数量')
    }

    stages {
        stage('Code Analyze') {
            agent any
            steps {
               echo "1. 代码静态检查"
            }
        }
        stage('Maven Build') {
            agent any
            steps {
                echo "2. 代码编译打包"
                sh 'mvn clean package -Dfile.encoding=UTF-8 -Dmaven.test.skip=true'
            }
        }
        stage('Docker Build') {
            agent any
            steps {
                echo "3. 构建Docker镜像"
                echo "镜像地址: ${DOCKER_IMAGE}"
                script {
                    def profile = "dev"
                    image_tag = "dev." + env.GIT_TAG
                    if (env.gitlabTargetBranch == "develop") {
                        image_tag = "dev." + env.GIT_TAG
                        profile = "dev"
                    } else if (env.gitlabTargetBranch == "pre-release") {
                        image_tag = "test." + env.GIT_TAG
                        profile = "test"
                    } else if (env.gitlabTargetBranch == "master"){
                        // master分支则直接使用Tag
                        image_tag = env.GIT_TAG
                        profile = "prod"
                    }
                    //通过--build-arg将profile进行设置,以区分不同环境进行镜像构建
                    sh "docker build  --build-arg profile=${profile} -t ${DOCKER_IMAGE}:${image_tag} ."
                    sh "docker push ${DOCKER_IMAGE}:${image_tag}"
                    sh "docker rmi ${DOCKER_IMAGE}:${image_tag}"
                }
            }
        }
        stage('Helm Deploy') {
            agent any
            steps {
                echo "4. 部署到K8s"
                sh "helm upgrade -i --namespace=${K8S_NAMESPACE} --set image.repository=${DOCKER_IMAGE} --set image.tag=${image_tag} ${GIT_REPO} ./helm/"
            }
        }
    }
}

3.3 Helm配置

通过helm 客户端创建对应版本的 chart 配置文件清单:

helm create springbootdemo

得到相应的文件清单如下:
image.png

1) 我们需要修改其中的 values.yaml 文件. 注意以下带注释的部分.

# 启动容器副本数
replicaCount: 2
image:
  # 项目打包出来的 docker image 提交在本地镜像仓库中
  repository: registry.faceless.com:5443/facelessdemo/springbootdemo
  pullPolicy: IfNotPresent
  # 默认image标签. 需在Jenkinsfile中的 `sh helm upgrade` 命令中覆盖.
  tag: "dev.latest"

imagePullSecrets: []
# 项目名
nameOverride: "springbootdemo"
fullnameOverride: ""

# 容器的端口暴露及环境变量配置
container:
  port: 8080
  env: []

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}
podSecurityContextext: {}
securityContext: {}

# 本示例通过Ingress提供对外服务, 所以Service使用ClusterIP模式即可
service:
  type: ClusterIP
  port: 8080

# Ingress配置域名和路径. 测试时需要将`dev.springbootdemo.cn`域名指向k8s的任一台worker的IP.
ingress:
  enabled: true
  annotations: {}
  hosts:
    - host: dev.springbootdemo.cn
      paths: [/]
  tls: []

resources: {}
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 4

nodeSelector: {}
tolerations: []
affinity: {}

# Probe延时参数
probe:
  livenessDelaySeconds: 120
  readinessDelaySeconds: 120

2) templates/depolyment.yaml 文件. templates 文件夹下的文件是 K8S deploymnet, service, ingress 等通用配置模板, 一般情况下无需修改. 但是对于 SrpingBoot 项目, 特别是涉及到启动过程中需要连接数据库/消息队列/其他第三方服务的情况, 默认在启动后就进行服务探测(Probe). 而此时可能 tomcat 还没起来, 所以容易误报为服务失败, 导致无端重启容器. 因此我们需要修改 templates/depolyment.yaml 文件, 将其中 Probe 延时改为一个相对比较合理的时间.

我们修改 templates/depolyment.yaml文件, 在 .spec.template.spec.containers 节点下增加 .livenessProbe.readinessProbe 两个节点. 其他部分没有任何改动, 所以这里只给出这一段代码片段:

      containers:
        - name: {{ .Chart.Name }}
          # 省略...
          ports:
            - name: http
              containerPort: {{ .Values.container.port }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds:  {{ .Values.probe.livenessDelaySeconds }}
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds:  {{ .Values.probe.readinessDelaySeconds }}

 四. 配置 Jenkins

1) 新建一个 Item, 取一个名字 (比如: SpringBootDemo-K8S), 选择 Pipeline 类型.
image.png

2) 进入到配置页面, General -> Description字段输入项目描述.
3) 如果你的 Jenkins 可以外网访问, 或者跟你的 Gitlab 处于同一局域网, 可以由 Gitee 或者 Gitlab 触发 WebHook, 那么在 Build Triggers 模块进行相应的配置. 本示例因为 Gitee 无法触达内网部署的 Jenkins, 所以没有配置 Build Triggers. 后面需要手动触发编译流程.
4) Pipeline的定义如下图所示 (注意, 我这里取的是base分支, 如果你需要取master分支, 将下图中的3处base改为master:
image.png

最后, 手动触发编译, Pipeline 的 Stage View 如下图所示:
image.png

你可能感兴趣的:(jenkins,kubernetes,springboot,持续集成,ci)