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

  • 前面部署方案存在的问题:
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图1一次只能选择一个微服务部署。
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图2只有一条生产部署服务器。
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图3每个微服务只有一个实例,容错率低。
  • 优化方案:
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图4在一个Jenkins工程中可以选择多个微服务同时发布。
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图5在一个Jenkins工程中可以选择多台生产服务器同时部署。
    • Jenkins Docker SpringCloud微服务持续集成(下) - 图6每个微服务都是以集群高可用的形式部署。

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

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

3 修改所有微服务配置

3.1 修改注册中心的配置

  • application.yaml
  1. # 集群版
  2. spring:
  3. application:
  4. name: Eureka-HA
  5. ---
  6. server:
  7. port: 10086
  8. spring:
  9. # 指定当profile=eureka-server1
  10. profiles: eureka-server1
  11. eureka:
  12. instance:
  13. # 指定当profile=eureka-server1,主机名是eureka-server1
  14. hostname: 192.168.18.103
  15. client:
  16. service-url:
  17. defaultZone: http://192.168.18.103:10086/eureka,http://192.168.18.104:10086/eureka
  18. ---
  19. server:
  20. port: 10086
  21. spring:
  22. profiles: eureka-server2
  23. eureka:
  24. instance:
  25. hostname: 192.168.18.104
  26. client:
  27. service-url:
  28. defaultZone: http://192.168.18.103:10086/eureka,http://192.168.18.104:10086/eureka
  • 在启动微服务的时候,加入参数spring.profile.active来读取对应的配置。

3.2 其他微服务配置

  • 除了Eureka注册中心以外,其他微服务的配置都需要加入所有Eureka的服务。
  • 商品微服务的配置:
  1. server:
  2. port: 9001 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. # 配置 eureka
  7. eureka:
  8. client:
  9. service-url: # Eureka Server的地址
  10. defaultZone: http://192.168.18.103:10086/eureka,http://192.168.18.104:10086/eureka
  11. instance:
  12. instance-id: service-product
  13. prefer-ip-address: true
  • 订单微服务的配置:
  1. server:
  2. port: 9002 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. # 配置 eureka
  7. eureka:
  8. client:
  9. service-url: # Eureka Server的地址
  10. defaultZone: http://192.168.18.103:10086/eureka,http://192.168.18.104:10086/eureka
  11. instance:
  12. instance-id: service-order
  13. prefer-ip-address: true

3.3 提交代码到Gitlab中

  • 略。

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

4.1 Jenkins安装Extended Choice Parameter插件

  • 略。

4.2 创建流水线项目

设计Jenkins集群项目的构建参数之创建流水线项目.png

4.3 添加参数

4.3.1 项目名称

设计Jenkins集群项目的构建参数之添加参数.png

设计Jenkins集群项目的构建参数之添加参数2.png

设计Jenkins集群项目的构建参数之添加参数3.png

设计Jenkins集群项目的构建参数之添加参数4.png

设计Jenkins集群项目的构建参数之添加参数5.png

4.3.2 最后效果

设计Jenkins集群项目的构建参数之添加参数6.png

