1 集群版持续集成流程

Jenkins+Docker+SpringCloud(集群)_docker

1.1 单机版问题

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

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

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

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

1.2 优化方案

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

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

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

2 部署方案优化

2.1 注册中心配置

在启动微服务的时候,加入参数: spring.profiles.active 来读取对应的配置

# 集群版
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.137.129
  client:
    service-url:
     # 将自己注册到eureka-server1、eureka-server2这个Eureka上面去
      defaultZone: http://192.168.137.129:10086/eureka/,http://192.168.137.128:10086/eureka/
---
server:
  port: 10086
spring:
  profiles: eureka-server2
eureka:
  instance:
    hostname: 192.168.137.128
  client:
    service-url:
      defaultZone: http://192.168.137.129:10086/eureka/,http://192.168.137.128:10086/eureka/

2.2 其他微服务配置

除了Eureka注册中心以外,其他微服务配置都需要加入所有Eureka服务 
#Eureka配置
eureka:
  client:
    service-url:
      defaultZone: http://192.168.137.128:10086/eureka,http://192.168.137.129:10086/eureka
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
    prefer-ip-address: true

2.3 安装Extended Choice Parameter插件

支持多选框

2.4 创建流水线项目

tensquare_backend_cluster

2.5 添加参数

字符串参数:分支名称

Jenkins+Docker+SpringCloud(集群)_nginx_02

多选框:项目名称

Jenkins+Docker+SpringCloud(集群)_docker_03

Jenkins+Docker+SpringCloud(集群)_微服务_04

Jenkins+Docker+SpringCloud(集群)_docker_05

Jenkins+Docker+SpringCloud(集群)_docker_06

最后效果

Jenkins+Docker+SpringCloud(集群)_nginx_07

2.6 微服务构建镜像,上传私服

Jenkins+Docker+SpringCloud(集群)_nginx_08

Jenkins+Docker+SpringCloud(集群)_docker_09

Jenkins+Docker+SpringCloud(集群)_微服务_10

此时镜像启动是报错的,因为注册中心配置做了修改

2.7 微服务多服务器远程发布

2.7.1 配置远程部署服务器

拷贝公钥到远程服务器

ssh-copy-id 192.168.137.128

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

Jenkins+Docker+SpringCloud(集群)_微服务_11

2.7.2 配置信任Harbor私服地址

"insecure-registries": ["192.168.137.130"]

2.7.3 添加参数

多选框:部署服务器

Jenkins+Docker+SpringCloud(集群)_docker_12

Jenkins+Docker+SpringCloud(集群)_docker_13

Jenkins+Docker+SpringCloud(集群)_nginx_14

Jenkins+Docker+SpringCloud(集群)_docker_15

最终效果

Jenkins+Docker+SpringCloud(集群)_nginx_16

2.7.4 修改Jenkinsfile

Jenkins+Docker+SpringCloud(集群)_微服务_17

Jenkins+Docker+SpringCloud(集群)_docker_18

2.7.5 deployCluster.sh部署脚本

1、上传deploy.sh文件到/opt/jenkins_shell目录下,且文件至少有执行权限!

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

3、授予执行权限
chmod a+x deployCluster.sh

2.7.6 构建效果

Jenkins+Docker+SpringCloud(集群)_微服务_19

2.8 注册中心

Jenkins+Docker+SpringCloud(集群)_微服务_20

Jenkins+Docker+SpringCloud(集群)_微服务_21

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

Jenkins+Docker+SpringCloud(集群)_docker_22

2.9.1 nginx新增配置文件

cat /data/nginxConf/nginxZuul.conf
log_format  zuul  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"'
                  '"$upstream_addr" "$upstream_bytes_sent" ';
