可能会出现IP地址不统一的情况,那是因为是在两台电脑上配置的,不影响操作,只需要注意即可。
1 Jenkins+Docker+SpringCloud持续集成说明
1.1 概述

- 大致流程说明:
开发人员每天将代码提交到Gitlab代码仓库。
Jenkins从Gitlab中拉取项目代码,编译并打成jar包,然后构建Docker镜像,将镜像上传到Harbor私有仓库。
Jenkins发送SSH远程命令,让生产部署服务器从Harbor私有仓库拉取镜像到本地,然后创建容器。
最后,用户可以访问到容器。
1.2 服务器列表
| 服务器名称 | IP地址 | 安装的软件 |
|---|---|---|
| 代码托管服务器 | 192.168.18.100 | Gitlab(已安装) |
| 持续集成服务器 | 192.168.18.101 | Jenkins(已安装)、Maven(已安装)、Docker18.06.1-ce |
| Docker仓库服务器 | 192.168.18.102 | Docker18.06.1-ce、Harbor2.1.3 |
| 生产服务器 | 192.168.18.103 | Docker18.06.1-ce |
2 微服务项目说明

- 源码地址。
3 环境准备
3.1 Docker安装
- 略。
3.2 Harbor的安装
3.2.1 前提说明
- Harbor是在IP为192.168.18.102的服务器上安装的,并且使用的Docker Compose进行安装,而Docker Compose依赖于Docker,所以需要先安装Docker。
- 关闭防火墙。
3.2.2 安装Docker Compose
- 安装Docker Compose(网速不够,请点这里docker-compose.zip):
sudo curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- 查看Docker Compose是否安装成功:
docker-compose version
3.2.3 安装Harbor
- 下载Harbor(网络不够,请点这里harbor-offline-installer-v2.1.3.tgz):
wget https://github.com/goharbor/harbor/releases/download/v2.1.3/harbor-offline-installer-v2.1.3.tgz
- 解压Harbor:
tar -zxvf harbor-offline-installer-v2.1.3.tgz -C /usr/local
- 修改Harbor的配置:
cd /usr/local/harbor
mv harbor.yml.tmpl harbor.yml
vim harbor.yml
# 修改部分hostname: 192.168.18.102http:port: 85# 将https注释掉# https:# https port for harbor, default is 443# port: 443# The path of cert and key files for nginx# certificate: /your/certificate/path# private_key: /your/private/key/path
- 安装Harbor:
./prepare
./install.sh
- 启动、停止、重新启动Harbor:
# 启动docker-compose up -d
# 停止docker-compose stop
# 重新启动docker-compose restart
3.2.4 访问Harbor
- 访问地址:http://192.168.1.102:85。
- Harbor的默认账户和密码:admin/Harbor12345。
3.3 在Harbor中创建用户和项目
3.3.1 创建项目
- Harbor的项目分为公开和私有。
- 公开项目:所有的用户都可以访问,通常存放公共的镜像,默认有一个library公开项目。
- 私有项目:只有授权用户才可以访问,通常存在项目本身的镜像。
- 我们可以为微服务创建一个项目:

3.3.2 创建用户


创建用户为:xudaxian/Xudaxian12345678。
3.3.3 给私有项目分配用户
- 进入xudaxian-mall项目—>成员。

| 角色 | 权限说明 |
|---|---|
| 访客 | 对于指定的项目拥有只读权限 |
| 开发人员 | 对于指定的项目拥有读写权限 |
| 维护人员 | 对于指定的项目拥有读写权限,创建webhooks |
| 项目管理员 | 除了读写权限,同时拥有用户管理/镜像扫描等管理权限 |
3.3.4 以新用户登录Harbor

3.4 把镜像上传到Harbor
- 先拉取一个镜像,比如MySQL5.7。
docker pull mysql:5.7
- 查询镜像:
docker images

- 给镜像打tag:
docker tag mysql:5.7 192.168.18.102:85/xudaxian-mall/mysql:5.7

