1.关于主机部署
1.1. 趋势
部署环境一直在演变,从物理机,虚拟机,到容器化平台,这个趋势是明显的,是不可否认的,这个我们不做过多讨论,
本文所提的方案是在需要主机(物理机、虚拟机)部署的前提下进行的。
1.2.优点
- 简单,没有过多的依赖与封装,简单意味着可靠
- 故障定位快,部署环境越复杂,因环境引起的故障定位越困难,当然此话有点绝对,但统计学上看,是这样的
1.3.缺点
资源限制欠缺
比如限制CPU占用,内存占用,虽然java有内存限制的方法,但此方法只适用java,其他开发语言的项目,可能也有,但限制方式不统一,同时很多语言没有限制的方法控制能力不足
比如动态缩容,扩容,这些高级特性,需要自己写服务去实现,容器化平台很多是带这个功能
2. 部署设计
2.1.运维分工
-
运维人员
- 数据库、缓存、存储、操作系统等基础中间件
- 系统监控
- 告警处理进度的跟踪及汇总
- 服务简单的起停操作,日志查看
-
开发人员(自己负责的服务)
- 服务的上线
- 服务线上告警及故障的处理
2.2.用户隔离
- 不同项目有共用机器(这个不常见)
- 同一个项目,不同环境有共用机器(比如预发布环境与生产环境有部分主机重叠)
- 同一个项目,中间件和业务服务共用机器
#以开放平台项目为例(osp),应用运行在普通用户
useradd osp && echo Osp_2018 | passwd --stdin osp
2.3.目录设计
- 为了用户隔离,应用部署目录放在用户目录下
- 为了避免JRE的相互干扰,在~/apps/java目录存放自己的Server JRE(比如server-jre-8u172-linux-x64.tar.gz解压缩到java目录),并在app_env.conf明确指定JAVA_EXE,这样大家升级jdk相互不干扰
/home/osp/apps
- apps
应用部署根目录- java
JRE目录 - {app_code}
具体的应用目录- {server_port}
应用端口号,以支持单机部署多个实例- app_env.conf
应用启动脚本参数文件 - run.sh
应用启动脚本 - logs
应用日志目录,日志文件一定要限制大小,比如springboot 默认的一个文件10M,最多存7个文件 - {app_code}-{app_version}.jar
应用部署包
- app_env.conf
- {server_port}
- java
2.4 应用启动脚本
脚本已经开源,欢迎大家完善这个脚本,开源项目地址:https://github.com/Hanson1330/spring-boot-run
2.4.1. boot.conf
#java程序路径(必需)
JAVA_EXE=~/apps/java/jdk1.8.0_181/bin/java
#注册中心地址(如果未启用注册中心,可注释)
#DISCOVERY_URI="http://user:password@127.0.0.1:8761/eureka/"
#配置文件选择
PROFILES_ACTIVE=dev
#服务端口(必需)
SERVER_PORT=8080
#JVM配置(必需)
JVM_ARGS="-server -Xms256m -Xmx1024m -Djava.io.tmpdir=/var/tmp -Duser.timezone=Asia/Shanghai"
#命令行参数
CMD_LINE_ARGS=""
2.4.2 run.sh
#!/bin/bash
# -------------------------------------------------------------------------------
# version: 2.0
# Date: 2018-08-25 10:00
# Author: Ma Yonglong
# Email: yonglong.ma@hpe.com
# Description: Spring Boot应用运行脚本,支持Mac,Linux
# -------------------------------------------------------------------------------
#应用主目录
cd "$(dirname "$0")" || exit 1
APP_HOME=$(pwd)
LOG_DIR="${APP_HOME}/logs"
PID_FILE="${APP_HOME}/app.pid"
BOOT_CONF="${APP_HOME}/boot.conf"
#应用JAR
APP_JAR="$(find "${APP_HOME}" -name "*.jar" 2>/dev/null | head -n 1)"
#应用名称
APP_NAME=${APP_JAR##*/}
APP_NAME=${APP_NAME%.jar}
#操作
ACTION=$1
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
usage() {
echo $'\n\n\n'
echoRed "Usage: ${0} support command {start|stop|restart|status|cleanup}"
echo $'\n\n\n'
exit 1
}
psCheck() {
echo "--------------All instances on this machine--------------"
echo "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND" && echo ""
ps aux | grep "$APP_NAME" | grep -E -v "grep"
}
#根据PID_FILE检查是否在运行
isRunning() {
[[ -f "$PID_FILE" ]] || return 1
ps -p "$(<"$PID_FILE")" &>/dev/null
}
#1.检查操作参数
[ $# -gt 0 ] || usage
#2.引入启动配置
if [ -r "$BOOT_CONF" ]; then
. "$BOOT_CONF"
else
echoRed "Missing or unreadable $BOOT_CONF"
echo $'\n\n\n'
exit 1
fi
#基础配置
BASE_ARGS="--spring.profiles.active=$PROFILES_ACTIVE --server.port=$SERVER_PORT"
if [ ! "$DISCOVERY_URI" = "" ]; then
BASE_ARGS="$BASE_ARGS --eureka.client.serviceUrl.defaultZone=$DISCOVERY_URI"
fi
RUN_EXE="$JAVA_EXE $JVM_ARGS -jar $APP_JAR $BASE_ARGS $CMD_LINE_ARGS"
start() {
echo "--------------Starting $APP_NAME:"
echo $'\n\n\n'
#检查jdk
if [ -z "$JAVA_EXE" ]; then
echoRed "Result: Start failed,Cannot find a Java JDK. Please check JAVA_EXE in boot.conf"
echo $'\n\n\n'
exit 1
fi
#检查已经运行
if (isRunning); then
echoYellow "Result: Running, no need to start"
echo $'\n\n\n'
exit 0
fi
#打印启动命令
echo "-------Boot Command: "
echo "nohup $RUN_EXE >/dev/null 2>${LOG_DIR}/error.log &"
echo $'\n\n\n'
#创建错误日志文件
mkdir -p "$LOG_DIR" && touch "${LOG_DIR}/error.log"
#启动
nohup $RUN_EXE >/dev/null 2>>"${LOG_DIR}/error.log" &
#记录pid到pid文件
echo $! >"$PID_FILE"
#命令执行异常,快速失败
sleep 0.5
if (! isRunning); then
echoRed "Result: Start failed" && rm -f "$PID_FILE"
echo $'\n\n\n'
exit 1
fi
#启动几秒钟中后失败的情况,6秒内失败
sleep 6
if (! isRunning); then
echoRed "Result: Start failed" && rm -f "$PID_FILE"
echo $'\n\n\n'
exit 1
fi
#启动几秒钟中后失败的情况,10秒内失败
sleep 4
if (! isRunning); then
echoRed "Result: Start failed" && rm -f "$PID_FILE"
echo $'\n\n\n'
exit 1
fi
#启动几秒钟中后失败的情况,启动在10秒外失败的比例比较低,而且也不可能一直等,这种情况交给监控告警来解决
echoGreen "Result: Start success,Running (PID: $(<"$PID_FILE"))"
echo $'\n\n\n'
#检查本机存在的实例
psCheck
}
stop() {
echo "--------------Stopping $APP_NAME:"
echo $'\n\n\n'
if (! isRunning); then
echoYellow "Result: Not running" && rm -f "$PID_FILE"
echo $'\n\n\n'
return 0
fi
kill "$(<"$PID_FILE")" 2>/dev/null
#30秒后强制退出
TIMEOUT=30
while isRunning; do
if ((TIMEOUT-- == 0)); then
kill -KILL "$(<"$PID_FILE")" 2>/dev/null
fi
sleep 1
done
rm -f "$PID_FILE"
echoGreen "Result: Stop success"
echo $'\n\n\n'
}
status() {
echo "--------------Status $APP_NAME:"
echo $'\n\n\n'
if isRunning; then
echoGreen "Result: Running (PID: $(<"$PID_FILE"))"
else
echoYellow "Result: Not running"
fi
echo $'\n\n\n'
psCheck
}
cleanup() {
echo "--------------Cleanup $APP_NAME:"
echo $'\n\n\n'
if ! isRunning; then
[[ -d "$LOG_DIR" ]] || {
echoGreen "Result: Log does not exist, there is no need to clean up" && echo $'\n\n\n'
return 0
}
rm -rf "$LOG_DIR"
echoGreen "Result: Log cleared"
else
echoYellow "Result: Please stop the application first and then clean up the log"
fi
echo $'\n\n\n'
}
case "$ACTION" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
cleanup)
cleanup
;;
*)
usage
;;
esac
#成功退出
exit 0