Linux systemd 服务详解

Linux 系统中也有“服务”这一说法, 也即 systemd service unit,通过服务我们可以便捷地管理一些程序功能,也可以作为程序开机自启的一个手段之一。今天我来分享一下如何创建自己简易的 Linux 服务。本文主要讲述如何在 Linux 下自定义 service.

1. 服务文件位置介绍

/usr/lib/systemd/system/

随安装的 RPM 软件包分发的系统单元文件。该目录中包含的是软件包安装的单元,也就是说通过 yum、dnf、rpm 等软件包管理命令管理的 systemd 单元文件,都放置在该目录下。

/run/systemd/system/

这个目录一般是进程在运行时动态创建 unit 文件的目录,一般很少修改,除非是修改程序运行时的一些参数时,即 Session 级别的,才在这里做修改。

/etc/systemd/system/

Systemd 创建的单元文件 systemctl enable 以及为扩展服务而添加的单元文件。此目录优先于包含运行时单元文件的目录。

1.1. 三个文件位置的关系

包管理软件初始安装的包中,如果包含 systemd 单元文件也即服务定义文件,一般会放在/usr/lib/systemd/system/。
如果要开启该服务,会在/etc/systemd/system/目录创建一个软链接指向/usr/lib/systemd/system/中的服务定义文件。

其中/etc/systemd/system/又分为 basic.target.wants, graphical.target.wants, multi-user.target.wants 等子目录,具体服务链接至哪个子目录是由服务定义文件中的
Install 部分的 WantedBy 属性所决定的。

/run/systemd/system/则是放置程序运行时动态创建 unit 文件。

例如:

1
2
3
4
$ sudo systemctl enable clamd@scan.service
# 等同于
$ sudo ln -s '/usr/lib/systemd/system/clamd@scan.service' '/etc/systemd/system/multi-user.target.wants/clamd@scan.service'

2. 新建服务文件

每一个服务在 Linux 有它自己的对应的配置文件,这个文件可以通过文本编辑器编辑,扩展名为 xxx.servive(xxx 为服务名称)。这些文件位于/usr/lib/systemd/system 目录下。
在这个目录下新建 service 文件即可创建我们的服务。文件的内容结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=服务描述
After=服务依赖(再这些服务后启动本服务)

[Service]
Type=服务类型
ExecStart=启动命令
ExecStop=终止命令
ExecReload=重启命令

[Install]
WantedBy=服务安装设置

服务配置文件分为 Unit、Service 和 Install 三大部分。

Unit 部分:提供对服务的描述和定义服务的依赖关系。

Service 部分:服务(Service)类型的 Unit 特有的,用于定义服务的具体管理和操作方法

Install 部分:这个部分指定了服务在 Systemd 系统中的安装设置。

2.1. Unit 部分介绍

  • Description:描述这个 Unit 文件的信息
  • Documentation:指定服务的文档,可以是一个或多个文档的 URL 路径
  • Requires:依赖的其它 Unit 列表,列在其中的 Unit 模板会在这个服务启动时的同时被启动。并且,如果其中任意一个服务启动失败,这个服务也会被终止
  • Wants:与 Requires 相似,但只是在被配置的这个 Unit 启动时,触发启动列出的每个 Unit 模块,而不去考虑这些模板启动是否成功
  • After:与 Requires 相似,但是在后面列出的所有模块全部启动完成以后,才会启动当前的服务
  • Before:与 After 相反,在启动指定的任务一个模块之间,都会首先确证当前服务已经运行
  • Binds To:与 Requires 相似,失败时失败,成功时成功,但是在这些模板中有任意一个出现意外结束或重启时,这个服务也会跟着终止或重启
  • Part Of:一个 Bind To 作用的子集,仅在列出的任务模块失败或重启时,终止或重启当前服务,而不会随列出模板的启动而启动
  • OnFailure:当这个模板启动失败时,就会自动启动列出的每个模块
  • Conflicts:与这个模块有冲突的模块,如果列出的模块中有已经在运行的,这个服务就不能启动,反之亦然

2.2. Service 部分介绍

服务生命周期控制相关

Type:定义启动时的进程行为,它有以下几种值:

  • Type=simple:默认值,执行 ExecStart 指定的命令,启动主进程

  • Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出

  • Type=oneshot:一次性进程,Systemd 会等当前服务退出,再继续往下执行

  • Type=dbus:当前服务通过 D-Bus 启动

  • Type=notify:当前服务启动完毕,会通知 Systemd,再继续往下执行

  • Type=idle:若有其他任务执行完毕,当前服务才会运行