5 把多个项目提交SonarQube进行代码审核

  • 修改Jenkinsfile文件,内容如下:
  1. //定义git凭证ID
  2. def git_auth = "7d5c4945-2533-41e2-bd47-5dd97eb37f38"
  3. //git的url地址
  4. def git_url = "git@192.168.18.100:develop_group/jenkinscloud.git"
  5. //定义tag
  6. def tag = "1.0"
  7. // 定义Harbor的URL地址
  8. def harbor_url = "192.168.18.102:85"
  9. // 镜像库项目名称
  10. def harbor_project = "xudaxian-mall"
  11. // Harbor的登录凭证id
  12. def harbor_auth = "b6cf3cb5-8a33-457d-93da-65c46f0135b2"
  13. node {
  14. // == 修改部分 获取当前选择的项目名称 ==
  15. def selectedProjectNames = "${project_name}".split(",")
  16. // == 修改部分
  17. stage('拉取代码') {
  18. checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
  19. }
  20. stage('代码审查') {
  21. // == 修改部分
  22. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  23. //jenkinscloud-eureka@10086
  24. def projectInfo = selectedProjectNames[i];
  25. //当前的项目名称 jenkinscloud-eureka
  26. def currentProjectName = "${projectInfo}".split("@")[0]
  27. //当前的项目端口 10086
  28. def currentProjectPort = "${projectInfo}".split("@")[1]
  29. //定义当前Jenkins的SonarQubeScanner工具的环境
  30. def scannerHome = tool 'sonarqube-scanner'
  31. //引用当前Jenkins的SonarQube环境
  32. withSonarQubeEnv('sonarqube-8.6.0') {
  33. sh """
  34. cd ${currentProjectName}
  35. ${scannerHome}/bin/sonar-scanner
  36. """
  37. }
  38. }
  39. // == 修改部分
  40. }
  41. //如果有公共子工程
  42. // stage('编译,安装公共的子工程') {
  43. // sh "mvn -f jenkinscloud-common clean install"
  44. // }
  45. stage('编译,打包微服务工程') {
  46. // dockerfile:build 可以触发插件的执行
  47. sh "mvn -f ${project_name} clean install dockerfile:build "
  48. }
  49. stage('上传镜像') {
  50. //定义镜像的名称
  51. def imageName = "${project_name}:${tag}"
  52. //给镜像打上标签
  53. sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
  54. //把镜像推送到Harbor
  55. withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  56. // 登录到Harbor
  57. sh "docker login -u ${username} -p ${password} ${harbor_url}"
  58. //镜像的上传
  59. sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
  60. sh "echo '镜像上传到Harbor仓库中成功'"
  61. }
  62. //删除本地镜像
  63. sh "docker rmi -f ${imageName}"
  64. sh "docker rmi -f ${harbor_url}/${harbor_project}/${imageName}"
  65. }
  66. // 定义远程执行命令
  67. def execCommand = "/usr/local/deploy.sh $harbor_url $harbor_project $project_name $tag $port"
  68. stage('拉取镜像和发布应用') {
  69. // 远程部署调用进行项目部署
  70. 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)])
  71. }
  72. }

6 多个项目打包及构建镜像、上传私服

  • 修改Jenkinsfile文件,内容如下:
  1. //定义git凭证ID
  2. def git_auth = "7d5c4945-2533-41e2-bd47-5dd97eb37f38"
  3. //git的url地址
  4. def git_url = "git@192.168.18.100:develop_group/jenkinscloud.git"
  5. //定义tag
  6. def tag = "1.0"
  7. // 定义Harbor的URL地址
  8. def harbor_url = "192.168.18.102:85"
  9. // 镜像库项目名称
  10. def harbor_project = "xudaxian-mall"
  11. // Harbor的登录凭证id
  12. def harbor_auth = "b6cf3cb5-8a33-457d-93da-65c46f0135b2"
  13. node {
  14. // == 获取当前选择的项目名称 ==
  15. def selectedProjectNames = "${project_name}".split(",")
  16. stage('拉取代码') {
  17. checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
  18. }
  19. stage('代码审查') {
  20. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  21. //jenkinscloud-eureka@10086
  22. def projectInfo = selectedProjectNames[i];
  23. //当前的项目名称 jenkinscloud-eureka
  24. def currentProjectName = "${projectInfo}".split("@")[0]
  25. //当前的项目端口 10086
  26. def currentProjectPort = "${projectInfo}".split("@")[1]
  27. //定义当前Jenkins的SonarQubeScanner工具的环境
  28. def scannerHome = tool 'sonarqube-scanner'
  29. //引用当前Jenkins的SonarQube环境
  30. withSonarQubeEnv('sonarqube-8.6.0') {
  31. sh """
  32. cd ${currentProjectName}
  33. ${scannerHome}/bin/sonar-scanner
  34. """
  35. }
  36. }
  37. }
  38. //如果有公共子工程
  39. // stage('编译,安装公共的子工程') {
  40. // sh "mvn -f jenkinscloud-common clean install"
  41. // }
  42. stage('编译,打包微服务工程') {
  43. // == 修改部分 ==
  44. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  45. //jenkinscloud-eureka@10086
  46. def projectInfo = selectedProjectNames[i];
  47. //当前的项目名称 jenkinscloud-eureka
  48. def currentProjectName = "${projectInfo}".split("@")[0]
  49. //当前的项目端口 10086
  50. def currentProjectPort = "${projectInfo}".split("@")[1]
  51. // dockerfile:build 可以触发插件的执行
  52. sh "mvn -f ${currentProjectName} clean install dockerfile:build "
  53. }
  54. // == 修改部分 ==
  55. }
  56. stage('上传镜像') {
  57. // == 修改部分 ==
  58. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  59. //jenkinscloud-eureka@10086
  60. def projectInfo = selectedProjectNames[i];
  61. //当前的项目名称 jenkinscloud-eureka
  62. def currentProjectName = "${projectInfo}".split("@")[0]
  63. //当前的项目端口 10086
  64. def currentProjectPort = "${projectInfo}".split("@")[1]
  65. //定义镜像的名称
  66. def imageName = "${currentProjectName}:${tag}"
  67. //给镜像打上标签
  68. sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
  69. //把镜像推送到Harbor
  70. withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  71. // 登录到Harbor
  72. sh "docker login -u ${username} -p ${password} ${harbor_url}"
  73. //镜像的上传
  74. sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
  75. sh "echo '镜像上传到Harbor仓库中成功'"
  76. }
  77. //删除本地镜像
  78. sh "docker rmi -f ${imageName}"
  79. sh "docker rmi -f ${harbor_url}/${harbor_project}/${imageName}"
  80. }
  81. // == 修改部分 ==
  82. }
  83. // 定义远程执行命令
  84. def execCommand = "/usr/local/deploy.sh $harbor_url $harbor_project $project_name $tag $port"
  85. stage('拉取镜像和发布应用') {
  86. // 远程部署调用进行项目部署
  87. // 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)])
  88. }
  89. }

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

