意图

建立这个项意图意图在于,自己的项目若想每次发布到线上,每次都需求经过一下这样一段流程化操作。

打包项目->上传到服务器->构建镜像->运转容器。

因此希望经过 Jenkins 这样的平台,完结代码一旦 push 后,主动完结以上的流程。 建立期间遇到了许多问题,花了两天时刻建立,希望能协助一些小伙伴避雷。

本文作为本人学习所记载,仅供参考。

项目地址: github.com/ylhao666/si…

完结进程

开发环境

软件 版别 备注
Centos 8.2.2004 腾讯云服务器
Docker 20.10.12 运转在云服务器上
Jenkins latest jenkinsci/blueocean 官方镜像
SpringBoot 2.6.3 完结简略的 Web 运用

基于 Docker 建立 Jenkins

建立 Jenkins 的方法有多种,详细能够检查 官方文档,本次运用容器运转,将 Jenkins 布置在 Docker 中,运转语句如下:

docker run \
  -dp 8080:8080 \
  -v jenkins-data:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$HOME":/home \
  --restart=always
  jenkinsci/blueocean

大约解释一下各个参数的意思

  1. -dp 映射宿主机 8080 端口到 Jenkins 8080 端口上,用于拜访 Jenkins 主页,一起将 Jenkins 运转在后台
  2. -v jenkins-data:/var/jenkins_home 映射卷到 /var/jenkins_home 途径,保存了 Jenkins 的基本信息,此项确保重新运转容器后,数据不会被清空,主张映射
  3. -v /var/run/docker.sock:/var/run/docker.sock 映射 docker.sock 文件,这样履行 docker 指令时,呼应的是宿主机,详细能够检查此文章,此操作是完结本次布置的关键
  4. -v "$HOME":/home 映射宿主机 home 目录到 Jenkins home
  5. --restart=always Docker 重启时,跟随重启

运转成功后,拜访 http://your_ip:8080,跟随指引完结 Jenkins 的装置

留意:装置插件时,挑选装置推荐的插件

创立项目

编写 SpringBoot 项目

接下来,基于 SpringBoot 建立一个简易的 Web 运用

引进依靠

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

写一个 HelloController,供给一个 /hello 的接口,用于测验

@RestController
public class HelloController {
    @GetMapping("hello")
    public String hello() {
        return "Hello World";
    }
}

指定默许运转端口,由于 8080 现已被 Jenkins 运用了,换成其他端口

server:
  port: 9092

指定构建参数,指定生成的 Jar 包称号,引进 SpringBoot maven 插件,用于构建 Jar 包

<build>
  <finalName>My-App</finalName>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

在本地测验,运转成功,拜访 127.0.0.1:9092/hello,回来 Hello World

把项目托管到 Github 上,用于被 Jenkins 拉取

github.com/ylhao666/si…

Jenkins 创立项目
  1. 翻开 Jenkins 页面,新建使命

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 使命名写项目名,勾选流水线

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 挑选拉取代码的方法,运转时从 Git 代码库拉取代码

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

留意:需求添加凭据,以便于拉取代码,设置 ssh 方法或许 username & password 方法都能够

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 指定构建分支,现已默许的 Jenkinsfile 称号

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

新建项意图初始作业就完结了,接下来开端编写 Jenkinsfile,界说流水线逻辑

编写 Jenkinsfile

运用 Jenkins 的关键在于 Jenkinsfile 的编写,需求了解一点 shell

完结 Jenkinsfile 主动补全

IDEA 好像不支撑 Jenkinsfile 的语法提醒,需求手动装备一下,装备完写起来便利一点

  1. 获取 gdsl 界说文件,首要拜访 http://{{your_ip}}:8080/job/{{your_project_name}}/pipeline-syntax/gdsl 获取 gdsl 文件,仿制下来,到项意图 src.main.java 目录下,新建 pipeline.gdsl 文件,张贴仿制的内容。

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 设置 IDEA ,令其识别 Jenkinsfile 文件支撑 Groovy

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 在项目根目录,新建 Jenkinsfile 文件,测验发现现已能够主动补全

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

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

能够从 git 地址获取完好的 pileline.gdsl 文件

编写 Jenkinsfile

本次完结的流水线,首要进行 Jar 包的构建,并将其打包成 Docker 镜像,然后将镜像运转在宿主机的 Docker 容器上

