☰
systemd核心机制解析:从service到target的系统服务管理实战
2026/10/1 13:08:14 网站建设 项目流程

开头

一说起 systemd,可能很多人第一反应是“开机启动的”,再问深入一点,就剩一堆systemctl命令背了又忘。其实 systemd 里最核心的两个概念就是service和target,一个是“怎么把一个程序跑起来”,一个是“系统到底要跑到什么状态”。这两样弄明白了,日常运维里的服务管理、开机自启、系统切换状态,基本都能顺下来。

这篇文章从实用角度出发,把service和target的完整用法、文件结构、命令参数、常见报错一锅端出来。无论你是刚接触 Linux 的新手,还是已经被 systemd 坑过几次的运维,都可以照着里面的步骤实操一遍。像job for docker.service failed、service redis does not support chkconfig这类高频问题,也会在最后一章专门拆解,看完就能直接排查。

1. systemd 到底解决了什么问题:从启动脚本到依赖管理

1.1 老派 init 的痛点,以及 systemd 的破局思路

在 systemd 出现之前,Linux 用的是 SysV init,系统启动就是按顺序执行/etc/rc.d/下面一堆脚本。这种方式最大的问题是“串行”:一个服务起完了才轮到下一个,开机越来越慢。而且脚本之间的依赖关系全靠人肉约定,经常出现“我明明把 nginx 排在 database 后面了,结果数据库还没就绪,nginx 已经报错退出”的尴尬。

systemd 把思路整个换掉了:不按顺序排队,而是把每个服务声明成独立单元,标清楚“我依赖谁”“谁依赖我”,然后并行启动。启动速度明显提升,依赖关系也通过配置显性化了。这就是为什么现在主流发行版全部切到 systemd,因为它的模型确实更符合现代服务器的复杂度。

1.2 Unit(单元)模型:一切服务都是文件

systemd 里所有可被管理的对象都叫unit,中文叫“单元”。它们统一以.service、.target、.socket、.timer、.mount这类后缀结尾,本质就是一个.ini风格的文本文件。你不需要理解太多抽象的东西,只要记住一句话:unit 就是文件,管理 unit 就是管理文件。

常见的 unit 类型有这些:

  • service:定义一个后台服务进程,比如 nginx、docker、sshd。
  • target:定义一组 unit 的逻辑集合,对应“系统状态”,比如多用户模式、图形界面模式。
  • socket:定义 socket 监听,可以做到按需启动服务,比如客户端一连接才拉起对应服务。
  • timer:定义定时任务,替代 crontab 的一种方式。
  • mount/automount:定义挂载点和自动挂载行为。
  • path:监控文件或目录变化,触发服务。

日常用到的 90% 场景就是service和target,这也是这篇文章的主角。

1.3 service 与 target 的关系:服务是谁,状态是什么

把电脑比作一个公司,service就是具体干活的员工,比如 sshd 负责接待远程连接,nginx 负责处理网页请求。而target就是公司的工作模式:上班模式(multi-user)、带展厅的接待模式(graphical)、下班关机(poweroff)。

一个 target 里可以包含很多个 service,它不直接“运行”什么,而是通过在配置里声明依赖和引用来决定要拉起哪些服务。你执行systemctl isolate multi-user.target,实际上就是让系统停掉不在这个 target 里的服务、启动需要的服务,最终把系统切到对应的运行状态。这就是整个 systemd 管理模型的核心逻辑。

2. service 单元:从零编写到常用命令

2.1 service 文件放哪里,优先级怎么判断

service 文件的标准位置有三个,按优先级从高到低排列:

  • /etc/systemd/system/:管理员手动创建或修改的地方,优先级最高。
  • /run/systemd/system/:运行时生成的临时单元,重启后消失。
  • /usr/lib/systemd/system/:软件包安装时自带的单元文件,也就是发行版默认配置。

为什么要分这么多层?因为 systemd 允许你用高优先级目录里的文件覆盖低优先级目录里的同名文件。比如 nginx 包自带的/usr/lib/systemd/system/nginx.service,你想调参数,不要直接改原文件,而是复制一份到/etc/systemd/system/nginx.service再改。这样软件包升级时原文件被替换了,你的自定义配置依然生效。

查看当前某个服务实际生效的是哪个文件,用systemctl cat nginx.service就能看到完整内容和来源路径。

2.2 service 文件的三段式结构:Unit、Service、Install

一个标准的 service 文件长成这样:

