前言
公司项目使用Coding进行管理,每次打包部署都需要经历以下流程
- 代码提交
 
- 项目打包
 
- 登录服务器
 
- 上传应用到服务器
 
- 执行部署脚本
 
- 发现Bug ->修改Bug -> 重复第一道流程
 
了解过Jenkins等持续集成工具后,查看到Coding也有相关服务,并且Coding还提供一台云主机来进行构建操作。
如果你也长期经历以上流程,强烈建议你了解一下持续集成,因为手动部署实在是麻烦又耗时,使用持续集成后省下的时间又可以多写两行Bug了🤪
需求
我们希望每次写完代码,提交到主分支以后,项目能够自动编译打包,上传至服务器,并自动重启应用
前期工作
凭证管理
官方文档,凭证管理 https://coding.net/help/docs/project-settings/credential.html
生成Rsa私钥
登录服务器,生成Rsa私钥
生成好的文件在/root/.ssh路径下
将id_rsa.pub添加到authorized_keys文件中,并重启sshd服务
添加至Coding凭证管理
1.将id_rsa文件中的内容添加至Coding

2.在录入凭据页面,输入相关信息
- 选择「SSH 私钥」凭据类型
 
- 输入凭据名称,必填项,长度不超过 255 个字符
 
- 输入 SSH 私钥,必填项
 
- 输入私钥口令,非必填
 
- 输入描述,非必填
 
3.勾选需要授权的持续集成功能。只有进行凭据授权后,在使用 CODING 持续集成功能模块创建构建计划时才有权限使用该凭据。
4.点击「创建」即可完成创建。录入成功的凭据会显示在凭据管理页面。
白名单释放
因为我们使用的是阿里云的服务器,所以需要释放Coding的IP,如果你使用的不是阿里云可忽略
Coding文档:https://coding.net/help/docs/ci/faq/job-fail.html#aliyun
执行 SSH 命令访问阿里云主机时提示 Connection reset 错误。
此问题是阿里云侧白名单未放行 CODING IP 所致。前往阿里云「安全管控平台」→「安全管控」→「新增访问白名单」,将构建机的 IP 加入至白名单中可以防止其在访问云主机时被拦截。
CODING 构建机所使用的出口 IP 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | # 中国上海节点   111.231.92.100   81.68.101.44   # 中国香港节点   124.156.164.25   119.28.15.65   # 美国硅谷节点   170.106.136.17   170.106.83.77
 
  | 
 
构建计划
创建构建计划
首先进入Coding的项目中,找到持续集成-构建计划,新建构建计划

点击自定义构建流程

Jenkinsfile
创建自定义流程以后我们开始编写Jenkinsfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
   |  pipeline {   agent any   stages {     stage('检出') {       steps {         checkout([           $class: 'GitSCM',           branches: [[name: GIT_BUILD_REF]],           userRemoteConfigs: [[             url: GIT_REPO_URL,             credentialsId: CREDENTIALS_ID           ]]])         }       }
        stage('编译') {         steps {           echo '构建中...'           sh 'mvn clean package'           echo '构建完成'           echo '当前所在位置:'           sh '''pwd ls'''           sh '''ls ${project_target_path} '''           sh '''tar -zcf /root/workspace/tmp.tar.gz ${project_target_path}/*.jar ls /root/workspace/'''         }       }
        stage('重启应用') {         steps {           echo '开始发送文件到远端服务器...'           script {             def remote = [:]             remote.name = 'web-server'             remote.allowAnyHosts = true             remote.host = host             remote.port = port as Integer             remote.user = user             // 把「CODING 凭据管理」中的「凭据 ID」填入 credentialsId,而 id_rsa 无需修改             withCredentials([sshUserPrivateKey(credentialsId: credentialsId, keyFileVariable: 'id_rsa')]) {               remote.identityFile = id_rsa               // SSH 上传文件到远端服务器               sshPut remote: remote, from: "/root/workspace/tmp.tar.gz", into: '/tmp/'               // 解压缩               sshCommand remote: remote, command: "tar -zxf /tmp/tmp.tar.gz -C /tmp/"               //复制文件到运行目录               sshCommand remote: remote, sudo: true, command: "cp -R /tmp/${project_target_path}/* ${project_path}"               //执行脚本重启应用               sshCommand remote: remote, sudo: true, command: sh_path_command
 
              }           }
            echo '部署成功...'         }       }
      }   }
 
  | 
 
Jenkinsfile文件中有七个环境变量字段,如下
| 字段值 | 
字段名称 | 
字段注释 | 
示例 | 
| project_path | 
项目存放的目录地址 | 
jar在服务器上实际存放的地址 | 
/usr/local/java/demo | 
| sh_path_command | 
需要执行的sh命令 | 
一般是 sh 脚本所在位置加执行的操作 | 
sh /usr/local/java/demo/start.sh restart  此命令是执行/usr/local/java/demo目录下start.sh脚本的restart操作 | 
| host:目标服务器地址 | 
服务器IP地址 | 
111.111.111.111 | 
 | 
| port | 
SSH端口号 | 
端口号 | 
22 | 
| user | 
SSH用户名 | 
登录用户名 | 
root | 
| credentialsId | 
SSH登录凭据 | 
ssh登录凭证 | 
选择项,需要配置私钥并添加至coding凭证管理中 | 
| project_target_path | 
jar包所在目录 | 
执行打包命令后jar所在的目录 | 
demo/target | 
以上环境变量需要添加至该构建计划的【变量与缓存】中,如图:

我们的项目采用Maven构建,我勾选上了缓存Maven目录,这样每次构建就不需要重复下载依赖了,速度更快
启动脚本
此脚本来自ruoyi-vue项目,脚本内容需要调整$AppName和$APP_HOME参数,$JVM_OPTS参数可酌情调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
   | #!/bin/sh
 
 
 
  AppName=jenkins_demo-0.0.1-SNAPSHOT.jar
  JVM_OPTS="-Dname=$AppName  -Duser.timezone=Asia/Shanghai -Xms512M -Xmx512M -XX:PermSize=256M -XX:MaxPermSize=512M -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps  -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
  APP_HOME="/usr/local/java/demo" LOG_PATH=$APP_HOME/logs/$AppName.log
  if [ "$1" = "" ]; then     echo -e "\033[0;31m 未输入操作名 \033[0m  \033[0;34m {start|stop|restart|status} \033[0m"     exit 1 fi
  if [ "$AppName" = "" ]; then     echo -e "\033[0;31m 未输入应用名 \033[0m"     exit 1 fi
  function start() {     echo "开始执行start"    cd $APP_HOME    nohup java -jar  $APP_HOME/$AppName > nohup.out & 2>&1 &     echo "结束执行start" }
  function stop() {     echo "Stop $AppName"
  	PID="" 	query(){ 		PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` 	}
  	query 	if [ x"$PID" != x"" ]; then 		kill -TERM $PID 		echo "$AppName (pid:$PID) exiting..." 		while [ x"$PID" != x"" ] 		do 			sleep 1 			query 		done 		echo "$AppName exited." 	else 		echo "$AppName already stopped." 	fi }
  function restart() {     stop     sleep 2     start }
  function status() {     PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`     if [ $PID != 0 ];then         echo "$AppName is running..."     else         echo "$AppName is not running..."     fi }
  case $1 in     start)     start;;     stop)     stop;;     restart)     restart;;     status)     status;;     *)
  esac
 
  | 
 
疑问/讨论
如果使用Nginx配置了负载均衡,部署了多个Jar,该持续集成应该怎么实现呢?评论区有答案吗?