jenkins+docker自动发版java后端完整流程

发布于:2025-02-23 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、前言

公司用的jenkins+docker自动发版,本人自己也试着配置了下,踩了无数的坑后,终于配置成功了。在此做下笔记,总结下遇到的坑和解决办法。

二、jenkins配置

1.首先,升级jenkins一定要小心,不要随便升级。

本来想给jenkins下载个权限管理插件Matrix-based security(基于矩阵的安全性),结果提示jenkins版本太低,用不了;
问chatgpt,怎么升级jenkins,回答说换个新版jenkins的war包就行;

然后就出了各种问题,换了个比较新的war包后、流水线格式都乱了,根本没法用;
回退成原来的war包,发现流水线内容丢失,出现了无法识别的数据,吓个半死,之前的流水线出了问题直接凉凉;

最后干脆换了个最新版的jenkins的war包,好歹流水线能正常显示了,但是权限管理插件还是用不了。

白忙活一场,证明升级jenkins,只换war包还是有问题的;可能需要装个完整版才行,然后想办法把旧的流水线迁移到新的jenkins。

2.创建流水线与配置

在这里插入图片描述

首先创建一个自由格式的;

在这里插入图片描述

这个链接类型,选的是Gitee,代码在Gitee上;

在这里插入图片描述

Url是gitee的代码地址;Credentials用的是ssh链接方式;代码分支选的是拉取master分支。

3.ssh链接方式配置方法

在这里插入图片描述
打开自己的gitee项目,下载时选ssh,这个链接就是填写到jenkins的下载链接(直接git@gitee.com)开头就行,不用修改;

在这里插入图片描述
下面这个命令,是告诉你怎么创建ssh公钥和私钥;
把这个命令复制到cmd窗口执行,一路回车,就会生成一个公钥和私钥;
在这里插入图片描述
其中,第一个回车是让你选择一个文件生成位置,不选就按默认路径;
第二个回车是让你输入加密空间,就当是读取私钥时用的密码就行,如果为空就是没有密码直接就能读取;
第三个回车是确认私钥密码;

然后就在提示的位置生成了公钥id_rsa.pub,私钥id_rsa

下一步,把公钥配置到自己的gitee项目上;
在这里插入图片描述

打开自己的一个项目,点击右上的设置(Settings),左下是Key management,再点右边的添加key;

在这里插入图片描述
标题随便输入,下面的内容就是刚才创建的id_rsa.pub里面的内容,整个复制填进去就行,不用改;

添加完成后,回到jenkins,把刚才创建的私钥id_rsa配置进去;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本人项目里有其它的私钥了,就随便选一个,点全局域按钮;
在这里插入图片描述

点击添加;

在这里插入图片描述
类型选择ssh类型,作用域是global,ID随便写,Username随便写;私钥选直接输入;然后点击右边的Add按钮,把之前创建的私钥id_rsa整个复制进去就行,注意不要删掉第一行和最后一行注释;

下面的Passphrase就是刚才创建私钥时输入的密码,解密私钥用的,如果为空就不用写。(感觉像密码钥匙,没有密码就用不了钥匙一样)

创建好之后,jenkins流水线就能用这个ssh来拉取代码了。

4.jenkins报错无法拉取代码解决方法

这里有一个坑,jenkins流水线,填写刚才的gitee代码地址、选择ssh验证用私钥后,可能会报错;

因为jenkins默认对于ssh的设置是known hosts file模式,也就是知道代码服务器主机的意思;

如果没有配置过,那么jenkins就会认为,你这个代码地址的主机不安全,不是我已知的主机,不能拉取代码;

解决方法有两个,第一个,就是修改jenkins设置:
在这里插入图片描述
在这里插入图片描述
把这个改成Accept first connection,但是这样改不太好;

第二个方法就是,让jenkins知道这台代码主机。

可以登录jenkins所在的服务器,找到配置文件目录,例如/var/lib/jenkins/.ssh/known_hosts,这个文件里就是jenkins已知主机的配置;

然后执行:

ssh-keyscan -H gitee.com >> ~./known_hosts

这样,就能把gitee.com添加到jenkins已知主机列表里,就不会报错了。

5.配置jenkins代码钩子

代码钩子的作用是,当gitee上的代码被push后,gitee就会自动给你的jenkins服务器发送一个请求,表示自己的代码被更新了;

然后jenkins就会自动执行流水线操作,自动发版。

如果不需要就跳过,需要的话就如下配置:

在这里插入图片描述
在这里插入图片描述
这个trigger里,选择了监听push events,opened pull requests events,然后jenkins给了一个钩子地址,这个稍后配置到gitee上;

jenkins还给了一个钩子密码,secret token for gitee webhook,这个稍后也配置到gitee上。

在这里插入图片描述
在这里插入图片描述
然后回到gitee仓库,选webhook,添加,这里配置了push事件,url和password就写jenkins提供的那两个,添加,就完成了;
后续这个代码仓库、触发push时,就会调用jenkins接口,header参数里传密码,通知jenkins。

