init、service、systemd、systemctl

Linux 服务管理方式有三种:initservicesystemctl

init

https://zh.wikipedia.org/wiki/Init

历史上,Linux 的启动一直采用 init 进程。

在类Unix 的计算机操作系统中,Init(初始化的简称)是在启动计算机系统期间启动的第一个进程。

Init 是一个守护进程,它将持续运行,直到系统关闭。它是所有其他进程的直接或间接的父进程。

init 的参数全在/etc/init.d目录下,init.d 目录中存放的是一系列系统服务的管理(启动与停止)脚本。/etc/init.d 是指向 /etc/rc.d/init.d 的软连接。

init.d 初始化脚本称之为System V风格初始化,是System V系统传统之一,后来成为一些Unix系统的共同特性的源头。值得一提的是,在 /etc 目录下可能还包含 rc#.d 目录,这也是System V风格,#为数字0到6,为系统的运行级别runlevel。可见System V风格影响深远。

service 命令 可执行 init.d 目录中相应服务的脚本。service 是控制系统服务的实用工具。

1
$ sudo /etc/init.d/nginx start

service

service 命令是 System V init 的一层封装,本质上就是调用 /etc/init.d/服务名 脚本去执行对应的 start/stop/restart 操作。

1
2
3
service nginx start
# 等价于
/etc/init.d/nginx start

为什么这样说?

  • service 只是一个工具脚本,位于 /sbin/service
  • 它会去查找 /etc/init.d/ 目录中的对应脚本,然后执行
  • 所以调用 service nginx start 实际就是执行 /etc/init.d/nginx start

type 命令验证它:

1
2
type service
# service is /usr/sbin/service

对比 systemd:

特性 System V init systemd
状态追踪 手动处理 自动
启动顺序 手动配置 + 数字排序 自动依赖图
并发启动 不支持(一个个启动,不能并发) ✅ 支持,并且支持按需启动(lazy activation)。
日志 各服务输出独立 统一 journalctl
配置格式 脚本 .service 文件(ini 风格)

systemd

相关链接:

https://zh.wikipedia.org/wiki/Systemd

https://wiki.archlinuxcn.org/wiki/Systemd

https://systemd.io/#manual-pages 官方手册,分为两部分:索引页和指令页。索引页列出 systemd 项目中的所有手册页。

索引页:

https://www.freedesktop.org/software/systemd/man/latest/index.html

https://www.jinbuguo.com/systemd/systemd.index.html 中文

指令页:

https://www.freedesktop.org/software/systemd/man/latest/systemd.directives.html

https://www.jinbuguo.com/systemd/systemd.directives.html 中文

systemd 是一个 Linux 系统基础组件的集合,提供了 系统和服务管理器(System and Service Manager)。是 Linux 系统中最新的初始化系统(init),它主要的设计目的是克服 System V init 固有的缺点,提高系统的启动速度。根据 Linux 惯例,字母 d 是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。

执行 ps -ef 发现,PID 等于 1 的进程是 systemd。也就是说,systemd 已经取代了 init 成为系统的第一个进程,其他进程都是它的子进程。

查看 Systemd 版本信息的方式之一:systemctl --version

systemd.unit

本手册列出了所有单元类型通用的配置选项。这些选项需要在单元文件的 [Unit] 或 [Install] 部分进行配置。除了通用 [Unit] 和 [Install] 部分外,每个单元可能还有特定于类型的部分,例如服务单元的 [Service] 部分。

Unit 文件是 systemd 用于描述“单元”的配置文件,是 systemd 管理 Unit 的“说明书”,告诉 systemd 该服务如何启动、何时启动、依赖谁、崩溃后怎么办 ……。

Unit 文件的常见类型后缀:service, socket, device, mount, automount, swap, target, path, timer, slice, scope。

