后台进程需要有人负责启动、停止、收集日志和处理异常退出。systemd 的 service unit 声明这些规则。排障时要区分 unit 配置是否加载、进程是否存活,以及业务是否已经可用。
先读当前状态
systemctl status nginx --no-pager
systemctl cat nginx
systemctl show nginx -p FragmentPath -p MainPID -p ActiveState -p SubState -p Result
journalctl -u nginx -b --no-pager -n 100status 提供摘要,cat 展示主文件与 drop-in,show 便于读取结构化属性。日志用启动批次和时间窗定位,先看最早异常,再看重复重启的结果。
enable 管理开机依赖链接,start 启动当前实例;enable --now 同时执行这两件事。执行 daemon-reload 让管理器重新读取 unit 文件,应用自身配置是否重读还取决于 reload 或重启操作。
单元文件目录与加载优先级
unit 文件可以放在多个目录,加载时按优先级合并。本地安装或覆盖的单元要放在优先级更高的目录。
| 优先级 | 目录路径 | 说明 |
|---|---|---|
| 最高 | /etc/systemd/system/ |
本地管理员自定义或覆盖的单元文件 |
| 中 | /run/systemd/system/ |
运行时动态生成的单元 |
| 低 | /usr/lib/systemd/system/ |
软件包安装提供的默认单元 |
| 低 | /lib/systemd/system/ |
旧路径,通常是上一项的符号链接 |
| 低 | /usr/local/lib/systemd/system/ |
本地手动安装的单元 |
查看完整的搜索路径列表:
systemd-analyze unit-paths/usr/lib/systemd/system/ 属于软件包,下次升级会被覆盖,不要直接修改。需要调整时改用下面的 drop-in 机制,在 /etc/systemd/system/ 下覆盖。
写清启动与退出约定
下面是一个前台运行的示例应用单元,保存为 /etc/systemd/system/example-app.service。app 用户、程序路径及工作目录需要先准备好。
[Unit]
Description=Example application
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=exec
User=app
Group=app
WorkingDirectory=/opt/example-app
ExecStart=/opt/example-app/bin/server
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
NoNewPrivileges=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.targetType=exec 能发现可执行文件启动失败,但业务端口可能还未准备好。支持 sd_notify 的应用可使用 Type=notify 报告就绪;Type=forking 对应会派生后台进程的传统服务。选项含义见 systemd.service。
After=network.target 指定排序,不保证 DNS 或远程数据库可用。应用仍需要处理依赖暂时不可用的情况。重启策略也必须有间隔和频率限制,避免错误配置引发连续启动。
sudo systemd-analyze verify /etc/systemd/system/example-app.service
sudo systemctl daemon-reload
sudo systemctl enable --now example-app随后访问真实业务端点。服务 active 和请求成功是两项独立检查。这里的 5 秒重启间隔和 30 秒停止预算只是示例参数,应按应用关闭连接与落盘所需时间调整。
使用 drop-in 修改现有服务
通过 systemctl edit example-app 创建覆盖配置,避免修改发行版管理的 unit 文件。它会在 /etc/systemd/system/example-app.service.d/override.conf 打开编辑窗口,保存后自动执行 daemon-reload。覆盖列表型指令时要核对合并语义,例如替换 ExecStart 常需先写空的 ExecStart= 清空原值。
systemd 加载单元时自动扫描 <单元名>.d/ 目录下的 .conf 文件,按目录优先级合并进主配置,后面的覆盖前面的。不在 /etc/systemd/system/ 直接改动,也方便追查哪些文件参与覆盖。
修改前记录现有 drop-in,修改后执行语法验证和 systemctl daemon-reload。涉及 ExecStart、运行用户或环境变量时,还需 systemctl restart example-app 创建新进程,再检查 MainPID、实际命令行和业务响应。应用自身支持的 reload 仅用于对应应用配置。回滚时恢复此次修改前的文件,再执行相同步骤;已有其他人的覆盖配置时,不能直接用全量恢复命令抹掉它们。
日志查看:journalctl
systemd 把日志统一收到 journald,由 journalctl 查询。先分清三个概念:
| 概念 | 说明 |
|---|---|
systemd-journald |
后台服务,负责收集日志(内核、服务 stdout/stderr、syslog 转发等) |
| journal | 日志数据库,位于 /run/log/journal/(内存)或 /var/log/journal/(持久化) |
journalctl |
用户态查询、过滤、导出命令 |
基础查询
# 查全部(默认分页)
journalctl
# 不分页直接输出
journalctl --no-pager
# 最近 N 行
journalctl -n 100
# 实时跟随(像 tail -f)
journalctl -f按服务过滤
调试某个服务起不来时最常用:
journalctl -u nginx
journalctl -u nginx -n 200
journalctl -u nginx -f按启动会话过滤
# 本次启动以来的日志
journalctl -b
# 上一次启动的日志(服务刚重启崩溃时可查)
journalctl -b -1
# 列出所有启动会话
journalctl --list-boots按时间过滤
journalctl --since today
journalctl --since yesterday
journalctl --since "10 min ago"
journalctl --since "1 hour ago"
journalctl --since "2026-06-01 09:00:00" --until "2026-06-01 10:00:00"按日志级别过滤
journalctl -p err # 仅错误
journalctl -p warning # 警告及以上
journalctl -p info # info 及以上优先级从低到高:debug → info → notice → warning → err → crit → alert → emerg。
按进程 / 用户过滤
# 按 PID
journalctl _PID=1234
# 当前用户的用户服务日志
journalctl --user
journalctl --user -u myservice查询用户服务(--user)的日志要求该用户的 systemd 实例已启用:未开 linger 时,只有处于活跃登录会话才能读取,必要时 loginctl enable-linger <user> 保持实例常驻。
日志占用与清理
服务器跑久了 /var/log/journal/ 可能占掉数 GB:
# 看占用
journalctl --disk-usage
# 按大小清理到 200MB
sudo journalctl --vacuum-size=200M
# 按时间清理(保留 7 天)
sudo journalctl --vacuum-time=7d
# 按文件数清理
sudo journalctl --vacuum-files=5可以放进 cron 或 systemd timer 定时执行。
持久化日志
journald 默认可能只存内存,重启后日志丢失,/var/log/journal/ 不存在就是未持久化的表现。检查:
ls /var/log/journal/ 2>/dev/null || echo "未持久化"编辑 /etc/systemd/journald.conf 启用持久化并限制占用:
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemKeepFree=1G| 配置项 | 含义 |
|---|---|
Storage=persistent |
强制写到磁盘 |
Storage=auto(默认) |
有 /var/log/journal/ 就持久化,没有就内存 |
SystemMaxUse=500M |
journal 最多占 500MB |
SystemKeepFree=1G |
磁盘至少留 1GB 给其它用途 |
改完重启 journald:
sudo systemctl restart systemd-journald从退出原因收敛故障
| 观察 | 排查方向 | 后续动作 |
|---|---|---|
| executable 或权限错误 | 路径、执行权限、挂载参数 | 用服务用户验证可读与可执行权限 |
| 端口占用 | 已有实例或冲突配置 | ss -lntp 查占用进程 |
| 反复重启 | 程序异常、依赖失败、启动超时 | 先修首个异常,再重置失败状态 |
| SIGKILL / 内存相关日志 | 主机或 cgroup 内存压力 | 联查内核日志和 memory.events |
| active 但请求失败 | 应用路由、连接池、远程依赖 | 发业务请求并关联应用日志 |
Restart= 可以恢复进程,数据一致性仍由应用和存储协议保证。对写操作,恢复后还要检查提交状态与重复执行的处理方式。
排查服务启动失败的标准流程
新装服务跑不起来时按顺序排查,同时兼顾配置层面与进程层面:
# 1. 看状态
systemctl status nginx
# 2. 看最近 100 行日志
journalctl -u nginx -n 100
# 3. 实时跟随日志并手动重启
journalctl -u nginx -f &
systemctl restart nginx
# 4. 仍定位不到时,直接前台跑 ExecStart 看错误输出
systemctl cat nginx # 找到 ExecStart 字段
/usr/sbin/nginx -g 'daemon off;' # 直接执行,观察输出异常对应检查方向:端口占用用 ss -lntp;文件找不到检查路径与权限;配置语法错误可直接前台执行复现。
systemctl 命令速查
| 命令 | 含义 |
|---|---|
systemctl status <unit> |
查看服务详细状态 |
systemctl start <unit> |
立即启动 |
systemctl stop <unit> |
立即停止 |
systemctl restart <unit> |
重启(先停再启) |
systemctl reload <unit> |
重新加载配置(不中断,需服务支持) |
systemctl enable <unit> |
设置开机自启 |
systemctl disable <unit> |
取消开机自启 |
systemctl is-enabled <unit> |
查询是否开机自启 |
systemctl is-active <unit> |
查询是否正在运行 |
systemctl list-units --type=service |
列出所有服务单元 |
systemctl list-unit-files --type=service |
列出所有服务文件及启用状态 |
systemctl daemon-reload |
重新加载所有单元文件 |
systemctl edit <unit> |
创建 drop-in 覆盖配置 |
systemctl cat <unit> |
查看完整生效配置(含覆盖) |
systemctl show -P FragmentPath <unit> |
查这个单元真正加载的是哪个文件 |
排查“改的配置有没有生效”时,先看 systemctl show -P FragmentPath nginx.service 确认加载的文件,再看 systemctl cat nginx.service 的合并结果。在 /etc/systemd/system/ 写了同名 .service 却忘了 daemon-reload,管理器仍用旧文件,怎么 restart 都无效。
实用经验
- 改配置必
daemon-reload:单元文件改完不 reload 等于没改。 - 不要直接改
/usr/lib/systemd/system/下的文件,用systemctl edit创建 drop-in。 - 生产服务
enable带--now:systemctl enable --now nginx等于 enable + start。 journalctl -u xxx -f是调试新服务最直接的方式,开一个终端常驻。- journal 太大别全删,按
--vacuum-time=30d保留近期内容。