5 Jenkins+Docker+SpringCloud微服务持续集成(下)

5 Jenkins+Docker+SpringCloud微服务持续集成(下)

5.1 Jenkins+Docker+SpringCloud部署方案优化

上面部署方案存在的问题:

  1. 一次只能选择一个微服务部署

  2. 只有一台生产者部署服务器

  3. 每个微服务只有一个实例,容错率低

优化方案:

  1. 在一个Jenkins工程中可以选择多个微服务同时发布

  2. 在一个Jenkins工程中可以选择多台生产服务器同时部署

  3. 每个微服务都是以集群高可用形式部署

5.2 Jenkins+Docker+SpringCloud集群部署流程说明

image-20210605143000381

5.3 修改所有微服务配置

注册中心配置

# 集群版
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 来读取对应的配置

image-20210605180522055

修改其他微服务配置

除了Eureka注册中心以外,其他微服务配置都需要加入所有Eureka服务

# Eureka配置
eureka:
client:
  service-url:
    defaultZone: http://192.168.5.6:10086/eureka,http://192.168.5.7:10086/eureka # 追加Eureka访问地址

image-20210605180635666

提交代码

5.4 设计Jenkins集群项目的构建参数

1)安装Extended Choice Parameter插件

Extended Choice Parameter

image-20210605181140166

2)创建流水线项目

image-20210605181150741

image-20210605182200245

3)添加参数 字符串参数:分支名称

image-20210605182420557

多选框:项目名称

image-20210605181220450

image-20210605181229583

tensquare_eureka_server@10086,tensquare_zuul@10020,tensquare_admin_service@9001,tensquare_gathering@9002

image-20210605181234406

image-20210605181238650

最后效果:

image-20210605182817185

image-20210605181249712

5.5 完成微服务构建镜像,上传私服

//gitlab的凭证
def git_auth = "14ae86e8-c3b4-4d7d-afe1-8c23d9fed317"
//gitlab的地址
def git_url = "git@192.168.5.4: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)编译镜像测试

image-20210605190150622

5.6 完成微服务多服务器远程发布

1)环境配置

  • 安装docker

  • 拷贝公钥到远程服务器

  • 系统配置->添加远程服务器

image-20210605191406954

2)添加参数

多选框:部署服务器

image-20210605191926521

image-20210605191837132

master_server,slave_server_01

image-20210605191910240

image-20210605191851064

最终效果:

image-20210605191805774

image-20210605192003551

3)修改Jenkinsfile构建脚本

//gitlab的凭证
def git_auth = "14ae86e8-c3b4-4d7d-afe1-8c23d9fed317"
//gitlab的地址
def git_url = "git@192.168.5.4: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网关中定义的服务器地址来进行远程部署

image-20210606104820110

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 "容器启动成功"

6)项目测试

image-20210605213049518

image-20210605213149389

image-20210605213058133

image-20210605213130939

5.7 Nginx+Zuul集群实现高可用网关

1)添加nginx.conf文件

user  nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;


events {
worker_connections 1024;
}


http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;
upstream zuulServer{
server 192.168.5.6:10020 weight=1;
server 192.168.5.7:10020 weight=1;
}
server {
listen 80;
listen [::]:80;
server_name localhost;

#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
server {
listen 85;
listen [::]:85;
server_name _;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
location / {

### 指定服务器负载均衡服务器
proxy_pass http://zuulServer/;
}
#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;

location = /50x.html {
root /usr/share/nginx/html;
}
}
}

2)修改Dockerfile

FROM nginx
COPY ./dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf
EXPOSE 80

3)修改代码连接后端微服务服务

image-20210605222218909

image-20210605222204061

4)项目测试

由于是测试阶段,如果前端容器没有开通85端口,那么一定会造成Network Error网关错误,正常环境中建议部署一台专门的转发服务器,来达成轮询访问的效果

image-20210605223146532

造成这种原因是因为转发请求并没有到达容器内的Nginx 所有并不能完成转发,本次测试也是在启动容器前,修改了容器启动脚本,开通了85端口,所以可以正常访问

image-20210605223450294

image-20210605223508101

image-20210605223539822

image-20210605223522570

5.8 Jenkins的Master-Slave分布式构建

5.8.1 什么是Master-Slave分布式构建

image-20210522095627670

Jenkins的Master-Slave分布式构建,就是通过将构建过程分配到从属Slave节点上,从而减轻Master节点的压力,而且可以同时构建多个,有点类似负载均衡的概念。

5.8.2 如何实现Master-Slave分布式构建

1)开启代理程序的TCP端口

Manage Jenkins -> Configure Global Security

image-20210606110515666

image-20210606110005688

2)新建节点

Manage Jenkins—Manage Nodes—新建节点

image-20210606110532674

image-20210606110558846

image-20210606110621806

image-20210606110801614

image-20210606110824274

3)节点部署

image-20210606111018742

下载jar包到slave节点服务器(slave节点服务器必须有java环境)

image-20210606111308326

java -jar agent.jar -jnlpUrl http://192.168.5.3:808/computer/slave_01/jenkins-agent.jnlp -secret 2d2371ae0ed0e91ad74c67aabecb5af24d001ff7f5fb440cb5bc663b57a52898 -workDir "/root/jenkins"

4)完成部署

image-20210606111342589

5)拉取测试

image-20210606111724278

image-20210606111750125

 

posted @ 2021-06-07 09:18  孤独的小人物  阅读(333)  评论(0)    收藏  举报