RemainAfterExit:值为 true 或 false(默认)。当配置为 true 时,Systemd 只会负责启动服务进程,之后即便服务进程退出了,Systemd 也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出,然后等待消息按需启动的特殊类型服务使用的。

ExecStart:启动当前服务的命令

ExecStartPre:启动当前服务之前执行的命令

ExecStartPos:启动当前服务之后执行的命令

ExecReload:重启当前服务时执行的命令

ExecStop:停止当前服务时执行的命令

ExecStopPost:停止当其服务之后执行的命令

RestartSec:自动重启当前服务间隔的秒数

Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括 always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog

TimeoutStartSec:启动服务时等待的秒数,这一配置对于使用 Docker 容器而言显得尤为重要,因其第一次运行时可能需要下载镜像,严重延时会容易被 Systemd 误判为启动失败杀死。通常,对于这种服务,将此值指定为 0,从而关闭超时检测

TimeoutStopSec:停止服务时的等待秒数,如果超过这个时间仍然没有停止,Systemd 会使用 SIGKILL 信号强行杀死服务的进程

服务上下文配置相关

Environment:为服务指定环境变量

EnvironmentFile:指定加载一个包含服务所需的环境变量的列表的文件,文件中的每一行都是一个环境变量的定义

Nice:服务的进程优先级,值越小优先级越高,默认为 0。其中 -20 为最高优先级,19 为最低优先级

WorkingDirectory:指定服务的工作目录

RootDirectory:指定服务进程的根目录(/ 目录)。如果配置了这个参数,服务将无法访问指定目录以外的任何文件

User:指定运行服务的用户

Group:指定运行服务的用户组

MountFlags:服务的 Mount Namespace 配置,会影响进程上下文中挂载点的信息,即服务是否会继承主机上已有挂载点,以及如果服务运行执行了挂载或卸载设备的操作,是否会真实地在主机上产生效果。可选值为 shared、slaved 或 private

shared:服务与主机共用一个 Mount Namespace,继承主机挂载点,且服务挂载或卸载设备会真实地反映到主机上

slave:服务使用独立的 Mount Namespace,它会继承主机挂载点,但服务对挂载点的操作只有在自己的 Namespace 内生效,不会反映到主机上

private:服务使用独立的 Mount Namespace,它在启动时没有任何任何挂载点,服务对挂载点的操作也不会反映到主机上

LimitCPU / LimitSTACK / LimitNOFILE / LimitNPROC 等:限制特定服务的系统资源量,例如 CPU、程序堆栈、文件句柄数量、子进程数量等

注意:如果在 ExecStart、ExecStop 等属性中使用了 Linux 命令,则必须要写出完整的绝对路径。对于 ExecStartPre 和 ExecStartPost 辅助命令,若前面有个 “-” 符号,表示忽略这些命令的出错。因为有些 “辅助” 命令本来就不一定成功,比如尝试清空一个文件,但文件可能不存在。

2.3. Install 部分介绍

在理解 Install 部分前我们需要向了解两个基本概念:

Unit 和 Target
Unit 是 Systemd 管理系统资源的基本单元,可以认为每个系统资源就是一个 Unit,并使用一个 Unit 文件定义。在 Unit 文件中需要包含相应服务的描述、属性以及需要运行的命令。

Target 是 Systemd 中用于指定系统资源启动组的方式,相当于 SysV-init 中的运行级别。

简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。从这个意义上说,Target 这个概念类似于”状态点”,启动某个 Target 就好比启动到某种状态。

2.3.1. Install 属性详解

这部分配置的目标模块通常是特定运行目标的 .target 文件,用来使得服务在系统启动时自动运行。这个区段可以包含三种启动约束:

  • WantedBy:和 Unit 段的 Wants 作用相似,只有后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入 /etc/systemd/system 目录下面以 <Target 名> + .wants 后缀构成的子目录中,如 “/etc/systemd/system/multi-user.target.wants/“
  • RequiredBy:和 Unit 段的 Wants 作用相似,只有后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入 /etc/systemd/system 目录下面以 <Target 名> + .required 后缀构成的子目录中
  • Also:当前 Unit enable/disable 时,同时 enable/disable 的其他 Unit
  • Alias:当前 Unit 可用于启动的别名

3. Systemctl 命令

service 管理

查看当前系统的所有 Unit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 列出正在运行的 Unit
$ systemctl list-units

# 列出所有Unit,包括没有找到配置文件的或者启动失败的
$ systemctl list-units --all

# 列出所有没有运行的 Unit
$ systemctl list-units --all --state=inactive

# 列出所有加载失败的 Unit
$ systemctl list-units --failed

# 列出所有正在运行的、类型为 service 的 Unit
$ systemctl list-units --type=service