7.1 前提说明

  • Jenkins Docker SpringCloud微服务持续集成(下) - 图15远程部署服务器(192.168.18.103和192.168.18.104)已安装Docker,并信任Harbor私有仓库。
  • Jenkins Docker SpringCloud微服务持续集成(下) - 图16关闭了防火墙。
  • Jenkins Docker SpringCloud微服务持续集成(下) - 图17在Publish over SSH那边增加一台远程部署服务器。

在Publish over SSH那边增加一台远程部署服务器.png

7.2 在Jenkins项目中增加一个Extended Choice Parameter参数

  • 在Jenkins项目中增加一个Extended Choice Parameter参数的目的是可以选择部署到那个服务器,选择Check 。

在Publish over SSH那边增加一台远程部署服务器.png

7.3 在192.168.18.103和192.168.18.104服务器中创建deployCluster.sh

  • 在远程部署服务器的/usr/local目录下,创建deployCluster.sh文件:
  1. cd /usr/local
  1. vim deployCluster.sh
  1. #! /bin/sh
  2. #接收外部参数
  3. harbor_url=$1
  4. harbor_project_name=$2
  5. project_name=$3
  6. tag=$4
  7. port=$5
  8. profile=$6
  9. imageName=$harbor_url/$harbor_project_name/$project_name:$tag
  10. echo "$imageName"
  11. #查询容器是否存在,存在则删除
  12. containerId=`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'`
  13. if [ "$containerId" != "" ] ; then
  14. #停掉容器
  15. docker stop $containerId
  16. #删除容器
  17. docker rm $containerId
  18. echo "成功删除容器"
  19. fi
  20. #查询镜像是否存在,存在则删除
  21. imageId=`docker images | grep -w $project_name | awk '{print $3}'`
  22. if [ "$imageId" != "" ] ; then
  23. #删除镜像
  24. docker rmi -f $imageId
  25. echo "成功删除镜像"
  26. fi
  27. # 登录Harbor
  28. docker login -u xudaxian -p Xudaxian12345678 $harbor_url
  29. # 下载镜像
  30. docker pull $imageName
  31. # 启动容器
  32. docker run -di -p $port:$port $imageName $profile
  33. echo "容器启动成功"
  • 设置权限:
  1. chmod +x deployCluster.sh

