基于ECS的springMVC项目通过jenkins实现CI/CD

一、背景

       由于公司服务器都是阿里云服务器,会涉及到项目的频繁更新,于是就有了服务更新的一整套流程。整体思路是:从测试环境拉取代码,程序灰度更新,代码从灰度拉到生产,线上服务重启。

二、从测试环境拉取代码

2.1 执行时shell

1. 到测试环境机器给待更新的服务打tar包

2. 把打好的tar包传给执行这个项目的机器,/target目录是软链接,链接到一个共享nas盘,其他机器挂载这个盘就可以实现数据共享

3. 删除测试环境的tar包

#!/bin/sh
ip="47.XXX.209.34"
projectName=${project//_/-}
path=/programs/$project

echo path:$path 
echo projectName:$projectName
echo "ssh -p 3721 root@${ip} \"cd $path/target/ ; ls -t; tar -zcf $projectName-new.tar.gz $projectName\""
echo "scp -P 3721 root@$ip:$path/target/$projectName-new.tar.gz /target"
echo "ssh -p 3721 root@${ip} \"rm -f $path/target/$projectName-new.tar.gz\""

ssh -p 3721 root@${ip} "cd $path/target/ ; ls -t; tar -zcf $projectName-new.tar.gz $projectName"
scp -P 3721 root@$ip:$path/target/$projectName-new.tar.gz /target
ssh -p 3721 root@${ip} "rm -f $path/target/$projectName-new.tar.gz"

echo "scp ok"

 三、程序灰度更新

3.1 项目配置

1. 项目名称

2.更新或只回到生产

3.是否更新xml

扫描二维码关注公众号,回复: 8915304 查看本文章

4.项目运行的节点,221为灰度机器,挂载了26的共享nas盘

3.2 执行时shell

1. 通过字符串截取得到容器名称,并停止这个容器,删除原项目的目录【本质是项目编译完的target下的项目目录】

2. 从/env-b拉取生产代码,灰度机器221共享了生产B环境代码nas盘

3.进入更新目录,删除WEB-INF文件夹,解压从测试环境拉过来的压缩包

4.更新灰度环境项目目录,根据条件判断是否需要更新xml【properties里面配置的是生产数据库配置,一般不需要变更】

5.启动更新项目的docker容器

#!/bin/sh
projectVals=(${projectVal//,/ })
project=${projectVals[1]}
echo `date "+%Y-%m-%d %H:%M:%S"`
echo $project

scProjectDir="/env-b/$project"
projectDir="/opt/app/$project"
updateDir="/opt/update/$project"

containerName=${projectVals[0]}
echo "docker stop $containerName"
docker stop $containerName

echo "删除原项目$projectDir"
rm -rf $projectDir

echo "拉取生产代码$scProjectDir"
cp -r $scProjectDir  $projectDir

if [ "true" = "$isFlush" ] ; then

  echo "开始更新代码"
  cd /opt/update
  rm -rf $project/WEB-INF
  tar -zxf /plk/target/$project-new.tar.gz
  
  cd ${projectDir}
  
  rm -rf WEB-INF/lib WEB-INF/classes/com
  cp -r ${updateDir}/WEB-INF/lib WEB-INF
  cp -r ${updateDir}/WEB-INF/classes/com WEB-INF/classes
  
  if [ "yes" = "$xml" ] ; then
    echo "rm -rf $project/WEB-INF/classes/*.xml"
    echo "cp -r $updateDir/WEB-INF/classes/*.xml  $project/WEB-INF/classes/"
    
    rm -rf WEB-INF/classes/*.xml
    cp -r $updateDir/WEB-INF/classes/*.xml  WEB-INF/classes/
  fi

fi

echo "docker start $containerName"
docker start $containerName
sleep 35s
tail -n 400 /docker/tomcat/logs/$containerName/catalina.out


四、拉取代码到生产

4.1 项目配置

1.项目名称

2.项目运行节点,10.8.0.159这台机器是监控看板

4.2 执行时shell

1. 根据传入的参数备份线上项目代码,159共享了有生产B环境代码的nas盘

2.删除线上代码的lib和com文件夹,从灰度环境拷贝class目录到生产环境

#!/bin/sh
today=`date "+%Y%m%d-%H%M"`

bakDir="/bak"
projectDir="/opt/apps/$project"

echo "bakDir=$bakDir"
echo "projectDir=$projectDir"
echo "today=$today"

cd /opt/apps

echo "backup and update"

echo "tar -zcf $bakDir/$project-$today.tar.gz $project"
tar zcf $bakDir/$project-$today.tar.gz $project

echo "rm -rf $project/WEB-INF/lib  $project/WEB-INF/classes/com"
rm -rf $project/WEB-INF/lib  $project/WEB-INF/classes/com

echo "scp -r [email protected]:/opt/app/$project/WEB-INF/lib  $project/WEB-INF/lib"
scp -r [email protected]:/opt/app/$project/WEB-INF/lib  $project/WEB-INF/lib

echo "scp -r [email protected]:/opt/app/$project/WEB-INF/classes/*  $project/WEB-INF/classes"
scp -r [email protected]:/opt/app/$project/WEB-INF/classes/*  $project/WEB-INF/classes/

五、线上服务重启

5.1 项目参数

1.项目名称

2.项目运行节点,10.8.0.159这台机器是监控看板

5.2 执行时shell

1. 获取项目名称,服务所在ECS的ip地址,提供者暴露的dubboPort【如果一个服务默认的端口为0,说明他仅仅是一个消费者】。

2.传入stop通过dubboadmin.py把提供者在zookeeper上置为空

3.远程到需要更新应用的ECS上,传入tomcatname和b作为panme 【ppset.sh脚本会把PP_OPTS参数暴露出来,tomcat启动的时候JVM会加载这些参数】

4.如果是第一台更新的机器,输出日志【因为只要一台更新成功了,后面的机器就不会报错了】

5.给服务启动预留休息时间,启动完之后启动下一台机器的服务【每台业务服务器都挂载了有生产代码的nas盘】

#!/bin/sh
today=`date "+%Y%m%d-%H%M"`

projectVals=(${projectVal//,/ })

tomcatName=${projectVals[0]}
hoststr=${projectVals[1]}
dubboPort=${projectVals[2]}
i=0

echo "restart"

hosts=${hoststr//;/ }

if [ "redpacket" = "$tomcatName" ] ; then
	count=1
fi

for host in ${hosts[@]} 
do
    if (( $dubboPort > 0 )) ; then
    	echo "dubbo pause service"
    	echo "cd /monitor/python ; python dubboadmin.py 10.8.0.75 $host $dubboPort stop"
    	cd /monitor/python ; python dubboadmin.py 10.8.0.75 $host $dubboPort stop
    
    	sleep 2s
    fi
        
        echo "ssh root@$host \"/plk/ppset.sh $tomcatName b ; sh /opt/restart.sh $tomcatName\""
        ssh root@$host "/plk/ppset.sh $tomcatName b ; sh /opt/restart.sh $tomcatName"
        
     if (( i == 0 )) ; then
         sleep 35s
         ssh root@$host "tail -n 500 /opt/$tomcatName-tomcat/logs/catalina.out"
     fi
    
    (( i++ ))
    
    echo "$host $tomcatName restart end sleep $sleepTime s"
    sleep $sleepTime
    
done


if (( $dubboPort > 0 )) ; then
    sleep 30
    
    for host in ${hosts[@]} 
    do
        echo "dubbo pause service"
        echo "cd /monitor/python ; python dubboadmin.py 10.8.0.75 $host $dubboPort start"
        cd /monitor/python ; python dubboadmin.py 10.8.0.75 $host $dubboPort start
    done

fi

附:

1. ppset.sh脚本,PP_OPTS参数会在tomcat启动的时候加载到JVM里面,实现pinpoint的服务注册。

ipaddr=$(ip addr | awk '/^[0-9]+: / {}; /inet.*global/ {print gensub(/(.*)\/(.*)/, "\\1", "g", $2)}'|head -1);ipaddr=${ipaddr:5}
pname=${1}-${2}
echo proj=${pname}
export PP_OPTS=" -javaagent:/opt/app/pinpoint-agent-1.8.2/pinpoint-bootstrap-1.8.2.jar -Dpinpoint.agentId=${pname}-${ipaddr} -Dpinpoint.applicationName=${pname} "
echo "$PP_OPTS"

2. dubboadmin.py 脚本

1.引入kazoo模块连接到生产zookeeper

2.通过getchildren方法获得"/dubbo/"+str(intf)+"/providers"子目录节点,把获取到的接口放到providerList列表

3.根据传入的参数判断zookeeper需要执行的操作

%3A,%3F,%3D这些是url编码,详情见ttps://grox.net/utils/encoding.html

#!/usr/bin/env python
from kazoo.client import KazooClient
import logging
import sys

logging.basicConfig()

zk = KazooClient(hosts=str(sys.argv[1])+":2181")
zk.start()

acl=sys.argv[4]

providerList=[]
server_addr=str(sys.argv[2])+"%3A"+str(sys.argv[3])

print server_addr

interfaces=zk.get_children("/dubbo")

for intf in interfaces:
    providers=zk.get_children("/dubbo/"+str(intf)+"/providers")
    for provider in providers:
        if provider.find(server_addr) > -1:
            providerList.append(str(provider))

for provider in providerList:
    str1=provider.split("%3F",1)[0]
    str2=str1.split("%2F")[3]
    str1=str1.replace("dubbo","override")
    str1=str1+"%3Fcategory%3Dconfigurators%26disabled%3Dtrue%26dynamic%3Dfalse%26enabled%3Dtrue"
    str3="/dubbo/"+str2+"/configurators/"+str1
    print str3

    try:
        if acl == "stop":
            zk.ensure_path(str3)   //创建str3空的node
        else:
            zk.delete(str3)     //删除str3的子node
    except Exception as e:
        print ('error', e)
    finally:
        print ('ok')

zk.stop()
发布了161 篇原创文章 · 获赞 40 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_36441027/article/details/103118337