# 查看 Unit 配置文件的内容
$ systemctl cat docker.service

查看 service 的状态

1
2
3
4
5
6
7
8
# 显示系统状态
$ systemctl status

# 显示单个 Unit 的状态
$ ystemctl status bluetooth.service

# 显示远程主机的某个 Unit 的状态
$ systemctl -H root@rhel7.example.com status httpd.service

Unit 的管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 立即启动一个服务
$ 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

查看 Unit 的依赖关系

1
2
3
4
5
6
7

# 列出一个 Unit 的所有依赖,默认不会列出 target 类型
$ systemctl list-dependencies nginx.service

# 列出一个 Unit 的所有依赖,包括 target 类型
$ systemctl list-dependencies --all nginx.service

Target 管理

Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。

在传统的 SysV-init 启动模式里面,有 RunLevel 的概念,跟 Target 的作用很类似。不同的是,RunLevel 是互斥的,不可能多个 RunLevel 同时启动,但是多个 Target 可以同时启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 查看当前系统的所有 Target
$ systemctl list-unit-files --type=target

# 查看一个 Target 包含的所有 Unit
$ systemctl list-dependencies multi-user.target

# 查看启动时的默认 Target
$ systemctl get-default

# 设置启动时的默认 Target
$ sudo systemctl set-default multi-user.target

# 切换 Target 时,默认不关闭前一个 Target 启动的进程,systemctl isolate 命令改变这种行为,关闭前一个 Target 里面所有不属于后一个 Target 的进程
$ sudo systemctl isolate multi-user.target

日志管理

Systemd 通过其标准日志服务 Journald 提供的配套程序 journalctl 将其管理的所有后台进程打印到 std:out(即控制台)的输出重定向到了日志文件。

Systemd 的日志文件是二进制格式的,必须使用 Journald 提供的 journalctl 来查看,默认不带任何参数时会输出系统和所有后台进程的混合日志。

默认日志最大限制为所在文件系统容量的 10%,可以修改 /etc/systemd/journald.conf 中的 SystemMaxUse 来指定该最大限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

# 查看所有日志(默认情况下 ,只保存本次启动的日志)
$ sudo journalctl

# 查看内核日志(不显示应用日志):--dmesg 或 -k
$ sudo journalctl -k

# 查看系统本次启动的日志(其中包括了内核日志和各类系统服务的控制台输出):--system 或 -b
$ sudo journalctl -b
$ sudo journalctl -b -0

# 查看上一次启动的日志(需更改设置)
$ sudo journalctl -b -1

# 查看指定服务的日志:--unit 或 -u
$ sudo journalctl -u docker.servcie

# 查看指定服务的日志
$ sudo journalctl /usr/lib/systemd/systemd

# 实时滚动显示最新日志
$ sudo journalctl -f

# 查看指定时间的日志
$ sudo journalctl --since="2012-10-30 18:17:16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"

# 显示尾部的最新 10 行日志:--lines 或 -n
$ sudo journalctl -n

# 显示尾部指定行数的日志
$ sudo journalctl -n 20

# 将最新的日志显示在前面
$ sudo journalctl -r -u docker.service

# 改变输出的格式:--output 或 -o
$ sudo journalctl -r -u docker.service -o json-pretty

# 查看指定进程的日志
$ sudo journalctl _PID=1

# 查看某个路径的脚本的日志
$ sudo journalctl /usr/bin/bash

# 查看指定用户的日志
$ sudo journalctl _UID=33 --since today

# 查看某个 Unit 的日志
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today

# 实时滚动显示某个 Unit 的最新日志
$ sudo journalctl -u nginx.service -f

# 合并显示多个 Unit 的日志
$ journalctl -u nginx.service -u php-fpm.service --since today

# 查看指定优先级(及其以上级别)的日志,共有 8 级
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b

# 日志默认分页输出,--no-pager 改为正常的标准输出
$ sudo journalctl --no-pager

# 以 JSON 格式(单行)输出
$ sudo journalctl -b -u nginx.service -o json

# 以 JSON 格式(多行)输出,可读性更好
$ sudo journalctl -b -u nginx.serviceqq
-o json-pretty

# 显示日志占据的硬盘空间
$ sudo journalctl --disk-usage

# 指定日志文件占据的最大空间
$ sudo journalctl --vacuum-size=1G

# 指定日志文件保存多久
$ sudo journalctl --vacuum-time=1years

4. 参考文档

Linux 添加自定义服务

Chapter 10. Managing Services with systemd

可能是史上最全面易懂的 Systemd 服务管理教程!

作者

鹏叔

发布于

2024-09-04

更新于

2024-09-04

许可协议

评论