背景:
需求是这样的,有一个服务,有6个子进程,每次系统重启都要一个一个启动,很繁琐,需要配置到开机启动里
而目前系统已经抛弃了chkconfig的配置方式,转而使用systemd来配置开机启动进程了
所以需求就变成了把服务配置到systemd开机启动中,服务包含6个子进程
配置这个踩了不少坑,特地记录下:
首先这里贴出systemd的基础知识:
https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html
https://blog.csdn.net/bandaoyu/article/details/124358513
下面为我主要遇到的问题:
1.Syntax error: "(" unexpected
参考文档:https://blog.csdn.net/weixin_45312249/article/details/126368386
https://www.u72.net/jishu/show-88269.html
这个问题的原因为:
debian/ubuntu上sh命令默认是指向dash,而不是bash
又因为dash是比bash还轻量的,只支持基本的shell功能,
其中不包括刚才那种数组初始化,所以才会识别不了,直接报Syntx error
解决办法:
第一种方案:直接用 bash test.sh,或者./test.sh,这两种方式来执行脚本。这里每次都需要指定用bash解析脚本
第二种方案:通过配置文件取消dash(一劳永逸的方法)
1) sudo dpkg-reconfigure dash 在选择项中选No,搞定了!
我分析后认为既然ubuntu使用dash肯定有他的考量,我直接使用bash就行,所以我写的启动脚本如下
其中需要有个传参:start|stop|status|restart,就是把每个模块的启动脚本合到一个脚本 startkd.sh
#!/bin/bash bash /data/deploy/LBS/service/2d-tile-server/2dtile.sh $1 bash /data/deploy/LBS/service/3d-tile-server/3dtile.sh $1 bash /data/deploy/LBS/service/krs-pdds/krs-pdds.sh $1 bash /data/deploy/LBS/service/webapi/run.sh $1 bash /data/deploy/LBS/service/mspserver/run.sh $1 bash /data/deploy/LBS/service/pdds-search/pdds-search.sh $1
2.直接执行启动脚本可以实现程序启停,但是通过systemctl start 程序起不来
这个问题真的困扰了我好久,还好一步一步去摸索逐渐摸清了头绪
(1)systemctl 配置参数Type
首先参照这个文档:https://blog.csdn.net/propor/article/details/130871521
在/usr/lib/systemd/system下新建文件kd.service
[Unit] Description=Kuandeng server After=network.target [Service] Type=simple Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/java11/bin" User=appadmin Group=appadmin ExecStart=/data/deploy/LBS/service/startkd.sh start ExecStop=/data/deploy/LBS/service/startkd.sh stop Restart=on-failure [Install] WantedBy=multi-user.target
配置完成,执行
sudo systemctl daemon-reload
systemctl start kd
本以为一气呵成的时候,一看进程傻眼了,程序都没起来
一开始只会折腾ExecStart这个启动命令,但是改了一圈一点用没有
我逐渐意识到可能不是启动命令的问题,直到看到这篇文章才有了头绪
https://www.cnblogs.com/startscorpio/p/12915088.html
Type类型有:
simple(默认):#以Execstart字段启动的进程为主进程
forking:#Execstart 字段以fox()方式启动,,此时父进程将退出,子进程将成为主进程(后台运行),一般都设置为forking
oneshot : #类似于simple,但只执行一次,systemd会等他执行完,才执行其他服务
dbus: #类似于simple,但会等待D—Bus信号后启动
notify: #类似与simple ,但结束后会发出通知信号,然后systemd才启动其他服务
idle: #类似与simple,但要等到其他任务都执行完,才启动该服务
我不禁灵机一动,forking不就是正适合我的应用场景吗,start.sh主进程执行完退出,他启动的子进程常驻后台
然后我着重搜索关键词:systemd forking
找到了这篇:https://blog.csdn.net/icandoit_2014/article/details/121467310
觉得可行之后我修改了kd.service脚本
[Unit] Description=Kuandeng server After=network.target [Service] Type=forking Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/java11/bin" User=appadmin Group=appadmin ExecStart=/data/deploy/LBS/service/startkd.sh start ExecStop=/data/deploy/LBS/service/startkd.sh stop Restart=on-failure [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload
systemctl start kd
一顿操作之后还是报错,于是来到第二个问题
(2)systemd启动是如何判断主进程和健康程度的
操作完,还是没有起来,我又去仔细查看了那个forking文档,直到看到这句给了我启发
回到forking类型的服务。由于daemon类的进程会有一个瞬间退出的中间父进程(如上面PID=7913的nginx进程),systemd是如何知道哪个进程是应该被监控的服务主进程(Main PID)呢?
答案是靠猜。没错,systemd真的就是靠猜的。当设置Type=forking时,有一个GuessMainPID指令其默认值为yes,它表示systemd会通过一些算法去猜测Main PID。当systemd的猜测无法确定哪个为主进程时,后果是严重的:systemd将不可靠。因为systemd无法正确探测服务是否真的失败,当systemd误认为服务失败时,如果本服务配置了自动重启(配置了Restart指令),重启服务时可能会和当前正在运行但是systemd误认为失败的服务冲突(比如出现端口已被占用问题)。
多数情况下的猜测过程很简单,systemd只需去找目前存活的属于本服务的leader进程即可。但有些服务(少数)情况可能比较复杂,在多进程之间做简单的猜测并非总是可靠。
systemd是如何判断主进程呢,如果靠猜的话,那得保证执行过程尽量干净才行,这样他才会准确,突然我想到了手动执行脚本时,里面有一些报错
虽然不影响服务启动,但是会影响systemd判断,
于是我找到最后一个启动脚本,把里面的报错排除了,确保启动没有异常信息后,再执行
sudo systemctl daemon-reload
systemctl start kd
这时候奇迹出现了!服务起来了!
但是高兴的太早了,仔细一看少了一个服务
本来以为是不是脚本顺序问题,特地调整了几次startkd.sh脚本启动顺序,还是无济于事
于是我想到要不去看看那个服务的启动脚本
好家伙果然有报错,那就说得通了,systemd发现启动有报错,所以把这个进程杀了,这也就是为什么手动执行脚本能正常启动,systemd起不来的原因了!
看到这里我不禁感叹systemd这个进程做的真的很完善
于是后面就着重处理这个问题了,其实本机配置了默认java_home,所以有没有这个配置无所谓,但是这个配置在哪里呢,找了一圈没找到
后来想到了搜索下试试
这不就找到了吗,果断把这行注释掉
然后再执行
sudo systemctl daemon-reload
systemctl start kd
这次重终于成功了!6个进程都起来了
3.意外发现的其他问题场景:systemd systemctl ExecStart超时处理
参考文档:https://blog.csdn.net/xht555/article/details/110674215
执行“systemctl status 服务名”查看服务状态,可以看到由于systemd一直在等待run.sh脚本执行完成,但超时时间到了,自动结束了服务:leanote.service start operation timed out. Terminating.
# systemctl status leanote.service
● leanote.service
Loaded: loaded (/usr/lib/systemd/system/leanote.service; enabled; vendor preset: disabled)
Active: failed (Result: timeout) since 五 2020-12-04 21:32:28 CST; 2min 24s ago
Process: 4279 ExecStart=/data/leanote/bin/run.sh & (code=killed, signal=TERM)
12月 04 21:30:58 db-srv run.sh[4279]: DEBUG 21:30:58 revel controller_type.go:64: Registered controller: App\memberindex section=controller
12月 04 21:30:58 db-srv run.sh[4279]: DEBUG 21:30:58 revel controller.go:523: RegisterController:Registered controller section=controller controller=App\\memberindex
12月 04 21:30:58 db-srv run.sh[4279]: DEBUG 21:30:58 revel controller_type.go:64: Registered controller: App\memberuser section=controller
12月 04 21:30:58 db-srv run.sh[4279]: DEBUG 21:30:58 revel controller.go:523: RegisterController:Registered controller section=controller controller=App\\memberuser
12月 04 21:30:58 db-srv run.sh[4279]: DEBUG 21:30:58 revel server.go:106: InitServerEngine: Found server engine and invoking section=server name=go
12月 04 21:30:58 db-srv run.sh[4279]: Listening on.. 0.0.0.0:9000
12月 04 21:32:28 db-srv systemd[1]: leanote.service start operation timed out. Terminating.
12月 04 21:32:28 db-srv systemd[1]: Failed to start leanote.service.
12月 04 21:32:28 db-srv systemd[1]: Unit leanote.service entered failed state.
12月 04 21:32:28 db-srv systemd[1]: leanote.service failed.
解决这个问题也很简单,借助“bash -c”,bash -c的作用是将一个长字符串当做一条完整的命令来执行,如果在脚本路径后面加上后台运行符号(&),run.sh脚本就会在后台运行,不会一直处于挂起状态,systemd也就不会一直等待run.sh执行完成了。经过测试,完全符合预期,因此,最终的解决方案是:
ExecStart=/bin/bash -c "/data/leanote/bin/run.sh &"