Jenkins创建项目

  1. 创建一个流水线项目

    image-20221130151213350

  2. 配置代码仓库为gitee仓库

    image-20221130152910533

  3. 配置gitee webhook

    image-20221130153319926

    image-20221130153428838

    • 先保存jenkins的配置,然后去gitee上进行配置

      image-20221130154907797

      image-20221130155831156

      image-20221130160519669

  4. 配置流水线

    image-20221130161952710

    image-20221130165245960

    image-20221130165346043

jenkins上的配置到这里就完成了,现在准备springboot项目的Jenkinsfile配置。

Jenkinsfile配置

实现Jenkinsfile语法提示

在springboot项目的项目根目录下新建Jenkinsfile文件

image-20221130165912397

然后配置一下Jenkinsfile的语法提示:

  1. 配置IDEA识别Jenkinsfile文件为Groovy语法。

    image-20221130170221574

  2. 请求jenkins接口获取jenkins pipeline的一些语法规则http://192.168.181.105:10880/job/spring-boot-template/pipeline-syntax/gdsl

  3. 在任意一个模块src.main.java目录下新建pipeline.gdsl文件,然后把刚才从接口请求的内容copy进去。

注意:从 Jenkins 获取的 gdsl 文件可能存在一些未自动补全的字段

编写Jenkinsfile文件

首先贴一份Jenkinsfile配置

//file:noinspection GroovyAssignabilityCheck
pipeline {
    // 指定任务在哪个集群节点中执行
    agent any

    // 声明全局变量,方便后面使用
    environment {
        // 应用名称
        APP_NAME = 'SpringBootTemplate'
        // 应用版本
        APP_VERSION = "1.0.0"
        // 打包后,需要部署服务的jar名称
        JAR_NAME = 'module-template'
        // JAR URL
        JAR_URL = 'module-template/target'
        // 启动参数
        // SERVER_OPTS ="-Xmx512m -Xms64m"
        // SERVER_PARAMS ="--spring.profiles.active=docker --server.port=8080"
        SERVER_OPTS = ''
        SERVER_PARAMS = ''
    }

    stages {
        // 构建jar
        stage('Build') {
            agent {
                docker {
                    image 'maven:3.6.3-slim'
                    args '-v /root/.m2:/root/.m2'
                }
            }
            steps {
                // 运行打包脚本
                sh 'sh ./jenkins/scripts/build.sh'
                // 暂存jar包,避免不同agent获取不到文件
                stash includes: "${env.JAR_URL}/*.jar", name: 'jar'
            }
        }
        // 测试阶段
        stage('Test') {
            steps {
                sh 'sh ./jenkins/scripts/test.sh'
            }
        }

        // 部署容器
        stage('Deploy') {
            environment {
                IMAGE_NAME = 'spring_boot_template'
                IMAGE_VERSION = '1.0.0'
                MAIN_SERVER_PORT = '8899'
                EMS_SERVER_PORT = '9898'
            }

            steps {
                // 获取Build stage构件的Jar包
                unstash 'jar'
                sh 'sh ./jenkins/scripts/deploy.sh'
            }

            post {
                failure {
                    echo '部署失败'
                }

            }
        }
    }

    post {
        always {
            echo 'Always'
        }
        success {
            echo "Success"
        }
        failure {
            echo 'Failure'
        }

    }

}

Jenkinsfile实现流程大致为,首先进行 Jar 包的构建,并将其打包成 Docker 镜像,然后将镜像运行在宿主机的 Docker 容器上

编写构建阶段的Build Stage
  1. 首先定义两个环境变量 JAR_NAME,JAVA_URL,用于 Maven 进行打包时使用

    environment {
        JAR_NAME = 'module-template'
        JAR_URL = 'module-template/target'
    }
    
  2. 定义Buidl Stage,使用Maven容器进行项目构建

    // 构建jar
    stage('Build') {
        agent {
            docker {
                image 'maven:3.6.3-slim'
                // 将maven仓库目录挂载到宿主机中,方便复用。
                args '-v /root/.m2:/root/.m2'
            }
        }
    }
    

    由于我们的 Jenkins 是运行于宿主机的 Docker 上的,并且在运行时指定了 docker.sock 文件的映射,因此构建阶段运行的 Maven 容器是运行在宿主机上的,相当于在宿主机上运行 docker run -v /root/.m2:/root/.m2 maven:3.6.3-slim,同时映射宿主机的本地 Maven 仓库,以便于复用依赖。

  3. 指定运行时步骤 steps,定义 build.sh 脚本,把所有指令放在脚本里执行。

    steps {
        // 运行打包脚本
        sh 'sh ./jenkins/scripts/build.sh'
        // 暂存jar包,避免不同agent获取不到文件
        stash includes: "${env.JAR_URL}/*.jar", name: 'jar'
    }
    
  4. 编写build.sh脚本。

    # 构建 Jar 包,跳过测试
    mvn -B -DskipTests clean package
    

    build.sh 进行的工作很简单,只是对项目进行打包。

