victor-0807 / blog

个人博客
0 stars 1 forks source link

Spring Boot 主机部署方案 #7

Open victor-0807 opened 6 years ago

victor-0807 commented 6 years ago

1.关于主机部署

1.1. 趋势

部署环境一直在演变,从物理机,虚拟机,到容器化平台,这个趋势是明显的,是不可否认的,这个我们不做过多讨论, 本文所提的方案是在需要主机(物理机、虚拟机)部署的前提下进行的。

1.2.优点

1.3.缺点

2. 部署设计

2.1.运维分工

2.2.用户隔离

  1. 不同项目有共用机器(这个不常见)
  2. 同一个项目,不同环境有共用机器(比如预发布环境与生产环境有部分主机重叠)
  3. 同一个项目,中间件和业务服务共用机器
    #以开放平台项目为例(osp),应用运行在普通用户
    useradd osp && echo Osp_2018 | passwd --stdin osp

    2.3.目录设计

  4. 为了用户隔离,应用部署目录放在用户目录下
  5. 为了避免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 应用部署包

dir.png

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