部署方案@应用系统单点切换解决方案

1 背景介绍

  1. 对于应用主要部署在云虚拟机上,云虚拟机作为IT服务最主要也是最重要的基础设施,其稳定性和可用性对公司业务的影响至关重要。
  2. 当生产云虚拟机发生重大故障时,由于应用节点大都是单节点部署,只能选择处理时间较长的主机恢复方案进行修复,因此导致服务停机长达数小时,部分业务处理中断。因此,提高服务的可用性成为了眼下急需解决的问题。
  3. 对于java应用部署方式为两种:基于tomcat的war部署以及普通的jar部署。不规范不统一的部署,给日常版本发布以及系统运维带来一些额外的成本,而故障恢复也需要一个统一且简单的操作以减少恢复时间,因此,规范应用的部署,并实现自动化是首先要解决的问题。

2 整体方案

要消灭单点问题,最终需要全部更换为集群部署方案。应用服务的集群部署包括:需针对实时服务、定时任务、工作流以及其他存在前后依赖关系的任务等,制定对应的集群方案,这是一个相对漫长且复杂的过程。但是,集群部署除了需要更多的主机资源之外,还需要对各个系统进行适应性改造。因此,整体方案上,采用循序渐进的方式从单节点部署过渡到集群部署上,结合已有的主机资源,过渡方案采用冷备方式,虽然不能直接提升服务的可用性,但可以大大减少故障恢复的时间。

  1. 对每个应用系统的主机进行克隆冷备,平时不启动,因此只消耗部分硬盘资源,不占用稀缺的内存和CPU资源,故障发生时,先将原来的主机直接下线,然后启动克隆主机,部署最新的应用版本后即可恢复服务,服务恢复时间从原来的数小时降低到半小时以内。
  2. 对于应用的部署,从应用的系统用户、权限、部署目录结构、日志的命名以及日志文件的切换等这几个方面进行规范,力求规格统一。同时,采用Jenkins+maven实现自动发布部署,出于系统安全考虑,测试环境和生产环境需要隔离。

3 部署工作

3.1 应用部署规范

  • 3.1.1 方案说明

用户

组名称 组ID 备注
staff 600

用户名称 用户ID 用户目录
601 staff /home/
cxwh 602 staff /home/cxwh

应用目录结构

目录 属主/组 备注
/data/ {应用用户名}:staff 应用存放目录
/data/{应用用户名}/java {应用用户名}:staff Jdk软件安装包存放目录
/data/{应用用户名}/tomcat {应用用户名}:staff tomcat安装包存放目录
/data/{应用用户名}/config {应用用户名}:staff 项目环境相关配置文件,一般是xxx.properties
/data/{应用用户名}/application {应用用户名}:staff 当前运行版本的版本目录,采用软链接到release对应版本
/data/{应用用户名}/deploy {应用用户名}:staff 发版时文件存放位置
/data/{应用用户名}/release {应用用户名}:staff 应用文件发版后存放的位置
/data/{应用用户名}/tools {应用用户名}:staff 应用相关工具存放位置,例如版本发布脚本
/data/{应用用户名}/logs {应用用户名}:staff 应用,脚本日志目录
/data/{应用用户名}/temp {应用用户名}:staff 临时文件目录
/data/{应用用户名}/resource {应用用户名}:staff 数据文件等资源存放位置
/data/{应用用户名}/backup {应用用户名}:staff 备份目录

包部署命名规范

应用简称-版本号-日期-环境标识.war
其中,
应用简称: 一般与应用用户名一致
版本号: x.x.x,x是整数,可以是两位
日期: 格式是yyyymmdd
环境标识: 与maven上的profile的id一致,一般是dev/sit/uat/prod,分别表示开发/集成测试/用户验收测试/生产

日志规范

  1. 基于tomcat的应用
    Tomcat日志文件名:tomcat.应用简称.YYYYMMDD.log,以天为单位存储切割
    应用日志文件名:应用简称.YYYYMMDD.log,以天为单位存储切割
  2. 其他应用
    由于存在多样性,原则上不做要求
  • 3.1.2 实施步骤
  1 按用户与组新建系统组以及用户
  2 按卷组、逻辑卷规划新建/调整文件系统
  3 按应用部署结构规划新建目录并分配权限
  4 根据需要在指定目录下安装java、tomcat等软件
  5 配置java,tomcat等参数,系统用户主目录下的.bash_profile配置,tomcat的server.xml配置
  6 将应用生产版本按目录结构规范分别存放部署
  7 停止旧的应用服务
  8 启动新部署的应用服务
  9 检查验证应用服务

3.2 自动化部署

  • 3.2.1 方案说明

