部署方案@应用系统单点切换解决方案
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}