unit 文件存放路径(优先级从高到低)

  • /etc/systemd/system/ :Local configuration。用户自定义 unit 文件的习惯位置。该目录有很多 *.target.wants/ 子目录,它们决定哪些服务随 target 自启动,其中都是指向其他 unit 文件的符号链接。

    [!IMPORTANT]

    systemctl enable nginx 做了以下几件事:

    1、按 unit 文件存放路径的优先级(从高到低)查找 unit 文件

    2、根据 [Install] 段中的 WantedBy=RequiredBy= 字段,决定符号连接创建在哪个 .target.wants/ 子目录下。比如:

    1
    2
    [Install]
    WantedBy=multi-user.target

    会创建:

    1
    2
    3
    Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
    #
    Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld@3306.service to /etc/systemd/system/mysqld@.service.

    3、下次启动 multi-user.target 时,就会自动启动该 unit。

    提示:任何 unit 类型,只要它的 [Install] 段中有 WantedByRequiredBy,就可以被 enable,就可以实现开机/触发自启。

  • /run/systemd/system/ :Runtime units。程序运行时动态生成 unit 文件的位置(临时)。很少触碰,除非需要修改程序运行时的一些参数。

  • /usr/lib/systemd/system//lib/systemd/system/ :Units of installed packages。安装软件包时默认生成 unit 文件的位置。专门供软件发行者使用。/lib/systemd/ 通常是指向 /usr/lib/systemd/符号链接

  • /etc/systemd/user//usr/lib/systemd/user/ :用户级 unit。

systemd.exec

本手册列出了 service, socket, mount, swap 单元所共有的 、 用于定义进程执行环境的配置选项(亦称”配置指令”或”单元属性”)。

此外,通过 cgroup 控制资源占用的选项位于 systemd.resource-control(5). 手册中。它们是对本文所列选项的补充。

systemd.service

Type=

配置服务通知管理器服务启动完成的机制。可选值包括:simple(默认)、execforkingoneshotdbusnotifynotify-reloadidle


simple(默认)

如果设置为 simple(这是默认值——当指定了 ExecStart=,但没有设置 Type=BusName=,且未使用凭据时),那么服务管理器会在主服务进程刚刚通过 fork() 被创建出来时,就立即认为该服务已启动完成。也就是说,它不会等到该进程完成各种属性配置,甚至不会等到它调用 execve() 来真正执行服务程序。这种方式启动非常快,但有一定风险。

通常,更推荐使用 Type=exec,详见下文说明。

simple 模式下,ExecStart= 所指定的进程应当是服务的主进程。如果这个服务需要对系统中的其他进程提供功能,那么它的通信接口(例如 socket)应当在服务启动前就准备好,比如通过 systemd 的 socket 激活机制预先创建好。因为 systemd 会在服务主进程刚创建完(但服务程序还未真正开始运行)时,就立刻开始启动后续依赖的服务单元。

需要特别注意的是:这意味着即便服务的程序实际上无法成功执行(例如指定的 User= 用户不存在,或服务的可执行文件缺失),systemctl start 命令仍然会报告“启动成功”。


exec

exec 类型与 simple 类型类似,但服务管理器会在主服务程序被成功执行(即 execve() 调用成功)之后,才将该服务单元视为“已启动”。在此之前,服务管理器会延迟启动后续的服务单元

换句话说:

  • 使用 simple 类型时,systemd 会在服务进程调用 fork() 返回之后立即继续处理其他任务,不管实际的服务程序是否成功运行;
  • 而使用 exec 类型时,systemd 会等到服务进程完成 fork()execve(),即真正开始执行服务的主程序之后,才继续后续操作。

这也意味着,对于 exec 类型的服务,如果服务的主程序无法成功执行(例如指定的 User= 用户不存在,或者服务的可执行文件丢失),systemctl start 命令会报告启动失败,而不是像 simple 那样误报成功。

另外,当使用 凭据(credentials) 时(参见 LoadCredential=,详见 systemd.exec(5) 手册),将隐式采用 Type=exec 类型。

使用场景:见 Consul 开机自启filebrowser 开机自启


forking

如果将类型设置为 forking,管理器会在由管理器派生出的子进程(fork 出的二进制)退出后立刻将该单元视为已启动。此类型的使用并不推荐,建议改用 notifynotify-reloaddbus