针对目前产线上,每次投产发布应用时,研发人工部署的情况比较多,使得运维工作开展时,需要研发人员紧密配合。为了提高效率和实时性,搭建一套自动化部署平台,将应用系统的部署发布流程做成自动化模式。这样在产线投产时,避免人工部署的情况,降低出错几率,加速发布流程。同时流程得以标准化,便于系统管理与日常运维。最后在应用系统的部署发布流程中,可以做到即使没有对应的应用系统的研发人员,运维人员也可自行发包,提高运维效率。

  • 3.2.2 实施步骤
  1 申请自动化部署平台服务器,安装常用软件以及本次使用的开源框架Jenkins。
  2 根据应用部署规范,将应用服务器的用户、目录进行标准化统一,编写日常运维脚本,如发布、启停等脚本。
  3 收集各应用系统发版流程,根据项目的特点制定自动化部署流程,最后归档成操作手册。
  4 将操作手册分发到各应用服务负责人,按照操作手册,部署配置Jenkins。
  5 配置用户权限以及Jenkins的网络访问权限,以最小化原则配置Jenkins,使得特定角色和特定环境才可以访问。
  6 选择发版日,应用系统逐个进行自动化发版验证。

4 附录

4.1 规范目录创建创建脚本

  • 脚本create_app.sh 代码

