背景

在linux上开发时,往往需要将自己的程序做成服务,并且实现服务开机自动重启,以及服务崩溃后自动重启功能,本文就对该功能的实现做简单介绍,实现方法很简单,使用linux系统的systemd即可实现。

systemd介绍

历史上,linux的启动一直采用init进程,比如

$ sudo /etc/init.d/apache2 start
或者
$ service apache2 start

这种方法有两个缺点。

  • 一是启动时间长。init进程是串行启动,只有前一个进程启动完,才会启动下一个进程。
  • 二是启动脚本复杂。init进程只是执行启动脚本,不管其他事情。脚本需要自己处理各种情况,这往往使得脚本变得很长。

Systemd 就是为了解决这些问题而诞生的。它的设计目标是,为系统的启动和管理提供一套完整的解决方案。根据 Linux 惯例,字母d是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。使用了 Systemd,就不需要再用init了。Systemd 取代了initd,成为系统的第一个进程(PID 等于 1),其他进程都是它的子进程。

systemctl是 Systemd 的主命令,用于管理系统。对于用户来说,最常用的是下面这些命令,用于启动和停止 Unit(主要是 service)。

# 立即启动一个服务
$ sudo systemctl start apache.service
 
# 立即停止一个服务
$ sudo systemctl stop apache.service
 
# 重启一个服务
$ sudo systemctl restart apache.service
 
# 杀死一个服务的所有子进程
$ sudo systemctl kill apache.service
 
# 重新加载一个服务的配置文件
$ sudo systemctl reload apache.service
 
# 重载所有修改过的配置文件
$ sudo systemctl daemon-reload
 
# 显示某个 Unit 的所有底层参数
$ systemctl show httpd.service
 
# 显示某个 Unit 的指定属性的值
$ systemctl show -p CPUShares httpd.service
 
# 设置某个 Unit 的指定属性
$ sudo systemctl set-property httpd.service CPUShares=500

本文主要是对systemd的使用进行介绍,如果想进一步了解systemd的基本知识,可查阅相关资料

服务端脚本

这里我们写一个php的服务脚本server.php,用来实现一个服务:

<?php
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_bind($sock, '0.0.0.0', 10000);
for (;;) {
    socket_recvfrom($sock, $message, 1024, 0, $ip, $port);
    $reply = str_rot13($message);
    socket_sendto($sock, $reply, strlen($reply), 0, $ip, $port);
}

运行后可使用lsof指令来查看端口占用情况

lthpc@lthpc:~$ lsof -i:10000
COMMAND   PID  USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
php     40446 lthpc    3u  IPv4 37381218      0t0  UDP *:10000 

使用nc指令模拟客户端测试

$ nc -u 127.0.0.1 10000
Hello, world!
Uryyb, jbeyq!

创建服务

接下来使用systemd创建一个服务,写一个服务配置文件/etc/systemd/system/rot13.service

[Unit]
Description=ROT13 demo service
After=network.target
StartLimitIntervalSec=0
 
[Service]
Type=simple
Restart=always
RestartSec=1
WorkingDirectory=/path/
ExecStart=/usr/bin/env php /path/to/server.php
 
[Install]
WantedBy=multi-user.target

有几点需要注意,为了使服务能够自动无限次重启,需要增加以下几个配置

StartLimitIntervalSec=0
Restart=always
RestartSec=1

关于配置文件的具体参数含义,可参考该文档

如果要设置环境变量,则需要在Service中添加Environment,比如

Environment=PYTHONPATH=$PYTHONPATH:/root/robot/models-master/research/:/root/robot/models-master/research/slim

也可以把环境变量的配置写在一个文件中,然后使用EnvironmentFile指定环境变量文件的位置

设置好后,可以运行如下语句启动服务

$ systemctl start rot13

启动时如果报错The name org.freedesktop.PolicyKit1 was not provided by any .service files,则是因为权限问题,在启动命令前加sudo即可。

运行后,便启动了名为rot13的服务,可使用status查看服务状态

root@qiuyedx-host:~/workspace_zong/tcptest$ systemctl status rot13
● rot13.service - ROT13 demo service
   Loaded: loaded (/etc/systemd/system/rot13.service; disabled; vendor preset: enabled)
   Active: active (running) since 一 2023-3-15 11:25:43 CST; 1min 28s ago
 Main PID: 44532 (php)
    Tasks: 1
   Memory: 5.2M
      CPU: 24ms
   CGroup: /system.slice/rot13.service
           └─44532 php /home/lthpc/workspace_zong/tcptest/server.php
 
3月 15 11:25:43 root systemd[1]: Started ROT13 demo service.

为了开机自动启动,执行下以下命令

$ systemctl enable rot13

同样的,可以使用nc指令模拟客户端测试,可以看到服务已经正常启动运行了!

如果想取消开机自动启动,使用以下命令

$ systemctl disable rot13

自动重启

为了测试是否可以正常自动重启,我们手动杀掉启动的服务进程,再查看进程号发现已经更换PID号了,说明重启过进程,并且使用nc -u 127.0.0.1 10000指令测试依然可以调用服务

$ systemctl status rot13 | grep PID
 Main PID: 44532 (php)
$ sudo kill 44532
$ systemctl status rot13 | grep PID
 Main PID: 44255 (php)

注意输入systemctl stop rot13时服务是不会重启的,所以如果有参数需要修改,直接运行stop指令改完再start就可以了

查看服务状态

可以使用 journalctl 工具查看指定服务的输出,使用 -u 指定服务名称,使用 -f 可以实时输出

sudo journalctl -n -f -u baoshen.service
  • -n 表示 返回最新的日志
  • -f 表示 持续显示最新的日志,类似 tail -f
  • -u 表示只显示哪个service的日志
  • -r 反向倒序输出

普通用户服务

上边介绍的服务默认是用 root 权限执行,如果要创建普通用户的服务,方法如下:

cd ~/.config
mkdir systemd && cd systemd
mkdir user && cd user
touch test.service

按照上边的格式写一个服务的内容,启动的方法是

systemctl --user daemon-reload
systemctl --user start test.service

其它命令与 root 的类似,只需要加 --user 关键字即可

示例

我们拿v2ray的开机自启举个例子。

在 /etc/systemd/system/ 中添加配置文件,例如:

v2ray.service文件如下:

[Unit]
Description=V2Ray Service
Documentation=https://www.v2fly.org/
After=network.target nss-lookup.target

[Service]
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json
Restart=on-failure
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

注意,[Service]中的ExecStart需要填写正确的启动相应服务(e.g. v2ray)的命令,路径一定要改为自己的路径!

添加完毕之后,在终端输入以下命令添加开机自启:

systemctl enable v2ray

如果需要关闭开机自启,则用以下命令:

systemctl disable v2ray

相关资料


A Student on the way to full stack of Web3.