编写测试阶段Test Stage
  1. 编写test.sh脚本

    # test
    echo "已执行测试脚本"
    

    在这里可以对代码做一些测试,Jenkins 也支持对测试的结果进行展示,具体可以查看官方文档

  2. 编辑Jenkinsfile文件

    // 测试阶段
    stage('Test') {
        steps {
            sh 'sh ./jenkins/scripts/test.sh'
        }
    }
    
编写部署阶段Deploy Stage
  1. 新增部署阶段环境变量

    stage('Deploy') {
        environment {
            // 镜像名称
            IMAGE_NAME = 'spring_boot_template'
            // 镜像版本号
            IMAGE_VERSION = '1.0.0'
            // SpringBoot项目所使用的端口,我这里使用的netty,所以占用了两个
            MAIN_SERVER_PORT = '8899'
            EMS_SERVER_PORT = '9898'
            // 启动参数
            // SERVER_OPTS ="-Xmx512m -Xms64m"
            // SERVER_PARAMS ="--spring.profiles.active=docker --server.port=8080"
            SERVER_OPTS = ''
            SERVER_PARAMS = ''
            // 应用名称
            APP_NAME = 'SpringBootTemplate'
            // 应用版本
            APP_VERSION = "1.0.0"
        }
    }
    

    用于指定镜像名,镜像版本,服务运行的端口,应用名称,应用版本

  2. 定义Deploy Stage

    stage('Deploy') {
        steps {
            // 获取Build stage构件的Jar包
            unstash 'jar'
            sh 'sh ./jenkins/scripts/deploy.sh'
        }
        post {
            failure {
                echo '部署失败'
            }
        }
    }
    

    定义 steps,首先从暂存中获取 Build Stage 阶段构建的 Jar 包,然后运行 deploy.sh 脚本。 post 可以根据不同的运行结果进行不同的响应,这里如果部署失败的话,打印 部署失败,可以使用 email 进行告警,具体可以查看清理和通知

    post {
        failure {
            mail to: 'team@example.com',
                 subject: "Failed Pipeline: ${currentBuild.fullDisplayName}",
                 body: "Something is wrong with ${env.BUILD_URL}"
        }
    }
    
  3. 定义Dockerfile

    本项目的Dockerfile比较复杂,因为其中包含视频转码,需要使用ffmpeg。

    ## 基础镜像
    FROM aurorxa/oracle-jdk:8
    ## 维护人信息
    MAINTAINER junpzx<junpzx@163.com>
    ## 创建文件夹
    RUN mkdir -p /mnt/workspace/app
    ## 设置工作目录
    WORKDIR /mnt/workspace/app
    ## 往镜像中复制
    ARG JAR_NAME=${JAR_NAME}
    ARG MAIN_SERVER_PORT=${MAIN_SERVER_PORT}
    ARG EMS_SERVER_PORT=${EMS_SERVER_PORT}
    COPY ${JAR_NAME}.jar app.jar
    ## 安装EasyMedia需要的资源
    RUN yum -y install libxcb libx11-xcb1 libxss1 libasound2 libxkbfile1 alsa-lib-devel \
        && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  \
        && echo 'Asia/Shanghai' >/etc/timezone
    ## 暴露端口
    EXPOSE ${MAIN_SERVER_PORT}
    EXPOSE ${EMS_SERVER_PORT}
    # 环境变量
    # docker run -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=docker --server.port=8080" xxx
    ENV TZ=Asia/Shanghai
    ENV JAVA_OPTS=""
    ENV PARAMS=""
    ## 执行命令
    ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar app.jar $PARAMS" ]
    

    一般来说,以下Dockerfile可以满足大部分需求。

    ## 基础镜像
    FROM moxm/java:1.8-full
    ## 维护人信息
    MAINTAINER junpzx<junpzx@163.com>
    ## 创建文件夹
    RUN mkdir -p /mnt/workspace/app
    ## 设置工作目录
    WORKDIR /mnt/workspace/app
    ## 往镜像中复制
    ARG JAR_NAME=${JAR_NAME}
    ARG MAIN_SERVER_PORT=${MAIN_SERVER_PORT}
    ARG EMS_SERVER_PORT=${EMS_SERVER_PORT}
    COPY ${JAR_NAME}.jar app.jar
    ## 暴露端口
    EXPOSE ${MAIN_SERVER_PORT}
    EXPOSE ${EMS_SERVER_PORT}
    # 环境变量
    # docker run -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=docker --server.port=8080" xxx
    ENV TZ=Asia/Shanghai
    ENV JAVA_OPTS=""
    ENV PARAMS=""
    ## 执行命令
    ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar app.jar $PARAMS" ]
    

    在项目目录下,创建docker/Dockerfile文件,用于构建镜像。

  4. 编写deploy.sh脚本

    # 复制Jar到docker目录
    cp "${JAR_URL}/${JAR_NAME}.jar" "docker/${JAR_NAME}.jar"
    
    # 构建镜像
    docker build --build-arg JAR_NAME="${JAR_NAME}" --build-arg EMS_SERVER_PORT="${EMS_SERVER_PORT}" --build-arg MAIN_SERVER_PORT="${MAIN_SERVER_PORT}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" ./docker
    
    # 删除旧容器
    containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -aq)
    if [ "${containerId}" != "" ]; then
        docker rm -f "${containerId}"
    fi
    
    # 运行新容器
    docker run --restart=always -dp "${MAIN_SERVER_PORT}:${MAIN_SERVER_PORT}" -p "${EMS_SERVER_PORT}:${EMS_SERVER_PORT}" -e JAVA_OPTS="${SERVER_OPTS}" -e  PARAMS="${SERVER_PARAMS}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}"
    
    # 判断容器运行情况,未运行则抛出异常
    docker ps -f name="${APP_NAME}-${APP_VERSION}"
    containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -q)
    
    if [ "${containerId}" = "" ]; then
        exit 42
    fi
    
    • 首先,复制从 Build Stage 构建的 Jar 包到 docker 目录下

    • 开始构建镜像,镜像名从环境变量中获取,同时传递构建参数 JAR_NAME,EMS_SERVER_PORT,MAIN_SERVER_PORT,指定上下文为 docker 目录

    • 根据容器名称获取 Docker 中运行的旧容器id,删除旧容器

    • 运行新容器,${MAIN_SERVER_PORT}:${MAIN_SERVER_PORT},${EMS_SERVER_PORT}:${EMS_SERVER_PORT} 映射运行端口,"${APP_NAME}-${APP_VERSION}" 指定容器名称,JAVA_OPTS="${SERVER_OPTS}传递java配置,PARAMS="${SERVER_PARAMS}传递项目配置。

    • 判断容器运行情况,未运行则抛出异常,终止流水线进行异常告警

    Jenkinsfile 到这里就编写完了,接下来可以尝试运行流水线。