通常情况下,配置在 ExecStart= 的进程应在启动过程中调用 fork()。父进程在完成启动并建立所有通信通道后会退出,而子进程则继续作为主要服务进程运行,当父进程退出时服务管理器便会将该单元视为已启动。这与传统 UNIX 服务的行为相同。如果使用此设置,建议同时指定 PIDFile= 选项,以便 systemd 能够可靠地识别服务的主进程。当父进程退出后,管理器会继续启动后续单元。


oneshot

Type=oneshot 的行为类似于 simple,但区别在于服务管理器会在主进程退出之后才认为该单元已启动完成,随后才会启动后续的依赖单元。对于此类型的服务,RemainAfterExit= 选项尤其有用。如果既未指定 Type=,又未指定 ExecStart=,则默认会推断为 Type=oneshot

请注意,如果在未设置 RemainAfterExit= 的情况下使用此类型,则该服务单元将永远不会进入 “active(激活)” 状态,而是会直接从 “activating(激活中)” 状态过渡到 “deactivating(去激活)” 或 “dead(停止)” 状态,因为 systemd 并未配置一个需要持续运行的进程来维持活跃状态,特别是,这意味着该类型的服务在运行完成后(且未设置 RemainAfterExit= 的情况下)将不会显示为已启动,而是显示为 dead(停止)状态。


dbus

Type=dbus 的行为类似于 simple,但此类型的单元必须指定 BusName=,服务管理器会在指定的总线名称被获取后才认为该单元已启动。如果指定了 BusName=,则默认类型即为 dbus

配置了此选项的服务单元会隐式地依赖于 dbus.socket 单元。此类型的服务单元在指定总线名称尚未获取时被视为处于激活中(activating)状态,而在总线名称被占用期间则被视为已激活(activated)。一旦总线名称被释放,服务将被视为不再可用,这会导致服务管理器尝试终止该服务所属的任何残留进程。因此,将释放总线名称作为关机逻辑的一部分的服务应当准备好接收 SIGTERM(或 KillSignal= 中配置的信号)。


notify

Type=notify 的行为类似于 exec,但要求服务在完成启动后通过 sd_notify(3) 或等效调用发送 "READY=1" 通知消息。systemd 会在收到此通知消息之后才继续启动后续依赖单元。如果使用此类型,应当设置 NotifyAccess=,以开放 systemd 提供的通知套接字的访问权限;

如果未设置 NotifyAccess= 或将其设置为 none,则会强制设置为 main

如果服务支持重新加载,并且使用信号触发重新加载,则建议使用 notify-reload 类型。


notify-reload

Type=notify-reload 的行为类似于 notify,但有一个区别:当服务被要求重新加载时,会向服务的主进程发送 SIGHUP UNIX 信号,并且服务管理器会等待关于重新加载完成的通知。

在启动重新加载过程时,服务应当通过 sd_notify(3) 发送一条通知消息,其中包含 "RELOADING=1" 字段,并且 "MONOTONIC_USEC=" 设置为当前单调时间(即 clock_gettime(2)CLOCK_MONOTONIC)的微秒数,并以十进制字符串格式表示。一旦重新加载完成,必须发送另一条通知消息,其中包含 "READY=1"。使用这种服务类型并实现该重新加载协议,是提供 ExecReload= 命令以重新加载服务配置的高效替代方案。

发送的信号可以通过 ReloadSignal= 进行调整。


idle

Type=idle 的行为与 simple 非常相似,但服务程序的实际执行会被延迟,直到所有活动的作业都已分发完成。此类型可用于避免 shell 服务的输出与控制台上的状态输出交错。需要注意的是,这种类型仅用于改善控制台输出效果,并不适合作为通用的单元排序工具,而且该类型的效果存在一个 5 秒的超时限制,超时后服务程序仍会被执行。


.service 模板及说明

vim /etc/systemd/system/demo.service