access_log  /var/log/nginx/access.log  zuul;
upstream zuulServer{
    hash $remote_addr consistent;
    server 192.168.137.128:10020 weight=1;
    server 192.168.137.129:10020 weight=1;
}
server {
    listen 9091 default_server;
    listen [::]:9091 default_server;
    server_name _;
    root /usr/share/nginx/html;
    # Load configuration files for the default server block.
    location / {
        ### 指定服务器负载均衡服务器
        proxy_pass http://zuulServer/;
    }
}

2.9.2 重启nginx容器

更新nginx容器

docker rm -f tensquare_web_nginx

docker run -itd -p 9090:80 -p 9091:9091 -v /data/nginxConf/nginxZuul.conf:/etc/nginx/conf.d/nginxZuul.conf -v /data/nginx:/usr/share/nginx/html --name tensquare_web_nginx hub.atomgit.com/amd64/nginx:1.25.2-perl

2.9.3 修改前端Nginx的访问地址

Jenkins+Docker+SpringCloud(集群)_nginx_23

2.9.4 重新构建前端

docker logs -f tensquare_web_nginx

Jenkins+Docker+SpringCloud(集群)_微服务_24

3 附录补充

3.1 后端Jenkinsfile

//gitlab凭据
def git_auth='8c8a0774-7d4c-48a9-8f52-dd1a04f71320'
//sonar token凭据
def sonar_token='869f9770-0467-496a-84f9-d7b2c729fd8f'
//构建版本的名称
def tag = "latest"
//Harbor私服地址
def harbor_url = '192.168.137.130'
//Harbor项目名称
def harbor_project_name = 'tensquare'
//Harbor凭据
def harbor_auth = 'e70d6e6b-5c46-4dd4-b96a-fb59f6d2443c'
node {
    //把选择的项目信息转换为数组
    def selectedProjects = "${project_name}".split(',')
    //把选择的服务信息转换为数组
    def selectedServers = "${publish_server}".split(',')

    stage('拉取代码') { 
        checkout scmGit(branches: [[name: "*/${branch}"]], 
        extensions: [cleanBeforeCheckout()], 
        userRemoteConfigs: [[credentialsId: "${git_auth}", 
                           url: 'http://192.168.137.129/root/tensquare_backend.git']])
    }
    stage('代码审查') {
        def scannerHome = tool 'sonar-scanner'
        withSonarQubeEnv(credentialsId: "${sonar_token}") {
            for (int i=0;i<selectedProjects.size();i++){
                //取出每个项目的名称和端口
                def currentProject = selectedProjects[i]
                //项目名称
                def currentProjectName = currentProject.split('@')[0]
                //项目端口
                def currentProjectPort = currentProject.split('@')[1]
                sh """
                    cd ${currentProjectName}
                    sed -i "s#project_name#${currentProjectName}#g" sonar-project.properties
                    ${scannerHome}/bin/sonar-scanner
                """
                echo "${currentProjectName} 完成代码审查"
            }
        }
    }
    stage('整体构建') {
        def mvnHome = tool 'M2'
        //编译、整体构建,每个stage都会重新回到${WORKSPACE}
        sh " ${mvnHome}/bin/mvn clean package "
    }
    stage('构建镜像、推送harbor') {
        for (int i=0;i<selectedProjects.size();i++){
            //取出每个项目的名称和端口
            def currentProject = selectedProjects[i]
            //项目名称
            def currentProjectName = currentProject.split('@')[0]
            //项目端口
            def currentProjectPort = currentProject.split('@')[1]
            def imageName = "${harbor_url}/${harbor_project_name}/${currentProjectName}:${tag}"
            //生成镜像
            sh " cd ${currentProjectName} && docker build -t ${imageName} --build-arg JAR_FILE=target/${currentProjectName}-1.0-SNAPSHOT.jar . "
            //登录harbor并上传镜像
            withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
              //登录
              sh " docker login -u ${username} -p ${password} ${harbor_url} "
              //上传镜像
              sh " docker push ${imageName} "
            }
            //删除本地镜像
            sh " docker rmi ${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'){
                    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} 完成编译,构建镜像"
        }
    }
}