Java Spring Boot 打包部署方案

Java Spring Boot 打包部署方案

背景

最近在做一个内网的项目,项目前后端分离,后端用Spring Boot,前端用Ant Design React。部署的环境是学校内网,部署是通过4
G网卡+VPN连接到内网的机器部署的,考虑到Docker需要连接网络且镜像很大,我们采用最原始的部署方式:后端打成jar包传到服务器上,然后java -jar xxx.jar运行。

这其中就遇到了一些问题:

  1. 网络慢,打出来的jar包60M左右,要传10分钟。而大部分时候jar包中以来的其他库都是相同的,所以想把依赖的java包分离出来。
  2. 在机器上部署工序繁杂,大部分都是备份、复制、重启等操作。所以想写个shell脚本减少部署的工作量。
  3. 配置文件也要从 Spring Boot 的 jar 包中分离出来,方便在服务器上配置

打包配置

项目采用Gradle做依赖管理,我在build.gradle中写了自定义的任务packTarpackTarWithoutLibs,分别可以打出含有依赖库和不含依赖库的tar.gz包,而这个包解压后的结构是这样的:

app-server-1.0.0
|-bin/
| 	|-app.sh # 应用脚本,能用于启动、停止、查看状态、查看日志、安装、升级
|-config/
|	|-application.yml #配置文件
|-lib/
|	|-a.jar # 依赖的jar
|	|-b.jar
|	|-c.jar
|-app.jar # 应用的jar

以下就是我的Gradle打包配置:

build.gralde:

buildscript {
    repositories {
        maven {
            url 'https://maven.aliyun.com/nexus/content/groups/public'
        }
    }
    // 引入 spring boot 插件
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.3.3.RELEASE"
    }
}

group = 'com.xxx'
version = '1.0.12'

// 插件
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

sourceCompatibility = 1.8

configurations {
    compileClasspath {
        extendsFrom annotationProcessor
    }
}

repositories {
    maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
}

// 依赖
dependencies {
    // ......
    implementation 'org.springframework.boot:spring-boot-starter-web'
	// ......
}

// =========================== 打包 ===========================

// 复制依赖的jar
task copyJar(type: Copy) {
    fileMode 0755
    delete "$buildDir/libs/lib"
    from configurations.default
    into "$buildDir/libs/lib"
}

// 删除依赖的jar
task removeLibs(type: Delete) {
    delete "$buildDir/libs/lib"
}

// 复制配置文件
task copyConfig(type: Copy) {
    fileMode 0755
    delete "$buildDir/libs/config"
    from('src/main/resources') {
        include 'application-prod.yml'
    }
    rename 'application-prod.yml', 'application.yml'
    into "$buildDir/libs/config"
}

// 复制脚本
task copyScript(type: Copy) {
    fileMode 0755
    delete "$buildDir/libs/bin/app.sh"
    from('src/main/resources') {
        include 'app.sh'
    }
    into "$buildDir/libs/bin"
}

bootJar {
    fileMode 0755
    archiveFileName = 'app-server.jar'
    // 例外所有的jar
    excludes = ["*.jar",'application.yml','application-prod.yml','app.sh']
    // lib目录的清除和复制任务
    dependsOn copyJar
    dependsOn copyConfig
    dependsOn copyScript
    // 指定classpath
    manifest {
        attributes "Manifest-Version": 1.0,
                   "Class-Path": configurations.default.files.collect {"lib/$it.name"}.join(' ')
    }
}

// 打Tar包
task packTar(type: Tar) {
    dependsOn bootJar
    archiveFileName = "app-server-${archiveVersion.get()}.tar.gz"
    destinationDirectory = file("$buildDir/dist")
    from "$buildDir/libs"
    fileMode 0755
}

// 打Tar包
task packTarWithoutLibs(type: Tar) {
    dependsOn bootJar
    dependsOn removeLibs
    archiveFileName = "app-server-nolib-${archiveVersion.get()}.tar.gz"
    destinationDirectory = file("$buildDir/dist")
    from "$buildDir/libs"
    fileMode 0755
}

应用部署脚本包含有前后端部署安装的逻辑,假设应用名称是app,app应用有两个模块serverbrowser,shell脚本如下:

app.sh:

#!/bin/bash