6.配置docker打包命令

继续jenkins配置:

在这里插入图片描述
增加一步,执行shell脚本;

参考内容如下:

pwd

export JAVA_HOME=/var/lib/jenkins/workspace/test/jdk8/jdk1.8.0_212

/usr/bin/mvn clean package -f ./pom.xml

echo 'start'

service_name="homepage-backend-1.0"

#search image id
IID=$(docker images | grep "$service_name" | awk '{print $3}')
echo "IID $IID"
if [ -n "$IID" ]
then
    echo "exist $service_name image,IID=$IID"
    #remvoe image
    docker image rm $service_name
    echo "delete $service_name image"
    #build image
    docker build -t $service_name .
    echo "build $service_name image"
else
    echo "no exist $service_name image,build docker"
    #build image
    docker build -t $service_name .
    echo "build $service_name image"
fi



# save image
docker save $service_name > ./$service_name.tar

说明:

jenkins执行这个流水线后,第一步就是从gitee上拉取代码,上面配置过了;

第二步就是执行这个sh脚本;

首先,创建流水线的名字叫test,所以jenkins就会在本地/var/lib/jenkins/workspace/里,创建一个文件夹test,也就是/var/lib/jenkins/workspace/test/,然后把代码拉取到这个地方,这个sh脚本执行时的当前路径,也是这个路径;

然后执行export JAVA_HOME,是临时创建一个环境变量,只在这个脚本内生效,选择了1.8版本的jdk,打包会用;

然后执行/usr/bin/mvn clean package -f ./pom.xml,这个命令就是选择让/usr/bin/mvn这个文件,执行命令clean package,也就是服务器上安装了maven,maven执行打包命令,根据当前路径的./pom.xml来执行;代码已经拉取到这个路径下了,pom.xml文件是存在的;

打包完毕后,开始执行docker命令;
声明一个变量service_name,值是homepage-backend-1.0;
先用docker images查看下这个镜像是否存在,如果存在,就docker image rm删除这个镜像,docker build -t $service_name .创建一个新镜像;如果不存在,就直接创建一个新镜像;

注意最后的.是有作用的,表示用当前路径的Dockerfile来创建镜像;

创建完成后,就用docker save $service_name > ./$service_name.tar把这个镜像保存到当前路径,名字是homepage-backend-1.0.tar

7.DockerFile内容

第六步中,主要就是先用maven把项目打成jar包,然后用docker把jar包打成tar包。
需要服务器上安装了docker,并且当前路径有Dockerfile文件;

本人项目的文件就是/var/lib/jenkins/workspace/test/Dockerfile,内容如下:

# 使用java8 (1.8)
FROM openjdk:8-jdk-alpine

#创建一个挂载点
VOLUME /tmp

# 需要暴露的端口,这个不用
#EXPOSE 9090

#安装字体
RUN apk add --update font-adobe-100dpi ttf-dejavu fontconfig

#复制到容器内部
COPY ./myproject/target/my.jar my.jar

# 容器启动时执行的方法
ENTRYPOINT ["java","-Duser.timezone=GMT+09","-jar","/my.jar", "&"]

说明:
首先给容器内选了jdk8;
然后创建了一个挂载点/tmp,这个是把docker容器内的/tmp路径映射到宿主机的/tmp目录下,这样相当于容器内/tmp里的东西、也会写到宿主机/tmp;相当于做了数据持久化,当容器关闭的时候这些数据也会留下来;

然后把当前路径下的./myproject/target/my.jar复制到docker容器根目录下;这个jar包,是mvn打包完成后生成在target目录里的;

然后容器启动时,自动执行java -Duser.timezone=GMT+09 -jar /my.jar &命令。

8.jenkins把文件推送到目标服务器

现在的效果是、在当前路径下生成了tar包;需要把这个包推送到目标服务器,然后在目标服务器上启动这个docker镜像。

在这里插入图片描述

jenkins增加一步,Send files or execute commands over SSH
选择目标服务器;
填写source files,就是当前路径下打包好的homepage-backend-1.0.tar
填写远程服务器路径,到时候传过去就是/home/web/webcode/homepage-backend-1.0.tar
填写需要执行的命令,先执行了cd命令,然后执行sh文件。

9.jenkins配置目标服务器方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

jenkins设置,找到SSH Server,增加;
name随便,hostname,例如128.128.128.1,username,例如root,远程服务器起始地址,/,远程服务器key,也用的ssh-key,私钥整个复制进去就行。

远程服务器配置ssh登录密钥的方法:
也是同样,先生成ssh公钥私钥;
然后找到服务器,某个用户的目录,然后找到authorized_keys文件,例如:/home/user1/.ssh/authorized_keys;(root用户在/root/.ssh)
把公钥里的内容整个复制到这个文件里面即可。

私钥就配置到jenkins里,登录用。

10.目标服务器启动docker命令

