返回

Systemd 服务启用后开机不自启?原因与解决方法

Linux

解决 Systemd 服务启用后开机不自启的疑难杂症

不少朋友可能遇到过这样的情况:自己写了个 Systemd 服务,配置好 systemctl enable 也显示成功创建了链接,按理说应该开机自动运行。可奇怪的是,重启电脑后用 systemctl status 一看,服务状态却是 inactive (dead),压根没跑起来。但要是手动 systemctl start,服务又能正常启动,运行起来也没毛病。甚至有时候,连通过 apt 安装的带 Systemd 服务的软件包(比如 zram-config)也遇到同样的问题。

这到底是咋回事呢?明明几年前配置好的服务一直用得好好的,系统从 Ubuntu 18.04 LTS 一路用过来,怎么现在就不灵了?

来看一个典型的出问题的服务配置 (myapp.service):

[Unit]
Description="myapp"
After=syslog.target network-online.target
Wants=network-online.target

[Service]
Restart=always
RestartSec=10
User=root
Group=root
WorkingDirectory=/opt/myapp
ExecStart=/usr/local/bin/myapp
KillMode=control-group

[Install]
WantedBy=multi-user.target

服务确认是启用了的:

$ sudo systemctl enable myapp
Created symlink /etc/systemd/system/multi-user.target.wants/myapp.service → /lib/systemd/system/myapp.service.

但重启后状态不对:

$ sudo systemctl status myapp
● myapp.service - "myapp"
   Loaded: loaded (/lib/systemd/system/myapp.service; enabled; vendor preset: enabled)
   Active: inactive (dead)

查看日志 journalctl -u myapp -f,可能只看到旧的启动和停止记录,或者干脆没有新的启动尝试记录。手动启动 /usr/local/bin/myapp 程序本身没报错,甚至丢到 tmux 里跑几天都稳如老狗。偏偏 Systemd 在开机时就是拉不起它。

更让人头大的是,新装的 zram-config 服务,默认就该是开机启动的,装完重启后查状态,也是 inactive (dead)

$ sudo apt install zram-config && sudo reboot
# 重启后
$ sudo systemctl status zram-config
● zram-config.service - Initializes zram swaping
   Loaded: loaded (/lib/systemd/system/zram-config.service; enabled; vendor preset: enabled)
   Active: inactive (dead)

手动启动它,倒是能成功,状态变为 active (exited)(因为 zram-config 通常是配置完就退出的类型)。

$ sudo systemctl start zram-config
$ sudo systemctl status zram-config
● zram-config.service - Initializes zram swaping
   Loaded: loaded (/lib/systemd/system/zram-config.service; enabled; vendor preset: enabled)
   Active: active (exited) since Mon 2020-01-27 12:25:55 CET; 1s ago
  Process: 5541 ExecStart=/usr/bin/init-zram-swapping (code=exited, status=0/SUCCESS)
 Main PID: 5541 (code=exited, status=0/SUCCESS)
... (省略日志) ...

既然自定义服务和官方包里的服务都这样,那问题可能出在 Systemd 本身或者系统环境上,而不是单一服务的配置。是不是非得重装系统才能解决?别急,先试试下面这些方法排查一下。

一、 问题分析

碰上这种怪事,先冷静分析一下可能的原因:

  1. 依赖问题 :服务单元文件里配置了 After=Wants= (或者 Requires=),指定了服务启动必须在某些目标(target)或服务之后。如果这些前置条件在启动时未能满足,或者满足得太晚,Systemd 可能就跳过或者延迟启动你的服务。特别是 network-online.target,这是个出了名的“坑”,很多时候它并不像期望的那样可靠地表示网络真的“在线”了,或者等待它会拖慢启动流程,导致依赖它的服务超时或失败。
  2. 服务类型 (Type=) 不匹配 :Systemd 需要知道你的服务进程是如何工作的。[Service] 段落里有个 Type= 指令(虽然例子里没写,会使用默认值 simple)。如果你的 ExecStart 脚本会 fork 出子进程然后自己退出(后台运行模式),那么 Type=simple 就不合适了,应该用 Type=forking 并配合 PIDFile= 指令。如果你的服务启动后主进程就一直运行在前台,那 Type=simple 是对的。zram-config 这类执行一次就结束的任务,通常是 Type=oneshot。类型搞错了,Systemd 就无法正确判断服务状态,可能刚启动就认为它挂了。
  3. 启动顺序或竞争条件 :开机过程中,大量服务并行启动,可能会有资源竞争或者依赖关系没完全理顺的情况。你的服务可能正好撞上了某个短暂的系统不稳定状态,导致启动失败。
  4. Systemd 自身或目标状态异常 :极其罕见的情况下,Systemd 守护进程本身可能出问题,或者某些关键的 target(比如 multi-user.target)没能正常激活。
  5. 脚本/程序在启动环境下的问题 :虽然手动执行没问题,但开机启动时环境变量、可用资源、文件系统挂载情况等都和用户登录后不一样。脚本可能依赖了某个在启动早期还没准备好的东西(比如特定挂载点、设备文件)。
  6. 配置未正确加载 :修改了 .service 文件后,忘记执行 sudo systemctl daemon-reload,导致 Systemd 用的还是旧配置。