[Unit] Description=My Custom Web Server After=network.target Wants=network-online.target [Service] Type=simple User=www-data WorkingDirectory=/var/www/html EnvironmentFile=/etc/myapp/env.conf ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yaml ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.target

逐个拆解一下:

[Unit]段描述这个服务的基本信息和依赖关系。

  • Description:服务说明,systemctl status里能看到。
  • After:注明本服务应该在哪些 unit 之后再启动,控制顺序。
  • Wants:弱依赖,有就启动,没有或失败不影响本服务。
  • Requires:强依赖,如果依赖的 unit 启动失败,本服务也会启动失败。
  • Before:与 After 相反,声明本服务要在某个 unit 之前启动。

注意,After只管启动顺序,不负责把依赖拉起来;“拉起依赖”是Wants或Requires的职责。两者经常配合使用:用Wants声明依赖,用After控制先后。

[Service]段定义服务怎么运行,这是最核心的部分。

  • Type:服务进程类型。simple表示主进程直接在前台运行;forking表示主进程会 fork 一个子进程,父进程退出,常见于传统 daemon 程序;oneshot用于一次性任务,执行完就退出;notify表示进程启动完成后会通过 sd_notify 通知 systemd。
  • ExecStart:启动命令,必须是绝对路径。注意这里不是 shell,不能写管道符、重定向这类语法。
  • ExecStartPre/ExecStartPost:在启动前或启动后执行的附加命令。
  • ExecReload:重载操作,一般用来发信号给服务进程。
  • ExecStop:停止命令,不写的话 systemd 会先发 SIGTERM,再发 SIGKILL。
  • Restart:进程退出后是否自动拉起。always是任何情况都重启;on-failure是非正常退出才重启。对于 web 服务,always更稳妥;对于批量任务,on-failure更合适。
  • RestartSec:重启前等待的秒数,防止疯狂重启打满 CPU。
  • User/Group:以哪个用户身份运行服务。安全底线,不要用 root 跑业务服务。
  • Environment/EnvironmentFile:设置环境变量,EnvironmentFile可以外挂一个配置文件。
  • WorkingDirectory:工作目录。
  • StandardOutput/StandardError:标准输出和错误日志去向,默认走 systemd journal。

[Install]段用于定义服务“被启用”时怎么挂到系统里。

  • WantedBy:最常用的一个,表示执行systemctl enable时,会在对应 target 的.wants目录里创建一个软链接。比如WantedBy=multi-user.target,enable 之后/etc/systemd/system/multi-user.target.wants/下就多了个指向你的 service 的链接。
  • RequiredBy:效果类似,但创建在.requires目录里,是强依赖关系。
  • Alias:给服务起别名,enable 时一并创建符号链接。

2.3 实操:五分钟写一个自己的 service

假设我手头有一个 Python 写的 web 服务,/opt/myapp/server.py,需要以www-data用户运行,监听 8000 端口。现在把它做成系统服务。

第一步,创建 service 文件:

sudo vim /etc/systemd/system/myapp.service

写入内容:

