zoukankan      html  css  js  c++  java
  • 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
    
    
  • 相关阅读:
    mysql8.0.x中datasource信息
    IDEA关联mysql失败Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezon'
    SpringSecurity配置文件
    druid监控
    Redis安装教程
    一个简单的springboot+mybatis-plus+thymeleaf的学生管理系统
    RestFul风格
    Vue利用v-for渲染时表单信息出不来
    springboot项目Invalid bound statement (not found): com.xxxx.dao.xxxDAO.xxx解决方法
    JSON
  • 原文地址:https://www.cnblogs.com/caibh/p/13812460.html
Copyright © 2011-2022 走看看