完整文件

Dockerfile

项目根目录 docker/Dockerfile

## 基础镜像
FROM aurorxa/oracle-jdk:8
## 维护人信息
MAINTAINER junpzx<junpzx@163.com>
## 创建文件夹
RUN mkdir -p /mnt/workspace/app
## 设置工作目录
WORKDIR /mnt/workspace/app
## 往镜像中复制
ARG JAR_NAME=${JAR_NAME}
ARG MAIN_SERVER_PORT=${MAIN_SERVER_PORT}
ARG EMS_SERVER_PORT=${EMS_SERVER_PORT}
COPY ${JAR_NAME}.jar app.jar
## 安装EasyMedia需要的资源
RUN yum -y install libxcb libx11-xcb1 libxss1 libasound2 libxkbfile1 alsa-lib-devel \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  \
    && echo 'Asia/Shanghai' >/etc/timezone
## 暴露端口
EXPOSE ${MAIN_SERVER_PORT}
EXPOSE ${EMS_SERVER_PORT}
# 环境变量
# docker run -e JAVA_OPTS="-Xmx512m -Xms64m" -e PARAMS="--spring.profiles.active=docker --server.port=8080" xxx
ENV TZ=Asia/Shanghai
ENV JAVA_OPTS=""
ENV PARAMS=""
## 执行命令
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar app.jar $PARAMS" ]

build.sh

项目根目录 docker/jenkins/scripts/build.sh

mvn -B -DskipTests clean package

deploy.sh

项目根目录 docker/jenkins/scripts/deploy.sh

# 复制Jar到docker目录
cp "${JAR_URL}/${JAR_NAME}.jar" "docker/${JAR_NAME}.jar"

# 构建镜像
docker build --build-arg JAR_NAME="${JAR_NAME}" --build-arg EMS_SERVER_PORT="${EMS_SERVER_PORT}" --build-arg MAIN_SERVER_PORT="${MAIN_SERVER_PORT}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" ./docker

# 删除旧容器
containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -aq)
if [ "${containerId}" != "" ]; then
    docker rm -f "${containerId}"
fi

# 运行新容器
docker run --restart=always -dp "${MAIN_SERVER_PORT}:${MAIN_SERVER_PORT}" -p "${EMS_SERVER_PORT}:${EMS_SERVER_PORT}" -e JAVA_OPTS="${SERVER_OPTS}" -e  PARAMS="${SERVER_PARAMS}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}"

# 判断容器运行情况,未运行则抛出异常
docker ps -f name="${APP_NAME}-${APP_VERSION}"
containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -q)

if [ "${containerId}" = "" ]; then
    exit 42
fi

test.sh

项目根目录 docker/jenkins/scripts/test.sh

echo '已执行测试脚本'