既然连新装的 zram-config 也中招,依赖问题(尤其是 network-online.target)和服务类型不匹配的可能性就更大了。因为系统包的服务通常配置比较规范,如果它也起不来,说明问题可能具有普遍性。

二、 解决方案

接下来,咱们一步步排查,尝试解决问题。

方案一:深入检查 Systemd 日志和状态

光看 systemctl status 有时候不够,得挖得更深一点。

  1. 查看失败的单元:
    重启后,立即执行这个命令,看看开机过程中有没有哪些单元(服务、目标等)启动失败了。

    sudo systemctl list-units --failed
    

    如果这里列出了你的服务或其他相关依赖,那就有线索了。

  2. 检查服务的详细日志:
    journalctl 查看特定服务的日志,-b 参数表示只看本次启动以来的日志。

    sudo journalctl -u myapp.service -b
    sudo journalctl -u zram-config.service -b
    

    仔细看日志里的错误信息,哪怕只是警告,也可能暗示了问题所在。注意看启动尝试的时间点,以及有没有什么错误提示。

  3. 查看完整启动日志:
    如果单个服务的日志看不出名堂,那就看整个系统的启动日志,找找看你的服务计划启动的那个时间点前后,系统在忙啥,有没有其他报错。

    sudo journalctl -b
    

    日志量可能很大,可以用 / 搜索关键词,比如你的服务名 myapp,或者 error, failed, timeout 等。

  4. 分析启动耗时和依赖链:
    这两个命令能帮你了解启动流程和服务的依赖关系。

    sudo systemd-analyze blame
    sudo systemd-analyze critical-chain myapp.service
    

    blame 显示每个单元启动花了多长时间,critical-chain 显示启动指定服务需要依次满足哪些依赖。这有助于判断是不是某个依赖项拖慢了整个过程。

方案二:审视和调整服务单元文件 (myapp.service)

回到配置文件本身,仔细检查下设置,特别是 [Unit][Service] 部分。

  1. 明确指定 Type=
    虽然例子中省略了 Type=,导致默认为 simple,但最好还是明确写出来。如果 /usr/local/bin/myapp 是个启动后主进程持续运行的程序,那 Type=simple 是合适的。如果它是个启动后会退出的脚本(比如 zram-config 这种),应该用 Type=oneshot。如果它启动后会 fork 到后台运行,则用 Type=forking 并配合 PIDFile= 指向进程号文件。

    [Unit]
    Description="myapp"
    After=syslog.target network.target # 尝试换成 network.target
    Wants=network.target             # 尝试换成 network.target
    
    [Service]
    Type=simple # 明确指定类型,根据你的程序行为选择 simple, forking, oneshot 等
    Restart=always
    RestartSec=10
    User=root
    Group=root
    WorkingDirectory=/opt/myapp # 确认这个目录在启动时存在且 root 可访问
    ExecStart=/usr/local/bin/myapp # 确认路径和权限无误
    KillMode=control-group
    
    [Install]
    WantedBy=multi-user.target
    
  2. 调整依赖关系:
    network-online.target 确实可能是个麻烦制造者。可以试试:

    • 换成 network.target :这个目标通常比 network-online.target 更早达到,它只表示网络堆栈初始化完成,不保证能上网。如果你的服务启动时只需要基本的网络接口,不需要马上联网,可以试试替换:
      After=syslog.target network.target
      Wants=network.target
      
    • 移除网络依赖 :如果你的服务启动初期完全不需要网络,可以干脆把 After=Wants= 里的网络相关目标删掉。让它先跑起来,网络的事等服务内部逻辑自己处理。
      After=syslog.target # 移除网络依赖
      
    • Wants= vs Requires=Wants= 是弱依赖,如果依赖项启动失败,服务本身仍然会尝试启动。Requires= 是强依赖,依赖项失败,服务自身就不会启动。如果网络对你的服务是启动时绝对必要的,可以考虑用 Requires=network-online.target,但这可能会让服务启动失败的概率增加。
  3. 检查路径和权限:
    再次确认 WorkingDirectory=/opt/myapp 这个目录在系统启动早期就存在,并且 User=root 有权限读写(如果需要的话)。ExecStart=/usr/local/bin/myapp 的路径也要绝对正确,并且文件有执行权限 (chmod +x)。

