likes
comments
collection
share

如何优雅地部署Springboot in Ubuntu

作者站长头像
站长
· 阅读数 7

前情提要

众所周知,Springboot内置了Tomcat,默认打jar包,部署非常方便

$ java -jar test.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.1)

好,舒服了,下班!

别急

在你关上电脑的那一瞬间,终端与服务器断开连接,Springboot后端直接BOMB

好的,你可以加班了

Why

SSH终端中开启的进程默认是终端的子进程,终端关闭后,子进程也会一并停止(收到SIGHUP信号)

肿么办

nohup java -jar test.jar &

问题不大,只要加上nohup&即可

  • nohup:No Hang UP,意为忽略挂断信号(SIGHUP)。即使SSH会话结束,这个进程仍会继续运行
  • &:将命令放入后台执行。当你在命令的末尾加上&,这个命令会立即返回,而不会阻塞当前的终端

完美,酱紫Springboot就可以在后台安稳运行了

下班!

等一下

没五分钟,前端坐不住了:

诶,你这个怎么500 Internal Server Error

你又回到了工位上

打开电脑,连上服务器,面对空白的终端,心想:

诶,我日志呢

尬住了

不过好在,ls一下,发现test.jar同目录多了一个nohup.out文件,还好里面就是日志,呼呼呼

自定义日志路径

不过,有没有办法自定义一下日志文件名和目录呢

nohup java -jar test.jar > test.log 2>&1 &

可以使用>符号,来重定向标准输出

  • > test.log:重定向标准输出(标识为1)到test.log文件
  • 2>&1:将标准错误(2)重定向到标准输出(&1)(取地址符!,C++人狂喜)

好的,酱紫,标准输出,和错误输出,都到test.log文件了,完美

太Perfect了,我就是命令行的神,下班!

坐下

前端说的500 Internal Server Error还没解决呢,下啥班

哦哦,那行吧

看了眼日志,在IDEA里吭哧吭哧一顿改,重新打包,拷贝到服务器上

重启

那么问题来了,如何重启Springboot

这个简单

ps -aux | grep java

然后再根据进程ID,执行kill

完美,接下来只要再依葫芦画瓢进行java -jar就好啦hhhhhhh

哦 又忘了 nohup

配置文件

话说,Springboot默认在工作目录下寻找.yml | .properties配置文件,而不是.jar同目录下哦

配置文件加载优先级:

  • 当前工作目录(从中启动应用的目录)下的/config子目录。
  • 当前工作目录
  • classpath下的/config包。
  • classpath的根路径。

酱紫最好手动指定一下绝对路径哦

nohup java -jar test.jar --spring.config.location=file:/www/Springboot/TestDir/ > test.log 2>&1 & 

好的,怎么好像手敲的压力开始增大了 /汗

(小声:还有虚拟机参数 -Xmx1024M -Xms256M 巴拉巴拉的)

(小声:还有开机自启动巴拉的)

Systemd 服务进程

不会吧,不会吧,不会还有人命令行裸敲java -jar,美其名曰部署

Systemd

systemd 是 Linux 上的一种系统和服务管理器,用于管理系统上运行的进程和服务。systemd 服务是一种可以由 systemd 管理的后台进程或服务

我们可以将Springboot程序制作为systemd服务,就可以方便地启动、停止、重启以及状态检测

How

首先,我们需要将Springboot程序注册为Systemd服务

很简单,只要在/etc/systemd/system/目录下,创建myapp.service文件即可(服务单元文件,文件名即服务名)

[Unit]
Description=My Spring Boot Application
# 服务描述:这里描述了服务的功能或用途
After=syslog.target network.target
# 依赖性和顺序:指定了该服务在哪些服务之后启动。这里指定了在syslog和网络服务(network.target)之后启动。

[Service]
User=somebody     
# 运行服务的用户
WorkingDirectory=/opt/Test/ 
# 工作目录
ExecStart=java -jar Test-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
# 启动命令:指定启动服务时执行的命令
SuccessExitStatus=143
# 成功退出状态码:当服务以这些状态码退出时,将被视为正常退出。143是Spring Boot应用在接收到SIGTERM信号时的标准退出代码

