解决 Systemd 启动问题:常见故障与解决方案

使用 journalctl、检查失败单元、救援目标、fstab 修复、依赖审查和 initramfs 调试来诊断 systemd 启动问题。

解决 Systemd 启动问题:常见故障与解决方案

Linux 启动问题往往让人感到紧迫,因为你通常会先失去熟悉的工具。SSH 可能无法连接,图形登录界面可能永远不会出现,而控制台可能会将你丢入紧急模式,并显示一条看起来比实际情况更糟糕的消息。对于 systemd 启动问题,最佳的第一步不是猜测。找到启动停止的点,然后通过单元日志、挂载失败、依赖错误或早期内核消息反向排查。

本指南重点关注内核将 systemd 作为 PID 1 启动后发生的故障,以及一些从控制台看起来像 systemd 故障的邻近问题:错误的 /etc/fstab 条目、initramfs 问题和引导加载程序错误。

理解 Systemd 启动过程

Systemd 通过一个“单元”系统来管理 Linux 启动过程。这些单元描述了各种系统资源和服务,例如服务 (.service)、挂载点 (.mount)、设备 (.device) 和目标 (.target)。目标是特殊的单元,用于分组其他单元,并代表启动过程中的特定同步点或状态,例如 multi-user.target(传统的运行级别 3)或 graphical.target(运行级别 5)。

典型的启动过程包括:

  1. 内核初始化:内核加载并初始化硬件。
  2. Initramfs 阶段:加载初始 RAM 文件系统,其中包含挂载根文件系统所需的基本驱动程序和工具。
  3. Systemd 启动:Systemd 作为 PID 1 接管,启动 default.target(通常符号链接到 multi-user.targetgraphical.target)。
  4. 单元激活:Systemd 读取单元文件,解析依赖关系,并以高度并行的方式启动服务和挂载。

启动问题可能发生在这些阶段的任何一个,但本指南主要关注 systemd 启动后显现的问题。

初步分类:访问启动日志

当系统无法正常启动时,第一步也是最关键的一步是访问启动日志。这些日志提供了关于问题原因的线索。如果你的系统无法启动到图形环境甚至标准 TTY,你将需要使用替代方法。

1. 使用 journalctl(从救援/紧急模式或 Live 介质)

journalctl 是查询 systemd 日志的工具。如果你的系统可以启动到救援模式紧急模式,或者你正在使用 Live USB/CD 访问磁盘,那么 journalctl 是你的主要工具。

查看上一次启动的日志:

journalctl -b -1

查看自系统启动以来的所有消息:

journalctl -b

查看与失败单元相关的日志:

journalctl -b -p err..emerg # 显示错误、严重、警报、紧急消息
journalctl -b --since "-5min" # 显示当前启动最后 5 分钟的日志

如果你在 Live 环境中,并不总是需要完整的 chroot 来读取日志。挂载已安装的系统并将 journalctl 指向它:

mount /dev/mapper/vg0-root /mnt
journalctl --directory=/mnt/var/log/journal -b -1

在没有持久化日志的系统上,较旧的启动日志可能不存在于 /var/log/journal 下。在这种情况下,请检查 /var/log 下特定发行版的日志,或者在系统足够健康时启用持久化日志记录后重现启动。

2. 使用 dmesg

dmesg 显示内核环形缓冲区,其中包含内核在启动期间的消息。这对于 systemd 完全接管之前发生的早期启动问题特别有用。

dmesg

3. 检查单元状态

一旦进入可用的 shell(救援模式、紧急模式或带有 chroot 的 Live 环境),你可以检查所有 systemd 单元的状态。

systemctl --failed

此命令列出所有启动失败的单元。要获取特定失败单元的详细信息,请使用:

systemctl status <unit_name>.service

并查看其特定的日志条目:

journalctl -u <unit_name>.service -b

常见的 Systemd 启动问题及解决方案

1. 服务失败和单元故障

问题:关键服务无法启动,阻止系统达到所需目标(例如 multi-user.target)。这通常表现为系统进入紧急模式。

症状systemctl --failed 显示一个或多个单元处于“failed”状态。journalctl -u <unit_name>.service 显示指示服务无法启动原因的错误消息。

常见原因

  • 配置错误:配置文件中存在拼写错误、路径不正确、缺少依赖项。
  • 缺少文件/依赖项:服务尝试访问不存在或无法访问的文件或目录。
  • 资源耗尽:服务尝试分配过多内存或其他资源。
  • 权限问题:服务没有读取/写入文件或执行命令所需的权限。

解决方案

  1. 识别失败单元:使用 systemctl --failed
  2. 检查日志:运行 journalctl -u <unit_name>.service -b 获取详细的错误消息。
  3. 更正配置:编辑服务的配置文件(例如 /etc/systemd/system/<unit_name>.service/etc/ 中的文件)。注意 ExecStartWorkingDirectoryUserGroupEnvironment 指令。
  4. 检查依赖项:确保所有 Wants=Requires=After=Before= 指令正确指定,并且所需服务已启用。
  5. 重启并重新启用:进行更改后,运行 systemctl daemon-reload,然后尝试 systemctl start <unit_name>.servicesystemctl enable <unit_name>.service