# 程序名
APP=app
# 程序目录
APP_PATH="/usr/local/iot/$APP"
SERVER_NAME="$APP-server"
SERVER_JAR="$APP-server.jar"
TODAY="`date +%Y%m%d`"

# 启动前需要设置的环境变量
setEnvVars(){
  # 如果使用默认的glibc的ptmalloc2内存分配器,为避免64M arena问题,应加上这个参数
  # export MALLOC_ARENA_MAX=4
  # 使用jemalloc内存分配器
  export LD_PRELOAD=/usr/local/lib/libjemalloc.so
  # jvm崩溃是导出core文件
  ulimit -c unlimited
}

# JVM启动参数
JVM_FLAGS=(
    '-Xms500M'
    '-Xmx1500M'
    '-XX:NativeMemoryTracking=detail'
)

#使用说明,用来提示输入参数
usage() {
    echo -e "Usage: ./app.sh <command>"
    echo -e "commands:"
    echo -e "	 status"
    echo -e "	 start"
    echo -e "	 stop"
    echo -e "	 restart"
    echo -e "	 log"
    echo -e "	 install <module> <version>"
    echo -e "	 update <server-part> <version>"
    echo -e "<module>:"
    echo -e "	 server"
    echo -e "	 browser"
    echo -e "<server-part>:"
    echo -e "	 jar"
    echo -e "	 config"
    echo -e "<version>:"
    echo -e "	 when install, it can install sever or browser module tar.gz file like:"
    echo -e "	 - $APP-server-1.0.11.tar.gz"
    echo -e "	 - $APP-browser-1.0.11.tar.gz
"
    echo -e "	 when update, it can only update server module from tar.gz file like:"
    echo -e "	 - $APP-server-nolib-1.0.11.tar.gz"
    exit 1
}
 
############################### 检查状态 ###############################
is_exist(){
  pid=`ps -ef|grep $SERVER_JAR|grep -v grep|awk '{print $2}' `
  #如果不存在返回1,存在返回0
  if [ -z "${pid}" ]; then
   return 1
  else
    return 0
  fi
}

############################### 重启 ###############################
restart(){
  stop
  start
}

############################### 启动 ###############################
start(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${SERVER_JAR} is already running. pid=${pid} ."
  else
    cd "$APP_PATH/server"
    setEnvVars
    nohup java ${JVM_FLAGS[*]} -jar $SERVER_JAR >/dev/null 2>&1 &
    status
  fi
}
 
############################### 停止 ###############################
stop(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "kill Java process: $pid"
    kill -9 $pid
  else
    echo "${SERVER_JAR} is not running"
  fi
}
 
############################### 查看状态 ###############################
status(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${SERVER_JAR} is running. Pid is ${pid}"
  else
    echo "${SERVER_JAR} is NOT running."
  fi
}

############################### 查看日志 ###############################
log(){
  tail -fn 1000 "$APP_PATH/log/$APP.log"
}

############################### 更新server模块的jar或config ###############################
update(){
  serverPart=$1
  version=$2
  if [ -z "$serverPart" ] ;then
    echo "invalid <server-part>, it can't be empty. example: $APP update jar 1.0.11, 'jar' is the <server-part>"
    exit 1
  fi
  if [ "$serverPart" != "jar" ] && [ "$serverPart" != "config" ] ;then
    echo "invalid <server-part>, it must be 'config' or 'jar', like: $APP update jar 1.0.11"
    exit 1
  fi
  if [ -z "$version" ] ;then
    echo "invalid <version>, it can't be empty. example: $APP update jar 1.0.11, '1.0.11' is the <version>"
    exit 1
  fi
  versionFile="$APP_PATH/version/$APP-server-nolib-$version.tar.gz"
  if [ ! -f "$versionFile" ]; then
    echo "invalid <version>, corresponding tar.gz file not exist: $versionFile"
    exit 1
  fi
  if [ "$serverPart" == "jar" ]; then
    updateJar "$version"
  fi
  if [ "$serverPart" == "config" ]; then
    updateConfig "$version"
  fi
}