[Install]
WantedBy=multi-user.target
# 安装设置:指定当使用 'systemctl enable' 命令启用服务时,该服务应该被哪个目标所引用
# 这意味着,当系统达到multi-user.target时,你的服务会自动启动
# multi-user.target 适用于多用户的运行级别,这意味着服务将在系统达到多用户模式时启动

有几点需要注意

  1. Springboot配置为系统服务后,不需要再nohup,默认开启新后台进程
  2. 我们指定了工作目录,会自动寻找配置文件,不需要再指定
  3. 关于工作目录的选址,最好是放在/opt/下,而不是用户的home目录,这样方便之后Nginx访问静态资源,而且这个目录是专门用于第三方应用的
  4. 关于User,随便指定一个普通用户就行了(最好不要root)
  5. 一般只需要更改WorkingDirectory & ExecStart,其他的无伤大雅

完成之后,我们需要通知systemd服务重载配置文件

sudo systemctl daemon-reload

然后启动myapp服务

sudo systemctl start myapp

服务默认是没有开机自启动的,不过也可以方便地开启或关闭

sudo systemctl enable myapp
sudo systemctl disable myapp

停止 & 重启也是信手拈来

sudo systemctl stop myapp
sudo systemctl restart myapp

还可以使用status命令查看运行状态

sudo systemctl status myapp.service
● myapp.service - My Spring Boot Application
     Loaded: loaded (/etc/systemd/system/myapp.service; disabled; vendor preset: enabled)
     Active: active (running) since Sat 2024-01-06 20:45:56 CST; 5 days ago
   Main PID: 109708 (java)
      Tasks: 37 (limit: 4150)
     Memory: 210.4M
        CPU: 7min 33.637s
     CGroup: /system.slice/myapp.service
             └─109708 java -jar Test-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

Jan 06 20:45:58 ubuntu-HK java[109708]:   .   ____          _            __ _ _
Jan 06 20:45:58 ubuntu-HK java[109708]:  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
Jan 06 20:45:58 ubuntu-HK java[109708]: ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
Jan 06 20:45:58 ubuntu-HK java[109708]:  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
Jan 06 20:45:58 ubuntu-HK java[109708]:   '  |____| .__|_| |_|_| |_\__, | / / / /
Jan 06 20:45:58 ubuntu-HK java[109708]:  =========|_|==============|___/=/_/_/_/
Jan 06 20:45:58 ubuntu-HK java[109708]:  :: Spring Boot ::                (v3.0.1)

我们可以看到.service文件的位置,是否开机自启,以及当前的状态(active (running))

同时,我们还能看到程序的部分日志

日志

对了,应用程序变成一个系统服务之后,日志是如何管理的呢

Default

默认情况下,应用的所有标准输出(stdout)和标准错误(stderr)日志都会被systemd捕获

systemd使用journald服务来管理这些日志,可以通过journalctl命令来查看和管理这些日志

sudo journalctl -u myapp

看似很方便,但是失去了对日志的控制权

如何分割日志,如何清理日志,都变得不太可控

LogBack

Springboot默认继承了Logback日志框架(实现了Slf4j日志门面)

所以只需要修改一下配置文件,即可输出日志到文件

logging.file.name=myapp.log
logging.file.path=/var/log

好的,现在日志已经以文件的形式保存下来了

但是为什么journalctl -u myapp还是可以看到日志呢?

这是因为日志同时输出到了控制台(标准输出、错误输出) & 文件

所以日志输出到了两个目的地,控制台的输出被journalctl捕获,这样势必会造成空间浪费

所以我们需要关闭控制台输出,并且为了更精细地控制日志策略(滚动分割等),我们最好还是自己写Logback的配置文件

Logback配置文件

我们可以在工程的Resources文件夹下新建logback-spring.xml文件