7.4 修改Jenkinsfile文件

  • 修改Jenkinsfile文件,内容如下:
  1. //定义git凭证ID
  2. def git_auth = "7d5c4945-2533-41e2-bd47-5dd97eb37f38"
  3. //git的url地址
  4. def git_url = "git@192.168.18.100:develop_group/jenkinscloud.git"
  5. //定义tag
  6. def tag = "1.0"
  7. // 定义Harbor的URL地址
  8. def harbor_url = "192.168.18.102:85"
  9. // 镜像库项目名称
  10. def harbor_project = "xudaxian-mall"
  11. // Harbor的登录凭证id
  12. def harbor_auth = "b6cf3cb5-8a33-457d-93da-65c46f0135b2"
  13. node {
  14. // == 获取当前选择的项目名称 ==
  15. def selectedProjectNames = "${project_name}".split(",")
  16. // 获取当前选择的服务器名称
  17. def selectedServers = "${publish_server}".split(",")
  18. stage('拉取代码') {
  19. checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
  20. }
  21. stage('代码审查') {
  22. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  23. //jenkinscloud-eureka@10086
  24. def projectInfo = selectedProjectNames[i];
  25. //当前的项目名称 jenkinscloud-eureka
  26. def currentProjectName = "${projectInfo}".split("@")[0]
  27. //当前的项目端口 10086
  28. def currentProjectPort = "${projectInfo}".split("@")[1]
  29. //定义当前Jenkins的SonarQubeScanner工具的环境
  30. def scannerHome = tool 'sonarqube-scanner'
  31. //引用当前Jenkins的SonarQube环境
  32. withSonarQubeEnv('sonarqube-8.6.0') {
  33. sh """
  34. cd ${currentProjectName}
  35. ${scannerHome}/bin/sonar-scanner
  36. """
  37. }
  38. }
  39. }
  40. //如果有公共子工程
  41. // stage('编译,安装公共的子工程') {
  42. // sh "mvn -f jenkinscloud-common clean install"
  43. // }
  44. stage('编译,打包微服务工程') {
  45. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  46. //jenkinscloud-eureka@10086
  47. def projectInfo = selectedProjectNames[i];
  48. //当前的项目名称 jenkinscloud-eureka
  49. def currentProjectName = "${projectInfo}".split("@")[0]
  50. //当前的项目端口 10086
  51. def currentProjectPort = "${projectInfo}".split("@")[1]
  52. // dockerfile:build 可以触发插件的执行
  53. sh "mvn -f ${currentProjectName} clean install dockerfile:build "
  54. }
  55. }
  56. stage('上传镜像') {
  57. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  58. //jenkinscloud-eureka@10086
  59. def projectInfo = selectedProjectNames[i];
  60. //当前的项目名称 jenkinscloud-eureka
  61. def currentProjectName = "${projectInfo}".split("@")[0]
  62. //当前的项目端口 10086
  63. def currentProjectPort = "${projectInfo}".split("@")[1]
  64. //定义镜像的名称
  65. def imageName = "${currentProjectName}:${tag}"
  66. //给镜像打上标签
  67. sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
  68. //把镜像推送到Harbor
  69. withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  70. // 登录到Harbor
  71. sh "docker login -u ${username} -p ${password} ${harbor_url}"
  72. //镜像的上传
  73. sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
  74. sh "echo '镜像上传到Harbor仓库中成功'"
  75. }
  76. //删除本地镜像
  77. sh "docker rmi -f ${imageName}"
  78. sh "docker rmi -f ${harbor_url}/${harbor_project}/${imageName}"
  79. }
  80. }
  81. stage('拉取镜像和发布应用') {
  82. for(int i = 0 ; i< selectedProjectNames.length ; i++) {
  83. //jenkinscloud-eureka@10086
  84. def projectInfo = selectedProjectNames[i];
  85. //当前的项目名称 jenkinscloud-eureka
  86. def currentProjectName = "${projectInfo}".split("@")[0]
  87. //当前的项目端口 10086
  88. def currentProjectPort = "${projectInfo}".split("@")[1]
  89. for(int j =0;j<selectedServers.length;j++){
  90. //获取当前遍历的服务器名称
  91. def currentServerName = selectedServers[j]
  92. //加上参数格式: --spring.profiles.active=eureka-server1/eureka-server2
  93. def activeProfiles = "--spring.profiles.active="
  94. if(currentServerName =="192.168.18.103"){
  95. activeProfiles = activeProfiles + "eureka-server1"
  96. }else if(currentServerName == "192.168.18.104"){
  97. activeProfiles = activeProfiles + "eureka-server2"
  98. }
  99. // 定义远程执行命令
  100. def execCommand = "/usr/local/deployCluster.sh $harbor_url $harbor_project $currentProjectName $tag $currentProjectPort $activeProfiles"
  101. // 远程部署调用进行项目部署
  102. sshPublisher(publishers: [sshPublisherDesc(configName: "${currentServerName}", 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)])
  103. }
  104. }
  105. }
  106. }