示例:自定义 Web 服务 mywebapp.service 因其数据库不可用而失败。

# 检查状态
systemctl status mywebapp.service

# 检查日志以获取线索
journalctl -u mywebapp.service -b

# 编辑单元文件(例如在 /etc/systemd/system/mywebapp.service 中)
# 添加/修改 After= 指令以确保数据库先启动
# 例如 After=postgresql.service mysql.service

# 重新加载 systemd 并重试
systemctl daemon-reload
systemctl start mywebapp.service
systemctl enable mywebapp.service # 确保下次启动时启动

2. 文件系统问题

问题:损坏的文件系统或 /etc/fstab 中的错误条目可能会阻止系统挂载关键分区,从而导致紧急模式。

症状:关于 fsck 失败、mount 错误的消息,或者系统进入“紧急模式”并显示“Give root password for maintenance (or type Control-D to continue)”之类的消息。

常见原因

  • 脏文件系统:不当关机、断电。
  • 错误的 /etc/fstab:UUID/设备路径拼写错误、文件系统类型错误、非关键挂载缺少 noauto
  • 硬件故障:磁盘损坏。

解决方案

  1. 进入紧急模式:如果提示,请输入 root 密码。
  2. 检查 /etc/fstab:仔细检查 /etc/fstab 是否有任何错误。暂时用 # 注释掉可疑的行。
  3. 小心运行 fsck:仅在文件系统已卸载,或在你的发行版文档中说明安全的维护上下文中以只读方式挂载时,手动检查和修复文件系统。对于非根分区:
    umount /dev/sdb1
    fsck -f /dev/sdb1
    
    如果根文件系统需要修复,请从 Live 介质或救援环境启动,并从那里运行 fsck。避免在重要磁盘上首先使用 fsck -y;除非你已有备份或了解损坏情况,否则请检查提示。
  4. 重启:进行更改或运行 fsck 后,尝试重启。

3. 依赖冲突和单元排序

问题:服务以错误的顺序启动,或者单元具有冲突的依赖关系,导致死锁或失败。

症状:服务超时、服务因其依赖项未就绪而失败、systemd-analyze plot 显示长链或循环。

常见原因

  • 单元文件中 Wants=Requires=After=Before= 指令配置错误。
  • 单元期望的资源尚不可用。

解决方案

  1. 分析启动顺序:使用 systemd-analyze 可视化启动过程。

    • systemd-analyze blame:按启动时间排序显示服务,突出显示慢速单元。
    • systemd-analyze critical-chain:显示直接影响整体启动时间的关键单元路径。
    • systemd-analyze plot > boot.svg:生成整个启动依赖图的 SVG 图像,对于复杂问题非常有用。
  2. 检查单元依赖项:使用 systemctl list-dependencies <unit_name> 查看单元需要什么以及什么依赖于它。

  3. 调整单元文件指令

    • After=Before=:控制单元的排序。如果 A.serviceAfter=B.service,则 A 将在 B 之后启动(如果 B 被启动)。对于大多数排序需求,请使用 After=
    • Wants=:表示弱依赖。如果 A.service Wants=B.service,则当 A 启动时 B 也会启动,但即使 B 失败,A 也会继续。
    • Requires=:表示强依赖。如果 A.service Requires=B.service,则当 A 启动时会拉入 B,如果 B 无法启动,则 A 失败。如果 B 被显式停止,则 A 也会被停止。
    • Conflicts=:确保如果当前单元启动,则特定单元停止,反之亦然。
    • PartOf=:将一个单元的生命周期链接到另一个单元(例如,如果一个 slice 被停止,则所有 PartOf 它的单元也会被停止)。

    提示:对于大多数依赖项,始终优先使用 After=Wants=,以避免创建可能导致死锁或级联故障的紧密耦合。

4. 内核恐慌 / Initramfs 问题

问题:系统在很早的阶段启动失败,通常在 systemd 完全接管之前,显示“Kernel panic - not syncing”或与 dracutinitramfs 相关的消息。

症状:早期启动失败,通常显示大量文本,包含堆栈跟踪或关于缺少根设备、未找到 /dev/root 等的消息。

常见原因

  • 缺少内核模块:Initramfs 不包含根文件系统所需的驱动程序(例如 LVM、RAID、特定磁盘控制器)。
  • 内核/Initramfs 损坏:文件已损坏。
  • 内核参数错误:GRUB 中的 root= 参数指向错误的设备。

解决方案

  1. 重建 Initramfs:这是一个常见的修复方法。启动到 Live 环境或其他内核,chroot 到你的系统,然后重建 initramfs。
    # Dracut 示例(Fedora/RHEL/CentOS)
    dracut -f -v /boot/initramfs-$(uname -r).img $(uname -r)
    
    # mkinitcpio 示例(Arch Linux)
    mkinitcpio -P
    
    # update-initramfs 示例(Debian/Ubuntu)
    update-initramfs -u -k all
    
  2. 验证 GRUB 配置:检查 /boot/grub/grub.cfg(或如果你重新生成它,则检查 /etc/default/grub)以确保 root= 参数和 initrd 路径正确。
  3. 内核参数:如果你怀疑某个特定模块丢失或导致问题,可以尝试在 GRUB 中添加内核参数(例如 rd.break 以进入 initramfs shell 进行调试)。

