意图
建立这个项意图意图在于,自己的项目若想每次发布到线上,每次都需求经过一下这样一段流程化操作。
打包项目->上传到服务器->构建镜像->运转容器。
因此希望经过 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
大约解释一下各个参数的意思
-
-dp
映射宿主机 8080 端口到 Jenkins 8080 端口上,用于拜访 Jenkins 主页,一起将 Jenkins 运转在后台 -
-v jenkins-data:/var/jenkins_home
映射卷到/var/jenkins_home
途径,保存了 Jenkins 的基本信息,此项确保重新运转容器后,数据不会被清空,主张映射 -
-v /var/run/docker.sock:/var/run/docker.sock
映射docker.sock
文件,这样履行 docker 指令时,呼应的是宿主机,详细能够检查此文章,此操作是完结本次布置的关键 -
-v "$HOME":/home
映射宿主机home
目录到 Jenkinshome
上 -
--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 创立项目
- 翻开 Jenkins 页面,新建使命
- 使命名写项目名,勾选流水线
- 挑选拉取代码的方法,运转时从 Git 代码库拉取代码
留意:需求添加凭据,以便于拉取代码,设置 ssh
方法或许 username & password
方法都能够
- 指定构建分支,现已默许的
Jenkinsfile
称号
新建项意图初始作业就完结了,接下来开端编写 Jenkinsfile,界说流水线逻辑
编写 Jenkinsfile
运用 Jenkins 的关键在于 Jenkinsfile 的编写,需求了解一点 shell
完结 Jenkinsfile 主动补全
IDEA 好像不支撑 Jenkinsfile 的语法提醒,需求手动装备一下,装备完写起来便利一点
- 获取 gdsl 界说文件,首要拜访
http://{{your_ip}}:8080/job/{{your_project_name}}/pipeline-syntax/gdsl
获取 gdsl 文件,仿制下来,到项意图 src.main.java 目录下,新建 pipeline.gdsl 文件,张贴仿制的内容。
- 设置 IDEA ,令其识别 Jenkinsfile 文件支撑 Groovy
- 在项目根目录,新建 Jenkinsfile 文件,测验发现现已能够主动补全
留意:从 Jenkins 获取的 gdsl
文件可能存在一些未主动补全的字段
能够从 git 地址获取完好的 pileline.gdsl
文件
编写 Jenkinsfile
本次完结的流水线,首要进行 Jar 包的构建,并将其打包成 Docker 镜像,然后将镜像运转在宿主机的 Docker 容器上
完好的 Jenkinsfile 能够在附录检查
编写构建阶段 Build Stage
- 首要界说一个环境变量
PACKAGE_NAME
,用于 Maven 进行打包时运用
environment {
PACKAGE_NAME = 'My-App'
}
- 界说
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 库房,以便于复用依靠。
- 指定运转时步骤
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
获取。
- 编写
build.sh
脚本
# 构建 Jar 包,越过测验
mvn -B -DskipTests clean package
build.sh
进行的作业很简略,只是对项目进行打包。
编写测验阶段 Test Stage
- 编写
Test Stage
// 单元测验
stage('Test') {
steps {
sh 'sh ./jenkins/scripts/test.sh'
}
}
跟 Build Stage
相同,将指令抽离到脚本履行
- 编写
test.sh
脚本
# test
echo "Test"
在这儿能够对代码做一些测验,Jenkins 也支撑对测验的成果进行展示,详细能够检查官方文档
编写布置阶段 Deploy Stage
- 界说新的环境变量
environment {
IMAGE_NAME = 'my-app'
IMAGE_VERSION = '1.0.0'
SERVER_PORT = '7072'
APP_NAME = 'My-App'
APP_VERSION = '1.0.0'
}
用于指定镜像名,镜像版别,服务运转的端口,运用称号,运用版别
- 界说
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}"
}
}
- 界说 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
现已运转时参数,因此只能运用脚本,详细能够检查此发问。 - 履行脚本
- 编写
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 主页,点击刚刚创立的项目
点击翻开 BlueOcean
点击 Run,开端运转流水线
能够发现运转成功,一起能够检查每个 steps
打印的内容
绿了绿了芜湖
拜访 {{your_ip}}:7072/hello
,回来 Hello World
,能够发现现已成功布置到服务器上
设置 Git Webhooks 完结主动化布置
上面现已完结了手动布置,提交完代码后,点击一下就能够主动构建,布置。下面测验装备主动化布置,这儿运用到了 Git 的 Webhooks。
- 装备 Jenkins 在 Jenkis 的装备中,装备 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 填写到凭据里
能够点击连接测验,检查是否装备成功
- 设置 Webhooks
翻开你的 git 项目地址,挑选
Settings
–Webhooks
,大约 url 如下github.com/{{your_account}}/{{your_project_name}}/settings/hooks
点击 Add webhook
新增
Payload URL 填写 http://{{your_jenkins_url}}/github-webhook/
,一起挑选触发事件,当 push
后进行触发,生成后,能够点击测验触发,检查 Jenkins 日志,是否有收到触发事件。
- 装备项目触发器
翻开使命,检查构建触发器,勾选 GitHub hook trigger for GITScm polling
,保存
- 验证
修改代码,push
后,到使命主页,点击 Github Hook Log
,改写,能够看到 Push 记载
检查 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