Jenkins(七) Jenkins+Docker+SpringCloud微服务持续集成(上)
大致流程说明:
1. 开发人员每天把代码提交到 Gitlab 代码仓库
2.Jenkins 从 Gitlab 中拉取项目源码,编译并打成 jar 包,然后构建成 Docker 镜像,将镜像上传到 Harbor 私有仓库。
3.Jenkins 发送 SSH 远程命令,让生产部署服务器到 Harbor 私有仓库拉取镜像到本地,然后创建容器。
4. 最后,用户可以访问到容器
这里不讲述 Docker 的安装及基础命令的使用,学 dockers 可以看 Docker 概念、命令、DockerFile 等看这篇就够了 (opens new window) 这篇文章
# Harbor 镜像仓库安装及使用
Harbor(港口,港湾)是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器。除了 Harbor 这个私有镜像仓库之外,还有 Docker 官方提供的 Registry。相对 Registry,Harbor 具有很多优势:
- 提供分层传输机制,优化网络传输 Docker 镜像是是分层的,而如果每次传输都使用全量文件 (所以
用 FTP 的方式并不适合),显然不经济。必须提供识别分层传输的机制,以层的 UUID 为标识,确定
传输的对象。 - 提供 WEB 界面,优化用户体验 只用镜像的名字来进行上传下载显然很不方便,需要有一个用户界
面可以支持登陆、搜索功能,包括区分公有、私有镜像。 - 支持水平扩展集群 当有用户对镜像的上传下载操作集中在某服务器,需要对相应的访问压力作分
解。 - 良好的安全机制 企业中的开发团队有很多不同的职位,对于不同的职位人员,分配不同的权限,
具有更好的安全性。
# Harbor 安装
- 除了 docker 以外,还要安装 dockers-compose,Docker Compose 命令及使用 (opens new window) 你可以看这篇博客
- 下载 Harbor 的压缩包,地址 (opens new window),需要科式上网,下载完成开始解压
tar -xzf harbor-offline-installer-v1.9.2.tgz
mv harbor /opt/software/
# 修改配置文件内容
vim /opt/software/harbor/harbor.yml
# 修改hostname
hostname: 你自己机器的IP
# 修改端口
port: 85
2
3
4
5
6
7
8
- 安装 Harbor
cd /opt/software/harbor
./prepare
./install.sh
2
3
- 启动 Harbor
docker-compose up -d 启动
docker-compose stop 停止
docker-compose restart 重新启动
2
3
- 访问 Harbor http://IP:85,默认账户密码:admin/Harbor12345
# Harbor 使用
1. 创建项目
Harbor 的项目分为公开和私有的:
- 公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个 library 公开项目。
- 私有项目:只有授权用户才可以访问,通常存放项目本身的镜像。
我们可以为微服务项目创建一个新的项目:
2. 用户创建
3. 为用户分配项目
# 推送到镜像仓库
# 打标签,命令 docker tag nginx:1.17.1 Harbor的IP:端口/Harbor项目的名称/镜像名字
docker tag nginx:1.17.1 192.168.81.102:85/test/nginx:1.17.1
# 查看
docker images
# 会多一条镜像数据
192.168.81.102:85/test/nginx 1.17.1 98ebf73aba75 2 years ago 109MB
2
3
4
5
6
把 Harbor 地址加入到 Docker 信任列表
# 编辑docker文件
vim /etc/docker/daemon.json
# 加入这句话
"insecure-registries":["192.168.81.102:85"],
# 重启配置和docker
systemctl restart docker
# Harbor 的 test 项目是私有的,需要登录
docker login -u admin -p Harbor12345 192.168.81.102:85
# 推送到Harbor仓库里
docker push 192.168.81.102:85/test/nginx
2
3
4
5
6
7
8
9
10
其他服务下载镜像也需要把地址添加到 docker 的配置文件中,并且也需要使用 docker login 进行登录。复制以下的命令,到其他服务器下执行即可。
# 微服务构建到 docker 镜像
1. 把微服务提交到 SVN
2. 在 jenkins 上创建一个 Pipelin 类型的 item(项目)
3. 配置 Pipelin item 的构建时参数
这里讲一点,项目中很多的 jar 是有依赖关系的,如果单独打某个服务可能会包找不到依赖,所以建议第一次对根目录进行打包,会把依赖全部加载到 maven 库中,后期单个服务打包即可。
4. 在每个服务的 pom 文件中添加 plugin
<!-- 帮助读取项目中的dockerfile文件,帮我们构建docker镜像 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<repository>${project.artifactId}</repository>
<!-- 定义dockerfile 文件的参数 -->
<buildArgs>
<!-- 定义一个 JAVA_FILE 参数,指为我们项目的名称 -->
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
2
3
4
5
6
7
8
9
10
11
12
13
14
5. 在每个服务的根目录下编写 Dockerfile 文件
FROM openjdk:8-jdk-alpine
# 这个会读取在pom中声明的变量
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
# 对外端口
EXPOSE 10086
ENTRYPOINT ["java","-jar","/app.jar"]
2
3
4
5
6
7
6. 设置构建后镜像推至镜像仓库
7. 在项目的根目录中编写 Jenkinsfile
node {
// 版本
def tag = "1.0"
// 镜像仓库的地址
def harbor_url = "192.168.81.102:85"
// 镜像仓库的项目,这里建议项目名称和jenkins的item项目名称、以及harbor的项目名称保持一致,否则用一下脚本会出问题
def harbor_project = "demo"
// 拉取代码
stage('pull code') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: '80dfe5c5-1684-47b1-a410-6f53ceb3c543', url: 'http://192.168.81.15:3000/biguncle/test.git']]])
}
// 编译并推送镜像仓库
stage('build project') {
if ("${project_name}" == 'demo' ) {
echo '打包根目录'
sh 'mvn clean package dockerfile:build'
} else {
echo "打包子目录 ${project_name}"
sh "mvn -f ${project_name} clean package dockerfile:build"
}
echo "把jar上传镜像仓库"
def oldImageName = "${project_name}:latest"
def newImageName = "${harbor_url}/${harbor_project}/${project_name}:${tag}"
// 改名称 做规范
sh "docker tag ${oldImageName} ${newImageName}"
// 删除之前的 镜像
sh "docker rmi ${oldImageName}"
// 推送到 dockers仓库
withCredentials([usernamePassword(credentialsId: '8a3d7ab1-4cd6-482c-86c9-a12aa6404d98', passwordVariable: 'harbor_password', usernameVariable: 'harbor_account')]) {
// 登录
sh "docker login -u ${harbor_account} -p ${harbor_password} ${harbor_url}"
// 上传
sh "docker push ${newImageName}"
echo "镜像推送成功"
}
}
// 发送邮件
stage('send email') {
emailext body: '''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sansserif">
<tr>
<td>(本邮件是程序自动下发的,请勿回复!)</td>
</tr>
<tr>
<td><h2>
<font color="#0000FF">构建结果 - ${BUILD_STATUS}</font>
</h2></td>
</tr>
<tr>
<td><br/>
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建日志:
<a href="${BUILD_URL}console">${BUILD_URL}console</a>
</li>
<li>构建 Url :
<a href="${BUILD_URL}">${BUILD_URL}</a>
</li>
<li>工作目录 :
<a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a>
</li>
<li>项目 Url :
<a href="${PROJECT_URL}">${PROJECT_URL}</a>
</li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">Changes Since Last Successful Build:</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br/>%c<br/>",showPaths=true,changesFormat="<pre>[%a]<br/>%m</pre>",pathFormat=" %p"}
</td>
</tr>
<tr>
<td><b>Failed Test Results</b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica,sans-serif">
$FAILED_TESTS
</pre>
<br/>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">构建日志 (最后 100行):</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">
${BUILD_LOG,maxLines=100}
</textarea>
</td>
</tr>
</table>
</body>
</html>''', mimeType: 'text/html', subject: '43243214321', to: '875730567@qq.com'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# jenkins 自动化服务拉取镜像并启动
这部操作需要安装一个 Publish Over SSH 插件,安装好后在 Manager Jenkins -> Configure System 进行配置
更改 Jenkinsfile 脚本,添加远程机器拉取镜像并启动容器,具体语法怎么使用,可以到 流水线语法中查看
node {
// 版本
def tag = "1.0"
// 镜像仓库的地址
def harbor_url = "192.168.81.102:85"
// 镜像仓库的项目,这里建议项目名称和jenkins的item项目名称、以及harbor的项目名称保持一致,否则用一下脚本会出问题
def harbor_project = "demo"
// 拉取代码
stage('pull code') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: '80dfe5c5-1684-47b1-a410-6f53ceb3c543', url: 'http://192.168.81.15:3000/biguncle/test.git']]])
}
// 编译并推送镜像仓库
stage('build project') {
if ("${project_name}" == 'demo' ) {
echo '打包根目录'
sh 'mvn clean package dockerfile:build'
} else {
echo "打包子目录 ${project_name}"
sh "mvn -f ${project_name} clean package dockerfile:build"
}
echo "把jar上传镜像仓库"
def oldImageName = "${project_name}:latest"
def newImageName = "${harbor_url}/${harbor_project}/${project_name}:${tag}"
// 改名称 做规范
sh "docker tag ${oldImageName} ${newImageName}"
// 删除之前的 镜像
sh "docker rmi ${oldImageName}"
// 推送到 dockers仓库
withCredentials([usernamePassword(credentialsId: '8a3d7ab1-4cd6-482c-86c9-a12aa6404d98', passwordVariable: 'harbor_password', usernameVariable: 'harbor_account')]) {
// 登录
sh "docker login -u ${harbor_account} -p ${harbor_password} ${harbor_url}"
// 上传
sh "docker push ${newImageName}"
echo "镜像推送成功"
}
// 远程调用脚本,port 最好也添加 jenkins项目配置里的参数配置,作为参数传进来
echo "执行远程命令 /home/server/deploy.sh ${harbor_url} ${harbor_project} ${project_name} ${tag} ${port}"
sshPublisher(publishers: [sshPublisherDesc(configName: 'test_103', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/home/server/deploy.sh ${harbor_url} ${harbor_project} ${project_name} ${tag} ${port}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
// 发送邮件
stage('send email') {
emailext body: '''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sansserif">
<tr>
<td>(本邮件是程序自动下发的,请勿回复!)</td>
</tr>
<tr>
<td><h2>
<font color="#0000FF">构建结果 - ${BUILD_STATUS}</font>
</h2></td>
</tr>
<tr>
<td><br/>
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建日志:
<a href="${BUILD_URL}console">${BUILD_URL}console</a>
</li>
<li>构建 Url :
<a href="${BUILD_URL}">${BUILD_URL}</a>
</li>
<li>工作目录 :
<a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a>
</li>
<li>项目 Url :
<a href="${PROJECT_URL}">${PROJECT_URL}</a>
</li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">Changes Since Last Successful Build:</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<ul>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br/>%c<br/>",showPaths=true,changesFormat="<pre>[%a]<br/>%m</pre>",pathFormat=" %p"}
</td>
</tr>
<tr>
<td><b>Failed Test Results</b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica,sans-serif">
$FAILED_TESTS
</pre>
<br/>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">构建日志 (最后 100行):</font></b>
<hr size="2" width="100%" align="center"/>
</td>
</tr>
<tr>
<td>
<textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">
${BUILD_LOG,maxLines=100}
</textarea>
</td>
</tr>
</table>
</body>
</html>''', mimeType: 'text/html', subject: '43243214321', to: '875730567@qq.com'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
重新构建,其他服务器就会自动拉取了。
其他服务器一定要有 jenkins 服务器的公钥,构建过程 SSH 不会输出任何信息只会告诉你 EXEC 执行了多久,需要自己去测一下。
# vue 前端使用 Jenkins 部署
1. 首先需要安装 NodeJS 插件
2. 到 Manager Jenkins->Global Tool Configuration->NodeJS
3. 创建一个流水线的前端项目,根据脚本把配置补全
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: '80dfe5c5-1684-47b1-a410-6f53ceb3c543', url: 'http://192.168.81.15:3000/biguncle/test_vue.git']]])
}
stage('打包,部署网站') {
//使用NodeJS的npm进行打包,这个和 以上的 name 保持一致
nodejs('nodejs12'){
sh '''
npm install
npm run build
'''
}
//=====以下为远程调用进行项目部署========
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server',transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '',execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes:false, patternSeparator: '[, ]+', remoteDirectory: '/usr/share/nginx/html',remoteDirectorySDF: false, removePrefix: 'dist', sourceFiles: 'dist/**')],usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
从脚本中可以看出,我们需要一个 branch 参数,还要配置 前端项目的 远程 server 地址
这里没有使用 execCommand 的命令,而是通过 sourceFiles、removePrefix、remoteDirectory,sourceFiles 代表我们 copy 哪个文件,remoteDirectory 远程目录,特就是 copy 到 nginx 所在目录
57-62