- 推送镜像:
docker push 192.168.18.102:85/xudaxian-mall/mysql:5.7

之所以报错,是因为Docker没有将Harbor加入到信任列表中。
- 在服务器192.168.18.101和192.168.18.102以及192.168.18.103把Harbor地址加入到Docker信任列表中:
vim /etc/docker/daemon.json
{"exec-opts": ["native.cgroupdriver=systemd"],"insecure-registries": ["192.168.18.102:85"],"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]}
- 重启Docker:
systemctl daemon-reload
systemctl restart docker
- 重启Harbor:
cd /usr/local/harbor
docker-compose restart
- 再次执行推送命令,提示权限不足:
docker push 192.168.18.102:85/xudaxian-mall/mysql:5.7

- 登录Harbor:
docker login -u xudaxian -p Xudaxian12345678 192.168.18.102:85

- 推送镜像:
docker push 192.168.18.102:85/xudaxian-mall/mysql:5.7


3.5 从Harbor上拉取镜像
3.5.1 前提说明
- 服务器的IP是192.168.18.103。
3.5.2 把Harbor地址加入到Docker信任列表中
- 把Harbor地址加入到Docker信任列表中
vim /etc/docker/daemon.json
{"exec-opts": ["native.cgroupdriver=systemd"],"insecure-registries": ["192.168.18.102:85"],"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]}
- 重启Docker:
systemctl daemon-reload
systemctl restart docker
3.5.3 Docker命令登录Harbor
- Docker命令登录Harbor:
docker login -u xudaxian -p Xudaxian12345678 192.168.18.102:85
3.5.4 拉取镜像
- 拉取镜像:
docker pull 192.168.18.102:85/xudaxian-mall/mysql:5.7
4 微服务持续集成
4.1 项目代码上传到Gitlab
- 在IDEA操作即可,参考之前的步骤,将微服务的代码上传到Gitlab中。

4.2 从Gitlab拉取项目源码
4.2.1 在Jenkins中新建和微服务同名的流水线项目

4.2.2 创建Jenkinsfile文件
- Jenkinsfile:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}}

4.3 提交到SonarQube代码审查
4.3.1 在Jenkins的项目jenkinscloud,添加一个参数



4.3.2 在每个微服务的根目录中添加sonar-project.properties文件
- jenkinscloud-eureka微服务的根目录添加sonar-project.properties文件,内容如下:
# must be unique in a given SonarQube instancesonar.projectKey=jenkinscloud-eureka# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.sonar.projectName=jenkinscloud-eurekasonar.projectVersion=1.0# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.# This property is optional if sonar.modules is set.sonar.sources=.sonar.exclusions=**/test/**,**/target/**sonar.java.binaries=.sonar.java.source=11sonar.java.target=11# sonar.java.libraries=**/target/classes/**# Encoding of the source code. Default is default system encodingsonar.sourceEncoding=UTF-8
- jenkinscloud-order微服务的根目录添加sonar-project.properties文件,内容如下:
# must be unique in a given SonarQube instancesonar.projectKey=jenkinscloud-order# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.sonar.projectName=jenkinscloud-ordersonar.projectVersion=1.0# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.# This property is optional if sonar.modules is set.sonar.sources=.sonar.exclusions=**/test/**,**/target/**sonar.java.binaries=.sonar.java.source=11sonar.java.target=11# sonar.java.libraries=**/target/classes/**# Encoding of the source code. Default is default system encodingsonar.sourceEncoding=UTF-8
- jenkinscloud-product微服务的根目录添加sonar-project.properties文件,内容如下:
# must be unique in a given SonarQube instancesonar.projectKey=jenkinscloud-product# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.sonar.projectName=jenkinscloud-productsonar.projectVersion=1.0# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.# This property is optional if sonar.modules is set.sonar.sources=.sonar.exclusions=**/test/**,**/target/**sonar.java.binaries=.sonar.java.source=11sonar.java.target=11# sonar.java.libraries=**/target/classes/**# Encoding of the source code. Default is default system encodingsonar.sourceEncoding=UTF-8