注意:单元文件的注释必须独占一行,可以包含中文

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
# demo.service
[Unit]
Description= demo server
Documentation= https://zh.wikipedia.org/wiki/Systemd
# After 和 Before 用于配置单元之间的启动顺序依赖关系。
After=syslog.target network.target network-online.target # 配置单元在列出的单元开始启动之后启动。
Before= # 配置的单元在列出的单元开始启动之前启动。
Wants= # 弱依赖关系。列出的单元将在配置单元启动时启动。即使列出的单元无法启动也不影响配置单元的启动。这是将一个单元的启动与另一个单元的启动挂钩的推荐方式。
Requires= # 强依赖关系。列出的单元将在配置单元启动时启动。如果其中一个列出的单元启动失败或因设置了After导致未能启动,则配置单元也不会启动。
BindsTo= # 更强的依赖关系。除了 Requires 的效果外,它还声明如果与之绑定的单元被停止,该单元也将别停止。
ConditionPathExists= # 条件检查:检查文件是否存在。如果指定的绝对路径名不存在,则条件失败。前面带有感叹号 ("!"),表示测试结果被否定,即只有当路径不存在时,该单元才会启动。
ConditionPathIsSymbolicLink= # 同 ConditionPathExists

[Service]
Type=
#PIDFile= # 配合 forking
#NotifyAccess= # 配合 notify
# 配置在请求重新加载服务配置时,向服务的主进程发送的 UNIX 进程信号。默认值为 SIGHUP。此选项仅在使用 Type=notify-reload 时生效。
#ReloadSignal=SIGHUP

# 设置进程在执行时使用的用户与组。
User=<user>
Group=<group>

# 定义哪些退出码(exit code)应被视为“成功退出”,从而防止 systemd 误判服务“失败”并重启或报错。默认情况下,systemd 认为:0 ➜ 成功,非0 ➜ 失败。
SuccessExitStatus=0
# 控制服务是否使用私有的临时目录。默认false,通常情况下建议关闭。
PrivateTmp=false

# 服务启动时执行的命令
ExecStart=
# 在 ExecStart= 命令之前或之后执行的额外命令,分别位于 ExecStart= 命令之前或之后。语法与 ExecStart= 命令相同。无论服务类型(即 Type=)如何,均允许多个命令行,且这些命令将依次顺序执行。如果这些命令中任何一个(未以 "-" 开头)执行失败,其余命令将不再执行,且该单元被视为执行失败。
ExecStartPre=
ExecStartPost=

# 用于停止通过 ExecStart= 启动的服务的命令。
ExecStop=
# 设置在该服务停止之后所执行的命令行。
ExecStopPost=
# 设置当该服务 被要求重新载入配置时 所执行的命令行。
ExecReload=

# 当服务进程 正常退出、异常退出、被杀死、超时的时候, 是否重新启动该服务。 所谓"服务进程" 是指 ExecStart=, ExecStartPre=, ExecStartPost=, ExecStop=, ExecStopPost=, ExecReload= 中设置的进程。 当进程是由于 systemd 的正常操作(例如 systemctl stop|restart)而被停止时, 该服务不会被重新启动。
Restart=on-failure
# 配置在重新启动服务之前(通过 Restart= 配置)的睡眠时间。接受以秒为单位的无单位值,或时间跨度值,例如“5min 20s”。默认值为100ms。
RestartSec=100ms

# 设置该服务允许的最大启动时长。
TimeoutStartSec=90s
# 1、设置每个 ExecStop= 的超时时长。如果其中之一超时, 那么所有后继的 ExecStop= 都会被取消,并且该服务也会被 SIGTERM 信号强制关闭。如果该服务没有设置 ExecStop= ,那么该服务将会立即被 SIGTERM 信号强制关闭。
# 2、设置该服务自身停止的超时时长。
TimeoutStopSec=90s
# 配置 TimeoutStartSec= 和 TimeoutStopSec= 到指定值的简写方式。默认值90s
TimeoutSec=90s

# 如果 unit 文件中没有 LimitNOFILE 或 LimitNPROC,systemd 会继承 /etc/systemd/system.conf 或 /etc/systemd/user.conf 中的默认设置。
# 可通过 systemctl show <unit> 查看实际限制。systemctl show mysql | grep Limit
# 文件描述符的数量(最大打开文件数),等价 ulimit -n,最大值 `cat /proc/sys/fs/file-max`
LimitNOFILE=65535
# 进程的数量(最大进程/线程数),等价 ulimit -u
LimitNPROC=65535