目标服务器,有一个/home/web/webcode/start_homepage_backend_docker.sh文件,内容如下:

#!/bin/bash

service_name="homepage-backend-1.0"
image_port=9000
service_port=9000
image_pre=dev

image_log=/H_LOG
server_log=/home/web/webcode/logs

#查看容器id
CID=$(docker ps -a | grep "$service_name" | awk '{print $1}')
echo "CID $CID"
if [ -n "$CID" ]
then
    echo "exist $service_name container,CID=$CID"
    #停止
    docker stop $service_name
    #删除容器
    docker rm $service_name
else
    echo "no exist $service_name container"
fi
#查看镜像id
IID=$(docker images | grep "$service_name" | awk '{print $3}')
echo "IID $IID"
if [ -n "$IID" ]
then
    echo "exist $service_name image,IID=$IID"
    #删除镜像
    docker image rm $service_name
    echo "delete $service_name image"
    #构建
    docker load < ./$service_name.tar
    echo "load $service_name image"
else
    echo "no exist $service_name image,build docker"
    docker load < ./$service_name.tar
    echo "load $service_name image"
fi
#启动


echo "if use --network host, then -p is useless."

docker run -d --name $service_name --network host -e e_logback_path="$image_log" -e e_spring_active="$image_pre" -p $service_port:$image_port -v $server_log:$image_log $service_name
#查看启动日志
#docker logs -f  $service_name

说明:
声明了好几个变量,供后面用;

首先用docker ps -a查看当前所有启动的容器,如果有目标容器,就先docker stop 停止容器,docker rm删除容器;

然后用docker images查看当前所有镜像,如果有目标镜像,就先docker image rm删除镜像,然后用tar文件docker load < ./$service_name.tar创建一个镜像,否则就直接创建;这个文件就是jenkins传来的homepage-backend-1.0.tar

镜像有了后,就用docker run根据镜像启动容器,其中-d --name是启动后的容器名称,--network host表示这个容器的网络是主机模式,与宿主机共用ip端口;-e是传入两个系统环境变量;-p本来是把容器端口映射到宿主机端口用的,但是主机模式下这个写了没用;-v就是之前的VOLUME,把容器的路径映射到宿主机的路径;最后的一个参数$service_name表示容器启动依据的镜像名称。

11.docker相关笔记

docker可以分两部分,镜像与容器;
镜像用docker images查看;镜像可以方便的在不同服务器迁移(打好的tar包);
容器用docker ps -a查看;容器需要根据镜像启动;

个人感觉docker就相当于一个虚拟机了,一个linux上可以有好多个docker容器,每个容器/虚拟机里面都有自己的linux系统,有自己独立的ip:端口;

容器里有自己的文件路径,可以用-v映射到宿主机的路径,可以用来打印日志(查看日志的时候,在宿主机路径看就行了,不用进入容器内部了;当容器销毁,也不用担心日志消失);

容器有自己独立的ip:端口;可以用-p把容器端口映射到宿主机上;例如-p $service_port:$image_port-p 80:8080,假如容器内启动了一个jar包,端口是8080;现在使用宿主机ip:80就相当于访问容器内jar包的8080端口;

由于本人项目中,mysql也在宿主机上,之前可以用localhost:3306访问;但是如果jar包在容器内部,就不能用localhost:3306访问了(这样访问的是容器自己的3306端口);虽然可以用docker给宿主机虚拟出来的本地ip来访问(局域网ip),但是类似的本地应用还有很多;干脆换成--network host模式更简单,直接与宿主机共享ip端口,这样就可以继续用localhost访问了。

12.把docker运行时环境变量传递给jar包的方法

上面使用了-e e_spring_active="$image_pre",设置了两个docker容器环境变量,相当于-e e_spring_active="dev"
在java项目的application.yml里,是这样配置的:

spring:
  profiles:
    active: ${e_spring_active:dev}

这个意思是,如果有系统环境变量e_spring_active,那就用;如果没有,那就用dev
这样就能读取application-dev.yml或者application-prod.yml,根据参数不同使用不同配置文件。

13.目标服务器执行sh失败的坑

由于jenkins链接目标服务器的用户权限不足,又不想把管理员用户配置到jenkins里,就出现了两个权限问题;

一个是sh文件执行权限,先换管理员用户,使用chmod 777 start_homepage_backend_docker.sh给这个sh文件设置了所有用户都可以执行的权限;

一个是docker权限,需要把用户加入docker组(docker规定,要不是管理员,要不就是docker组用户,才能用),才能执行,否则也会报错。

#把user1添加到docker组
groups user1
sudo usermod -aG docker user1

还有一个坑,是因为命令有docker load < ./$service_name.tar,是把当前目录的$service_name.tar读取成镜像,所以一定要注意,执行命令时的位置在哪里,一定要先cd 到目标路径,才能找到这个tar文件,否则也会报错。(搜错误信息,很难想到是找不到tar的错误,坑)