4.3.3 修改Jenkins构建脚本
- Jenkinsfile:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }}
如果有jenkinscloud-common公共子工程,那么需要将spring-boot-maven-plugin插件从总工程移动到各个微服务工程,否则jenkinscloud-common公共子工程继承了总工程之后,spring-boot-maven-plugin插件会报错,因为找不到启动类。
4.4 编译打包微服务工程
- Jenkinsfile:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }stage('编译,打包微服务工程') {sh "mvn -f ${project_name} clean install"}}

需要将每个微服务工程都编译打包。
4.5 使用Docker编译、生成镜像
4.5.1 前提说明
- 利用dockerfile-maven-plugin插件来构建Docker镜像。
- 因为我用的是JDK11,而JDK9以上就移除了javax.activation.activation的jar包,所以需要在每个微服务工程都加上依赖:
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.3.0</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.3.0</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency>
4.5.2 在每个微服务的pom.xml中加入dockerfile-maven-plugin插件
- pom.xml
<build><plugins><plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.4.13</version><configuration><repository>${project.artifactId}</repository><tag>${project.version}</tag><buildArgs><JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE></buildArgs></configuration></plugin></plugins></build>
4.5.3 在每个微服务根目录下新建Dockerfile文件
- Dockerfile:
FROM openjdkARG JAR_FILECOPY ${JAR_FILE} app.jar# 注意每个项目公开的端口不一样EXPOSE 9000ENTRYPOINT ["java","-jar","/app.jar"]
4.5.4 修改Jenkinsfile
- Jenkinsfile:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }stage('编译,打包微服务工程') {// 修改部分// dockerfile:build 可以触发插件的执行sh "mvn -f ${project_name} clean install dockerfile:build"}}
4.6 上传镜像到Harbor镜像仓库
4.6.1 给镜像打tag
- Jenkinsfile:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"//定义tagdef tag = "1.0"// 定义Harbor的URL地址def harbor_url = "192.168.209.102:85"// 镜像库项目名称def harbor_project = "xudaxian-mall"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }stage('编译,打包微服务工程') {// dockerfile:build 可以触发插件的执行sh "mvn -f ${project_name} clean install dockerfile:build "}stage('上传镜像') {//定义镜像的名称def imageName = "${project_name}:${tag}"//给镜像打上标签sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"}}
4.6.2 在Jenkins中配置登录Harbor的凭证信息



767a60c2-c423-4737-a455-228a7e38ead7
4.6.3 通过片段生成器生成流水线脚本

- 并将生成的流水线脚本复制的Jenkinsfile中:
//定义git凭证IDdef git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"//git的url地址def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"//定义tagdef tag = "1.0"// 定义Harbor的URL地址def harbor_url = "192.168.209.102:85"// 镜像库项目名称def harbor_project = "xudaxian-mall"// Harbor的登录凭证iddef harbor_auth = "767a60c2-c423-4737-a455-228a7e38ead7"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }stage('编译,打包微服务工程') {// dockerfile:build 可以触发插件的执行sh "mvn -f ${project_name} clean install dockerfile:build "}stage('上传镜像') {//定义镜像的名称def imageName = "${project_name}:${tag}"//给镜像打上标签sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"//把镜像推送到HarborwithCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {// 登录到Harborsh "docker login -u ${username} -p ${password} ${harbor_url}"//镜像的上传sh "docker push ${harbor_url}/${harbor_project}/${imageName}"sh "echo '镜像上传到Harbor仓库中成功'"}}}
4.6.4 将镜像发布到Harbor中


4.7 拉取镜像和发布应用
4.7.1 前提说明
- 192.168.18.103已经安装了Docker,并且Docker已经设置了信任Harbor私有仓库。
- 192.168.18.103的防火墙已经关闭。
- 安装Jenkins的那台服务器(192.168.18.101)已经安装了 Publish Over SSH插件,以便给生产部署的服务器执行SSH远程调用。

4.7.2 配置远程部署服务器
- 在192.168.18.101中配置远程部署服务器(192.168.18.103):