改完配置后别忘了:

sudo systemctl daemon-reload  # 让 Systemd 重新加载配置
sudo systemctl reenable myapp.service # 建议重新enable一下确保链接最新
sudo reboot                 # 重启验证

方案三:处理网络依赖的“老大难”

network-online.target 依赖问题太常见了,值得单独拎出来说。

  1. 理解 network-online.target
    它本质上是等待一个“网络已配置且在线”的信号。这个信号通常由网络管理服务(如 systemd-networkdNetworkManager)配合相应的 wait-online 服务(如 systemd-networkd-wait-online.serviceNetworkManager-wait-online.service)发出。问题在于,这些 wait-online 服务的行为可能配置不当,或者等待条件过于严格(比如等待所有网卡都拿到 IP),或者干脆就没启用。

  2. 检查 wait-online 服务:
    看看你系统用的是哪个网络管理器,然后检查对应的 wait-online 服务状态。

    # 如果用 systemd-networkd
    sudo systemctl status systemd-networkd-wait-online.service
    
    # 如果用 NetworkManager
    sudo systemctl status NetworkManager-wait-online.service
    

    如果这个服务没启用或者失败了,network-online.target 自然也就达不到。你可以尝试 sudo systemctl enable --now 对应的服务(如果确定需要它的话),但更推荐的方法是调整你的服务单元,尽量不强依赖 network-online.target

  3. 服务设计层面的考量:
    最好的办法是让你的应用程序本身具备网络重连或等待逻辑。服务启动时即使网络没通,也能正常运行,然后在后台尝试连接或等待网络就绪。这样就能去掉对 network-online.target 的依赖,让服务启动更稳健。

方案四:检查 Systemd 目标状态和系统整体状态

如果上述调整都没用,得看看是不是 Systemd 的核心目标出了问题。

  1. 检查 multi-user.target
    这是绝大多数用户服务最终挂载的目标,相当于传统的运行级别 3 或 5。看看它的状态。

    sudo systemctl status multi-user.target
    

    它应该是 active 状态。如果不是,或者日志里有相关错误,说明系统启动流程本身可能遇到了大麻烦。

  2. 检查系统运行状态:
    这个命令告诉你 Systemd 认为系统现在处于什么状态。

    sudo systemctl is-system-running
    

    正常情况下应该是 running。如果是 degraded,说明有服务启动失败,可以用 systemctl --failed 查看是哪些。如果是 stoppingoffline 等奇怪状态,那系统问题就比较严重了。

方案五:终极手段:系统层面检查与修复

走到这一步,如果服务还是起不来,问题可能更深层。

  1. 文件系统检查:
    不排除文件系统错误干扰了 Systemd 的正常工作。可以在下次启动时强制进行磁盘检查。对于根分区,通常需要在 / 下创建一个名为 forcefsck 的空文件,或者修改 GRUB 启动参数。具体方法请根据你的系统和分区情况搜索操作。注意:文件系统检查有风险,务必确保有数据备份。

  2. 包管理器修复:
    尝试修复可能存在的破损软件包依赖。

    sudo apt update
    sudo apt --fix-broken install
    sudo apt full-upgrade # 确保系统是最新状态
    
  3. 回顾系统变更:
    想想问题是什么时候开始出现的?那之前有没有做过什么特别的操作?比如大的系统升级、安装了某些内核模块、修改了重要的系统配置?可以翻阅 /var/log/apt/history.log 看看最近的软件包安装/升级记录。

  4. 考虑重装(下下策):
    如果你的系统是长期运行、历经多次升级的,确实可能积累了一些难以排查的配置冲突或历史遗留问题。当所有诊断和修复手段都无效,并且系统表现出多方面不稳定时,备份好数据,进行一次全新、干净的操作系统安装,往往是最省时省力的方法。虽然不是技术上的“解决”,但能快速恢复一个健康的工作环境。

希望以上这些分析和步骤能帮你揪出 Systemd 服务开机不自启的元凶,让你的服务重新恢复自动运行。处理这类问题需要耐心,一步步来,多看日志,通常都能找到解决的蛛丝马迹。