完好的 Jenkinsfile 能够在附录检查

编写构建阶段 Build Stage

  1. 首要界说一个环境变量 PACKAGE_NAME,用于 Maven 进行打包时运用
environment {
    PACKAGE_NAME = 'My-App'
}
  1. 界说 Build Stage,将构建进程运转在 Maven 容器上
// 构建阶段
stage('Build') {
    agent {
        docker {
            image 'maven:3.6.3-slim'
            // 挂载在宿主机上,复用依靠文件
            args '-v /root/.m2:/root/.m2'
        }
    }
}

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

  1. 指定运转时步骤 steps,界说 build.sh 脚本,把所有指令放在脚本里履行
steps {
    sh 'sh ./jenkins/scripts/build.sh'
    // 暂存 Jar 包,防止不同 agent 下取不到文件
    stash includes: '**/target/*.jar', name: 'jar'
}

留意:由于流水线上不同的 stage 能够指定不同的 agent,以便运转在不同的环境。所以不同的 agent 下,数据是不共享的。因此 Build Stage 下构建完结的 Jar 包,能够经过 stash 指令进行暂存,后续在 Deploy Stage,能够经过 unstash 获取。

  1. 编写 build.sh 脚本
# 构建 Jar 包,越过测验
mvn -B -DskipTests clean package

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

编写测验阶段 Test Stage

  1. 编写 Test Stage
// 单元测验
stage('Test') {
    steps {
        sh 'sh ./jenkins/scripts/test.sh'
    }
}

Build Stage 相同,将指令抽离到脚本履行

  1. 编写 test.sh 脚本
# test
echo "Test"

在这儿能够对代码做一些测验,Jenkins 也支撑对测验的成果进行展示,详细能够检查官方文档

编写布置阶段 Deploy Stage

  1. 界说新的环境变量
environment {
    IMAGE_NAME = 'my-app'
    IMAGE_VERSION = '1.0.0'
    SERVER_PORT = '7072'
    APP_NAME = 'My-App'
    APP_VERSION = '1.0.0'
}

用于指定镜像名,镜像版别,服务运转的端口,运用称号,运用版别

  1. 界说 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}"
    }
}
  1. 界说 Dockerfile
FROM openjdk:8-jre-slim
ARG PACKAGE_NAME
WORKDIR /app
COPY ${PACKAGE_NAME}.jar ./${PACKAGE_NAME}.jar
RUN echo "java -jar ${PACKAGE_NAME}.jar \${@}" > ./entrypoint.sh 
    && chmod +x ./entrypoint.sh
ENTRYPOINT ["sh", "entrypoint.sh"]

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

大约解释一下 Dockerfile 的内容

  • 依据 openjdk:8-jre-slim 镜像进行构建
  • 界说构建参数 PACKAGE_NAME,用于构建时传递 Jar 包称号
  • 指定作业目录为 /app
  • 仿制 Jar 包到作业目录下
  • 界说运转脚本 entrypoint.sh,赋予运转权限。${@} 用于运转时承受从指令传递的参数。原本希望直接运用 ENTRYPOINT 指令,测验后好像无法一起承受 ARG 现已运转时参数,因此只能运用脚本,详细能够检查此发问。
  • 履行脚本
  1. 编写 deploy.sh 脚本
# 仿制 Jar 包到 docker 目录
cp "target/${PACKAGE_NAME}.jar" "docker/${PACKAGE_NAME}.jar"

# 构建镜像
docker build -t "${IMAGE_NAME}:${IMAGE_VERSION}" --build-arg PACKAGE_NAME="${PACKAGE_NAME}" docker

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

# 运转新容器
docker run --restart=always -dp "${SERVER_PORT}:${SERVER_PORT}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}" --server.port="${SERVER_PORT}"

# 判别容器运转状况,未运转则抛出反常
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 目录下
  • 开端构建镜像,镜像名从环境变量中获取,一起传递构建参数 PACKAGE_NAME,指定上下文为 docker 目录
  • 依据容器称号获取 Docker 中运转的旧容器id,删去旧容器
  • 运转新容器,${SERVER_PORT}:${SERVER_PORT} 映射运转端口,"${APP_NAME}-${APP_VERSION}" 指定容器称号,--server.port 传递运转时参数,指定运转端口
  • 判别容器运转状况,未运转则抛出反常,终止流水线进行反常告警