因为我将所有服务器的防火墙关闭,但是在实际生产环境下,需要通过ssh公钥和私钥来匹配使用,前面已经生成过公钥和私钥,将公钥发给部署服务器。操作步骤如下:
- 进入/root/.ssh目录:
cd /root/.ssh
- 将公钥发给部署服务器:
ssh-copy-id 192.168.18.103
- 在配置远程部署服务器的Path to key中配置私钥的路径:
/root/.ssh/id_rsa
4.7.3 在远程部署服务器创建部署的脚本
- 进入/usr/local目录:
cd /usr/local
- 编写deploy.sh脚本:
vim deploy.sh
#! /bin/sh#接收外部参数harbor_url=$1harbor_project_name=$2project_name=$3tag=$4port=$5imageName=$harbor_url/$harbor_project_name/$project_name:$tagecho "$imageName"#查询容器是否存在,存在则删除containerId=`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'`if [ "$containerId" != "" ] ; then#停掉容器docker stop $containerId#删除容器docker rm $containerIdecho "成功删除容器"fi#查询镜像是否存在,存在则删除imageId=`docker images | grep -w $project_name | awk '{print $3}'`if [ "$imageId" != "" ] ; then#删除镜像docker rmi -f $imageIdecho "成功删除镜像"fi# 登录Harbordocker login -u xudaxian -p Xudaxian12345678 $harbor_url# 下载镜像docker pull $imageName# 启动容器docker run -di -p $port:$port $imageNameecho "容器启动成功"
- 设置权限:
chmod +x deploy.sh
4.7.4 在Jenkins中设置参数
- 将端口作为外部的参数,在Jenkins中配置。

4.7.5 修改Jenkinsfile文件添加拉取镜像和发布应用的部署
- Jenkinfile:
//定义git凭证IDdef git_auth = "7d5c4945-2533-41e2-bd47-5dd97eb37f38"//git的url地址def git_url = "git@192.168.18.100:develop_group/jenkinscloud.git"//定义tagdef tag = "1.0"// 定义Harbor的URL地址def harbor_url = "192.168.18.102:85"// 镜像库项目名称def harbor_project = "xudaxian-mall"// Harbor的登录凭证iddef harbor_auth = "b6cf3cb5-8a33-457d-93da-65c46f0135b2"// 定义远程执行命令def execCommand = "/usr/local/deploy.sh $harbor_url $harbor_project $project_name $tag $port"node {stage('拉取代码') {checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])}stage('代码审查') {//定义当前Jenkins的SonarQubeScanner工具的环境def scannerHome = tool 'sonarqube-scanner'//引用当前Jenkins的SonarQube环境withSonarQubeEnv('sonarqube-8.6.0') {sh """cd ${project_name}${scannerHome}/bin/sonar-scanner"""}}//如果有公共子工程// stage('编译,安装公共的子工程') {// sh "mvn -f jenkinscloud-common clean install"// }stage('编译,打包微服务工程') {// dockerfile:build 可以触发插件的执行sh "mvn -f ${project_name} clean install dockerfile:build "}stage('上传镜像') {//定义镜像的名称def imageName = "${project_name}:${tag}"//给镜像打上标签sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"//把镜像推送到HarborwithCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {// 登录到Harborsh "docker login -u ${username} -p ${password} ${harbor_url}"//镜像的上传sh "docker push ${harbor_url}/${harbor_project}/${imageName}"sh "echo '镜像上传到Harbor仓库中成功'"}}//删除本地镜像sh "docker rmi -f ${imageName}"sh "docker rmi -f ${harbor_url}/${arbor_project}/${imageName}"stage('拉取镜像和发布应用') {// 远程部署调用进行项目部署sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.18.103', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommand}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])}}
4.8 需要更改各个微服务的application.yml文件的IP地址
- 略。
4.9 重新启动微服务持续集成流程
- 为什么?因为先前容器中的IP还是localhost,而不是远程部署服务器的IP地址。
