systemd 入门:service、timer、日志(journalctl)——让服务开机自启、定时执行
适合读者:会基本 Linux 命令(cd、ls、cat),第一次接触 systemd。
全文命令默认需要 root 权限(前面加 sudo),为简洁起见不再逐条重复。
本文在 Ubuntu 22.04 / Debian 12 上验证,RHEL 9 / CentOS Stream / Rocky / Alma 以及树莓派 OS 上完全通用。
—
一、什么是 systemd?为什么现代 Linux 都离不开它?
1.1 一句话版本
systemd 是现代 Linux 的"总管家":开机时它是第一个运行的程序(PID 1,“init 系统”),负责启动并管理其余一切——后台服务(service)、定时任务(timer)、日志(journal)、网络、DNS、时间同步、用户会话……你日常听到的"开机自启"“服务管理”“查系统日志”,基本都是它的事。
1.2 它是怎么成为主流的?
老系统的 init 是 SysVinit:一堆 /etc/init.d/ 下的 shell 脚本加 runlevel,串行启动,慢且难维护。systemd 在 2010 年前后陆续取代它,如今主流发行版早已"全员换装":
| 发行版 | systemd 成为默认 |
| Ubuntu | 15.04 起 |
| Debian | 8(jessie)起 |
| RHEL / CentOS | 7 起(RHEL 8/9、CentOS Stream、Rocky、Alma 同理) |
| openSUSE | 13.2 起 |
| Arch | 自始即然 |
| 树莓派 OS(Raspberry Pi OS) | 自始即然(Debian 血统) |
换句话说:只要你的系统是近 10 年发布的,开机跑的就是 systemd。 学它不是可选项,是必修课。
1.3 systemd 管什么?
| 管理对象 | 对应组件 / 单元 | 你日常的操作 |
| 后台服务 | *.service 单元 | systemctl start/stop/status |
| 定时任务 | *.timer 单元(配合 service) | systemctl list-timers |
| 日志 | journald → /var/log/journal/ | journalctl -u xxx -f |
| DNS 解析 | systemd-resolved | 一般不用碰 |
| 时间同步 | systemd-timesyncd | 一般不用碰 |
| 网络配置 | systemd-networkd | 轻量服务器 / 嵌入式常用 |
| 登录会话 | systemd-logind | loginctl |
| 开机阶段 | *.target 单元(如 multi-user.target) | systemctl get-default |
1.4 核心概念:单元(Unit)
systemd 把"一切可以被管理的东西"抽象成单元(unit),每个单元由一个 INI 风格的单元文件描述。常见类型:
| 后缀 | 名称 | 用途 |
.service | 服务单元 | 后台常驻进程 / 一次性任务 |
.timer | 定时器单元 | 按时间触发另一个单元(cron 的替代) |
.socket | 套接字单元 | 有连接进来才拉起服务(socket 激活) |
.target | 目标单元 | 一组单元的集合 / 开机阶段(multi-user.target = 多用户模式) |
.mount / .automount | 挂载单元 | 管理挂载点 |
.path | 路径单元 | 文件变化时触发 |
.slice / .scope | 资源单元 | CPU / 内存等资源限制 |
单元文件放哪里?(查找优先级从高到低)
/etc/systemd/system/ —— 你自己写的,系统更新不会动。本文所有自定义文件都写在这里。
/usr/lib/systemd/system/(部分发行版为 /lib/systemd/system/)—— 软件包安装时带过来的。
/run/systemd/system/ —— 运行时临时生成,重启即失。
经验法则:软件包自带的单元在 /usr/lib/...,自己写的放 /etc/systemd/system/。同名时 /etc 下的会覆盖系统自带的(这也是"改官方服务行为"的正规做法:systemctl edit xxx 会帮你生成 override)。
—
二、service 服务单元:逐行拆解
2.1 写第一个自定义服务
假设你有个小服务 myapp,可执行文件在 /opt/myapp/myapp(示例路径,按你的实际程序改)。
先建一个非 root 的运行用户(好习惯,下文 User= 会用到):
sudo useradd -r -s /usr/sbin/nologin myapp # 建一个不能登录的系统用户
sudo mkdir -p /opt/myapp
然后写单元文件:
sudo vim /etc/systemd/system/myapp.service
文件内容(先贴全量,下面逐段、逐行讲):
# /etc/systemd/system/myapp.service
# systemd 服务单元文件:INI 风格,分三段 [Unit] [Service] [Install]
# ============ [Unit] 元信息段:这个单元"是什么" ============
[Unit]
Description=myapp 示例服务 # 一句话描述,status/list 里会显示
Documentation=https://example.com/myapp # 文档链接(可选,纯展示)
After=network.target # 启动顺序:排在这个 target 之后
Wants=network.target # 弱依赖:需要网络,但缺了我也能启动
# ============ [Service] 运行段:进程"怎么跑" ============
[Service]
Type=simple # 进程模型:simple = systemd 直接拉起的进程
User=myapp # 以 myapp 用户身份运行(别用 root)
Group=myapp # 运行用户组
WorkingDirectory=/opt/myapp # 启动前先 cd 到这个目录
Environment=APP_ENV=production APP_PORT=8080 # 环境变量(空格分隔的 KEY=VALUE)
ExecStart=/opt/myapp/myapp --config /opt/myapp/config.yaml
#ExecStop=/opt/myapp/myapp --stop # 可选:自定义停止命令;不写则默认发 SIGTERM
Restart=on-failure # 异常退出(非 0 退出码/被信号杀死)时自动重启
RestartSec=5 # 两次重启之间等 5 秒
StandardOutput=journal # stdout 进 journal(默认值,可不写)
StandardError=journal # stderr 同理
# ============ [Install] 安装段:开机自启"装在哪" ============
[Install]
WantedBy=multi-user.target # enable 时挂到多用户模式 target
2.2 逐行拆解
[Unit] 段 —— “它是什么”
| 字段 | 含义 | 说明 |
Description= | 单元描述 | 给人看的,systemctl status / list-units 里显示 |
Documentation= | 文档 URL | 可选,纯展示 |
After= | 启动顺序 | 表示"我在这个 target 之后启动"。注意:只保证先后顺序,不产生依赖、也不等它真正完成 |
Wants= | 弱依赖 | 拉起目标;目标挂了我不受牵连 |
Requires= | 强依赖 | 拉起目标;目标挂了我也跟着停 |
BindsTo= | 绑定依赖 | 比 Requires 更狠:目标停了,我立刻被停 |
新手建议:After=network.target + Wants=network.target 是最常见的组合;真要"等网络可用",见第七节坑 #10。
[Service] 段 —— “它怎么跑”
| 字段 | 含义 |
Type= | 进程模型,见下表 |
ExecStart= | 启动命令,必须是绝对路径,可带参数;这是单元的核心 |
ExecStop= | 停止命令(可选);不写则默认发 SIGTERM,10 秒后 SIGKILL |
ExecReload= | systemctl reload 时执行的命令(可选) |
User= / Group= | 以哪个用户/组运行;省略就是 root(不推荐) |
WorkingDirectory= | 启动前 cd 到的目录 |
Environment= | 环境变量,多个用空格分隔 |
EnvironmentFile= | 从文件读环境变量;写成 -EnvironmentFile=/path 表示文件不存在也不报错 |
Restart= | no(默认)/ always / on-success / on-failure / on-abnormal |
RestartSec= | 重启间隔(秒) |
StandardOutput= / StandardError= | 输出去向:journal(默认)/ syslog / file:/path / null / inherit |
Type= 常见取值:
| 取值 | 含义 | 典型场景 |
simple(默认) | systemd fork 出进程,主进程就是 ExecStart 那个 | 绝大多数常驻服务 |
exec | 同 simple,但 fork 失败会报 failed | 想严格检测"进程到底起没起" |
forking | 程序自己 fork 成守护进程 | 老式 xxx -D 启动的程序 |
oneshot | 跑一次就结束 | 备份、清理、升级脚本(配合 timer 用) |
notify | 程序通过 sd_notify 告知"我准备好了" | 支持 systemd 通知协议的程序 |
idle | 同 simple,但等别的任务启动完再启动 | 想让开机其他服务先走 |
拿不准就用 simple(常驻)或 oneshot(跑一次就完),覆盖 90% 场景。
[Install] 段 —— “开机自启装在哪”
| 字段 | 含义 |
WantedBy= | systemctl enable 时,把本单元的软链建到该 target 的 .wants/ 目录下,从而实现开机自启 |
没有 [Install] 段的单元无法被 enable(很多 socket 激活单元、静态单元就是这样,它们由别的单元顺带拉起)。
2.3 让改动生效并启动
sudo systemctl daemon-reload # 重新读取所有单元文件(每次改完单元文件都要跑!)
sudo systemctl start myapp # 现在立即启动
systemctl status myapp # 看一眼状态
—
三、systemctl 常用命令速查表
3.1 命令速查
| 命令 | 作用 | 例子 |
systemctl start <unit> | 立即启动(不改变开机自启状态) | systemctl start myapp |
systemctl stop <unit> | 立即停止 | systemctl stop myapp |
systemctl restart <unit> | 重启(stop + start;没在跑也会先启动) | systemctl restart nginx |
systemctl reload <unit> | 重载配置、不断连接(需单元支持 ExecReload=) | systemctl reload nginx |
systemctl status <unit> | 状态:在跑吗、PID、是否 enable、最近 10 行日志 | systemctl status myapp |
systemctl enable <unit> | 设置开机自启(建软链,不启动当前进程) | systemctl enable myapp |
systemctl disable <unit> | 取消开机自启(不影响正在运行的进程) | systemctl disable myapp |
systemctl is-enabled <unit> | 查是否已 enable:enabled / disabled / static / masked | systemctl is-enabled myapp |
systemctl is-active <unit> | 查是否正在运行:active / inactive / failed | systemctl is-active myapp |
systemctl daemon-reload | 重读单元文件(改完 .service/.timer 必跑) | — |
systemctl list-units | 列出运行中的单元(默认只显示 active 的) | systemctl list-units --type=timer |
systemctl list-unit-files | 列出磁盘上所有单元文件及 enabled/disabled 状态 | systemctl list-unit-files --state=enabled |
systemctl enable --now <unit> | enable + start 一步到位 | systemctl enable --now myapp |
systemctl mask <unit> | 彻底封禁(任何方式都起不来,比 disable 狠) | systemctl mask getty@tty1 |
systemctl unmask <unit> | 解除 mask | systemctl unmask getty@tty1 |
systemctl reset-failed | 清除 failed 状态,让单元可以再次 start | systemctl reset-failed myapp |
journalctl -u <unit> -f | 实时跟踪该单元日志 | journalctl -u myapp -f |
3.2 重点:enable 和 start 是两回事!
这是新手最常搞混的点,务必分清:
| start | enable |
| 作用 | 此刻把进程拉起来 | 把单元注册进开机自启列表(在 target 的 .wants/ 目录建软链) |
| 影响范围 | 只影响本次开机(重启后失效,除非 enable 了) | 影响下次及以后每次开机 |
| 立即生效? | 是 | 否(不启动任何进程) |
| 文件层面 | 无(纯运行时操作) | 创建/删除 /etc/systemd/system/multi-user.target.wants/myapp.service 软链 |
由此推出三条常用组合:
- 装完服务,想"现在就跑 + 以后开机也跑" →
systemctl enable --now myapp
- 只想临时跑一次 →
systemctl start myapp
- 只取消开机自启、服务继续跑 →
systemctl disable myapp(重启后不再启动)
3.3 读懂 status 输出
● myapp.service - myapp 示例服务
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2025-01-06 09:00:01 CST; 2h 5min ago
Main PID: 1234 (myapp)
Memory: 8.2M
CPU: 432ms
CGroup: /system.slice/myapp.service
└─1234 /opt/myapp/myapp --config /opt/myapp/config.yaml
Jan 06 09:00:01 srv systemd[1]: Started myapp 示例服务.
Jan 06 09:00:01 srv myapp[1234]: listening on :8080
Loaded: loaded (路径; enabled; ...) —— 单元文件在哪、是否已 enable;
Active: active (running) since ... —— 正在运行、从何时开始;若为 failed,往下找日志;
- 末尾几行 = 该单元最近的日志(完整日志用
journalctl -u myapp)。
—
四、timer 定时单元:让任务"定时自动跑"
4.1 timer vs cron
| 维度 | cron | systemd timer |
| 配置位置 | /etc/crontab、crontab -e | /etc/systemd/system/*.timer |
| 日志 | 默认丢进 syslog,难追溯 | 自动进 journal,journalctl -u 一查一个准 |
| 错过任务 | 错过就错过(部分发行版靠 anacron 补救) | Persistent=true:错过后开机补跑一次 |
| 随机延迟 | 没有 | RandomizedDelaySec= 原生支持,多机错峰 |
| 查看下次执行时间 | 只能自己推算 | systemctl list-timers 直接显示 |
| 依赖管理 | 无 | 和普通单元一样支持 After/Requires |
cron 依然可用,两者不冲突;但新系统上写新任务,推荐 timer。
4.2 例子:每天凌晨 3:00 跑备份脚本
timer 由一对文件组成:*.timer 定义"什么时候“,同名的 *.service 定义”干什么"。
/etc/systemd/system/backup.service(干什么):
[Unit]
Description=每日数据备份(由 backup.timer 触发)
[Service]
Type=oneshot # 跑一次就结束——备份类任务的标准选择
User=backup
ExecStart=/opt/backup/backup.sh # 绝对路径
WorkingDirectory=/opt/backup
/etc/systemd/system/backup.timer(什么时候):
[Unit]
Description=每天凌晨 3:00 触发备份
[Timer]
OnCalendar=*-*-* 03:00:00 # 每天 03:00:00(本地时区)触发
Persistent=true # 到点时机器没开/没运行?下次开机补跑一次
RandomizedDelaySec=300 # 在计划时间上随机延迟 0~300 秒,避免多机同一秒抢资源
#OnBootSec=10min # (可选)开机后 10 分钟触发一次,不管时钟几点
#OnUnitActiveSec=1h # (可选)距上次激活 1 小时后再次触发
[Install]
WantedBy=timers.target # 启用时挂到 timers.target
4.3 timer 字段逐行拆解
| 字段 | 含义 | 备注 |
OnCalendar= | 按墙上时钟(日历时间)触发 | 语法见下表;默认本地时区,需要 UTC 可写 OnCalendar=UTC ... |
OnBootSec= | 按开机后经过的时间触发 | 例 OnBootSec=10min = 开机 10 分钟后 |
OnUnitActiveSec= | 按上次激活后经过的时间触发 | 循环任务常用,例 OnUnitActiveSec=1h = 每隔 1 小时 |
Persistent= | true 时:错过的那次任务,下次开机补跑一次 | 仅对 OnCalendar 有意义;防止"到点关机 = 任务丢失" |
RandomizedDelaySec= | 触发前加 0N 秒随机延迟 | 备份/同步类任务推荐,天然错峰 |
AccuracySec= | 触发精度窗口(默认 1 秒),systemd 在窗口内挑时机 | 一般不用动 |
一个 timer 可以同时写多个 OnXxx= 字段:满足其中任意一个就触发。
4.4 OnCalendar 时间表达式表
语法(比 crontab 更自由):[周] [月]-[日] [时]:[分]:[秒],任意位置可用 *。
| 表达式 | 含义 |
*-*-* 03:00:00 | 每天 03:00 |
Mon *-*-* 02:30:00 | 每周一 02:30 |
Mon,Wed,Fri *-*-* 09:00:00 | 每周一、三、五 09:00 |
*-*-1 00:00:00 | 每月 1 号 00:00 |
*-*-1,15 00:00:00 | 每月 1 号和 15 号 |
*-*-06 03:00:00 | 每月 6 号 03:00 |
Jan-01 00:00:00 | 每年 1 月 1 日 00:00 |
*-*-* 00:00:00;12:00:00 | 每天 00:00 和 12:00(分号分隔多个时间) |
UTC *-*-* 00:00:00 | UTC 时区的午夜 |
记忆法:*-*-* 就是"每月每天";周几写在最前面,不写就是"每天"。
注意:周几和日期同时限定时满足其一即触发(cron 语义),一般别这么写,容易踩坑。
4.5 启用与查看
sudo systemctl daemon-reload # 新单元文件,先 reload
sudo systemctl enable backup.timer # 开机自启(重启后自动开始计时)
sudo systemctl start backup.timer # 让当前开机的计时立刻生效(不重启也要跑就加这条)
systemctl list-timers # 列出所有 timer 及上次/下次触发时间
systemctl status backup.timer # 看这个 timer 的状态
list-timers 输出示例(假设今天是 1 月 5 日周日 08:30):
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2025-01-06 03:00:00 CST 18h Sun 2025-01-05 03:00:04 CST 5h ago backup.timer backup.service
两个细节:
- timer 只负责"按点按铃",真正干活的是同名 service——timer 名字决定它触发哪个 service(
backup.timer → backup.service),不需要显式写 Wants=。
- 手动测试:
sudo systemctl start backup.service。oneshot 服务跑完即止,不用等明天 3 点就能验证脚本——这正是 Type=oneshot 的妙处。
—
五、journalctl:systemd 的日志中枢
5.1 journal 是什么
- systemd 自带日志守护
journald:所有服务(stdout/stderr 重定向到 journal 的)、内核消息、登录事件,全部汇总进 journal。
- 二进制存储,位置:
/var/log/journal/(持久,重启保留);/run/log/journal/(内存,重启即失)。
- 查询工具就是
journalctl。
5.2 常用命令速查
| 命令 | 作用 |
journalctl | 看全部日志(进入分页器,q 退出) |
journalctl -u myapp | 只看 myapp 这个单元的日志 |
journalctl -u myapp -f | 实时跟踪 myapp 日志(Ctrl+C 退出),效果类似 tail -f |
journalctl -u myapp --since "2 hours ago" | 最近 2 小时的日志 |
journalctl -u myapp --since today --until "12:00" | 今天 0 点 ~ 12 点 |
journalctl -u myapp -p err | 只看 err 级别及以上(err / crit / alert / emerg) |
journalctl -n 50 | 只看最后 50 行 |
journalctl --no-pager | 不进分页器、直接全量打印(方便接 \| grep 管道) |
journalctl -b | 只看本次开机以来的日志 |
journalctl -b -1 | 看上一次开机的日志(排查"重启后才挂的"利器) |
journalctl --disk-usage | journal 一共占了多少磁盘 |
journalctl --vacuum-time=7d | 只保留最近 7 天的日志 |
journalctl --vacuum-size=200M | 把 journal 总量压到 200M 以内 |
journalctl -g "关键词" | 全文搜索(二进制层 grep,很快) |
journalctl -t mytag | 按 syslog 标识过滤,非 systemd 启动的程序也适用 |
时间参数很好认,直接写人话:"yesterday"、"2025-01-06"、"2025-01-06 03:00" 都可以。
5.3 和 tail /var/log/xxx.log 的区别
| 维度 | journalctl | tail /var/log/xxx.log |
| 来源 | 所有进 journal 的东西(服务、内核、登录)集中一处 | 只有该程序自己写进文件的那部分 |
| 按服务过滤 | -u 单元名,精确 | 得先知道它把日志写到哪个文件 |
| 按时间过滤 | --since / --until 内置 | 文件无统一时间戳格式时只能肉眼扫 |
| 按级别过滤 | -p err | 没有 |
| 跨重启 | 持久化在 /var/log/journal | 看程序自己是否轮转/保留 |
| 实时跟踪 | -f | tail -f |
| 对程序的要求 | 什么都不用做,往 stdout/stderr 打就行 | 程序必须主动写文件 |
| 存储格式 | 二进制(可压缩、可限额) | 纯文本 |
实践建议:systemd 时代的主流写法是程序只往 stdout/stderr 打日志,全部交给 journal 统一收;传统 /var/log/*.log 依然并存,老程序照查不误。
—
六、完整实战:备份脚本 → 开机可重启、定时自动跑、日志可查
目标:把 /opt/backup/backup.sh(把 /data 备份到 /backup)做成——
- 可以
systemctl start 手动跑;
- 每天凌晨 3:00 自动跑,错过就开机补跑;
- 日志随时
journalctl -u backup 查。
Step 0:写备份脚本
sudo mkdir -p /opt/backup /backup
sudo tee /opt/backup/backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
# 每天备份 /data 到 /backup,只保留最近 7 份
set -euo pipefail
SRC=/data
DEST=/backup
DATE=$(date +%F_%H%M)
TAR="${DEST}/data_${DATE}.tar.gz"
echo "[backup] 开始备份 ${SRC} -> ${TAR}"
tar -czf "$TAR" -C "$(dirname "$SRC")" "$(basename "$SRC")"
echo "[backup] 完成:${TAR}($(du -h "$TAR" | cut -f1))"
# 只保留最近 7 份(没有旧文件时不报错)
if ls -1t "${DEST}"/data_*.tar.gz >/dev/null 2>&1; then
ls -1t "${DEST}"/data_*.tar.gz | tail -n +8 | xargs -r rm -f
echo "[backup] 已清理旧备份"
fi
EOF
用 vim 手动建文件完全可以,上面的 tee + heredoc 只是为了方便整段复制粘贴。注意脚本往 stdout 打日志——这正是 journal 能收到的原因。
Step 1:赋执行权限 + 建运行用户(新手第一大坑!)
sudo chmod +x /opt/backup/backup.sh # 必须可执行,否则 ExecStart 报 Permission denied
sudo useradd -r -s /usr/sbin/nologin backup # 专用运行用户
sudo chown -R backup:backup /opt/backup /backup # 目录归它,读写才不报权限错误
sudo chmod 750 /opt/backup
Step 2:写 service 单元
sudo tee /etc/systemd/system/backup.service > /dev/null <<'EOF'
[Unit]
Description=每日数据备份
[Service]
Type=oneshot # 一次性任务:跑完即止
User=backup
WorkingDirectory=/opt/backup
ExecStart=/opt/backup/backup.sh # 绝对路径!
EOF
Step 3:写 timer 单元
sudo tee /etc/systemd/system/backup.timer > /dev/null <<'EOF'
[Unit]
Description=每天 03:00 触发数据备份
[Timer]
OnCalendar=*-*-* 03:00:00 # 每天凌晨 3 点
Persistent=true # 错过补跑
RandomizedDelaySec=120 # 最多随机延迟 2 分钟,错峰
[Install]
WantedBy=timers.target
EOF
Step 4:加载并启用
sudo systemctl daemon-reload # 改过单元文件,必须 reload
sudo systemctl enable backup.timer # 开机自启(重启后自动开始计时)
sudo systemctl start backup.timer # 当前开机立即生效(不等重启)
Step 5:验证
# 5.1 手动跑一次(验证脚本 + 权限 + 路径)
sudo systemctl start backup
systemctl status backup
# oneshot 跑完显示 "Active: inactive (dead)" 属正常,重点看日志和退出码
# 5.2 看这次跑的日志
journalctl -u backup --no-pager | tail -20
# 应看到:[backup] 开始备份 ... [backup] 完成 ... [backup] 已清理旧备份
# 5.3 看定时器排期
systemctl list-timers backup.timer
# NEXT 应显示明天的 03:00:xx(含最多 120 秒随机延迟)
# 5.4 实时跟踪(另开一个终端,再执行一次 systemctl start backup)
journalctl -u backup -f
Step 6:日常运维动作
| 场景 | 命令 |
改了 backup.sh,想马上跑一次 | systemctl start backup |
改了 .service / .timer 文件 | systemctl daemon-reload && systemctl restart backup.timer |
| 临时停用定时器(不卸载) | systemctl stop backup.timer(彻底停用用 disable) |
| 改备份时间 | 改 OnCalendar= → daemon-reload → systemctl restart backup.timer |
| 查最近一次跑没跑成功 | systemctl status backup;journalctl -u backup --since "3 hours ago" |
✅ 到此,你的备份任务已经做到:开机自启计时、定时自动跑、错过可补跑、失败可查日志。
—
七、常见坑与避坑表
| # | 坑 | 症状 | 原因 | 解决 |
| 1 | 改了单元文件没 daemon-reload | 改了 Restart=、User= 等不生效 | systemd 还在用内存里的旧配置 | 改完必跑 systemctl daemon-reload(养成肌肉记忆) |
| 2 | ExecStart 路径错/拼错 | start 立刻 failed,journal 里 No such file or directory | ExecStart 必须是存在的绝对路径,解释器也要绝对路径 | 先 ls -l /path/to/binary 核对;用 journalctl -u xx 看具体报错 |
| 3 | 脚本没 chmod +x | failed,journal 里 Permission denied | ExecStart 直接执行该文件,需要 x 权限 | chmod +x 脚本;或写成 ExecStart=/bin/bash /path/script.sh(不依赖 x 位) |
| 4 | enable 了但服务没在跑 | is-enabled 显示 enabled,is-active 却是 inactive | enable 只管开机,不会启动当前正在运行的系统里的服务 | systemctl start;或一开始就用 enable --now |
| 5 | 服务"秒退" | status 显示 inactive (dead),日志只有一行 | Type=simple 的进程正常退出就结束了;oneshot 更是如此 | 常驻服务:看 journal 查进程为何退出;一次性任务:本来就该配 timer |
| 6 | 权限不足(User= 非 root) | 写文件 Permission denied、目录不可读 | 以服务用户身份跑,但目录/文件还归 root 所有 | 用对 User=,并 chown 好相关目录;别在服务里读写 /root 下的东西 |
| 7 | 日志刷得太快 | journal 占满磁盘 / 日志被丢弃并提示 Rate limit hit | 循环里疯狂 print;journald 有频率限制(默认约 30 秒 1 万条) | 降低日志级别;journalctl --vacuum-time=7d 定期清;确需高频再调 journald 的 RateLimit 配置 |
| 8 | timer 到点没跑 | list-timers 正常,但任务没执行 | 机器到点时关机了,且没开补跑 | 加 Persistent=true(开机补跑);并确认 timer 已 enable + start |
| 9 | oneshot 的 status 看不太懂 | Active: inactive (dead),以为是挂了 | oneshot 跑完就是 dead,这是正常 | 看退出码和 journal;想让它显示"保持 active",加 RemainAfterExit=yes |
| 10 | 写了 After=network.target 启动时还是没网 | 服务启动瞬间 DNS 解析失败、连接被拒 | After= 只保证顺序,不等网络真正就绪(network.target 只是顺序锚点) | 脚本里做重试;或 Wants=network-online.target + After=network-online.target 等真正就绪 |
| 11 | 单元被 mask 过 | 怎么都起不来 | systemctl mask 比 disable 狠,软链指向 /dev/null | systemctl unmask xxx |
| 12 | RHEL 系 SELinux 拦路 | 日志 Permission denied,ls -lZ 看到上下文警告 | SELinux 上下文不匹配 | 临时 setenforce 0 验证;长期用 chcon / restorecon 或自定义策略 |
三条黄金排查习惯
systemctl status <unit> —— 先看第一屏(Loaded / Active / 最近日志);
journalctl -u <unit> --no-pager | tail -50 —— 看完整报错;
- 任何单元文件改动后,
systemctl daemon-reload —— 无脑执行。
—
八、systemd 生态速览(一句话版)
| 组件 | 一句话作用 |
systemd(PID 1) | 总控:服务、目标、开机流程都归它管 |
systemd-journald | 日志中枢:收集所有单元/内核日志存进 journal,journalctl 查询 |
systemd-resolved | DNS 解析器:提供本地 stub 127.0.0.53,统一管理 /etc/resolv.conf(Ubuntu 18.04 起默认启用) |
systemd-timesyncd | 轻量 NTP 客户端:用 SNTP 同步系统时间(RHEL 系默认 chronyd,功能等价) |
systemd-networkd | 网络配置守护:用 /etc/systemd/network/*.network 管理网卡/路由/DHCP,轻量机器上 NetworkManager 的替代品 |
systemd-logind | 管理用户登录会话,loginctl 查会话、做电源操作 |
systemd-hostnamed | 主机名/时区/部署环境,hostnamectl 改主机名 |
systemd-oomd | 内存不足时按策略杀进程,防止整机卡死 |
新手记忆:日常 99% 只用 systemctl + journalctl;resolved / networkd / timesyncd 知道"系统里这些服务是干嘛的"即可,真要配置再查对应 man 页(man systemd-resolved 等)。
—
写在最后:把今天的知识串一遍
- systemd = 现代 Linux 的总管家,一切皆单元(
.service / .timer / .target …);
- 写服务:
/etc/systemd/system/xxx.service 三段式——[Unit] 是什么、[Service] 怎么跑、[Install] 挂哪个 target;
- 命令口诀:start 管现在,enable 管开机,status 看状态,journalctl 查日志,改文件必
daemon-reload;
- 定时任务:
xxx.timer(OnCalendar 定时间、Persistent 防错过)+ 同名 xxx.service(Type=oneshot 干活);
- 日志:
journalctl -u xxx -f 实时跟踪,--since / -p 精确定位,比翻 /var/log 爽得多;
- 排错三板斧:
status → journalctl → daemon-reload。
(完)