systemd
systemd:
systemd
是当前 Linux 系统中主流的初始化系统和服务管理器,取代了传统的 SysVinit
。
核心优势
- 并行启动: 显著加快系统启动速度,服务可同时启动。
- 依赖管理: 精确控制服务启动和停止顺序。
- 统一管理: 为所有系统服务提供统一配置和行为。
- 进程跟踪: 基于
cgroups
,准确跟踪服务所有进程,避免孤儿进程。 - 按需启动: 服务只在需要时才启动,节省资源。
- 集中日志 (
Journald
): 统一收集和查询所有系统日志。
与 SysVinit
的主要区别
特性 | SysVinit | systemd |
---|---|---|
启动 | 串行 | 并行 |
配置 | Shell 脚本 | 单元文件 (.service ) |
日志 | 分散文本 | Journald (二进制日志) |
跟踪 | 基于 PID | 基于 Cgroups |
机制 | 简单初始化 | 集成更多系统组件、功能更强大 |
systemctl
:服务管理核心命令
这是与 systemd
交互的主要工具。
sudo systemctl start <服务名>
: 启动服务。sudo systemctl stop <服务名>
: 停止服务。sudo systemctl restart <服务名>
: 重启服务。sudo systemctl reload <服务名>
: 重新加载服务配置(不中断服务)。systemctl status <服务名>
: 查看服务状态。sudo systemctl enable <服务名>
: 设置服务开机自启动。sudo systemctl disable <服务名>
: 取消服务开机自启动。sudo systemctl enable --now <服务名>
: 立即启用并启动服务。systemctl is-active <服务名>
: 检查服务是否活跃。systemctl is-enabled <服务名>
: 检查服务是否已启用。sudo systemctl daemon-reload
: 在修改单元文件后,必须执行此命令,让systemd
重新加载配置。sudo systemctl mask <服务名>
: 阻止服务启动。sudo systemctl unmask <服务名>
: 解除服务阻止。systemctl list-units --type=service
: 列出所有服务单元。systemctl list-unit-files --type=service
: 列出所有服务单元文件及其状态。
单元文件 (.service
)
systemd
的核心配置是单元文件,通常位于 /etc/systemd/system/
和 /usr/lib/systemd/system/
。它们是描述服务、挂载点、定时器等的配置文件。
基本结构示例:
Ini, TOML
1 | [Unit] |
journalctl
:日志查询工具
用于查询 systemd
统一收集的二进制日志。
journalctl
: 查看所有日志。journalctl -f
: 实时查看最新日志。journalctl -u <服务名>
: 查看特定服务的日志。journalctl -u <服务名> -f
: 实时查看特定服务的日志。journalctl -b
: 查看本次启动以来的日志。journalctl --since "1 hour ago"
: 查看指定时间范围内的日志。journalctl -p err
: 只看错误日志。
1. 单元文件 (Unit Files) 详解
单元文件是 systemd
配置的核心,每个单元文件都定义了一个 systemd
可以管理的对象(服务、挂载点、定时器等)。
常见单元文件路径:
/usr/lib/systemd/system/
: 由软件包安装的默认单元文件。/etc/systemd/system/
: 系统管理员自定义或覆盖的单元文件。此路径下的文件优先级高于/usr/lib/systemd/system/
。/run/systemd/system/
: 运行时生成的单元文件。
单元文件常见节 (Sections):
[Unit]
节: 定义单元的通用信息和依赖关系。Description=
: 单元的简短描述。Documentation=
: 单元的文档链接(man page 或 URL)。After=
,Before=
: 定义单元的启动/停止顺序。例如,After=network.target
表示此单元在网络服务启动后才启动。Wants=
,Requires=
: 定义单元的弱/强依赖关系。Wants=
: 推荐的依赖方式。如果依赖的单元不存在或启动失败,当前单元仍会尝试启动。Requires=
: 强依赖。如果依赖的单元不存在或启动失败,当前单元也会失败。
Conflicts=
: 定义与此单元冲突的单元。如果冲突单元正在运行,此单元将停止它。PartOf=
: 如果此单元是另一个单元的一部分,当主单元停止时,此单元也会停止。
[Service]
节 (仅用于.service
单元): 定义服务的具体行为。Type=
: 定义服务进程的启动类型(simple
,forking
,oneshot
,dbus
,notify
,idle
)。simple
: 默认,ExecStart
命令是主进程。forking
:ExecStart
命令会fork
出一个子进程作为主进程,父进程退出。oneshot
: 命令执行一次后退出,systemd
认为服务已完成。
ExecStart=
: 启动服务的命令。ExecStop=
: 停止服务的命令。ExecReload=
: 重新加载服务配置的命令。Restart=
: 定义服务崩溃或退出时的重启策略(no
,on-success
,on-failure
,on-abnormal
,on-watchdog
,on-abort
,always
)。RestartSec=
: 两次重启之间的等待时间。User=
,Group=
: 运行服务的用户和组。WorkingDirectory=
: 服务的工作目录。Environment=
,EnvironmentFile=
: 设置环境变量。
[Install]
节: 定义单元在启用 (systemctl enable
) 时的行为。WantedBy=
,RequiredBy=
: 定义此单元应该被哪个target
单元“拉起”(即成为其依赖)。例如,WantedBy=multi-user.target
表示当multi-user.target
启动时,此服务也会被尝试启动。
[Unit]
[Unit]
节定义了 systemd
单元的元数据和依赖关系,控制单元启动顺序和与其他单元的交互。
Description=简短描述
: 单元的人类可读描述,systemctl status
会显示。比如:Description=My Custom Web Application
。Documentation=文档链接
: 单元的文档链接,可以是man
页或 URL。比如:Documentation=https://example.com/docs/my-app
。After=单元A 单元B ...
: 启动排序。此单元在列出的单元启动之后才启动。这是一种弱排序,即使依赖单元启动失败,也不影响当前单元尝试启动。比如:After=network.target postgresql.service
。Before=单元A 单元B ...
: 启动排序。此单元在列出的单元启动之前启动。同样是弱排序。Before
和After
互斥。比如:Before=multi-user.target
。Wants=单元A 单元B ...
: 弱依赖。此单元启动时,会尝试启动列出的单元。如果依赖单元不存在或启动失败,不会影响当前单元的启动。主要用于启动触发。比如:Wants=network-online.target
。Requires=单元A 单元B ...
: 强依赖。此单元启动时,会尝试启动列出的单元。如果任何依赖单元不存在、启动失败,或者在当前单元运行期间停止/失败,当前单元也会被停止/失败。用于保证依赖单元的存活。比如:Requires=postgresql.service
。Conflicts=单元A 单元B ...
: 冲突管理。如果列出的单元正在运行,此单元激活时会停止并禁用它们;反之亦然。用于管理互斥的服务。比如:Conflicts=apache2.service
。PartOf=主单元
: 组成部分。此单元是主单元的一部分,当主单元停止或重启时,此单元也会停止或重启。它隐式包含了After
依赖。比如:PartOf=web-server.target
。Requisite=单元A 单元B ...
: 严格强依赖。如果列出的单元在当前单元启动时未处于活动状态,当前单元将直接失败,而不会尝试启动它们。比Requires
更严格,用于当依赖必须已经运行时。比如:Requisite=some-critical-pre-existing-condition.service
。BindsTo=单元A 单元B ...
: 绑定依赖。当列出的单元停止时,此单元也会停止。与Requires
不同的是,如果BindsTo
的单元在启动时未就绪,此单元不会因此失败。它主要用于绑定到一些生命周期与当前单元紧密相关的资源,例如mount
单元。比如:BindsTo=var-log-audit.mount
。
[service]
systemd
单元文件中 [Service]
节(Section)里的指令 (Directives) 或选项 (Options)
这些都是 systemd
自身定义和理解的配置关键字。它们遵循 key=value
的格式,每个关键字都有特定的含义和作用。你可以在 systemd
的官方文档(man pages)中找到这些指令的详细说明,例如 man systemd.service
。
[Service]
节中可用的指令非常多,这里列举一些常见的,按功能分类:
1. 进程行为控制
Type=
: 定义服务的启动类型(例如simple
,forking
,oneshot
,notify
)。ExecStart=
: 启动服务的命令行。这是最重要的指令。ExecStop=
: 停止服务的命令行。ExecReload=
: 重新加载服务配置的命令行(不停止服务)。ExecStartPre=
,ExecStartPost=
,ExecStopPost=
: 在主命令执行之前/之后执行的附加命令。PIDFile=
: 如果Type=forking
,指定服务主进程的 PID 文件路径,systemd
会通过此文件跟踪进程。RemainAfterExit=yes
: 如果Type=oneshot
,即使ExecStart
命令退出,systemd
仍认为服务处于“活跃”状态。
2. 用户和权限
User=
: 运行服务的用户名。Group=
: 运行服务的组名。UMask=
: 为服务创建的文件设置默认权限掩码。PermissionsStartOnly=yes
: 只在ExecStart
期间应用权限设置。
3. 重启和错误处理
Restart=
: 定义服务崩溃或退出时的重启策略(例如on-failure
,always
,no
)。RestartSec=
: 两次重启尝试之间的等待时间。TimeoutStartSec=
: 服务启动的最大超时时间。TimeoutStopSec=
: 服务停止的最大超时时间。
4. 资源限制
LimitNOFILE=
: 增加或减少服务进程可以打开的最大文件描述符数量。LimitNPROC=
: 增加或减少服务进程可以创建的最大进程数量。CPUShares=
: CPU 共享权重,影响服务在 CPU 繁忙时的优先级。MemoryLimit=
: 限制服务可使用的最大内存量。IOWeight=
,BlockIOWeight=
: 设置服务 I/O 调度的权重。
5. 环境和目录
WorkingDirectory=
: 服务的工作目录。Environment=
: 设置服务的环境变量(可以直接在单元文件中定义)。EnvironmentFile=
: 从指定文件加载环境变量。
6. 安全性
ProtectSystem=
,ProtectHome=
: 限制服务对系统和用户目录的写入权限。ReadWritePaths=
,ReadOnlyPaths=
: 细粒度控制读写或只读路径。CapabilityBoundingSet=
: 限制服务进程的 Linux capabilities(能力)。PrivateTmp=yes
: 为服务提供独立的/tmp
和/var/tmp
目录,增强隔离性。NoNewPrivileges=yes
: 防止服务获取新的权限。
[Install]
控制服务开机自启动,[Install]
节定义了 systemctl enable/disable
命令如何处理单元,主要通过软链接使其在引导时自动启动。
WantedBy=目标单元A 目标单元B ...
: 最常用。此单元在列出的目标单元启动时被尝试启动(弱依赖)。即使此单元启动失败,也不影响目标单元。常见目标有multi-user.target
(服务器默认)、graphical.target
(图形界面) 和sockets.target
(套接字激活服务)。- 示例:
WantedBy=multi-user.target
- 示例:
RequiredBy=目标单元A 目标单元B ...
: 强依赖。此单元被列出的目标单元要求。如果此单元启动失败,目标单元也将失败。不如WantedBy=
常见,用于关键服务。- 示例:
RequiredBy=some-critical-custom.target
- 示例:
Alias=别名A 别名B ...
: 为单元创建额外名称。启用单元时,这些别名的软链接也会被创建。用于兼容性或更具描述性的名称。- 示例:
Alias=my-legacy-app.service
- 示例:
Also=单元A 单元B ...
: 当当前单元被启用或禁用时,同时启用或禁用列出的其他单元。用于管理一组相关单元。示例:
Ini, TOML
1
2
3
4# my-main-app.service
[Install]
WantedBy=multi-user.target
Also=my-worker.service my-db-proxy.service
systemd
单元文件中的指令名称(key
部分)是区分大小写的。**
例如:
- 你必须写
Group=
而不是group=
或GROUP=
。 - 你必须写
LimitNOFILE=
而不是limitnofile=
。
虽然有些值(比如布尔值 yes
/no
/true
/false
,或者时间单位 s
/min
/h
)不区分大小写,但指令本身必须严格按照其在 systemd
文档中定义的大小写来写。否则,systemd
将无法正确识别这些指令,可能会导致服务无法启动或行为异常,通常会报解析错误。
在实际操作中,最好总是复制粘贴官方文档或现有单元文件中的指令名,以避免大小写错误。
2. 特殊的单元类型
除了常见的 .service
单元,systemd
还提供了多种其他单元类型来管理不同的系统资源:
.target
单元:- 类似于
SysVinit
的**运行级别 (Runlevels)**,但更灵活。一个target
只是一个分组,用于将多个单元组织在一起,并作为同步点。 - 常见
target
与SysVinit
运行级别对应关系:poweroff.target
(runlevel 0): 关机。rescue.target
(runlevel 1): 单用户模式。multi-user.target
(runlevel 2, 3, 4): 多用户命令行模式。graphical.target
(runlevel 5): 多用户图形界面模式。reboot.target
(runlevel 6): 重启。
- 切换
target
:sudo systemctl isolate <target_name>
(例如:sudo systemctl isolate multi-user.target
)。 - 设置默认
target
:sudo systemctl set-default <target_name>
(例如:sudo systemctl set-default graphical.target
)。
- 类似于
.socket
单元:- 用于**套接字激活 (Socket Activation)**。服务只有在有连接请求到达其监听的套接字时才会被启动。这可以减少系统启动时的资源消耗,并提高并行性。
- 例如,SSH 服务可以配置为套接字激活,只有当有人尝试连接 SSH 端口时,
sshd.service
才会被启动。
.timer
单元:cron
的替代品,用于定时执行任务。- 通常与一个
.service
单元配对使用。.timer
单元定义了何时触发,然后激活对应的.service
单元。 - 示例:
OnCalendar=*:0/15
: 每隔 15 分钟触发一次。OnBootSec=10min
: 系统启动 10 分钟后触发。Persistent=true
: 如果系统在定时器触发时间关闭,下次启动时会立即执行错过的任务。
.path
单元:- 用于**路径激活 (Path Activation)**。当特定文件或目录发生变化时,激活对应的
.service
单元。 - 底层使用
inotify
机制。
- 用于**路径激活 (Path Activation)**。当特定文件或目录发生变化时,激活对应的
.slice
和.scope
单元:- 用于**资源管理 (cgroups)**。
slice
(切片): 用于分层组织和管理一组单元(服务、scope、其他 slice)的资源。它不直接包含进程,而是定义了 cgroup 树中的一个节点,用于分配 CPU、内存等资源。例如,system.slice
(系统服务) 和user.slice
(用户会话)。scope
(范围): 用于管理systemd
未直接启动的外部进程组,例如用户登录会话、容器或虚拟机。它们通过systemd
的 D-Bus API 在运行时注册。
3. systemctl
进阶使用
systemctl list-dependencies <单元名>
: 查看单元的依赖关系树。systemctl show <单元名>
: 显示单元的所有属性和当前状态(非常详细)。systemctl cat <单元名>
: 显示单元文件的完整内容,包括所有覆盖文件。systemctl edit <服务名>
: 安全地编辑单元文件,它会在/etc/systemd/system/<服务名>.d/override.conf
创建一个覆盖文件,只修改你指定的选项,而不会直接修改原始文件。systemctl edit --full <服务名>
: 复制原始单元文件到/etc/systemd/system/
并打开编辑,完全覆盖原始文件。systemctl link <路径/到/你的/单元文件>
: 将一个不在标准路径下的单元文件链接到systemd
可识别的路径,并启用它。systemctl set-environment <变量名>=<值>
: 设置全局环境变量(临时)。systemctl unset-environment <变量名>
: 取消全局环境变量。
4. journalctl
进阶使用
journalctl -k
或journalctl --dmesg
: 只显示内核日志 (dmesg)。journalctl _PID=<PID>
: 查看特定进程 ID 的日志。journalctl _COMM=<进程名>
: 查看特定进程名称的日志。journalctl _SYSTEMD_UNIT=<单元名>
: 相当于-u
选项。journalctl -S <日期>
/-U <日期>
: 查看从指定日期开始/到指定日期为止的日志。journalctl --disk-usage
: 查看日志文件占用的磁盘空间。sudo journalctl --vacuum-size=500M
: 将日志文件大小限制在 500MB。sudo journalctl --vacuum-time=30d
: 删除 30 天以前的日志。
5. 故障排除与诊断
systemctl status <服务名>
: 始终是诊断服务问题的首选。它会显示服务的当前状态、最近的日志条目和任何错误信息。journalctl -xe
: 显示最近的错误和警告日志,并提供详细的解释和建议。systemd-analyze
:systemd-analyze
: 显示系统启动时间。systemd-analyze blame
: 列出启动时耗时最长的单元。systemd-analyze critical-chain
: 显示启动关键路径上的单元及其耗时。systemd-analyze dot > boot.dot
+dot -Tsvg boot.dot -o boot.svg
: 生成启动过程的 SVG 图,可视化依赖关系和耗时。
检查依赖: 如果服务无法启动,使用
systemctl list-dependencies <服务名>
检查其依赖是否已启动或存在问题。覆盖文件 (Drop-in Files):
当你需要修改一个由软件包提供的单元文件时,强烈建议使用覆盖文件而不是直接修改原始文件。
在
/etc/systemd/system/<服务名>.d/
目录下创建.conf
文件(例如override.conf
)。这些文件只包含你想要修改或添加的特定配置项。
systemd
会在加载时将这些覆盖文件与原始单元文件合并,且覆盖文件中的设置会优先。例如,要修改
nginx.service
的重启间隔,可以创建/etc/systemd/system/nginx.service.d/custom-restart.conf
,内容如下:Ini, TOML
1
2[Service]
RestartSec=10s修改后,记得运行
sudo systemctl daemon-reload
。
实例
1. 服务故障自动恢复 (使用 Restart
和 RestartSec
)
场景: 你的 Web 应用(比如 Nginx 或自定义的 Python 应用)偶尔会因为内存溢出或代码 Bug 崩溃。你希望它能自动重启,而不是等你发现问题后再手动干预。
systemd
应用:
你可以在服务的单元文件中设置 Restart
策略。例如,对于一个名为 my-webapp.service
的服务:
Ini, TOML
1 | # /etc/systemd/system/my-webapp.service.d/restart.conf |
解释:
Restart=on-failure
: 这告诉systemd
,如果服务以非零退出代码(通常表示失败)或被信号终止(如 OOM killer 杀死),它应该自动重启。RestartSec=10s
: 在每次尝试重启前,systemd
会等待 10 秒,避免服务在崩溃循环中立即占用系统资源。
好处: 大大减少了人工干预,提高了服务的可用性。在夜间或非工作时间,服务也能自行恢复。
2. 精确控制服务启动顺序 (使用 After
和 Requires
)
场景: 你的应用程序依赖数据库(如 PostgreSQL)和消息队列(如 Redis)。你必须确保这些后端服务完全启动并监听端口后,你的应用程序才能启动。
systemd
应用:
在你的应用程序单元文件 (my-app.service
) 中定义依赖关系:
1 | # /etc/systemd/system/my-app.service |
解释:
After=postgresql.service redis.service
: 确保my-app.service
会在postgresql.service
和redis.service
都启动之后才尝试启动。Requires=postgresql.service redis.service
: 这是一个更强的依赖。如果postgresql.service
或redis.service
无法启动,my-app.service
也不会启动。如果它们在my-app.service
运行期间停止,my-app.service
也将被停止。
好处: 避免了应用程序在依赖的服务未就绪时启动失败,简化了启动流程的复杂性。
3. 按需启动服务 (使用 .socket
单元)
场景: 你有一些不常用的内部服务(例如一个管理工具或一个很少被访问的 Webhook 服务),它们不需要一直运行,只在有请求时才启动。
systemd
应用:
为服务创建一个 .socket
单元,并将其与 .service
单元关联。
创建
.socket
单元 (/etc/systemd/system/my-lazy-service.socket
):1
2
3
4
5
6
7
8
9[Unit]
Description=My Lazy Service Socket
[Socket]
ListenStream=8081 # 监听 TCP 端口 8081
Accept=yes # 为每个连接启动一个服务实例
[Install]
WantedBy=sockets.target创建
.service
单元 (/etc/systemd/system/my-lazy-service.service
):1
2
3
4
5
6
7
8
9[Unit]
Description=My Lazy Service
Requires=my-lazy-service.socket # 依赖 socket 单元
After=my-lazy-service.socket
[Service]
ExecStart=/usr/bin/python3 /opt/my-lazy-service/app.py # 你的服务启动命令
StandardInput=socket # 服务从 socket 接收输入
Type=simple启用并启动
socket
单元:1
2
3sudo systemctl enable my-lazy-service.socket
sudo systemctl start my-lazy-service.socket
sudo systemctl daemon-reload # 每次创建或修改单元文件后都要执行
解释: my-lazy-service.service
不会直接启动。当有客户端连接到服务器的 8081 端口时,systemd
会自动启动 my-lazy-service.service
来处理这个连接。服务处理完后可以退出,释放资源。
好处: 节省系统资源,尤其对于大量不常使用的服务,可以显著降低内存和 CPU 占用。
4. 解决单元文件冲突 (使用覆盖文件 *.d/
目录)
场景: 你想修改 Nginx 服务的启动用户或资源限制,但 Nginx 是通过软件包安装的,直接修改 /usr/lib/systemd/system/nginx.service
文件可能会在下次软件包更新时被覆盖。
systemd
应用:
使用覆盖目录 (.d/
) 来进行修改,这是一种非侵入性的、推荐的方式。
创建覆盖目录:
1
sudo mkdir -p /etc/systemd/system/nginx.service.d
创建覆盖文件 (
/etc/systemd/system/nginx.service.d/custom-user.conf
):1
2
3
4[Service]
User=mynginxuser
Group=mynginxuser
LimitNOFILE=65535 # 增加文件句柄限制重新加载
systemd
配置并重启服务:1
2sudo systemctl daemon-reload
sudo systemctl restart nginx.service
解释: systemd
会将 /etc/systemd/system/nginx.service.d/
目录下的所有 .conf
文件内容与原始的 nginx.service
文件合并。如果存在相同的配置项,覆盖文件中的设置会优先。这样,你的自定义配置就不会在软件包更新时丢失。
好处: 维护自定义配置的稳定性和持久性,避免了软件包更新带来的麻烦。
5. 高效日志管理 (使用 journalctl
)
场景: 你的服务器上运行着数十个服务,当出现问题时,你需要快速定位是哪个服务出了问题,或者查看特定时间段内的所有错误。
systemd
应用:
journalctl
是你的日志神器:
查看最近的错误信息,并提供上下文和建议:
1
journalctl -xe
查看 Nginx 服务在过去 30 分钟内的所有日志:
1
journalctl -u nginx.service --since "30 minutes ago"
实时跟踪 Web 应用日志,同时过滤掉不重要的 debug 信息:
1
journalctl -u my-webapp.service -f -p info..err # 只看 info、warning、error 等级别的日志
检查上次启动后哪个服务启动时间最长:
1
systemd-analyze blame
好处: journalctl
提供了强大的过滤、时间查询和实时跟踪功能,大大简化了日志分析和故障排查的过程,特别是面对大量服务和高并发日志时。