5.1 Jenkins+Docker+SpringCloud部署方案优化
上面部署方案存在的问题:
-
一次只能选择一个微服务部署
-
-
每个微服务只有一个实例,容错率低
优化方案:
-
在一个Jenkins工程中可以选择多个微服务同时发布
-
在一个Jenkins工程中可以选择多台生产服务器同时部署
-
每个微服务都是以集群高可用形式部署
5.2 Jenkins+Docker+SpringCloud集群部署流程说明
5.3 修改所有微服务配置
注册中心配置
# 集群版上面部署方案存在的问题:
一次只能选择一个微服务部署
每个微服务只有一个实例,容错率低
优化方案:
在一个Jenkins工程中可以选择多个微服务同时发布
在一个Jenkins工程中可以选择多台生产服务器同时部署
每个微服务都是以集群高可用形式部署
注册中心配置
# 集群版spring:
application:
name: EUREKA-HA
---
server:
port: 10086
spring:
# 指定profile=eureka-server1
profiles: eureka-server1
eureka:
instance:
# 指定当profile=eureka-server1时,主机名是eureka-server1
hostname: 192.168.5.6
client:
service-url:
# 将自己注册到eureka-server1、eureka-server2这个Eureka上面去
defaultZone: http://192.168.5.6:10086/eureka,http://192.168.5.7:10086/eureka
---
server:
port: 10086
spring:
profiles: eureka-server2
eureka:
instance:
hostname: 192.168.5.7
client:
service-url:
defaultZone: http://192.168.5.6:10086/eureka,http://192.168.5.7:10086/eureka
在启动微服务的时候,加入参数: spring.profiles.active 来读取对应的配置
修改其他微服务配置
除了Eureka注册中心以外,其他微服务配置都需要加入所有Eureka服务
# Eureka配置eureka:
client:
service-url:
defaultZone: http://192.168.5.6:10086/eureka,http://192.168.5.7:10086/eureka # 追加Eureka访问地址
提交代码
1)安装Extended Choice Parameter插件
Extended Choice Parameter
2)创建流水线项目
3)添加参数 字符串参数:分支名称
多选框:项目名称
tensquare_eureka_server@10086,tensquare_zuul@10020,tensquare_admin_service@9001,tensquare_gathering@9002
最后效果:
//gitlab的凭证
def git_auth = "14ae86e8-c3b4-4d7d-afe1-8c23d9fed317"
//gitlab的地址
def git_url = "[email protected]:root/tensquare_bak.git"
// 构建版本的名称
def tag = "latest"
//Harbor私服地址
def harbor_url = "192.168.5.5:8080"
//Harbor的项目名称
def harbor_project_name = "tensquare"
//Harbor的凭证
def harbor_auth = "cd0b948d-e82b-4c0c-8a7c-8c6b8fb5454b"
node {
// 获取当前选择的项目名称
def selectedProjects = "${project_name}".split(',')
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
}
stage('代码审查') {
for(int i=0;i<selectedProjects.size();i++){
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
//项目名称
def currentProjectName = currentProject.split('@')[0]
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
//定义当前Jenkins的SonarQubeScanner工具
def scannerHome = tool 'sonarqube-scanner'
//引用当前Jenkins SonarQube环境
withSonarQubeEnv('sonarqube') {
sh """
cd ${currentProjectName}
${scannerHome}/bin/sonar-scanner
"""
}
}
}
stage('编译,安装公共工程') {
//编译,安装公共工程
sh "mvn -f tensquare_common clean install"
}
stage('编译,打包微服务工程,上传镜像') {
for(int i=0;i<selectedProjects.size();i++){
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
//项目名称
def currentProjectName = currentProject.split('@')[0]
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
//编译,构建本地镜像
sh "mvn -f ${currentProjectName} clean package dockerfile:build"
//定义镜像名称
def imageName = "${currentProjectName}:${tag}"
//给镜像打标签
sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${imageName}"
//登录Harbor,并上传镜像
withCredentials([usernamePassword(credentialsId: "${harbor_auth}",passwordVariable: 'password', usernameVariable: 'username')]) {
//登录
sh "docker login -u ${username} -p ${password} ${harbor_url}"
//上传镜像
sh "docker push ${harbor_url}/${harbor_project_name}/${imageName}"
}
//删除本地镜像
sh "docker rmi -f ${imageName}"
sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${imageName}"
}
}
}
2)编译部署 for循环遍历分割详解
node {
// 获取当前选择的项目名称
def selectedProjects = "${project_name}".split(',')
stage('代码审查') {
for(int i=0;i<selectedProjects.size();i++){
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
//项目名称
def currentProjectName = currentProject.split('@')[0]
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
//定义当前Jenkins的SonarQubeScanner工具
def scannerHome = tool 'sonarqube-scanner'
//引用当前Jenkins SonarQube环境
withSonarQubeEnv('sonarqube') {
sh """
cd ${currentProjectName}
${scannerHome}/bin/sonar-scanner
"""
}
}
}
首先在node中定义获取当前项目的变量 def selectedProjects = "${project_name}".split(',')
selectedProjects
:这个是变量名称可以随意定义,
${project_name}
:这里指的Extended Choice Parameter参数化构建中定义的项目名称
.split(',')
:这是一个分割语法,意思为以","来进行分割,因为之前在Extended Choice Parameter参数化变量中是这样定义的,同理,如果这里更换为其他的分割服务符号,那么在.split(',')
更换为对应的分割符号
tensquare_eureka_server@10086,tensquare_zuul@10020,tensquare_admin_service@9001,tensquare_gathering@9002
这时就可以获取到对应的微服务,比如这个样子
selectedProjects=tensquare_eureka_server@10086
selectedProjects=tensquare_zuul@10020
selectedProjects=tensquare_admin_service@9001
selectedProjects=tensquare_gathering@9002
目前已经成功获取到对应的微服务,但是还是无法直接使用,因为服务名称和端口号为同一个字符串并且使用"@"符号进行连接,那么这里可以使用之前的方法,对"@"符号进行分割再次分割,得到最后的服务名称和端口
for(int i=0;i<selectedProjects.size();i++){
基础的for循环取出语句
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
这里可以理解为,逐步取出selectedProjects变量中每个项目的名称及端口号
//项目名称
def currentProjectName = currentProject.split('@')[0]
对currentProject变量以@符号进行分割,并获取第0个字符,可以理解为获取@符号左边的字符,可以得出服务名称
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
对currentProject变量以@符号进行分割,并获取第1个字符,可以理解为获取@符号右边的字符,可以得出服务名称
经过两次分割(第一个以","分割从Extended Choice Parameter参数化构建中获取服务,第二次以"@"分割分割获取到的服务名称和端口)获得到
项目名称变量(currentProjectName)
项目端口号变量(currentProjectPort)
在进行代码质量扫描时更换相对于的变量,并且在编译,打包微服务工程,上传镜像时使用的for循环分割与此处逻辑一致
3)编译镜像测试
1)环境配置
安装docker
拷贝公钥到远程服务器
系统配置->添加远程服务器
2)添加参数
多选框:部署服务器
master_server,slave_server_01
最终效果:
3)修改Jenkinsfile构建脚本
//gitlab的凭证
def git_auth = "14ae86e8-c3b4-4d7d-afe1-8c23d9fed317"
//gitlab的地址
def git_url = "[email protected]:root/tensquare_bak.git"
// 构建版本的名称
def tag = "latest"
//Harbor私服地址
def harbor_url = "192.168.5.5:8080"
//Harbor的项目名称
def harbor_project_name = "tensquare"
//Harbor的凭证
def harbor_auth = "cd0b948d-e82b-4c0c-8a7c-8c6b8fb5454b"
node {
// 获取当前选择的项目名称
def selectedProjects = "${project_name}".split(',')
// 获取当前选择的服务器名称
def selectedServers = "${publish_server}".split(',')
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
}
stage('代码审查') {
for(int i=0;i<selectedProjects.size();i++){
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
//项目名称
def currentProjectName = currentProject.split('@')[0]
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
//定义当前Jenkins的SonarQubeScanner工具
def scannerHome = tool 'sonarqube-scanner'
//引用当前Jenkins SonarQube环境
withSonarQubeEnv('sonarqube') {
sh """
cd ${currentProjectName}
${scannerHome}/bin/sonar-scanner
"""
}
}
}
stage('编译,安装公共工程') {
//编译,安装公共工程
sh "mvn -f tensquare_common clean install"
}
stage('编译,打包微服务工程,上传镜像') {
for(int i=0;i<selectedProjects.size();i++){
//取出每个项目的名称和端口
def currentProject = selectedProjects[i];
//项目名称
def currentProjectName = currentProject.split('@')[0]
//项目启动端口
def currentProjectPort = currentProject.split('@')[1]
//编译,构建本地镜像
sh "mvn -f ${currentProjectName} clean package dockerfile:build"
//定义镜像名称
def imageName = "${currentProjectName}:${tag}"
//给镜像打标签
sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${imageName}"
//登录Harbor,并上传镜像
withCredentials([usernamePassword(credentialsId: "${harbor_auth}",passwordVariable: 'password', usernameVariable: 'username')]) {
//登录
sh "docker login -u ${username} -p ${password} ${harbor_url}"
//上传镜像
sh "docker push ${harbor_url}/${harbor_project_name}/${imageName}"
}
//删除本地镜像
sh "docker rmi -f ${imageName}"
sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${imageName}"
//远程部署服务器应用
for(int j=0;j<selectedServers.size();j++){
//每个服务名称
def currentServer = selectedServers[j]
//添加微服务运行时的参数:spring.profiles.active
def activeProfile = "--spring.profiles.active="
if(currentServer=="master_server"){
activeProfile = activeProfile+"eureka-server1"
}else if(currentServer=="slave_server_01"){
activeProfile = activeProfile+"eureka-server2"
}
// 远程触发部署脚本
sshPublisher(publishers: [sshPublisherDesc(configName: "${currentServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deployCluster.sh $harbor_url $harbor_project_name $currentProjectName $tag $currentProjectPort $activeProfile", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
echo "${currentProjectName}完成编译,构建镜像!"
}
}
}
4)远程部署 for循环遍历分割详解
for(int j=0;j<selectedServers.size();j++){
//每个服务名称
def currentServer = selectedServers[j]
//添加微服务运行时的参数:spring.profiles.active
def activeProfile = "--spring.profiles.active="
if(currentServer=="master_server"){
activeProfile = activeProfile+"eureka-server1"
}else if(currentServer=="slave_server_01"){
activeProfile = activeProfile+"eureka-server2"
}
}
这里远程部署到对应的服务器和之前编译,打包微服务工程,上传镜像时使用的for循环分割逻辑基本一致,
首先在node中定义获取当前项目的变量 def selectedServers = "${publish_server}".split(',')
selectedServers
:这个是变量名称可以随意定义,
${publish_server}
:这里指的Extended Choice Parameter参数化构建中定义的服务器名称
.split(',')
:这是一个分割语法,意思为以","来进行分割,因为之前在Extended Choice Parameter参数化变量中是这样定义的,同理,如果这里更换为其他的分割服务符号,那么在.split(',')
更换为对应的分割符号
master_server,slave_server_01
这时就可以获取到对应的微服务,比如这个样子
selectedServers = master_server
selectedServers = slave_server_01
目前已经成功获取到对应服务器名称,但是还是无法直接使用,因为要对应之前在eureka网关中定义的服务器地址来进行远程部署
for(int j=0;j<selectedServers.size();j++){
基础的for循环取出语句,这里不能再次使用i,因为在之前已经使用过
//每个服务名称
def currentServer = selectedServers[j]
这里可以理解为,逐步取出selectedServers变量中服务器名称
//添加微服务运行时的参数:spring.profiles.active
def activeProfile = "--spring.profiles.active="
因为要读取服务中的配置文件,所以这里使用变量(activeProfile)定义了参数(--spring.profiles.active)
//if 判断语句
if(currentServer == "master_server"){
activeProfile = activeProfile+"eureka-server1"
}else if(currentServer == "slave_server_01"){
activeProfile = activeProfile+"eureka-server2"
}
这里可以理解为
当currentServer等于master_server时,就读取配置文件中的eureka-server1
当currentServer等于slave_server_01时,就读取配置文件中的eureka-server2
5)编写deployCluster.sh部署脚本
#! /bin/sh
#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_name=$3
tag=$4
port=$5
profile=$6
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 admin -p Harbor12345 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName $profile
echo "容器启动成功"
docker login -u admin -p Harbor12345 $harbor_url
docker pull $imageName
docker run -di -p p o r t : port: port:port $imageName $profile
echo “容器启动成功”
2)修改Dockerfile
FROM nginx
COPY ./dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
3)修改代码连接后端微服务服务
4)项目测试
由于是测试阶段,如果前端容器没有开通85端口,那么一定会造成Network Error
网关错误,正常环境中建议部署一台专门的转发服务器,来达成轮询访问的效果
造成这种原因是因为转发请求并没有到达容器内的Nginx 所有并不能完成转发,本次测试也是在启动容器前,修改了容器启动脚本,开通了85端口,所以可以正常访问
Jenkins的Master-Slave分布式构建,就是通过将构建过程分配到从属Slave节点上,从而减轻Master节点的压力,而且可以同时构建多个,有点类似负载均衡的概念。
1)开启代理程序的TCP端口
Manage Jenkins -> Configure Global Security
2)新建节点
Manage Jenkins—Manage Nodes—新建节点
3)节点部署
下载jar包到slave节点服务器(slave节点服务器必须有java环境)
java -jar agent.jar -jnlpUrl http://192.168.5.3:808/computer/slave_01/jenkins-agent.jnlp -secret 2d2371ae0ed0e91ad74c67aabecb5af24d001ff7f5fb440cb5bc663b57a52898 -workDir "/root/jenkins"
4)完成部署
5)拉取测试