如何在Linux系统中将Spring Boot应用程序打包为可执行jar as a Service ?这是推荐的方法吗,还是应该将这个应用程序转换为war并将其安装到Tomcat中?
目前,我可以从屏幕会话运行Spring引导应用程序,这很好,但需要在服务器重新启动后手动启动。
我正在寻找的是一般的建议/方向或样本init。D脚本,如果我的方法与可执行jar是适当的。
如何在Linux系统中将Spring Boot应用程序打包为可执行jar as a Service ?这是推荐的方法吗,还是应该将这个应用程序转换为war并将其安装到Tomcat中?
目前,我可以从屏幕会话运行Spring引导应用程序,这很好,但需要在服务器重新启动后手动启动。
我正在寻找的是一般的建议/方向或样本init。D脚本,如果我的方法与可执行jar是适当的。
当前回答
我自己刚刚抽出时间来做这件事,所以下面是到目前为止我在CentOS初始化方面的进展。D业务控制器脚本。到目前为止,它工作得很好,但我不是leet Bash黑客,所以我相信还有改进的空间,所以欢迎提出改进的想法。
首先,我为每个服务准备了一个简短的配置脚本/data/svcmgmt/conf/my-spring-boot-api.sh,用于设置环境变量。
#!/bin/bash
export JAVA_HOME=/opt/jdk1.8.0_05/jre
export APP_HOME=/data/apps/my-spring-boot-api
export APP_NAME=my-spring-boot-api
export APP_PORT=40001
我使用CentOS,所以为了确保我的服务在服务器重启后启动,我在/etc/init.d/my-spring-boot-api中有一个服务控制脚本:
#!/bin/bash
# description: my-spring-boot-api start stop restart
# processname: my-spring-boot-api
# chkconfig: 234 20 80
. /data/svcmgmt/conf/my-spring-boot-api.sh
/data/svcmgmt/bin/spring-boot-service.sh $1
exit 0
如您所见,它调用初始配置脚本来设置环境变量,然后调用我用来重新启动所有Spring Boot服务的共享脚本。共享脚本是所有内容的核心所在:
#!/bin/bash
echo "Service [$APP_NAME] - [$1]"
echo " JAVA_HOME=$JAVA_HOME"
echo " APP_HOME=$APP_HOME"
echo " APP_NAME=$APP_NAME"
echo " APP_PORT=$APP_PORT"
function start {
if pkill -0 -f $APP_NAME.jar > /dev/null 2>&1
then
echo "Service [$APP_NAME] is already running. Ignoring startup request."
exit 1
fi
echo "Starting application..."
nohup $JAVA_HOME/bin/java -jar $APP_HOME/$APP_NAME.jar \
--spring.config.location=file:$APP_HOME/config/ \
< /dev/null > $APP_HOME/logs/app.log 2>&1 &
}
function stop {
if ! pkill -0 -f $APP_NAME.jar > /dev/null 2>&1
then
echo "Service [$APP_NAME] is not running. Ignoring shutdown request."
exit 1
fi
# First, we will try to trigger a controlled shutdown using
# spring-boot-actuator
curl -X POST http://localhost:$APP_PORT/shutdown < /dev/null > /dev/null 2>&1
# Wait until the server process has shut down
attempts=0
while pkill -0 -f $APP_NAME.jar > /dev/null 2>&1
do
attempts=$[$attempts + 1]
if [ $attempts -gt 5 ]
then
# We have waited too long. Kill it.
pkill -f $APP_NAME.jar > /dev/null 2>&1
fi
sleep 1s
done
}
case $1 in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
esac
exit 0
当停止时,它将尝试使用弹簧引导驱动器来执行受控关机。但是,如果没有配置执行器或未能在合理的时间范围内关闭(我给它5秒,这真的有点短),进程将被杀死。
此外,脚本还假设运行应用程序的java进程是进程详细信息文本中唯一带有“my-spring-boot-api.jar”的进程。在我的环境中,这是一个安全的假设,这意味着我不需要跟踪pid。
其他回答
您还可以使用监控器,这是一个非常方便的守护进程,可以用来轻松地控制服务。这些服务是由简单的配置文件定义的,这些配置文件定义了在哪个目录下哪个用户执行什么,等等,有无数的选项。supervisor ord的语法非常简单,所以它是编写SysV初始化脚本的一个很好的替代方案。
这里有一个简单的监督配置文件,用于您试图运行/控制的程序。(把这个放到/etc/supervisor/conf.d/yourapp.conf)
/etc/supervisor/conf.d/yourapp.conf
[program:yourapp]
command=/usr/bin/java -jar /path/to/application.jar
user=usertorun
autostart=true
autorestart=true
startsecs=10
startretries=3
stdout_logfile=/var/log/yourapp-stdout.log
stderr_logfile=/var/log/yourapp-stderr.log
要控制应用程序,你需要执行监控器ctl,它会提示你启动、停止和状态你的应用程序。
CLI
# sudo supervisorctl
yourapp RUNNING pid 123123, uptime 1 day, 15:00:00
supervisor> stop yourapp
supervisor> start yourapp
如果监控器守护进程已经在运行,并且您已经为您的服务添加了配置,而没有重新启动守护进程,那么您可以在监控器shell中简单地执行一个重读和更新命令。
这确实为您提供了使用SysV Init脚本所具有的所有灵活性,而且易于使用和控制。看一下文档。
我的SysVInit脚本Centos 6 / RHEL(还不理想)。这个脚本需要ApplicationPidListener。
/etc/init.d/app的源代码
#!/bin/sh
#
# app Spring Boot Application
#
# chkconfig: 345 20 80
# description: App Service
#
### BEGIN INIT INFO
# Provides: App
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: Application
# Description:
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
exec="/usr/bin/java"
prog="app"
app_home=/home/$prog/
user=$prog
[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
lockfile=/var/lock/subsys/$prog
pid=$app_home/$prog.pid
start() {
[ -x $exec ] || exit 5
[ -f $config ] || exit 6
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 1
echo -n $"Starting $prog: "
cd $app_home
daemon --check $prog --pidfile $pid --user $user $exec $app_args &
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc -p $pid $prog
retval=$?
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
stop
start
}
reload() {
restart
}
force_reload() {
restart
}
rh_status() {
status -p $pid $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
restart
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
exit 2
esac
exit $?
配置文件/etc/sysconfig/app示例:
exec=/opt/jdk1.8.0_05/jre/bin/java
user=myuser
app_home=/home/mysuer/
app_args="-jar app.jar"
pid=$app_home/app.pid
对于SpringBoot 2.4.4,除了由 @ismael
我在maven pom.xml中有以下内容,使其成为可执行jar
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在这个问题中,@PbxMan的回答可以让你开始:
在Linux上运行Java应用程序作为服务
编辑:
还有另一种不太好的方式在重启时启动进程,使用cron:
@reboot user-to-run-under /usr/bin/java -jar /path/to/application.jar
这是可行的,但不能为应用程序提供良好的启动/停止界面。你仍然可以简单地杀死它……
我试图使springboot应用程序呈现为“init”。D”风格的shell脚本与压缩Java应用程序钉在最后
通过符号链接这些脚本从/etc/init.D /spring-app到/opt/spring-app.jar,并chmod jar使其可执行。D /spring-app启动/etc/init。D /spring-app stop”和其他可能的状态工作
假设是init。来自springboot的d风格脚本看起来他们有必要的魔法字符串(像# Default-Start: 2 3 4 5) chkconfig将能够将其作为“服务”添加。
但是我想让它和systemd一起工作
为了做到这一点,我尝试了上面其他答案中的许多食谱,但在Centos 7.2和Springboot 1.3上,它们都不适合我。大多数情况下,它们会启动服务,但无法跟踪pid
最后,我发现下面的方法对我有用,当/etc/init.D链接也到位了。一个类似于下面的文件应该安装为/usr/lib/systemd/system/spring-app.service
[Unit]
Description=My loverly application
After=syslog.target
[Service]
Type=forking
PIDFile=/var/run/spring-app/spring-app.pid
ExecStart=/etc/init.d/spring-app start
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target