systemd添加与管理系统服务

上周的文章中,我们介绍了几种Linux上的文件共享策略,并提到了可以用systemctl将程序注册为系统服务。本篇将主要介绍一下注册为系统服务的具体方法。

systemd简介

Linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 pid 为 1 的 init 进程。这个进程是系统的第一个进程,它以守护进程方式存在,是所有其他进程的祖先,负责产生其他所有用户进程。

早期的Linux 发行版的 init 系统是和 System V 相兼容的,被称为 System V init(也可简写为SysVinit),它依赖于Shell脚本进行启动项管理,因此具有概念简单清晰、易于维护等优点。然而随着Linux的发展以及Linux在移动设备上的广泛应用,SysVinit启动速度慢的缺点开始显现出来。人们开始改进这一启动系统,让它速度更快、支持的功能更多、更加自动化,并最终发展出了systemd(所有字母全小写)。

systemd的设计目标是,为系统的启动和管理提供一套完整的解决方案。根据 Linux 惯例,字母d是守护进程(daemon)的缩写。 systemd 这个名字的含义,就是它要守护整个系统。它并不是一个单独的程序或命令,而是一组命令,涉及系统的方方面面。其中,systemctl是 systemd 的主命令,用于管理系统。

下面是一些systemctl常用指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 重启系统
$ sudo systemctl reboot
# 关闭系统,切断电源
$ sudo systemctl poweroff
# CPU停止工作
$ sudo systemctl halt
# 暂停系统
$ sudo systemctl suspend
# 让系统进入冬眠状态
$ sudo systemctl hibernate
# 让系统进入交互式休眠状态
$ sudo systemctl hybrid-sleep
# 启动进入救援状态(单用户状态)
$ sudo systemctl rescue

systemd的开机启动项

Linux系统在启动时要进行大量的初始化工作,比如挂载文件系统和交换分区、启动各类进程服务等,这些都可以看作是一个一个的单元(unit),并用一系列单元文件表示它们。systemd用目标(target)代替了System V init中运行级别的概念,这两者的区别如下表所示。

System V init运行级别 systemd目标名称 systemd 目标作用
0 poweroff.target 关机
1 rescue.target 救援模式
2 multi-user.target 多用户的命令行界面
3 multi-user.target 多用户的命令行界面
4 multi-user.target 多用户的命令行界面
5 graphical.target 多用户的图形界面
6 reboot.target 重启
emergency emergency.target 救援模式

一般来说,我们最常接触到的运行级别只有multi-user.target多用户命令行界面,其次是graphical.target多用户图形界面(如果使用Linux桌面版的话)。因此,将服务进程的目标设定为multi-user.target准没错。

单元文件的文件名以 .service 结尾,文件内容由 Unit、Service 和 Install 三个区块组成,分别用于描述启动顺序与依赖关系、定义启动行为、定义服务安装到的目标。以下是一个 service 脚本样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]   
Description=test # 简单描述服务
After=network.target # 描述服务类别,表示本服务需要在network服务启动后在启动
Before=xxx.service # 表示需要在某些服务启动之前启动,After和Before字段只涉及启动顺序,不涉及依赖关系

[Service]
Type=forking # 设置服务的启动方式
User=USER # 设置服务运行的用户
Group=USER # 设置服务运行的用户组
WorkingDirectory=/PATH # 设置服务运行的路径(cwd)
KillMode=control-group # 定义systemd如何停止服务
Restart=no # 定义服务进程退出后,systemd的重启方式,默认是不重启
ExecStart=/start.sh # 服务启动命令,命令需要绝对路径(采用sh脚本启动其他进程时Type须为forking)

[Install]
WantedBy=multi-user.target # 多用户

上述单元文件必须放在合适的目录中才能发挥作用。在Linux的systemd启动管理服务中,/lib/systemd/system//etc/systemd/system/这两个目录都用于存放系统服务的单元文件,但二者有所不同:

  • /lib/systemd/system/目录是系统默认的服务单元文件,由系统或特定软件包来维护,不建议用户进行修改。
  • /etc/systemd/system/ 目录用于存放系统管理员创建或修改过的服务单元文件,在Linux启动过程中优先级更高。如果这个目录中存在与 /lib/systemd/system/中同名的单元文件,则后者的设置会被前者覆盖。

因此,我们可以通过在/etc/systemd/system/ 目录中添加或修改单元文件,从而实现对开机启动项的修改。

一个例子

经过前面的介绍,我们已经知道可以通过在/etc/systemd/system/中创建服务单元文件,从而实现开机自启动。这一小节中我们将展示一个实际例子(节选自 MCSManager的安装程序)。

首先,我们准备一份单元文件,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=MCSManager Daemon # 定义服务名称,即MCSManager Daemon

[Service]
WorkingDirectory=/opt/mcsmanager/daemon # 定义服务进程工作目录。下面的`app.js`存放在工作目录中。
ExecStart=${node_install_path}/bin/node app.js # 定义服务进程启动指令
ExecReload=/bin/kill -s QUIT $MAINPID # 定义服务进程重启指令
ExecStop=/bin/kill -s QUIT $MAINPID # 定义服务进程关闭指令
Environment="PATH=${PATH}" # 定义环境变量

[Install]
WantedBy=multi-user.target # 定义服务程序的启动目标。启动到`multi-user.target`,即多用户的文本界面

上面这段代码定义了一个服务进程。我们将它保存为mcsm-daemon.service 文件,并存放在/etc/systemd/system/ 目录下(需要root权限) 。这样以后,就可以使用 systemctl <command> mcsm-daemon.service 对这个进程进行操作了。

现在我们想让这个服务进程能够开机自启动。因此,我们运行下面的指令:

1
2
systemctl enable mcsm-daemon.service --now # 参数`--now`要求立即启动这个服务进程。
# 不加`--now`则仅仅是将这个服务进程设置为开机启动项,但不立即启动服务,需要用户手动重启系统才能生效。

其中, systemctl enable <service> 命令用于在系统启动时自动启动指定的服务。这个命令会创建一个符号链接,指向服务的单元文件。这样,当系统启动时,systemd会读取这些链接,并自动启动相应的服务。相应的,如果想要取消自启动,可以使用systemctl disable <service> 指令。

而如果要立即启动这个服务进程,则使用下面的指令:

1
systemctl start mcsm-daemon.service 

其中, systemctl start <service> 命令用于立即启动指定的服务。这个命令不会创建任何符号链接,也不会改变系统的启动设置。它只是告诉systemd立即执行服务的启动操作。如果服务已经处于运行状态,start 命令通常不会有任何效果。相应的,如果想要取消自启动,可以使用systemctl stop <service> 指令。

要查看服务进程的运行状态,可以使用下面的指令:

1
systemctl status  mcsm-daemon.service

其中, systemctl status <service> 命令用于查看服务进程的运行状态。上述指令的运行结果如下图所示:

image.png

以上。

参考文献