Jenkinsfile 到这儿就编写完了,接下来能够测验运转流水线。

所有完好的 sh 文件均能够在附录中获取

运转流水线

首要提交代码,然后拜访 Jenkins 主页,点击刚刚创立的项目

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

点击翻开 BlueOcean

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

点击 Run,开端运转流水线

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

能够发现运转成功,一起能够检查每个 steps 打印的内容

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

绿了绿了芜湖

拜访 {{your_ip}}:7072/hello,回来 Hello World,能够发现现已成功布置到服务器上

设置 Git Webhooks 完结主动化布置

上面现已完结了手动布置,提交完代码后,点击一下就能够主动构建,布置。下面测验装备主动化布置,这儿运用到了 GitWebhooks

  1. 装备 JenkinsJenkis 的装备中,装备 Github 服务器。称号随便填写,API URL 默许即可,关键是凭据,需求在 Github 中进行请求 access_token,能够点击这儿进行请求。依照官方的要求,至少请求如下的权限:
  • admin:repo_hook– for managing hooks (read, write and delete old ones)
  • repo– to see private repos
  • repo:status– to manipulate commit statuses

把生成得到的 access_token 填写到凭据里

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

能够点击连接测验,检查是否装备成功

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 设置 Webhooks 翻开你的 git 项目地址,挑选 SettingsWebhooks,大约 url 如下 github.com/{{your_account}}/{{your_project_name}}/settings/hooks

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

点击 Add webhook 新增

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

Payload URL 填写 http://{{your_jenkins_url}}/github-webhook/,一起挑选触发事件,当 push 后进行触发,生成后,能够点击测验触发,检查 Jenkins 日志,是否有收到触发事件。

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 装备项目触发器

翻开使命,检查构建触发器,勾选 GitHub hook trigger for GITScm polling,保存

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

  1. 验证

修改代码,push 后,到使命主页,点击 Github Hook Log,改写,能够看到 Push 记载

基于 Jenkins 实现 SpringBoot 项目自动化构建与部署

检查 Blue Ocean,能够看到流水线现已在运转中,这样就主动化布置就装备好了。

最终效果

最终,咱们完结了,代码从 push 到构建,到运转的全进程。

真不错,终于不必手动进行布置了

假如本文对你有所协助,就请点个 ,点个重视吧。

附录

Jenkinsfile

pipeline {
    agent any
    environment {
        APP_NAME = 'My-App'
        APP_VERSION = '1.0.0'
        PACKAGE_NAME = 'My-App'
    }
    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: '**/target/*.jar', name: 'jar'
            }
        }
        // 单元测验
        stage('Test') {
            steps {
                sh 'sh ./jenkins/scripts/test.sh'
            }
        }
        // 布置容器
        stage('Deploy') {
            environment {
                IMAGE_NAME = 'my-app'
                IMAGE_VERSION = '1.0.0'
                SERVER_PORT = '7072'
            }
            steps {
                unstash 'jar'
                sh 'sh ./jenkins/scripts/deploy.sh'
            }
            post {
                failure {
                    echo "布置失败"
                }
            }
        }
    }
    //    全局post
    post {
        always {
            echo "Always"
        }
        success {
            echo "Success"
        }
        failure {
            echo "Failure"
        }
    }
}

deploy.sh

# 校验 Jar 包是否存在
if ! test -f "target/${PACKAGE_NAME}.jar"; then
    echo "${PACKAGE_NAME}.jar 不存在"
    exit 43
fi
echo "仿制 Jar 包到 Docker 文件夹"
cp "target/${PACKAGE_NAME}.jar" "docker/${PACKAGE_NAME}.jar"

# 构建镜像
echo "开端构建镜像"
docker build -t "${IMAGE_NAME}:${IMAGE_VERSION}" --build-arg PACKAGE_NAME="${PACKAGE_NAME}" docker

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

# 运转新容器
echo "运转新容器, ContainerName: ${APP_NAME}-${APP_VERSION}"
docker run --restart=always -dp "${SERVER_PORT}:${SERVER_PORT}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}" --server.port="${SERVER_PORT}"

# 判别容器运转状况,未运转则抛出反常
echo "容器运转状况:"
docker ps -f name="${APP_NAME}-${APP_VERSION}"
containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -q)
if [ "${containerId}" = "" ]; then
    echo "容器未运转"
    exit 42
fi