前言
要做这个,大家要先知道systemctl和chkconfig是什么东西来的,然后再写脚本。
通常草稿篇往往意味着,这玩意没肉眼看起来那么简单。
Java 进程管理
RHEL 7 中 systemctl 的用法(替代service 和 chkconfig)
Linux Centos 7 systemctl(systemd)新增加service服务,并且开机启动
Centos7下的systemctl命令与service和chkconfig
CentOS 7 系列(三)系统服务配置 目标(Target)
CentOS 7 系列(四)系统服务配置 服务(Service)
spring boot 打成 jar 包采用 systemctl 设置自启动
注意,单纯实现start stop restart的话,一定要看看下面这一段脚本,参考一下
shell 实现java程序的start,stop,restart,status
实践 (废弃1)
*备注:这份东西是要废弃掉的,因为太麻烦了,还要写一个额外脚本,以后再完善。。。
还有,请将脚本放到:
vim /usr/lib/systemd/system/dev@MicroBase.service
这里。。。放到user下面还有unit not found的错误的。压根识别不了。
*
根据上面种种资料,
我们可以写出针对某个jar程序的shell管理脚本,譬如:
#!/bin/bash
###该脚本用于控制某个java app程序的关停。
echo "==========================="
echo "需求参数说明:"
echo "参数-e、环境,环境有test,dev及product,分别用于获得不同环境下jar的路径,然后监控该路径。一般情况一个服务器不会同时运行三个环境的jar程序的,不过以防万一而已。"
echo "参数-o、操作类型,有:start、stop、restart"
echo "==========================="
echo "==========================="
echo "正在执行:$0"
echo "==========================="
echo "==========================="
echo "脚本执行情况:"
echo "用户输入参数个数:$#"
echo "输入参数列表:$@"
echo "==========================="
###参数设定
str_env=""
str_op=""
#获取参数:
while getopts :e:o: OPTION;do
case $OPTION in
e)
str_env=$OPTARG
;;
o)
str_op=$OPTARG
;;
?)
;;
esac
done
if [ ! $str_env ]
then
echo "!!!!!请指定需要启动的环境! $str_env"
exit 1
fi
if [ $str_env = "dev" -o $str_env = "test" -o $str_env = "product" ]
then
echo ""
else
echo "env的值只能是dev,test,product三个!!!"
exit 1
fi
if [ $str_op = "start" -o $str_op = "stop" -o $str_op = "restart" ]
then
echo ""
else
echo "op的值只能是start,stop,restart三个之一!!!"
exit 1
fi
#变量只读
readonly str_env
readonly str_op
##############################自定义区域begin
###以下区域需根据各个项目各个实际模块不一样而进行配置
###好了,现在来设定程序路径,pid文件路径等等###
jar_file_path="/usr/micro-service/apps/${str_env}/MicroBase/MicroBaseApp/MicroBaseApp.jar"
DIALUP_PATH="/root/unix-dialup/micro-service/MicroBaseApp/${str_env}/"
DIALUP_PID="${DIALUP_PATH}/dialup.pid"
##############################自定义区域end
checkDialupPath(){
if [ ! -e DIALUP_PATH ]
then
mkdir -p $DIALUP_PATH
fi
}
checkDialupPath
start()
{
echo "启动线程 $prog: "
echo "您好,后台服务启动中 ..."
nohup java -jar $jar_file_path >/dev/null 2>&1 & new_agent_pid=$!
# java -jar $jar_file_path >/dev/null 2>&1 & new_agent_pid=$!
echo "$new_agent_pid" > $DIALUP_PID
echo "启动服务成功"
}
stop()
{
if [ -f $DIALUP_PID ];then
SPID=`cat $DIALUP_PID`
if [ "$SPID" != "" ];then
kill -9 $SPID
echo > $DIALUP_PID
echo "成功停止java程序。"
fi
fi
}
CheckProcessStata()
{
CPS_PID=$1
if [ "$CPS_PID" != "" ] ;then
CPS_PIDLIST=`ps -ef|grep $CPS_PID|grep -v grep|awk -F" " '{print $2}'`
else
CPS_PIDLIST=`ps -ef|grep "$CPS_PNAME"|grep -v grep|awk -F" " '{print $2}'`
fi
for CPS_i in `echo $CPS_PIDLIST`
do
if [ "$CPS_PID" = "" ] ;then
CPS_i1="$CPS_PID"
else
CPS_i1="$CPS_i"
fi
if [ "$CPS_i1" = "$CPS_PID" ] ;then
#kill -s 0 $CPS_i
kill -0 $CPS_i >/dev/null 2>&1
if [ $? != 0 ] ;then
echo "[`date`] MC-10500: Process $i have Dead"
kill -9 $CPS_i >/dev/null 2>&1
return 1
else
#echo "[`date`] MC-10501: Process is alive"
return 0
fi
fi
done
echo "[`date`] MC-10502: Process $CPS_i is not exists"
return 1
}
status()
{
SPID=`cat $DIALUP_PID`
CheckProcessStata $SPID >/dev/null
if [ $? != 0 ];then
echo "unixdialup:{$SPID} 已经停止 ...."
else
echo "unixdialup:{$SPID} 正常运行 ...."
fi
}
restart()
{
echo "尝试停止程序 ... "
stop
echo "尝试启动程序 ..."
start
}
case "$str_op" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
restart
;;
*)
echo $"Usage: $0 {start|stop|restart}"
RETVAL=1
esac
exit $RETVAL
好了,我们将它放到以下目录:
vim /usr/micro-service/apps/Service4MicroBase.sh
记得保存一下。
然后加个执行权限:
chmod +x /usr/micro-service/apps/Service4MicroBase.sh
然后–假如有看之前文章的话肯定会知道jar文件是存在的,譬如:
我们启动一下服务先:
/usr/micro-service/apps/Service4MicroBase.sh -e dev -o start
systemctl自定义服务
引用参考:
CentOS 7的服务systemctl脚本存放在:/usr/lib/systemd/,有系统(system)和用户(user)之分,像须要开机不登陆就能执行的程序,还是存在系统服务里吧,即:/usr/lib/systemd/system文件夹下
每个服务以.service结尾,通常会分为3部分:[Unit]、[Service]和[Install],我写的这个服务用于开机执行Node.js项目,详细内容例如以下:
[Unit]
Description=xiyoulibapi
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/node.js/pid
ExecStart=/usr/local/bin/forever start /node.js/xiyoulib/bin/www
ExecReload=/usr/local/bin/forever restart /node.js/xiyoulib/bin/www
ExecStop=/usr/local/bin/forever stop /node.js/xiyoulib/bin/www
PrivateTmp=true
[Install]
WantedBy=multi-user.target
好了,可以先自定义一个simple的unit服务玩一玩。
首先打开:
vim /usr/lib/systemd/user/dev@MicroBase.service
然后将下面systemctl的脚本复制一下:
顺便添加权限:
[Unit]
Description=微服务程序框架
After=network.target
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
ExecStart=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o start
ExecReload=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o restart
ExecStop=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o stop
PrivateTmp=true
[Install]
WantedBy=multi-user.target
chmod 754 /usr/lib/systemd/user/dev@MicroBase.service
服务启动
systemctl daemon-reload
systemctl start dev@MicroBase.service
实践–简化2
vim /usr/lib/systemd/system/dev@MicroBase.service
然后复制下面内容:
[Unit]
Description=base micro service
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
ExecStart="/usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar"
ExecStop=kill $MAINPID
Restart=always
PrivateTmp=true
[Install]
WantedBy=multi-user.target
接下来,
chmod 754 /usr/lib/systemd/user/dev@MicroBase.service
ps:
不要多手在添加一个x上去,譬如:
chmod +x /usr/lib/systemd/user/dev@MicroBase.service
会报错的,到时直接提示不要设置execute的权限的。
执行包:
systemctl daemon-reload && systemctl start dev@MicroBase.service
注意,看看状态:
systemctl status dev@MicroBase.service
好了,问题出来了,
下面解决一下。
按照字面意思是太频繁了,那么我们先重启再尝试一下。
完全不行,方向错误了。。。
参考:
[CentOS 7之Systemd详解之服务单元设置system.service(https://blog.csdn.net/yuesichiu/article/details/51485147)
StartLimitInterval=, StartLimitBurst=
限制该服务的启动频率。默认值是每10秒内不得超过5次(StartLimitInterval=10s StartLimitBurst=5)。
StartLimitInterval= 的默认值等于systemd配置文件中 DefaultStartLimitInterval= 的值,”0”表示取消启动频率限制。
StartLimitBurst= 的默认值等于systemd配置文件中 DefaultStartLimitBurst= 的值。
虽然这两个选项经常与 Restart= 一起使用,但是它们不只限制 Restart= 罗辑所导致的重启,而是限制所有类型的启动(包括手动启动)。 注意,当 Restart=逻辑所导致的重启超出了启动频率限制之后,Restart= 逻辑将会被禁用(也就是不会在下一个时间段内再次尝试重启), 然而,如果该单元随后又被手动重启,那么 Restart= 罗辑将被再次激活。 注意,”systemctl reset-failed …”命令会清除该服务的重启次数计数器,这通常用于在手动启动之前清除启动限制。
StartLimitInterval=0 加上这一句。。
加上以后变成:
然后重试:
………..这样就可以了,尼玛。。
/*
*
* ┌─┐ ┌─┐
* ┌──┘ ┴───────┘ ┴──┐
* │ │
* │ ─── │
* │ ─┬┘ └┬─ │
* │ │
* │ ─┴─ │
* │ │
* └───┐ ┌───┘
* │ │
* │ │
* │ │
* │ └──────────────┐
* │ │
* │ ├─┐
* │ ┌─┘
* │ │
* └─┐ ┐ ┌───────┬──┐ ┌──┘
* │ ─┤ ─┤ │ ─┤ ─┤
* └──┴──┘ └──┴──┘
* 神兽保佑
* 代码无BUG!
*/
好了,启动成功,那么我们stop关停它了。
结果:
…..
资料如下:
所以我们改成这样子:
验证一下:
这个日期不对,已经将kill变成 /usr/bin/kill了,也没有出现,而且是早前的记录,估计。。。可能正常了就不提示。那么从另一个方面验证一下,先后进行启动和关停操作,看看服务状态如何,
…以作者浅薄的jianshi4和英语水平,暂且认为启动和关停操作已经没问题了。
app执行演进 1、—使用nohup
为什么简简单单用个nohup都要写出来?
因为很可能会出问题的。婶婶感觉到运维的苦了。如果我不搞这个也不会发现。。。尼玛都是坑。
好,下面开始:
先将service的代码演进为如下:
看起来用了nohup感觉有进步了,
[Unit]
Description=base micro service
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
#ExecStart=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o startExecStart="/usr/bin/nohup java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase.log 2>&1 /usr/local/pids/dev-MicroBase.pid &"
ExecStop="/usr/bin/kill $MAINPID"
Restart=always
#取消启动频率限制吧。
StartLimitInterval=0
PrivateTmp=true
[Install]
WantedBy=multi-user.target
好了,
注意,你最好建立空白的log文件及pid文件,免得到时候报错找不到文件就麻烦了。
systemctl daemon-reload
systemctl stop dev@MicroBase.service
systemctl start dev@MicroBase.service
看看结果:
….什么鬼,出什么问题了???明明是nohup的正常用法来的
我们把java加上/usr/bin试试:
[Unit]
Description=base micro service
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
#ExecStart=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o start
ExecStart="/usr/bin/nohup /usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase.log 2>&1 /usr/local/pids/dev-MicroBase.pid &"
ExecStop="/usr/bin/kill $MAINPID"
Restart=always
#取消启动频率限制吧。
StartLimitInterval=0
PrivateTmp=true
[Install]
WantedBy=multi-user.target
看看结果:
运行中,这个真的醉了,—–所有都要用绝对路径。。包括java。。。
好了,我们的main class的代码如下:
package net.w2p.MicroBase;
import com.alibaba.fastjson.JSONObject;
import net.w2p.MicroBase.searcher.account.MemberCondition;
import net.w2p.MicroBase.service.account.MemberRoleService;
import net.w2p.MicroBase.service.account.MemberService;
import net.w2p.MicroBase.vo.account.Member;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.util.ArrayList;
public class Provider {
public static void main(String[] args) throws IOException {
System.out.println("服务已经启动...");
System.in.read();
}
}
日志及bad file descriptor问题
就一个服务已经启动的代码,我们看看日志如何获得。
正常情况是这样的。那么用nohup,直接使用命令:
看看日志文件:
ps:这段代码可是参考了网上主流文章而而写出来的,这样也错就只能说大家都错了或者大家都忽略了一些东西了。
所有,bad file descriptor是什么鬼?
直接执行正常,加了nohup就不行了?
查阅资料:
execute nohup command with playframework get Bad file descriptor error
这里抄录一下:
问:
I use playframework2.2 and sbt 0.13.1, I can run the sbt and start the server on command line
sbt start
it works ok. but when I run:
nohup sbt start
It run a while and then stop with log error:
(Starting server. Type Ctrl+D to exit logs, the server will remain in background) java.io.IOException: Bad file descriptor
at java.io.FileInputStream.read0(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:210)
at jline.internal.NonBlockingInputStream.read(NonBlockingInputStream.java:248)
at jline.internal.InputStreamReader.read(InputStreamReader.java:261)
at jline.internal.InputStreamReader.read(InputStreamReader.java:198)
at jline.console.ConsoleReader.readCharacter(ConsoleReader.java:2038)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.play$PlayConsoleInteractionMode$$anonfun$$waitEOF$1(PlayInteractionMode.scala:36)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1$$anonfun$apply$1.apply$mcV$sp(PlayInteractionMode.scala:45)
at play.PlayConsoleInteractionMode$$anonfun$doWithoutEcho$1.apply(PlayInteractionMode.scala:52)
at play.PlayConsoleInteractionMode$$anonfun$doWithoutEcho$1.apply(PlayInteractionMode.scala:49)
at play.PlayConsoleInteractionMode$.withConsoleReader(PlayInteractionMode.scala:31)
at play.PlayConsoleInteractionMode$.doWithoutEcho(PlayInteractionMode.scala:49)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.apply(PlayInteractionMode.scala:45)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.apply(PlayInteractionMode.scala:34)
at play.PlayConsoleInteractionMode$.withConsoleReader(PlayInteractionMode.scala:31)
at play.PlayConsoleInteractionMode$.waitForKey(PlayInteractionMode.scala:34)
at play.PlayConsoleInteractionMode$.waitForCancel(PlayInteractionMode.scala:55)
at play.PlayRun$$anonfun$24$$anonfun$apply$9.apply(PlayRun.scala:373)
at play.PlayRun$$anonfun$24$$anonfun$apply$9.apply(PlayRun.scala:352)
at scala.util.Either$RightProjection.map(Either.scala:536)
at play.PlayRun$$anonfun$24.apply(PlayRun.scala:352)
at play.PlayRun$$anonfun$24.apply(PlayRun.scala:334)
at sbt.Command$$anonfun$sbt$Command$$apply1$1$$anonfun$apply$6.apply(Command.scala:72)
at sbt.Command$.process(Command.scala:95)
at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:100)
at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:100)
at sbt.State$$anon$1.process(State.scala:179)
at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:100)
at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:100)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
at sbt.MainLoop$.next(MainLoop.scala:100)
at sbt.MainLoop$.run(MainLoop.scala:93)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:71)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:66)
at sbt.Using.apply(Using.scala:25)
at sbt.MainLoop$.runWithNewLog(MainLoop.scala:66)
at sbt.MainLoop$.runAndClearLast(MainLoop.scala:49)
at sbt.MainLoop$.runLoggedLoop(MainLoop.scala:33)
at sbt.MainLoop$.runLogged(MainLoop.scala:25)
at sbt.StandardMain$.runManaged(Main.scala:57)
at sbt.xMain.run(Main.scala:29)
at xsbt.boot.Launch$$anonfun$run$1.apply(Launch.scala:57)
at xsbt.boot.Launch$.withContextLoader(Launch.scala:77)
at xsbt.boot.Launch$.run(Launch.scala:57)
at xsbt.boot.Launch$$anonfun$explicit$1.apply(Launch.scala:45)
at xsbt.boot.Launch$.launch(Launch.scala:65)
at xsbt.boot.Launch$.apply(Launch.scala:16)
at xsbt.boot.Boot$.runImpl(Boot.scala:32)
at xsbt.boot.Boot$.main(Boot.scala:21)
at xsbt.boot.Boot.main(Boot.scala)
error[0m] [0mjava.io.IOException: Bad file descriptor[0m
error[0m] [0mUse 'last' for the full log.[0m
回答:
The error happens because standard input get redirected from /dev/null by nohup - you get the same error if you do play start < /dev/null. The sbt process starts the actual server in a separate process, the sets itself up to display logs and wait for you to type Ctrl-D or Ctrl-C. It uses JLine to wait for user input, which attempts to attach to the standard input as a terminal. /dev/null can't be used in this way, so it dies complaining of a bad file descriptor. However, the background server process continues running.
If you want to start Play non-interactively, you need to use the stage task. See Using the stage task in the Play documentation.
。。。。这里的意思似乎是,交互类型的程序才出这个问题—本身nohup就是放后台运行的。。。。看到这里我灵机一动。。。改改代码编译然后再试试。
ps:为什么这里要执着于这个问题,system.in.read的问题?因为这个方式是阻塞了主线程的方法,在此期间,微服务会一直开启一直保持处理状态,没有这个的话估计微服务运行不起来。也就是说,所有用nohup的后台程序都要考虑如何保持java程序一直运行而不是一闪而过
解决方案:
这里写链接内容
大家可以参考一下:
代码修改,本地测试通过:
好了,上传到jenkins,然后编译,然后部署到目标目录上面去:
第一,原始方法执行:
java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar
只能用ctrl c断掉,程序测试通过。
好了,清空一下日志,然后用nohup来执行。
echo ""> /usr/local/logs/dev-MicroBase.log
/usr/bin/nohup /usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase.log 2>&1 /usr/local/pids/dev-MicroBase.pid &
vim /usr/local/logs/dev-MicroBase.log
好了,没有刚才的问题了,那么,退出来看看nohup是不是真的有这个后台程序在执行。
jobs -l
有了这个程序了,我们可以切换到前台然后ctrl c直接杀死进程。
fg 1
好了,现在从支线进入跳回主线了,执行服务看看日志。如果能够运行就表示,app的服务化完成了。
#注意,确保之前没有后台程序
jobs -l
echo ""> /usr/local/logs/dev-MicroBase.log
systemctl daemon-reload
systemctl stop dev@MicroBase.service
systemctl start dev@MicroBase.service
systemctl status dev@MicroBase.service
#注意,既然用的是nohup,那么还是要看看有没有真的运行才行。
jobs -l
vim /usr/local/logs/dev-MicroBase.log
第一次jobs -l
无后台程序。
执行到status状态查看:
第二次jobs -l
日志内容:
没有nohup后台程序且日志内容为空。
。。。。
好吧,检查一下 所有服务列表:
systemctl -l
好了,查一下microbase的服务在哪里。
systemctl -l | grep 'MicroBase'
唯一的欣慰是在服务列表里面能找到这玩意,好了,下个问题,
1、code exited ,203什么意思?
2、输出的日志记录为什么没有日志输出?
既然这里看不到问题,那么我们看看日志:
journalctl -xe
然后发现压根没有启动成功嘛。。。。不能执行命令是什么意思?
要不,将命令的双引号都去掉试试。
[Unit]
Description=base micro service
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
#ExecStart=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o start
ExecStart=/usr/bin/nohup /usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase-2.log 2>&1 &
ExecStop=/usr/bin/kill -9 $MAINPID
Restart=always
#取消启动频率限制吧。
StartLimitInterval=0
PrivateTmp=true
[Install]
WantedBy=multi-user.target
然后:
#注意,确保之前没有后台程序
echo ""> /usr/local/logs/dev-MicroBase-2.log
systemctl daemon-reload
systemctl stop dev@MicroBase.service
systemctl start dev@MicroBase.service
systemctl status dev@MicroBase.service
journalctl -xe
vim /usr/local/logs/dev-MicroBase-2.log
执行状态时候的结果:
systemctl status dev@MicroBase.service
终于终于终于没有203错误了!
好了,看看服务的日志:
journalctl -xe
这次也没有报错—-还有:
服务已经启动这句话明明是程序system.out出来的,怎么会在服务日志里面?不应该在nohup的输出文件吗?
看看nohup日志:
vim /usr/local/logs/dev-MicroBase-2.log
。。。。还是空文件。。。