ubuntu 使用systemd systmctl配置服务开机启动,服务包含多个子进程

发布时间 2023-12-07 19:10:59作者: 06

背景:

需求是这样的,有一个服务,有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 &"