5. GRUB/引导加载程序问题

问题:系统甚至无法到达内核加载点,或者卡在 GRUB 菜单。

症状:“No boot device found”、GRUB 救援提示符,或 GRUB 无法加载内核。

常见原因

  • 引导加载程序损坏。
  • GRUB 配置错误,指向不存在的内核/initramfs。
  • BIOS/UEFI 设置阻止了正确的启动顺序。

解决方案

  1. 重新安装 GRUB:从 Live USB 启动,chroot 到你的系统,然后重新安装 GRUB 到 MBR/EFI 分区。
    # 示例
    mount /dev/sdaX /mnt # 挂载根分区
    
    mount /dev/sdaY /mnt/boot/efi # 如果是单独的 EFI 分区
    
    for i in /dev /dev/pts /proc /sys /run; do mount --bind $i /mnt$i; done
    chroot /mnt
    
    grub-install /dev/sda # 安装到主磁盘
    
    grub-mkconfig -o /boot/grub/grub.cfg # 重新生成 GRUB 配置
    
    exit
    umount -R /mnt
    reboot
    
  2. 检查 BIOS/UEFI 设置:确保正确的启动驱动器被优先选择。

高级故障排除技术

启动到救援/紧急模式

这些模式提供了一个最小的环境来进行故障排除。要进入它们:

  1. 在 GRUB 期间:按 e 编辑内核命令行。
  2. 找到 linux:找到以 linux(或 linuxefi)开头的行。
  3. 追加 systemd.unit=rescue.target 进入救援模式(大多数服务关闭,单用户 shell)。
  4. 追加 systemd.unit=emergency.target 进入紧急模式(最少服务,通常根文件系统只读)。
  5. Ctrl+XF10 启动。

使用 rd.break 进行 Initramfs 调试

在 GRUB 的内核命令行中追加 rd.break 将在挂载真实根文件系统之前,将你放入 initramfs 内部的 shell。这对于调试 initramfs 问题非常有用,例如缺少驱动程序或 LVM/RAID 设置问题。

进入 initramfs shell 后,你可以:

  • 检查 lsblkmount
  • 检查 /sysroot 中缺少的文件。
  • 尝试手动挂载根文件系统。

分析启动性能

虽然严格来说不是“故障”,但启动速度慢可能表明存在潜在问题或服务配置效率低下。

  • systemd-analyze blame:识别启动时间最长的服务。
  • systemd-analyze critical-chain:了解影响整体启动时间的关键依赖路径。

安全的恢复顺序

当你在控制台前,机器处于半启动状态时,保持恢复顺序简单明了:

  1. 如果可能,捕获屏幕上的确切错误。
  2. 运行 systemctl --failed
  3. 阅读 journalctl -b -p err..alert --no-pager
  4. 如果某个单元失败,阅读 journalctl -u unit-name -b
  5. 如果挂载失败,检查 /etc/fstab,使用 blkid 验证 UUID,并仅注释掉可疑的非关键挂载。
  6. 如果涉及根文件系统或 initramfs,在进行侵入性修复之前,切换到 Live 介质或救援模式。
  7. 编辑单元文件后,运行 systemctl daemon-reload,并在可能的情况下仅重启受影响的单元。

大多数 systemd 启动问题不是通过同时更改许多东西来解决的。一个错误的挂载行、一个丢失的磁盘、一个带有损坏 ExecStart= 的服务,或者一个排序错误,都会留下相当直接的线索。沿着这条线索,进行一次小的修复,并且只有在当前 shell 无法测试修复时才重启。

使用这些工具来识别瓶颈,并通过调整 After=Requires=TimeoutStartSec=Type= 指令来优化单元启动。

预防和最佳实践

  • 测试更改:在将单元文件修改部署到生产环境之前,先在测试环境中进行测试。
  • 备份配置:定期备份 /etc/ 或至少关键的 /etc/systemd/system/ 文件。
  • 理解单元指令:深入理解 systemd.service(5)systemd.unit(5) 手册页非常有用。
  • 使用 Drop-in 文件:不要直接修改 /lib/systemd/system/ 单元文件(可能会被更新覆盖),而是使用 drop-in 文件(/etc/systemd/system/<unit_name>.service.d/*.conf)进行自定义配置。
  • 保留内核:始终在系统上保留至少一个已知良好的旧内核,以便在新内核导致问题时启动。

结论

解决 systemd 启动问题需要系统的方法,从有效的日志分析开始。通过理解 systemd 基于单元的架构,并利用 journalctlsystemctlsystemd-analyze 等工具,你可以高效地查明启动失败的根本原因,无论是配置错误的服务、文件系统问题还是复杂的依赖冲突。启动到救援或紧急模式的能力,结合高级调试技术,使你能够在系统看似完全无响应时重新获得控制权。凭借这些策略和最佳实践,你将能够很好地应对大多数 systemd 启动挑战,并维护稳定、可靠的 Linux 操作。