DEVOPS-迁移SpringMVC应用到生产K8S集群

一、背景

     公司项目有springmvc和springboot两种类型,springmvc项目主要通过tomcat启动,springboot项目主要通过java  [option] -jar *.jar的方式启动。我们准备把springmvc项目和springboot项目先做成镜像先在灰度环境跑,如果没有问题我们再把镜像push到生产镜像仓库,最后选择在灰度验证通过的镜像运行在k8s上从而实现springmvc迁移。

二、实现原理

2.1 主要流程

  1. 从测试环境拉取已经编译好的代码
  2. 修改测试配置为生产配置
  3. 通过Dockerfile把已经修改好配置的程序打包成业务镜像
  4. 把镜像push到灰度镜像仓库,然后通过灰度镜像仓库的业务镜像启动
  5. 如果需要增量更新,可以预先把程序通过docker cp 拷贝出来,然后更新指定文件后docker cp回去,docker restart imageName,如果没有启动没有问题,可以docker commit  imageName  newImageName,最后push到生产镜像仓库。

三、应用运行在灰度环境

3.1 参数化构建过程参数

  • 项目名称,包含容器名称和项目名称

  • 版本号,用于打镜像tag

  • 测试环境地址,包含ip地址,测试环境标识

  • 是否为springboot项目,springmvc和springboot项目配置文件结构不一样,走的逻辑不同

  • 是否更新到灰度环境【1.226】,因为这个自由风格的项目运行在一台专门打镜像的机器上【0.26】,所以如果需要更新到灰度机器需要连接到灰度机器【1.226】执行运行指定业务镜像的脚本

3.2 参数化构建的执行时shell

3.2.1 执行时shell的基本步骤

  1. 通过shell字符串切割把传入的projectVals拆分成想要的字符串
  2. 把指定测试环境的服务代码拷贝到执行此项目的机器上【0.26】
  3. 进入WEB-INF/classes目录下把测试环境配置替换为生产配置,为制作生产镜像做准备
  4. 拷贝一份已经写好的springmvc的Dockerfile 【在3.2.2节中已说明】,替换项目名称为传入的项目名称
  5. 通过修改后的Dockerfile生成镜像,并push到灰度镜像仓库
  6. 如果需要在灰度机器上跑已经制作好的镜像,可以远程到灰度机器,执行projectName.sh脚本 【在3.2.3节中已说明】
  7. 如果需要增量更新,通过docker cp 把webapps/finalName 文件夹拷贝出来,增量更新后 restart容器,启动无误可以commit,最后push到生产镜像仓库供线上k8s使用
contentName="$project"
currentTime=`date "+%Y%m%d%H%M"`
echo $currentTime
export PATH="/opt/apache-maven-3.3.3/bin:$PATH"