if [ $# -lt 1 ];then
    echo "usage:$0 user_name"
    echo "其中:"
    echo "user_name 应用系统用户名"
    exit 0
fi

user_name=$1

change_pswd()
{
expect <<EOF
   set timeout 3
   spawn passwd $1
   expect "*新的 密码*"
   send "$2\r"
   expect "*新的 密码*"
   send "$2\r"
   expect "*成功更新*"
EOF
}

result=`type expect |grep "expect is"`
if [ "$result" == "" ];then
    echo "需要安装 expect 工具"
    yum install expect -y
fi

echo "即将创建系统用户名为[$user_name]的应用部署环境"
#echo "按任意键继续(ctrl+c 取消)..."
#read anykey

flag=`cat /etc/group |grep -w staff`
if [ "$flag" == "" ];then
    echo "新建[staff]组"
    groupadd -g 600 staff
fi

flag=`cat /etc/passwd |grep -w cxwh`
if [ "$flag" == "" ];then
    echo "新建[cxwh]用户(默认密码是:cxwh)"
    useradd -g staff cxwh
    change_pswd cxwh cxwh
fi

flag=`cat /etc/passwd |grep -w ${user_name}`
if [ "$flag" != "" ];then
    echo "[${user_name}]用户已经存在,不需要创建!"
else
    echo "新建[${user_name}]用户(默认密码:${user_name})"
    useradd -g staff ${user_name}
    change_pswd ${user_name} ${user_name}
fi

mkdir -p /data/${user_name}
mkdir -p /data/${user_name}/java
mkdir -p /data/${user_name}/tomcat
mkdir -p /data/${user_name}/application
mkdir -p /data/${user_name}/config
mkdir -p /data/${user_name}/deploy
mkdir -p /data/${user_name}/release
mkdir -p /data/${user_name}/tools
mkdir -p /data/${user_name}/logs
mkdir -p /data/${user_name}/temp
mkdir -p /data/${user_name}/resource
mkdir -p /data/${user_name}/backup

chown -R ${user_name} /data/${user_name}

bash_profile="/home/${user_name}/.bash_profile"

cat <<!! > $bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

# User specific environment and startup programs

PATH=\$PATH:\$HOME/bin

export PATH

export JAVA_HOME=/data/${user_name}/java
export PATH=.:/data/${user_name}/tools:\$JAVA_HOME/bin:\$PATH

alias cdd="cd /data/${user_name}/deploy"
alias cdl="cd /data/${user_name}/logs"
alias cda="cd /data/${user_name}/application"

set -o vi

!!

echo "创建完成."

  • 脚本create_app.sh 使用

1 用root登录应用对应的主机,然后用create_app.sh执行创建用户环境操作就可以了,比如app

  1 用root登录 
  2 把create_app.sh脚本传上去
  3 执行脚本 sh create_app.sh app
  4 验证一下用户,目录是否按规范创建好

2 脚本会执行以下操作

  1 如果没有安装 expect ,执行 yum install expect -y 安装
  2 如果没有 staff 组,执行 groupadd -g 600 staff 创建
  3 如果没有 cxwh 用户,执行 useradd -g staff cxwh 创建,初始密码与用户名一样
  4 假设要创建的用户是 pad,如果系统还没有 pad 用户, 执行 useradd -g staff pad 创建,初始密码与用户名一样
  5 在/data/${user_name}目录下用mkdir新建整套应用部署目录(如果已经存在,不受影响),并授权 chown -R ${user_name} /data/${user_name}
  6 修改 /home/${user_name}/.bash_profile 配置,主要是PATH,JAVA_HOME,alias

4.2 项目规范部署执行脚本

  • 脚本deploy.sh 代码
#!/bin/bash
source ~/.bash_profile

app_name=$1

#判断执行脚本是否输入应用名
if [ $# -lt 1 ];then
    echo "执行请输入: $0 app_name" >>$log_file 2>&1
    echo "其中app_name为应用名称" >>$log_file 2>&1
    cat $log_file
    exit 2
fi

#获取当前用户名
user_name=`whoami`

#获取当前时间
date=`date +%Y%m%d%H%M%S`

#获取当前服务器IP地址
LOCAL_IP=`ip addr show eth0 | grep global | awk -F ' ' '{print $2}' | sed 's/\/24//'`

logs_path=/data/${user_name}/logs
deploy_path=/data/${user_name}/deploy
release_path=/data/${user_name}/release
application_path=/data/${user_name}/application
backup_path=/data/${user_name}/backup/release
tomcat_path=/data/${user_name}/tomcat
#部署过程日志
log_file=${logs_path}/deploy.${app_name}.${date}.log


#备份IP信息
backup_server_list=(
#192.168.90.11
192.168.80.13
)
BACKUP_USER="appbak"
BACKUP_PSWD="Appbak%110"
BACKUP_PORT="22"

#写日志函数
log()
{
    echo "[`date +'%Y%m%d %H%M%S'`] $1 $2" >> $log_file
}
log_info()
{
    log "info " "$1"
}
log_warn()
{
    log "warn " "$1"
}
log_error()
{
    log "error " "$1"
}

#war包部署过程
echo "发版日志文件: ${log_file}"
log_info "${LOCAL_IP}应用[${app_name}]开始发版"

cd ${deploy_path}
log_info "1.在${deploy_path}目录下查找待发布的war包"
var=`ls *${app_name}*.war`
if [ "$var" == '' ];then
    log_error "待发布的war包不存在,发布失败!"
    cat ${log_file}
    exit 3
fi
release=${var%%.war}

log_info "2.待发布的war包: ${release}.war"


log_info "3.本地备份war包: ${release}.war --> ${backup_path}"
cp ${release}.war ${backup_path}/ >> ${log_file} 2>&1

#判断远程备份IP是否存在
port_test()
{
    echo ""|telnet $1 $2 2>/dev/null | grep "\^]" | wc -l
}
for element in ${backup_server_list[@]}
do 
    result=`port_test ${element} ${BACKUP_PORT}`
    if [ "${result}" == "1" ];then
    BACKUP_SERVER=${element}
    break
    fi
done
if [ "${BACKUP_SERVER}" == "" ];then
    log_error "无可用的远程备份主机,无法远程备份,不能发版!"
    cat $log_file
    exit 4
fi
BACKUP_BACK_PATH="application/${BACKUP_SERVER}/${user_name}"

log_info "4.远程备份war包: ${release}.war --> ${BACKUP_USER}@${BACKUP_SERVER}:${BACKUP_BACK_PATH}"

#远程备份war包函数
sftp_sample()
{
expect <<EOF
   set timeout 3                      
   spawn sftp -oPort=${BACKUP_PORT} ${BACKUP_USER}@${BACKUP_SERVER}
   expect {
    "*yes/no*" { send "yes\r"; exp_continue }
    "*password*" { send "${BACKUP_PSWD}\r" }
    }
   expect "sftp>"
   send "cd ${BACKUP_BACK_PATH}\r"
   expect "sftp>"
   send "put ${release}.war\r"
   expect "sftp>"
   send "exit\r"
EOF
}
sftp_sample 2>&1 >/dev/null

log_info "5.删除当前应用版本的软链接"
rm -rf ${application_path}/${app_name}
rm -rf ${release_path}/${app_name}*

log_info "6.解压新版本war包,并建立新版本的软链接"
unzip -o ${release}.war -d ${release_path}/${release} 2>&1 >/dev/null
ln -s ${release_path}/${release} ${application_path}/${app_name} >>$log_file 2>&1

bin_path=${application_path}/${app_name}/sbin
tag="java"
if [ ! -d ${bin_path} ];then
    bin_path=${tomcat_path}/bin/
    tag="tomcat"
fi

log_info "7.服务启停脚本目录:$bin_path"

log_info "8.停止${tag}服务"
sh ${bin_path}/shutdown.sh >>$log_file 2>&1
sleep 10
for p in `jps | grep Bootstrap | awk '{print $1}'`
do
       kill -9 $p
done

jps | grep Bootstrap 2>&1 >/dev/null
if [ "$?" = "0" ];then	
	echo "${tag}服务停止失败,请查验!" >>$log_file 2>&1
	cat ${log_file}
	exit 5
else
	echo "${tag} stoped." >>$log_file 2>&1
fi
#for p in `jps | grep Bootstrap | awk '{print $1}'`
#do
#	kill -9 $p
#done

log_info "9.启动${tag}服务"
sh ${bin_path}/startup.sh >>$log_file 2>&1

sleep 30

jps | grep Bootstrap >>$log_file 2>&1
if [ "$?" = "0" ];then	
	log_info "10.发版完成,请查看tomcat日志确保服务正常启动,并做生产验证."
else
	log_info "10.${tag}服务启动失败,请查验!"
	cat ${log_file}
	exit 6
fi

rm -rf ${deploy_path}/${release}.war
cat ${log_file}

posted @ 2020-02-17 15:57  默月  阅读(510)  评论(0编辑  收藏  举报