可能会出现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.102
http:
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凭证ID
def 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 instance
sonar.projectKey=jenkinscloud-eureka
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=jenkinscloud-eureka
sonar.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=11
sonar.java.target=11
# sonar.java.libraries=**/target/classes/**
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
- jenkinscloud-order微服务的根目录添加sonar-project.properties文件,内容如下:
# must be unique in a given SonarQube instance
sonar.projectKey=jenkinscloud-order
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=jenkinscloud-order
sonar.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=11
sonar.java.target=11
# sonar.java.libraries=**/target/classes/**
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
- jenkinscloud-product微服务的根目录添加sonar-project.properties文件,内容如下:
# must be unique in a given SonarQube instance
sonar.projectKey=jenkinscloud-product
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=jenkinscloud-product
sonar.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=11
sonar.java.target=11
# sonar.java.libraries=**/target/classes/**
# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8
4.3.3 修改Jenkins构建脚本
- Jenkinsfile:
//定义git凭证ID
def 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凭证ID
def 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 openjdk
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
# 注意每个项目公开的端口不一样
EXPOSE 9000
ENTRYPOINT ["java","-jar","/app.jar"]
4.5.4 修改Jenkinsfile
- Jenkinsfile:
//定义git凭证ID
def 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凭证ID
def git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"
//git的url地址
def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"
//定义tag
def 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凭证ID
def git_auth = "bacbbbb1-2df9-470d-adf8-5cb6dc496807"
//git的url地址
def git_url = "git@192.168.209.100:develop_group/jenkinscloud.git"
//定义tag
def tag = "1.0"
// 定义Harbor的URL地址
def harbor_url = "192.168.209.102:85"
// 镜像库项目名称
def harbor_project = "xudaxian-mall"
// Harbor的登录凭证id
def 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}"
//把镜像推送到Harbor
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登录到Harbor
sh "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目录:
- 将公钥发给部署服务器:
- 在配置远程部署服务器的Path to key中配置私钥的路径:
cd /root/.ssh
ssh-copy-id 192.168.18.103
/root/.ssh/id_rsa
4.7.3 在远程部署服务器创建部署的脚本
- 进入/usr/local目录:
cd /usr/local
- 编写deploy.sh脚本:
vim deploy.sh
#! /bin/sh
#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_name=$3
tag=$4
port=$5
imageName=$harbor_url/$harbor_project_name/$project_name:$tag
echo "$imageName"
#查询容器是否存在,存在则删除
containerId=`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'`
if [ "$containerId" != "" ] ; then
#停掉容器
docker stop $containerId
#删除容器
docker rm $containerId
echo "成功删除容器"
fi
#查询镜像是否存在,存在则删除
imageId=`docker images | grep -w $project_name | awk '{print $3}'`
if [ "$imageId" != "" ] ; then
#删除镜像
docker rmi -f $imageId
echo "成功删除镜像"
fi
# 登录Harbor
docker login -u xudaxian -p Xudaxian12345678 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName
echo "容器启动成功"
- 设置权限:
chmod +x deploy.sh
4.7.4 在Jenkins中设置参数
- 将端口作为外部的参数,在Jenkins中配置。
4.7.5 修改Jenkinsfile文件添加拉取镜像和发布应用的部署
- Jenkinfile:
//定义git凭证ID
def git_auth = "7d5c4945-2533-41e2-bd47-5dd97eb37f38"
//git的url地址
def git_url = "git@192.168.18.100:develop_group/jenkinscloud.git"
//定义tag
def tag = "1.0"
// 定义Harbor的URL地址
def harbor_url = "192.168.18.102:85"
// 镜像库项目名称
def harbor_project = "xudaxian-mall"
// Harbor的登录凭证id
def 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}"
//把镜像推送到Harbor
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登录到Harbor
sh "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地址。