其实也可以是logback.xml,但是这么写是不能读取Springboot的配置文件的(听说有些版本不能)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--从 yml|properties 配置文件中读取键值 以方便修改日志路径-->
    <springProperty scope="context" name="LOG_HOME" source="log.path" defaultValue="logs-default"/>
    <!--格式化输出:%d表示日期,%thread表示线程名,%p:级别 %msg:日志消息,%n是换行符-->
    <property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %p [%t] - %logger{50}  :  %m%n"/>
    <property name="PATTERN_COLOR" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%p) [%t] - %cyan(%logger{50})  :  %m%n"/>
    <!--高亮会输出多余字符,所以在FILE中不开启-->
    
    <!--配置控制台输出-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${PATTERN_COLOR}</pattern>
        </encoder>
    </appender>

    <!--文件日志, 按照每天生成日志文件,优先级高于yml文件中的logging.file.path-->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>7</MaxHistory>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>${PATTERN}</pattern>
        </encoder>
    </appender>

    <!--ALL < TRACE < DEBUG < INFO < WARN < ERROR < OFF-->
    <springProfile name="dev"><!-- dev环境不输出到文件 -->
        <root level="info">
            <appender-ref ref="STDOUT"/>
        </root>
    </springProfile>

    <springProfile name="prod"><!-- prod环境仅输出到文件 -->
        <root level="info">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
</configuration>

我们可以在.properties配置文件里指定日志文件路径(自定义键)

log.path=/logs

为了避免和内置的logging.file.path冲突,我们这里采用了自定义键

小贴士:其实我们在logback-spring.xml中自定义了输出的文件位置之后,logging.file.path会被覆盖,用户的xml配置文件优先级更高

但是为了语义更清晰,还是采用自定义键吧(ba)

Logback多环境配置

注意,我们采用了<springProfile name="dev">这样标签

ta可以根据Springboot当前激活的配置文件(环境),来动态得采取不同的日志策略,如:

  • dev开发环境中,仅输出日志到控制台
  • prod生产环境中,仅输出日志到文件(防止输出到控制台被journalctl捕获,产生两份日志)

Springboot多环境配置

刚刚提到了根据Springboot当前环境来选择日志策略

那么Springboot环境配置是什么意思呢

主要原因是我们在开发环境和生产环境中对于同一个配置属性可能需要不同的值

例如:数据库密码、日志存放路径、静态资源服务器地址等等

所以最方便的方式是写两套配置文件,然后再不同场景进行切换,如:

  • application.yml
  • application-dev.yml
  • application-prod.yml

注:命名规则:application-{profile}.yml 注:同名属性优先级:具体配置文件 > 通用配置文件(application.yml) 注:其实你想把不同环境配置文件写在一个文件里也可以,用---分割,但是别吧,大哥

诶等等,怎么有三个配置文件

你傻啊,通用的配置放application.yml就好了难道你要写两遍啊

好的,那要怎么切换配置呢

默认情况下,也就是default环境,只启用application.yml or properties(这俩一样的,不多赘述)

有三种方式可以切换

  • application.properties中写上:spring.profiles.active=dev or prod等等
  • java -jar的时候传入命令行参数--spring.profiles.active=prod来激活生产环境配置
  • IDEA中配置:编辑运行/调试配置->活动/有效配置(可以找找,这个简单,主要不想放图了)

怎么知道Springboot现在是什么环境呢?在程序启动的时候会输出

2024-01-12 19:41:19.130 INFO [main] - com.mrbean.test.TestApplication  :  The following 1 profile is active: "dev"

好的,酱紫就可以完美切换配置文件了,要是还有什么需要修改的,只要在生成环境新建一个application-prod.yml,就可以覆盖对应的属性了,这个大家应该很懂的,我就不说了

配置文件协作最佳实践

(这文就写不完了是吧!)

我们刚刚说了,有三个配置文件,但是一旦进入Git多人协作,就会直接爆炸

因为每个人的本地环境是不一样的,所以application-dev.yml势必会被改来改去,然后还上传Git

然后pull的时候就会直接覆盖你的开发配置,就会想要 * 队友了

辣怎么办

要么就是不上传application-dev.yml

那不成啊,那想新增/修改配置,其他成员完全阿巴怎么办

其实我们可以建立application-dev-template.yml文件,作为模板,非必要不修改这个文件

然后每个成员自己根据模板新建application-dev.yml,且加入.gitignore不上传Git

Peace

完美,这下总可以下班了吧

Ref

Springboot 指定运行时配置文件的几种方式_dspring.config.location-CSDN博客

Spring boot使用logback实现多环境配置 - 知乎 (zhihu.com)

31 SpringBoot多环境的切换(生产环境、开发环境、测试环境)_spring boot生产和开发环境-CSDN博客