Jenkins多分支流水线+Docker自动化部署SpringB
Jenkins
官方安装地址
安装最新版,支持jenkins多分支流水线(Multibranch Pipeline Job )
目录
- 安装docker-ce
- docker容器安装、运行jenkins
- 登录,配置jenkins
- 编写Jenkinsfile,Dockerfile,start.sh
- 构建jenkins多分支流水线工程
- 安装docker私有镜像仓库Harbor ; 安装方法
- Jenkins Pipeline 发送邮件配置
- 往后补充: 服务发布到Kubernetes集群()
安装docker-ce
- 卸载旧版本
如果是新机器可以忽略这一步,因为centos还没自带docker服务。
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
- 安装依赖包
yum install -y yum-utils device-mapper-persistent-data lvm2
- 添加Docker软件包源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
但是鉴于国内网络问题,建议使用国内阿里的源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 执行安装操作
安装:
yum install -y docker-ce
设置docker服务开机自启动:
systemctl enable docker
安装、运行jenkins
docker run \
-u root \
-itd \
--name=jenkins \
--restart=always \
-e TZ='Asia/Shanghai' \
-p 8080:8080 ‐p 5000:5000 \
-v /data/jenkins/jenkins_home:/var/jenkins_home \
-v /data/jenkins/home:/home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkinsci/blueocean
参数解析:
- -u 指定用户运行容器
- -itd -i, –interactive 交互式 -t, –tty 分配一个伪终端 -d, –detach 运行容器到后台
- --name=jenkins 指定容器名
- --restart=always 设置容器自启动
- -e 指定容器运行所属时区
- -p 8080:8080 容器的端口映射到宿主机上(“:”前数字为宿主机端口,“:”后数字为容器端口)
- -v /data/jenkins/home:/home 将容器的/home目录映射到宿主机上目录中的/data/jenkins/home子目录
- -v /var/run/docker.sock:/var/run/docker.sock Docker守护进程(Docker daemon)默认监听的Unix域套接字(Unix domain socket),容器中的进程可以通过它与Docker守护进程进行通信。简单来说容器使用宿主机docker命令
- jenkinsci/blueocean 镜像名字
登录jenkins
- 访问http://宿主机IP:8080访问Jenkins。如果无法访问请检查系统防火墙、云的安全组设置。
- 查看并填写初始密码
cat /data/jenkins/jenkins_home/secrets/initialAdminPassword
01.png
02.png
03.png
04.png
配置jenkins
容器内安装:jdk1.8 和 maven-3
maven & jdk.png
下载jdk,maven包(.tar),直接解压到/usr/local, 然后配置环境变量,再执行(docker exec jenkins soure /etc/profile)
配置:
系统管理 ==> 全局工具配置
jdk,maven
编写Jenkinsfile,Dockerfile,start.sh
1. 什么是Jenkinsfile
流水线语法
Jenkinsfile 是 Jenkins 2.x 核心特性 Pipeline 的脚本,由Groovy语言实现。Jenkinsfile一般是放在项目根目录,随项目一起受源代码管理软件控制,无需像创建“自由风格"(Jenkins FreeStyle)项目一样,每次可能需要拷贝很多设置到新项目,提供了一些直接的好处:
- Pipeline上的代码审查/迭代
- Pipeline的审计跟踪
- Pipeline的唯一真实来源,可以由项目的多个成员查看和编辑。
Pipeline支持:Declarative(声明式)和Scripted Pipeline(脚本式)两种格式。两者都支持建立Pipeline,两者都可以用于在Web UI中定义一个流水线Jenkinsfile,将Jenkinsfile文件创建并检查到源代码控制库中通常被认为是最佳做法。
Declarative Pipeline (声明式格式一)
当然,还有别的格式样例
pipeline {
agent any
stages {
stage('Build') {
steps {
//
}
}
stage('Test') {
steps {
//
}
}
stage('Prod') {
steps {
//
}
}
}
}
Scripted Pipeline(脚本式基本格式)
node {
stage('Build') {
//
}
stage('Test') {
//
}
stage('Prod') {
//
}
}
2. 编写声明式Jenkinsfile ; 使用文本编辑器(最好支持 Groovy 语法高亮显示)
工作原理:获取源代码 ==> maven打包 ==> 构建并推送镜像到Harbor ==>根据环境部署服务到服务器
pipeline {
agent any
environment {
registryUrl= "192.168.1.110:5000" #搭建docker私有仓库(Harbor)或者 用DockerHub
registry_user= "xxx"
registry_pass= "xxx"
env=test //"test" or "prod" #选择打包及发布环境
}
options {
timestamps() //设置在项目打印日志时带上对应时间
disableConcurrentBuilds() //不允许同时执行流水线,被用来防止同时访问共享资源等
buildDiscarder(logRotator(numToKeepStr: '3')) // 表示保留n次构建历史
}
//gitlab webhook触发器
//聚合项目,代码发生以下动作后,所有子项目将被触发构建,可选择使用
//triggers{
// gitlab( triggerOnPush: true, //代码有push动作就会触发job
// triggerOnMergeRequest: true, //代码有merge动作就会触发job
// branchFilterType: "NameBasedFilter", //只有符合条件的分支才会触发构建 “ALL/NameBasedFilter/RegexBasedFilter”
// includeBranchesSpec: "${JOB_BASE_NAME}") //基于branchFilterType值,输入期望包括的分支的规则
//}
stages{
stage('Print Message') { //打印信息
steps {
echo "workspace: ${WORKSPACE} build_id: ${BUILD_ID} branch(gitlab分支名): ${env.BRANCH_NAME}"
echo "registryUrl: ${registryUrl}"
}
}
//stage('Test') { //测试
// steps {
// echo 'Testing..'
// }
//}
stage('Build project') { //mvn打包
steps {
echo 'mvn打包'
script {
try {
sh 'mvn clean package -pl ${JOB_NAME%%/*} -am -amd -P${env} -Dmaven.test.skip=true' //springboot maven 多模块 jenkins 单独打包子项目
} catch (err) {
echo '打包失败 & End of Pipeline!!!'
}
}
}
}
stage('Build & Push Image') { //构建,推送镜像
steps {
echo '构建,推送镜像'
dir ('./') { //指定工作目录(默认为${WORKSPACE})
script {
try {
sh 'mv ${JOB_NAME%%/*}/Dockerfile ./'
sh 'docker login --username=${registry_user} --password=${registry_pass} ${registryUrl}'
def app = docker.build('${registryUrl}/${JOB_NAME%%/*}:${build_id}')
app.push('${build_id}')
} catch (err) {
echo '打包失败 & End of Pipeline!!!'
}
}
}
echo "delete local image"
sh 'docker rmi ${registryUrl}/${JOB_NAME%%/*}:${build_id}' //删除jenkins本地新建的镜像
}
}
stage('Deploy for test') {
when {
beforeAgent true
branch 'xxx' //填写test环境gitlab分支名(我的分支名:release_eat_away)
}
steps {
timeout(time: 40, unit: 'SECONDS') { // 设置远程部署超过40秒,将终止该步骤
echo '部署到开发环境'
sh 'bash ./start.sh ${JOB_NAME%%/*} ${registryUrl}/${JOB_NAME%%/*}:${build_id} ${env}' //位置变量1:工程名字 ; 位置变量2:镜像名字 ; 位置变量3:指定环境
}
}
stage('Deploy for prod') { //部署到生产环境
when {
beforeAgent true
branch 'xxx' //填写prod环境gitlab分支名(我的分支名:release-3.0-prod)
}
steps {
timeout(time: 40, unit: 'SECONDS') { // 设置远程部署超过40秒,将终止该步骤
echo '部署到生产环境'
sh 'bash ./start.sh ${JOB_NAME%%/*} ${registryUrl}/${JOB_NAME%%/*}:${build_id} ${env}' //位置变量1:工程名字 ; 位置变量2:镜像名字 ; 位置变量3:指定环境
}
}
stage('Delete Workspace') { //清理工作目录
steps {
echo "清理工作目录: ${WORKSPACE}"
deleteDir() //表示删除当前目录(${WORKSPACE})下内容,通常用在构建完毕之后清空工作空间
}
}
}
}
3. 编写Dockerfile
FROM java:latest
MAINTAINER qiujt qiujt123@163.com
WORKDIR /data/logs
ADD canfu-admin/target/canfu-admin-0.0.1-SNAPSHOT.jar canfu-admin.jar
EXPOSE 8091
ENTRYPOINT ["java","-jar","-Xms512m","-Xmx1024m","-XX:PermSize=512M","-XX:MaxPermSize=1024M","/canfu-admin.jar"]
- FROM 指定基础镜像,并且必须是第一条指令。(可以选择更小的镜像openjdk:8-jdk-alpine,不过一些后台项目验证码图片会出不来,慎用)
- MAINTAINER 指定维护者信息 语法:MAINTAINER user_name user_email
- WORKDIR:指定在创建容器后,终端默认登陆进来的工作目录,一个落脚点
- ADD 将宿主机目录下的文件拷贝进镜像且 ADD 命令会自动处理 URL 和解压 tar 压缩包
- EXPOSE 当前容器对外暴露出的端口
- ENTRYPOINT:指定一个容器启动时要运行的命令,ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数
4. 编写部署脚本(start.sh)
#/bin/bash
#ENV
#docker私有仓库(Harbor)
registryUrl=192.168.1.110:5000
registry_user="xxx"
registry_pass="xxx"
project_name=$1
image_name=$2
env=$3
node_user=root
if [ "${env}" == test ];then
#测试环境
node1=192.168.1.105
elif [ "${env}" == prod ];then
#生产环境
node1=192.168.1.106
else
echo '没有${env}环境!!!'
fi
#Prepare
echo "project_name: $1 , image_name: $2 , env:$3"
#Deploying
if [ $1 == canfu-eureka ];then
ssh $node_user@node1 "docker login --username=${registry_user} --password=${registry_pass} ${registryUrl} && docker pull $image_name && docker stop $project_name | xargs docker rm && docker run -itd --name=$project_name --restart=always -e TZ="Asia/Shanghai" -p 7002:7002 $image_name && docker image prune -a -f --filter 'until=1h' "
else
#可以像上面写成一个ssh"",为了解析方便,切分开(接下来为第一行,以此类推)
ssh $node_user@$node1 "docker login --username=${registry_user} --password=${registry_pass} ${registryUrl} “
ssh $node_user@$node1 “docker pull $image_name && docker stop $project_name | xargs docker rm ”
ssh $node_user@$node1 ”docker run -itd --name=$project_name --restart=always --net=host -e TZ="Asia/Shanghai" -P -v /data/logs/$project_name:/data/logs/$project_name $image_name“
ssh $node_user@$node1 “docker image prune -a -f --filter 'until=1h' ”
ssh $node_user@$node1 ”tailf /data/logs/$project_name/${project_name}.log"
fi
-
登录docker私有仓库(Harbor)
-
服务器拉取对应镜像,并且停止、删除对应原有服务容器
-
基于镜像创建容器。--net=bridge缺省设置(除注册中心外,其他为 --net=host )
-
删除1小时前拉取的,并且未被使用的镜像
-
打印服务输出日志
-
补充:由于我们的Pipeline还需要在远程服务器执行任务,需要通过ssh连接,那么我们就需要在Jenkins里面生成ssh的公钥密钥:
$ docker exec -it jenkins /bin/bash
$ ssh-keygen -C "root@jenkins"
在远程节点的~/.ssh/authorized_keys中添加jenkins的公钥(id_rsa.pub)
创建多分支流水线项目
SpringBoot聚合项目与Jenkinsfile ,Dockerfile , start.sh位置关系;及子项目下创建各个环境文件(application-test.yml 和 application-prod.yml)
Springboot聚合项目
指定环境文件
创建jenkins流水线项目
配置流水线项目
点击 “立刻扫描 多分支流水线”,接下来Jenkins会在分支源中扫描每个分支下的Jenkinsfile,如果该分支下有Jenkinsfile,那么就会创建一个
分支Job
(下图扫描到release_eat_away 和 release-3.0-prod两个分支下有Jenkinsfile,所以jenkins创建了两个Job)
(这里需要注意的是,只有需要部署的分支,才加上Jenkinsfile,不然Jenkins会将其余分支也创建一个分支job。例如:下图的release-3.0-beta 和 release_pressuretest)
扫描多分支流水线
到此,多分支流水线项目配置完毕,可以点击触发构建项目了
多分枝流水线.png