[Unit] Description=My Python Web App After=network.target [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/opt/myapp ExecStart=/usr/bin/python3 /opt/myapp/server.py Restart=always RestartSec=5 Environment=APP_ENV=production [Install] WantedBy=multi-user.target

第二步,重载 systemd 配置并启动:

sudo systemctl daemon-reload sudo systemctl start myapp.service sudo systemctl status myapp.service

daemon-reload这步千万不能省。你改了 service 文件之后,systemd 不会自动发现变化,不执行daemon-reload的话,启动的可能还是旧配置。我见过不少新手在配置文件里加了一行,然后反复 restart,发现没生效,就是因为忘了先 reload。

第三步,设置开机自启:

sudo systemctl enable myapp.service

enable 的本质就是在/etc/systemd/system/multi-user.target.wants/下面创建一个指向/etc/systemd/system/myapp.service的软链接。手动画等号的话,enable 和这个命令等价:

ln -s /etc/systemd/system/myapp.service /etc/systemd/system/multi-user.target.wants/myapp.service

看到这里就明白,systemd 没什么魔法,一切行为都是文件和链接的组合。

2.4 service 相关命令速查

命令作用
systemctl start foo.service立即启动服务
systemctl stop foo.service停止服务
systemctl restart foo.service重启服务,相当于 stop + start
systemctl reload foo.service发送重载信号,不中断服务,依赖 ExecReload
systemctl enable foo.service设置开机自启,创建软链接
systemctl disable foo.service取消开机自启,删除软链接
systemctl status foo.service查看服务状态和最近日志
systemctl is-active foo.service只输出 active 或 inactive,适合脚本判断
systemctl is-enabled foo.service输出 enabled 或 disabled,判断是否开机自启
systemctl cat foo.service查看完整 unit 配置和文件路径
systemctl show foo.service查看全部运行时属性,排障利器
systemctl list-units --type=service列出所有已加载的 service 单元
systemctl list-unit-files --type=service列出所有 service 文件及启用状态
systemctl kill foo.service给服务进程发信号,强杀时用

有一类常见误区:enable和start是两个动作。enable只负责开机自启,不立即启动;start只负责现在运行,不改开机设置。很多服务配置完发现“现在能跑,重启就废了”,基本就是只 start 忘 enable。

3. target 单元:系统状态的真正控制者

3.1 target 是什么:unit 的逻辑分组

target 从形式上讲也是一个 unit 文件,后缀是.target。它的内容通常非常简单,没有ExecStart,核心是依赖关系:

[Unit] Description=Multi-User System Documentation=man:systemd.special(7) Requires=basic.target Conflicts=rescue.service rescue.target After=basic.target rescue.service rescue.target AllowIsolate=yes

这段配置什么意思?Requires=basic.target说明要进入 multi-user 状态,必须先满足 basic.target;Conflicts说明它和救援模式互斥;AllowIsolate=yes表示这个 target 可以作为systemctl isolate的目标。

target 本质就是一堆 unit 的“汇总入口”。通过依赖关系和Wants/Requires,它把一堆服务聚合成一个可切换的系统状态。这也是为什么我前面说“target 对应系统状态”,因为它决定了服务集合的整体去留。

3.2 常用 target 一览,以及和运行级别的对应

在旧的 SysV init 里有运行级别 0-6,systemd 用 target 取代了这套体系。对照关系如下:

systemd target旧运行级别作用
poweroff.target0关机
rescue.target1单用户救援模式,最小环境
multi-user.target3多用户命令行模式,无图形界面
graphical.target5多用户图形界面模式
reboot.target6重启

日常运维中打交道最多的是multi-user.target和graphical.target。大部分服务器跑在multi-user.target,带桌面的系统默认目标才是graphical.target。

用systemctl get-default查看当前默认的启动 target,用systemctl set-default multi-user.target把默认启动目标改成命令行模式。set-default实际就是修改/etc/systemd/system/default.target这个软链接的指向,你可以用ls -l验证。

3.3 如何把 service 挂到 target 上:enable 的秘密

前面讲 service 的时候提过WantedBy会在对应 target 的.wants目录里创建软链接,这就是 service 和 target 衔接的关键机制。执行:

sudo systemctl enable myapp.service

之后你会发现:

ls -l /etc/systemd/system/multi-user.target.wants/

里面有myapp.service -> /etc/systemd/system/myapp.service这样的软链接。这个软链接就表示“当系统进入 multi-user.target 时,会自动拉起 myapp.service”。

如果你想手动建立一个“进入某个 target 时自动启动服务”的关系,也可以自己写软链接,不需要工具。但更规范的做法是在 service 文件的[Install]段写WantedBy=multi-user.target,然后 enable。这样既清晰,又可重复执行。

3.4 target 切换的实操命令

# 查看当前 target systemctl get-default # 设置默认 target sudo systemctl set-default multi-user.target # 切换到救援模式 sudo systemctl rescue # 切入某个 target(不改变默认设置) sudo systemctl isolate multi-user.target # 重启 / 关机 sudo systemctl reboot sudo systemctl poweroff

关于isolate有个重要坑:不是所有 target 都允许 isolate。只有配置了AllowIsolate=yes的 target 才能被切换。执行systemctl isolate前,建议先systemctl list-units --type=target看一眼当前有哪些 target,其中一部分用于内部组合(比如 basic.target),一部分用于切换状态(比如 multi-user.target)。

rescue也是生产环境排障常用操作:系统起不来时进救援模式,只挂载最基础的文件系统,方便你修复配置。从救援模式退回完整系统,执行systemctl isolate multi-user.target或者直接systemctl default(默认 target)。

3.5 自定义 target:按业务场景整合理想状态

target 也可以自己建。比如我有一组服务是“数据处理任务”,包括etl.service、worker.service、cleaner.service。与其每次一个个启动,不如建一个自定义 target:

[Unit] Description=Data Processing Stack Requires=etl.service worker.service cleaner.service After=network.target

保存在/etc/systemd/system/data-processing.target,然后:

sudo systemctl daemon-reload sudo systemctl isolate>Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xeu docker.service" for details.

这个报错本身只是“结果”,真正原因要看后边的提示。正确排查顺序是:

systemctl status docker.service journalctl -xe -u docker.service

status会显示进程的退出码和最后几行日志;journalctl -xeu能打印完整日志。常见原因就那几类:

  • 配置文件语法错误,进程起不来;
  • 端口被占用,Address already in use;
  • 权限不对,Permission denied;
  • 依赖项没就绪,比如数据库还没起来。

处理思路:先看日志定位直接原因,修完配置后daemon-reload再启动。不要反复无脑 restart,日志不会撒谎。

4.2 service redis does not support chkconfig:旧命令的"无效操作"

很多老教程还在用chkconfig redis on、chkconfig --list这套 SysV 时代的命令。在新系统上执行会得到:

service redis does not support chkconfig

这个报错的意思是:当前系统的服务管理已经是 systemd 了,redis 的启动脚本不提供 chkconfig 支持。你不需要 chkconfig,直接用 systemctl:

sudo systemctl enable redis sudo systemctl start redis

类似的情况还有service redis start这种写法,在部分发行版上可能仍能通过兼容层执行,但终极目标都是转发给 systemctl。熟悉现代语法,少走弯路。

4.3 指定的服务已标记为删除:系统状态残留

报错形式:

lash helper service 指定的服务已标记为删除。

这通常发生在 Windows 场景,但换成 Linux systemd 也有类似现象:service 文件被删除或 disable 时,某些进程还残留着旧状态,导致 systemd 认为服务处于“待删除”标记状态。处理办法:

sudo systemctl daemon-reload sudo systemctl reset-failed

daemon-reload让 systemd 重新扫描 unit 文件,reset-failed清掉失败状态。如果还不行,检查是否有多余的软链接残留在/etc/systemd/system/*.wants/目录下,手动清掉即可。

4.4 修改配置不生效、日志找不到,这些坑你迟早遇到

第一个坑:改完 service 文件忘记daemon-reload。这是 systemd 和传统 init 最大的差别之一,传统 init 改完脚本立即生效,systemd 不行。我的习惯是每次改完配置文件,先执行systemctl daemon-reload再执行 restart,形成肌肉记忆。

第二个坑:日志不知道去哪看。systemd 管的服务,日志默认进 journal,用journalctl -u 服务名查看。如果希望把服务日志输出到文件,可以在[Service]里写:

StandardOutput=append:/var/log/myapp.log StandardError=append:/var/log/myapp.err

但注意,append:语法要求 systemd 版本够新,老版本可能不支持。

第三个坑:Restart=always配合“服务自己退出”的场景。如果程序逻辑是跑完任务主动exit(0),你可能并不希望它被反复拉起来。这时候要用on-failure,只在真正出错时重启。

第四个坑:ExecStart不支持 shell 语法。很多人想写ExecStart=/bin/sh -c "command1 && command2",乍一看没问题,仔细一看才发现忘写-c。正确写法是:

ExecStart=/bin/sh -c "/usr/bin/command1 && /usr/bin/command2"

一定要把解释器显式写出来,因为 systemd 不会帮你套 shell。

4.5 清理 unit 状态:systemctl reset-failed 的正确用法

服务启动失败后,systemd 会把这个 unit 标记为failed状态。如果只是临时配置有误,后来配置改好了,你会看到有些命令会提醒你“active (failed)”,此时执行:

sudo systemctl reset-failed myapp.service

把失败计数和状态清掉,再启动就正常了。这个操作在测试阶段特别常用,因为反复改配置、反复启动失败后,systemd 会累积错误状态,不清理有时会阻碍后续操作。

5. 把时间花在理解模型上,而不是背命令

systemd 的命令多,但真正要记的核心就一条线:服务是service,状态是target,服务通过enable挂到 target 上,系统通过 target 决定跑哪些服务。所有命令都在围绕这条线转。

我自己刚接触 systemd 时也觉得很烦,总觉得命令太多、文件太多,直到有一天手动去翻了/etc/systemd/system/multi-user.target.wants/里的软链接,突然就通了。它没有多神秘,就是一堆配置文件和软链接的组合,你理解了文件之间的关系,命令行自然就记住了。

遇到问题先journalctl -xe,再systemctl status,配合systemctl cat看配置,九成问题都能在十分钟内定位。最后一个小技巧:给服务写After=network-online.target时,记得确认系统里有没有对应的 network-online 服务真的在运行,否则这个依赖写了也白写。类似这种看似不起眼的细节,在实际部署中往往才是最磨人的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询