1、阅读目标人群 需要对Spring cloud和Jenkins的原理和操作有基本的了解,本文不适合完全的新手阅读。
2、使用背景 当使用Spring Cloud构架的时候往往对于每一个微服务会进行集群部署,以达到高可用和扩展性能的目的。但是这样的架构和部署方式大大增加了部署的难度。假设我们有A\B\C\D\E,5个微服务,每个微服务都部署2台服务器,那么做一次大更新则需要发布10次项目才可以全部完成。如果这一切都需要人工操作那么将会非常的麻烦,而且容易出错。这个时候我们就需要借助Jenkins来帮我们发布项目,当你配置好Jenkins之后就只要点一个按钮就可以了。发布的过程会交给Jenkins自己来完成。
3、要解决的问题 当使用Jenkins发布之后,会存在一个问题:发布的过程中,对外提供服务会有短暂的不可用(持续时间为分钟级别)。这个就是我们要解决的问题。如何保证使用jenkins发布的时候保证对外提供持续稳定的服务。
4、出现问题的原因 如果不经过精心的设计,一般jenkins发布一个项目的流程如下:
从git\svn下载最新代码
在本地进行构建
kill本地java进程
启动刚刚构建好的jar包
问题出现在第三步和第四步的时候。当kill掉本地java进程时,并没有及时通知注册中心eureka该微服务在这台机器已经下线。而此时新构建的jar包还未能提供服务。于是网关zuul还是不断的把流量转发给这台机器的微服务,然而这个微服务已经不能正常工作了。其他微服务也没及时感知到这个微服务已经下线,于是也会导致其他微服务出错。
5、解决问题的原理 总结来说,为什么不能平滑发布是因为其他微服务(包括网关zuul)并没有立即感知到这个微服务下线了,所以还会继续转发流量过来。那么我们优化一下流程即可:
从git\svn下载最新代码
在本地进行构建
使用api通知注册中心eureka下线微服务
等待1分钟,让下线的状态广播到所有微服务
kill本地java进程
启动刚刚构建好的jar包
通过增加了第三步和第四步,我们在kill之前提前通知所有微服务下线,等1分钟之后就没有流量转发到这个微服务,于是这个微服务就可以安全的下线了。
具体eureka下线的接口可以参考这个文档 。
6、实战解决 6.1、缩短微服务拉去注册中心状态的时间 在上一节讲到等待1分钟,那么这1分钟的时间内就要保证将下线的状态通知到所有的微服务,这就要求其他微服务能在1分钟内从注册中心拉取到最新的微服务列表。具体的配置可以参考这两篇文章
6.2、jenkins发布脚本 这个shell的功能比较全,包含了“发布”,“自动备份”,“回滚”,”检测本次发布是否成功”这4个核心功能。使用时只需要根据自己的情况修改“变量区”的内容就好了。
额外的,这里要求jenkins多几个配置项以支撑归滚功能。
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 # # 发布环境 START_ENV="prod" # 项目名称 PROJECT="order-system" # 项目在git仓库中的路径 GIT_PROJECT_PATH="./" # 在注册中心的名称 APP_NAME="order-system" # 微服务的端口 APP_PORT="8080" # 注册中心的地址 EUREKA_URL="http://192.168.10.11:8761/eureka" # 备份路径 BAK_DIR="/tmp/backup/$PROJECT" # 最多备份数 MAX_BAK_NUM=10; # 部署路径 DEPLOY_DIR="/home/jenkins_deploy" # # 请勿修改该参数!是否在注册中心在线(1是0否) IS_ONLINE="0" # 获取ip getIp(){ IP=`/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v 172.|grep -v inet6|awk '{print $2}'|tr -d "addr:|地址"` if [ "$IP" = "" ];then echo -e "\033[31m can't get Ip \033[0m" exit 1 fi } # 指定进程是否在运行 isProcessRun(){ PIDS=`ps -ef |grep ${PROJECT} |grep -v grep | awk '{print $2}'` } # 服务下线 changeStatusOffline(){ echo "change status url:${EUREKA_URL}/apps/${APP_NAME}/${IP}:${APP_NAME}:${APP_PORT}/status?value=OUT_OF_SERVICE"; statusResult=`curl -X PUT -s -m 10 --connect-timeout 10 -i ${EUREKA_URL}/apps/${APP_NAME}/${IP}:${APP_NAME}:${APP_PORT}/status?value=OUT_OF_SERVICE` statusCode=`echo $statusResult|grep "HTTP"|awk '{print $2}'` if [[ "$statusCode" != "200" ]] && [[ "$statusCode" != "404" ]]; then echo -e "\033[31m offline service is fail!!!! response code:${statusCode} \033[0m" exit 1 else echo -e "\033[33m wait offline status notify other service,it will take 60 seconds... \033[0m" sleep 60 fi } # 服务上线 changeStatusUp(){ echo -e "\033[33m wait service ready to use,it will take 3 seconds... \033[0m" sleep 3 echo "change status url:${EUREKA_URL}/apps/${APP_NAME}/${IP}:${APP_NAME}:${APP_PORT}/status?value=UP"; statusResult=`curl -X PUT -s -m 10 --connect-timeout 10 -i ${EUREKA_URL}/apps/${APP_NAME}/${IP}:${APP_NAME}:${APP_PORT}/status?value=UP` statusCode=`echo $statusResult|grep "HTTP"|awk '{print $2}'` if [[ "$statusCode" != "200" ]] && [[ "$statusCode" != "404" ]]; then echo -e "\033[31m let service online is fail!!!! response code:${statusCode} \033[0m" exit 1 fi } # 备份jar之前的包 backUp(){ if [ ! -d ${BAK_DIR} ]; then mkdir -p "${BAK_DIR}" fi if [ "$?" != "0" ]; then echo -e "\033[31m backup fail,can't operate ${BAK_DIR} directory!!!!!!!!! \033[0m" exit 1 fi if [ -f ${DEPLOY_DIR}/${PROJECT}".jar" ]; then backUpPath="${BAK_DIR}/${PROJECT}_${BUILD_NUMBER}.jar" if [ -f ${backUpPath} ]; then echo -e "\033[31m backUp build number(${BUILD_NUMBER}) is repeat! \033[0m" exit 1 fi # 复制到备份文件夹 cp -f ${DEPLOY_DIR}/${PROJECT}.jar ${backUpPath} echo -e "\033[32m backup success!(${backUpPath}) \033[0m" else echo -e "\033[33m ${PROJECT}.jar is not found,skip backup process! \033[0m" fi } # 查看已经注册中心上是否已经关闭 isRemoteAlive(){ info=`curl -s -m 10 --connect-timeout 10 -i ${EUREKA_URL}/instances/${IP}:${APP_NAME}:${APP_PORT}` code=`echo $info|grep "HTTP"|awk '{print $2}'` if [ "$code" = "" ]; then echo -e "\033[31m eureka is not online!!!! \033[0m" exit 1 fi status=`echo $info|grep -e "OUT_OF_SERVICE" -e "<status>UP</status>"` if [ "$status" != "" ]; then echo -e "\033[32m online in eureka! \033[0m" IS_ONLINE=1 else echo -e "\033[33m offline in eureka! \033[0m" IS_ONLINE=0 fi } # 关停服务 shutdownService(){ # 先下线服务 changeStatusOffline # 获取是否运行了服务 isProcessRun echo -e "\033[32m service PIDS:$PIDS \033[0m" if [ "$PIDS" != "" ]; then ps -ef|grep "${PIDS}"|grep -v grep|cut -c 9-15|xargs kill if [ "$?" != "0" ]; then echo -e "\033[31m fail to kill process[${PIDS}]!!! \033[0m" exit 1 fi fi } # 构建 build(){ cd $WORKSPACE/$GIT_PROJECT_PATH echo -e "\033[32m mvn install .......... \033[0m" mvn clean package -e -U -q if [ ! -f $WORKSPACE/$GIT_PROJECT_PATH/target/${PROJECT}.jar ]; then echo -e "\033[31m maven build fail,jar is not found! ($WORKSPACE/$GIT_PROJECT_PATH/target/${PROJECT}.jar) \033[0m" exit 1 fi } # 部署服务 deloyService(){ if [ ! -d ${DEPLOY_DIR} ]; then mkdir -p "${DEPLOY_DIR}" fi cp -f $WORKSPACE/$GIT_PROJECT_PATH/target/${PROJECT}.jar ${DEPLOY_DIR} chmod a+rw ${DEPLOY_DIR} -R } # 启动服务 startService(){ BUILD_ID=dontKillMe nohup java -server -jar ${DEPLOY_DIR}/${PROJECT}.jar --spring.profiles.active=${START_ENV} --eureka.instance.metadata-map.sv=${SERVER_VERSION} & } # 回滚 rollback(){ if [ ! -f ${BAK_DIR}/${PROJECT}_${ROLLBACK_NUMBER}".jar" ]; then echo -e "\033[31m bakup not found (${BAK_DIR}/${PROJECT}_${ROLLBACK_NUMBER}.jar) \033[0m" exit 1 fi cp -f ${BAK_DIR}/${PROJECT}_${ROLLBACK_NUMBER}.jar ${DEPLOY_DIR}/${PROJECT}.jar startService } # 删除历史备份 deleteHistoryBackUp(){ # 当前备份总数 backUpCount=`ls -l ${BAK_DIR}| grep "^-" | wc -l` # 要删除的备份数量 deleteBakNum=`expr $backUpCount - $MAX_BAK_NUM ` if [ $deleteBakNum -gt 0 ]; then echo `ls -lt ${BAK_DIR}| tail -f -n ${deleteBakNum} |awk '{print $9}'` echo "delete ${deleteBakNum} backUp file" ls -lt ${BAK_DIR}| tail -f -n ${deleteBakNum} |awk '{print $9}' | xargs -I {} rm -f "${BAK_DIR}/{}" fi } # echo -e "\033[32m deploy_env:${DEPLOY_ENV} rollback number:${ROLLBACK_NUMBER} .\033[0m" # 获取ip getIp # 判断参数是否正确 if [ "$DEPLOY_ENV" = "rollback" ]; then if [[ ! "$ROLLBACK_NUMBER" =~ ^[0-9]+$ ]]; then echo -e "\033[31m ROLLBACK_NUMBER is illegal!!!!! \033[0m" exit 1 fi fi if [[ "$DEPLOY_ENV" = "new_deploy" ]] || [[ "$DEPLOY_ENV" = "hotfix_deploy" ]]; then if [[ ! "$SERVER_VERSION" =~ .+ ]]; then echo -e "\033[31m SERVER_VERSION is illegal!!!!! \033[0m" exit 1 fi fi # 如果是发布,则先构建(怕构建失败) if [[ "$DEPLOY_ENV" = "new_deploy" ]] || [[ "$DEPLOY_ENV" = "hotfix_deploy" ]] ; then # 构建 build # 备份 backUp fi # 停服务(如果启动了) shutdownService testCount=1 # 循环看是否停服务成功 while [[ "$PIDS" != "" ]]; do echo -e "\033[33m Service is shutting down... \033[0m" sleep 2 isProcessRun let "testCount++" if [ $testCount -eq 60 ] ; then echo -e "\033[31m wait too long to kill process \033[0m" exit 1 fi done if [[ "$DEPLOY_ENV" = "new_deploy" ]] || [[ "$DEPLOY_ENV" = "hotfix_deploy" ]] ; then # 部署 deloyService # 启动 startService elif [ "$DEPLOY_ENV" = "rollback" ] ; then # 回滚 rollback else echo -e "\033[31m $DEPLOY_ENV is illegal!!!!! \033[0m" fi checkCount=1 # 循环看是否启动服务成功 echo -e "\033[32m checking service is online.... \033[0m" echo -e "\033[32m check url:${EUREKA_URL}/instances/${IP}:${APP_NAME}:${APP_PORT} \033[0m" while [[ "$IS_ONLINE" = "0" ]]; do sleep 2 let "checkCount++" isRemoteAlive if [ $checkCount -eq 60 ] ; then echo -e "\033[31m wait too long to check service online status \033[0m" exit 1 fi done # 上线服务 changeStatusUp # 删除备份 deleteHistoryBackUp echo -e "\033[32m !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!deploy success!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \033[0m" #
原文链接:https://www.jdkdownload.com/jenkins_springcloud.html