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 提供了强大的过滤、时间查询和实时跟踪功能,大大简化了日志分析和故障排查的过程,特别是面对大量服务和高并发日志时。