######################### 将version目录下指版本的tar.gz文件copy到temp目录下解压出来 #########################
extractVersionTagFile(){
  version=$1
  tarFile="$APP_PATH/version/$SERVER_NAME-nolib-$version.tar.gz"
  tempDir="$APP_PATH/temp/$version"
  echo -e "copy to tempdir: $tarFile --> $tempDir"
  rm -rf "$tempDir"
  mkdir -p "$tempDir"
  cp "$tarFile" "$tempDir"

  echo -e "extract file:	 $tempDir/$SERVER_NAME-nolib-$version.tar.gz"
  cd "$tempDir"
  tar -xf "$SERVER_NAME-nolib-$version.tar.gz"
  rm "$SERVER_NAME-nolib-$version.tar.gz"
}

######################################### 更新jar文件 #########################################
updateJar(){
  # print version
  version=$1
  echo "update jar of version: $version"
  if  [ -z "$version" ] ;then
    echo "version is empty"
    exit 0
  fi
  # backup old jar
  oldJar="$APP_PATH/server/$SERVER_JAR"
  if [ -f $oldJar  ]; then
    echo -e "backup file:	 $oldJar --> $oldJar.bak"
    mv -f "$oldJar" "$oldJar.bak"
  fi
  # copy tar.gz file in version/ to temp/ and extract it
  extractVersionTagFile $version
  # copy new jar to server/
  echo -e "replace jar:	 $tempDir/$SERVER_JAR --> $APP_PATH/server/$SERVER_JAR"
  cp "$tempDir/$SERVER_JAR" "$APP_PATH/server/$SERVER_JAR"
}

######################################### 更新application.yml配置文件 #########################################
updateConfig(){
  # print version
  version=$1
  echo -e "update config of version: $version"
  if  [ -z "$version" ] ;then
    echo "version is empty"
    exit 0
  fi
  # backup old config file
  oldConfig="$APP_PATH/server/config/application.yml"
  if [ -f $oldConfig  ]; then
    echo -e "backup file:	 $oldConfig --> $oldConfig.$TODAY.bak"
    mv -f "$oldConfig" "$oldConfig.$TODAY.bak"
  fi
  # copy tar.gz file in version/ to temp/ and extract it
  extractVersionTagFile $version
  # copy new jar to server/config/
  echo -e "replace config:	 $tempDir/config/application.yml --> $APP_PATH/server/config/application.yml"
  cp "$tempDir/config/application.yml" "$APP_PATH/server/config/application.yml"
}

######################################### 安装 #########################################
# 语法:
#   app install <module> <version>
# 例如:
#   app install server 1.0.11
#   app install browser 1.0.8
install(){
  module=$1
  version=$2
  versionFile="$APP_PATH/version/$APP-$module-$version.tar.gz"
  if [ ! -f "$versionFile" ]; then
      echo "file not exist: $versionFile"
      exit 1
  fi
  # 备份
  oldModuleDir="$APP_PATH/$module"
  if [ -d $oldModuleDir  ]; then
    echo -e "backup module dir:	 $oldModuleDir --> $oldModuleDir-bak"
    rm -r "$oldModuleDir-bak"
    mv -f "$oldModuleDir" "$oldModuleDir-bak"
  fi
  installDir="$APP_PATH/$module" # example: /usr/local/iot/app/server
  mkdir -p "$installDir"
  echo -e "copy tar.gz:	 $versionFile --> $installDir"
  cp "$versionFile" "$installDir"
  # extact
  cd "$installDir"
  echo "extract tar.gz: $APP-$module-$version.tar.gz"
  tar -xf "$APP-$module-$version.tar.gz"
  rm "$APP-$module-$version.tar.gz"
  # install server module
  if [ "$module" == "server" ]; then
    echo "install server completed."
    echo "!!!! remember to restart server module: app restart !!!! "
  fi
  # or install browser module
  if [ "$module" == "browser" ]; then
    echo "install browser completed. change dir permission 755..."
    # change the browser module dir permission
    chmod 755 -R "$installDir"
    echo "!!!! remember to reload nginx: sudo nginx -s reload !!!! "
  fi
}


 
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
  "start")
    start
    ;;
  "stop")
    stop
    ;;
  "status")
    status
    ;;
  "log")
    log
    ;;
  "restart")
    restart
    ;;
  "update")
    update $2 $3
    ;;
  "install")
    echo "install 2: $2, 3: $3"
    install $2 $3
    ;;
  *)
    usage
    ;;
esac

原文地址:https://www.cnblogs.com/caibh/p/13812460.html