[Install]
# 详见 systemd.unit 部分
WantedBy=multi-user.target

Type=forking

Type=forking:systemd 会等待程序 fork(复制、克隆) 子进程并根据 PIDFile 来追踪主进程PIDFile 是由服务程序本身生成的,systemd 只是读取它来追踪主进程。在使用 Type=forking 时必须正确配置,否则 systemd 无法管理服务状态。

Nginx开机自启

Nginx 默认以守护进程模式启动(以 daemon 方式后台运行),即启动后会 fork 子进程并退出主进程。这与 Type=exec 不兼容。若强行使用 exec,Systemd 会误判主进程已退出(因为启动命令执行完,但没有持续运行的前台进程),从而认为服务启动失败。

如果非要改为 Type=exec,你必须要禁用 nginx 的守护模式

解决方法:关闭 Nginx 的守护模式,让它以前台方式运行:

1
2
3
4
[Service]
Type=exec
# 加上 -g "daemon off;" 参数,告诉 Nginx 以前台方式运行。
ExecStart=/opt/local/nginx/sbin/nginx -g "daemon off;"

systemctl

systemctl 是用来**管理 systemd 单元(unit)**的命令行工具,它可以用来启动、停止服务,设置开机启动,查看服务状态,以及控制系统本身(重启、关机、睡眠等)。

常用命令速查表(精选高频)

操作 命令 说明
启动服务 systemctl start nginx 立即启动服务
停止服务 systemctl stop nginx 停止服务
重启服务 systemctl restart nginx 通常用于服务配置变更
重载配置 systemctl reload nginx 不重启,只重读配置(服务需支持)
查看状态 systemctl status nginx 查看运行状态、日志、PID
设置开机启动 systemctl enable nginx 创建启动链接
取消开机启动 systemctl disable nginx 删除启动链接
查看是否开机启动 systemctl is-enabled nginx 输出 enableddisabled
重新加载 unit 配置 systemctl daemon-reload 修改 .service 文件后必需
查看已加载的 unit `systemctl list-units grep ‘’<br/>systemctl list-units –type=service
查看所有 unit `systemctl list-unit-files grep ‘’<br/>systemctl list-unit-files –type=service

systemctl 能管理哪些资源?

systemd 把系统中的组件抽象为不同类型的 unit(单元)systemctl 正是用来管理这些 unit 的:

单元类型 后缀 说明
服务 .service 后台程序或服务
目标 .target 逻辑启动阶段(类似 SysV 的 runlevel)
套接字 .socket 套接字触发的服务
定时器 .timer 定时任务(替代 cron
挂载点 .mount 文件系统挂载控制
设备 .device 设备事件
路径 .path 监控某路径触发服务

可以指定类型查看:

1
2
systemctl list-units --type=timer
systemctl list-units --type=socket

控制系统本身的操作

命令 说明
systemctl reboot 重启系统
systemctl poweroff 关机
systemctl suspend 挂起
systemctl halt 停止系统(关机但不切断电源)
systemctl rescue 进入救援模式
systemctl default 切换到默认 target(正常启动级别)

journalctl

https://www.runoob.com/linux/linux-comm-journalctl.html

journalctl 是 Linux 系统中用于查询和显示 systemd 日志的强大工具。作为 systemd 生态系统的一部分,它提供了集中化的日志管理功能,替代了传统的 syslog 服务。

核心特点:

  1. 二进制日志存储:日志存储在以 .journal 结尾的二进制文件,提高检索效率。
  2. 结构化日志:支持附加元数据和结构化日志字段
  3. 实时监控:可以实时跟踪日志变化
  4. 多种过滤方式:支持按时间、服务、优先级等多种条件过滤
1
2
3
4
journalctl                     # 查看所有日志
journalctl -u nginx.service # 查看某个服务
journalctl -xe # 查看最近报错(增强模式)
journalctl --since today # 查看今天的日志

总结:

类型 路径 持久性 是否推荐直接查看
持久化日志 /var/log/journal/ ✅ 是 ❌ 用 journalctl
内存中日志 /run/log/journal/ ❌ 重启丢失
文本形式日志 ❌(默认无) -