projectVals=(${projectVals//,/ })
projectName=${projectVals[0]}
containerArray=(${projectName//-/ })
container=${containerArray[1]}
project=${projectVals[1]}
finalName=${projectVals[2]}

if [ "$finalName" == "" ]; then
  finalName=$project
fi

tag=$finalName-${gitVersion##*/}-$currentTime

TestEnvs=(${TestEnv//,/ })
serverName=${TestEnvs[0]}
serverHost=${TestEnvs[1]}

if [ "$gitVersion" == "" ]; then
  echo "gitVersion不能为空"
  exit
fi

cd /plk/source

if [ "true" != "$isSpringBoot" ] ; then
  rm -rf $finalName
  sshpass -p $remotePasswd scp -r -P 3721 $serverHost:/programs/$project/target/$finalName .
  
  cd $finalName/WEB-INF/classes
  pwd
  ls
  if [ -f "$finalName-mq.properties" ] ; then
  	sed -i 's|.mg.addr|-mg-addr|g' $finalName-mq.properties
  	sed -i 's|mq-mg-addr|mq1-mg-addr:9876;mq2-mg-addr:9876;mq3-mg-addr|g' $finalName-mq.properties
  fi
  
  echo "set prod zookeeper"
  sed -i 's|{dubbo.registry.address}|{dubbo.registry.cluster.address}|g' $finalName-*.xml
    
  if [ ! -z "`grep 'jdbc.properties' $finalName-spring-context-server.xml`" ]; then
    echo "set prod mysql config"
    sed -i 's|jdbc.mg.addr:3306/moni|rm-***.mysql.rds.aliyuncs.com:3306/moni_b|g' *jdbc.properties
    sed -i 's|jdbc.mg.addr:3306/game|rm-***.mysql.rds.aliyuncs.com:3306/game_b|g' *jdbc.properties
    sed -i 's|.username=***|.username=***|g' *jdbc.properties
    sed -i 's|.password=***|.password=***|g' *jdbc.properties
  fi
  
  if [ ! -z "`grep 'mongo.properties' $finalName-spring-context-server.xml`" ]; then
    echo "set prod mongo config"
    sed -i 's|=mongo.mg.addr:27017|=dds-***.mongodb.rds.aliyuncs.com:3717,dds-***.mongodb.rds.aliyuncs.com:3717|g' *-mongo.properties
    sed -i 's|mongo.mg.addr|dds-***.mongodb.rds.aliyuncs.com|g' *-mongo.properties
    sed -i 's|mongo.readonly.mg.addr|dds-***.mongodb.rds.aliyuncs.com|g' *-mongo.properties
    
    sed -i 's|database=statistics|database=statistics_b|g' *-mongo.properties
    sed -i 's|database=everydaykline|database=everydaykline_b|g' *-mongo.properties
    sed -i 's|database=evaluation|database=evaluation_b|g' *-mongo.properties
    sed -i 's|database=safedata|database=safedata|g' *-mongo.properties
    
    sed -i 's|credentials=evaluation:***@evaluation|credentials=***:***@evaluation_b|g' *-mongo.properties
    sed -i 's|credentials=statistics:***@statistics|credentials=***:***@statistics_b|g' *-mongo.properties
    sed -i 's|credentials=everydaykline:***@everydaykline|credentials=***:***@everydaykline_b|g' *-mongo.properties
    sed -i 's|credentials=safedata:***@safedata|credentials=***:***@safedata|g' *-mongo.properties
    sed -i 's|27017|3717|g' *-mongo.properties
  fi
  
  if [ ! -z "`grep 'redis' $finalName-spring-context-server.xml`" ]; then
    echo "set prod redis config"
    sed -i 's|redis-cluster|redis|g' $finalName-spring-context-server.xml
    sed -i 's|=redis.mg.addr|=mgsc-***.redis.rds.aliyuncs.com|g' $finalName-*redis.properties
    sed -i 's|=Mogu07550831..|=Mogu07550831**|g' $finalName-*redis.properties
  fi

  cd /plk/source
  cp -f /plk/source/Dockerfile.mvc Dockerfile/Dockerfile.${finalName}
  sed -i "s|{project}|${finalName}|g" Dockerfile/Dockerfile.${finalName}

else
     //次数逻辑为springboot项目逻辑,下篇文章详细展开
fi

sudo docker build -f /plk/source/Dockerfile/Dockerfile.${finalName} -t registry-vpc.cn-shenzhen.aliyuncs.com/moguyun-pre/$dockerStore:$tag .
sudo docker login --username $dockerHubName --password $dockerHubPasswd registry-vpc.cn-shenzhen.aliyuncs.com
sudo docker push registry-vpc.cn-shenzhen.aliyuncs.com/moguyun-pre/$dockerStore:$tag 

if [ "true" == "$isPreUpdate" ] ; then
  sshpass -p Mogupro0601sz ssh root@$preEnvHost "cd /docker/k8s; sh -x ${projectName}.sh $tag 2>&1 &"
fi

if [ "true" == "$isIncUpdate" ] ; then
  sshpass -p Mogupro0601sz ssh root@$preEnvHost "cd /opt/apps; rm -rf $finalName && docker cp  $container:/opt/tomcat/webapps/$finalName ."
fi

3.2.2 springMVC项目的Dockerfile模板

  1. 定义源路径和目标路径,为拷贝文件做准备
  2. 定义RUN_OPTION,JVM内存大小分配【 tomcat 生产内存调优】,字符集编码
  3. 定义pname,这个参数会作为sysinit.sh 的第二个位置参数传递给脚本,最终传入CATALINA_OPTS,tomcat启动加载CATALINA_OPTS实现向pinpoint服务端的注册。【详情参考:通过Dockerfile编写tomcat镜像  pinpoint 客户端配置
  4. 定义RUNPORT,这个参数会作为sysinit.sh 的第三个参数传递给脚本,最终会做一个if判断,如果RUNPORT为""或者为0,tomcat里面的server.xml默认的8080端口就不会改变。【详情参考: 通过Dockerfile编写tomcat镜像

] # vim Dockerfile.mvc

FROM registry.moguyun.com/tomcat:9.26
MAINTAINER lijun moguyun.com

ENV SOURCEPATH {project}
ENV TARGETPATH /opt/tomcat/webapps
ENV RUN_OPTION "-Xms1g -Xmx4g -Xmn256m -Dfile.encoding=UTF-8"
ENV PNAME {project}
ENV RUNPORT 0

ADD $SOURCEPATH $TARGETPATH/$SOURCEPATH

EXPOSE 8080
CMD ["sh", "-c", "/bin/sysinit.sh \"$RUN_OPTION\" \"$PNAME\" \"$RUNPORT\" "]

3.2.3 灰度机器启动镜像脚本

  1. 定义项目名称,用于拼接项目日志路径
  2. 定义pname,通过docker -e加载的参数会覆盖Dockerfile里面定义的pname 
  3. 定义运行端口号,使用默认的桥接模式可以允许所有容器都使用8080端口,暴露在宿主机上的端口自定义 【详情参考: Docker网络模型对比 】
  4. 定义镜像名称,${3} 为镜像tag,通过jenkins传入
  5. 定义远程DEBUG端口号,这个参数被JVM_OPTION加载,JVM_OPTION作为一个变量被RUN_OPTIONS应用,作为sysinit的第一个位置参数,最终被CATALINA_OPTS变量引用,作为一个启动参数注入到tomcat里面  【详情参考: 通过Dockerfile编写tomcat镜像
  6. 通过docker 默认的桥接网络类型启动镜像,docker run -e 指定参数会覆盖在Dockerfile里面定义好的变量【详情参考:Docker run 命令的使用方法

] # vim commom.sh

#!/bin/sh
project=${1}
pro=${project//_/-}
pname=${2}
RUN_PORT=${4}
DEBUG_PORT=${5}
version="registry-vpc.cn-shenzhen.aliyuncs.com/moguyun-pre/env-b:${3}" 
JVM_OPTION="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=${DEBUG_PORT} 
-Duser.timezone=GMT+08 -Dfile.encoding=UTF-8  -server -Xms256m  -Xmx1024m "

echo "/docker/tomcat/logs/$project"
echo "JVM_OPTION=$JVM_OPTION"
echo "a1=$1"
echo "a2=$2"
echo "a3=$3"
echo "a4=$4"
echo "a5=$5"


docker stop $pname && docker rm $pname
docker run -d --name $pname  \
  -v /opt/image_data:/image_data:rw \
  -v /opt/image_data:/opt/image_data:rw \
  -v /etc/hosts:/etc/hosts:ro \
  -v /docker/tomcat/logs/$project:/opt/tomcat/logs:rw \
  -p $RUN_PORT:8080 \
  -p $DEBUG_PORT:$DEBUG_PORT \
  -e RUN_OPTION="$JVM_OPTION" \
  -e PNAME="$pname" \
  $version

sleep 35
docker logs  --tail 400 $pname
  1. 依次传入 project,pname,镜像tag,宿主机上运行端口号,远程debug端口号

] # vim financial.sh

/docker/k8s/common.run financial-shop-server financial $1 6207 9909

四、增量更新灰度应用

开发有时需要增量更新一个类,而不是全量,因为这个服务可能多个开发同时在开发,如果在上一个自由风格项目勾选了【isPreUpdate】,就会把容器里面的代码拷贝一份出来到 /opt/apps目录,然后通过winscp连接到灰度【1.226】机器,把增量类替换/opt/apps目录下的服务,然后docker cp拷贝到容器里面,重启容器。

4.1 增量更新

  • 通过winscp连到灰度机器【1.226】,然后把需要更新的类替换/opt/apps/目录下的指定服务类,下图就是已经把本地的类替换到灰度机器【1.226】的指定目录

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

4.2 重启容器

通过执行restartFinancial就可以重新启动了,我们把restartFinancial定义成一个别名,这个别名执行restartServer.sh脚本

4.2.1 重启服务脚本

  1. $1为应用名称
  2. $2为容器名称

] # vim restartServer.sh

#!/bin/bash
docker cp /opt/apps/$1  $2:/opt/tomcat/webapps/ && docker restart $2 ; docker logs $2 -f --tail 400

4.2.2 定义别名

] # vim ~/.bashrc

alias restartFinancial='sh -x /opt/apps/restartServer.sh financial-shop-server financial'

五、推送镜像到生产

推送镜像到远程仓库主要实现过程就是docker commit容器成镜像,然后docker push到生产镜像仓库。

5.1 参数化构建过程参数

  • 项目名称,包含容器名称和项目名称

  • 镜像仓库名称,定义了生产镜像存放仓库

  • 镜像仓库用户名/密码,用于登录远程镜像仓库

  • 运行节点,灰度机器【1.226】

5.2 执行时shell

  1. 把灰度重启之后不报错的容器打包成镜像
  2. 登录远程镜像仓库,由于本地登录远程镜像仓库的信息隔一段时间就会失效,所以每次push前都会docker login一次
  3. 推送镜像到远程仓库
currentTime=`date "+%Y%m%d%H%M"`
echo $currentTime

projectVals=(${projectVals//,/ })
containerName=${projectVals[0]}
projectName=${projectVals[1]}

echo "cd /opt/apps"
cd /opt/apps

echo "tag=${projectName}_${currentTime}"
tag=${projectName}_${currentTime}

echo "docker commit -m "$containerName is commited at $currentTime" $containerName registry-vpc.cn-***.com/moguyun-prod/env-b:$tag"
docker commit -m "$containerName is commited at $currentTime" $containerName registry-vpc.cn-***.com/moguyun-prod/env-b:$tag

echo "docker login --username $dockerHubName --password $dockerHubPasswd registry-vpc.cn-shenzhen.aliyuncs.com"
docker login --username $dockerHubName --password $dockerHubPasswd registry-vpc.cn-shenzhen.aliyuncs.com

echo "docker push registry-vpc.cn-shenzhen.aliyuncs.com/moguyun-prod/$repository:$tag"
docker push registry-vpc.cn-shenzhen.aliyuncs.com/moguyun-prod/$repository:$tag

六、更新生产镜像

6.1 使用指定镜像更新生产K8S

  • 进入K8S无状态应用编辑页面,选取指定的镜像名称和tag进行更新

6.2 验证应用

  • 无状态应用app-financial管理的pod全部都跑起来了

  • 查看financial应用的启动日志,由图中可以看出启动没有报错

至此:已经完成了SpringMVC应用迁移到生产K8S集群的所有步骤

 
发布了161 篇原创文章 · 获赞 40 · 访问量 12万+

猜你喜欢

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