systemctl —— systemd 服务管理的总入口
一句话定义
systemctl 是和 systemd 进程管理器对话的命令行工具。Ubuntu 16.04+ / CentOS 7+ / RHEL 7+ 几乎所有发行版都用 systemd 替代了 init(SysV)。systemctl 管的不只是"启动停止服务"——它管理 unit:服务(service)、socket、定时器(timer)、挂载(mount)、target(运行级别)等。
典型场景
- 启停服务:
systemctl start/stop/restart sshd - 改完配置重载:
systemctl reload nginx - 开机自启:
systemctl enable kubelet - 看状态:
systemctl status sshd - 排查"哪些服务开着启动?是不是 IDC 塞了 agent?":
systemctl list-unit-files --state=enabled - 写自定义 unit:
/etc/systemd/system/myapp.service+systemctl daemon-reload
核心 8 个动作
按使用频率排序:
| 动作 | 作用 | 立刻生效? | 重启后还在? |
|---|---|---|---|
status <unit> | 查看状态 | — | — |
start <unit> | 启动 | ✅ | ❌ |
stop <unit> | 停止 | ✅ | ❌ |
restart <unit> | 重启(先停后起) | ✅ | ❌ |
reload <unit> | 重载配置(不重启进程) | ✅ | ❌ |
enable <unit> | 设为开机自启 | ❌ | ✅ |
disable <unit> | 取消开机自启 | ❌ | ✅ |
enable --now <unit> | 开机自启 + 立即启动 | ✅ | ✅ |
注意:start 和 enable 是两件事:
start只在这次开机内起服务enable只在下次开机生效,这次开机内不会启动它- 通常你想要的是
systemctl enable --now <unit>(两件事一起做)
status —— 第一个该跑的诊断命令
$ systemctl status sshd
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2026-05-27 10:23:45 UTC; 3h 12min ago
Main PID: 1234 (sshd)
Tasks: 1 (limit: 4915)
Memory: 5.5M
CPU: 23ms
CGroup: /system.slice/ssh.service
└─1234 sshd: /usr/sbin/sshd -D
May 27 13:35:12 m1 sshd[5678]: Accepted publickey for root from 192.168.1.5
May 27 13:35:12 m1 sshd[5678]: pam_unix(sshd:session): session opened
读法(按行):
- Loaded: 配置文件路径 + enabled/disabled 状态 + vendor preset
- Active: 当前状态 + 多久了。
active (running)是好的;failed/inactive (dead)要查 - Main PID: 主进程 PID
- CGroup: 这个服务下属的所有进程(systemd 按 cgroup 分组)
- 最近 10 行日志(journalctl 的尾巴)
status 自动带最近日志——这是它最方便的地方,不用单独再 journalctl。
Active 的可能取值
| 取值 | 含义 |
|---|---|
active (running) | 正在跑 |
active (exited) | 跑过然后正常退出(典型:oneshot 服务) |
active (waiting) | 等触发(典型:socket、timer) |
inactive (dead) | 没在跑(手动停了 / 没启用) |
failed | 启动失败 / 跑挂了 |
activating / deactivating | 启停中间态 |
restart vs reload 的本质区别
| 动作 | 行为 | 副作用 |
|---|---|---|
restart | 杀进程 → 重新拉起 | 断所有现有连接 / 状态 |
reload | 给进程发 SIGHUP(或 unit 里指定的 reload 命令) | 不断连接,但只有支持的服务能这么做 |
不是所有服务都支持 reload。看 unit 里有没有 ExecReload=:
systemctl cat sshd | grep ExecReload
# ExecReload=/usr/sbin/sshd -t
# ExecReload=/bin/kill -HUP $MAINPID
有 → 可以 reload。没有 → 只能 restart。
养成肌肉记忆:
- nginx / sshd / haproxy →
reload(配置改了不断连接) - 应用类(你自己写的服务)→ 通常
restart(一般没实现 SIGHUP) - 系统配置类(sysctl、netfilter)→ 通常没办法 reload,只能 restart 或 daemon-reload
enable / disable —— 开机自启
systemctl enable kubelet # 标记为开机自启
systemctl disable kubelet # 取消
systemctl is-enabled kubelet # 查看(脚本里常用)
# enabled / disabled / static / masked
enable 实质:在 /etc/systemd/system/<某 target>.wants/<service> 创建一个 symlink,下次启动到该 target 时这个服务被拉起。
--now 一步到位
systemctl enable --now kubelet # 等价于 enable + start
systemctl disable --now kubelet # 等价于 disable + stop
强烈推荐 --now —— 否则你 enable 了一个服务、却发现现在没跑,必须再手动 start。
is-enabled 的几种取值
| 取值 | 含义 |
|---|---|
enabled | 标记为开机自启 |
disabled | 没标记 |
static | 没有 install 段,不能被 enable(其它服务依赖它时被拉起) |
masked | 被屏蔽(见下面) |
alias | 是别名 |
mask —— 比 disable 更狠
systemctl mask cups # 完全屏蔽(连依赖触发都拉不起来)
systemctl unmask cups
mask 把 unit 链接到 /dev/null,相当于让 systemd 完全不认识这个服务。区别:
disable—— 不会自启,但systemctl start还能起、依赖触发也能起mask—— 彻底封禁,任何方式都启不起来
用场景:
- 不想要的服务被某个依赖反复拉起(典型:apache、cups、avahi)
- IDC 模板里塞了你不要的 agent
不要随意 mask 系统关键服务,否则系统启动可能进 emergency。
list-units / list-unit-files
systemctl list-units # 当前已加载的所有 unit
systemctl list-units --type=service # 只看 service
systemctl list-units --state=failed # 看挂掉的
systemctl list-units --type=service --state=running # 跑着的服务
systemctl list-unit-files # 看所有"已安装"的 unit(含没运行的)
systemctl list-unit-files --state=enabled # 看开机自启的
装机审计常用:
systemctl list-unit-files --state=enabled | grep -iE 'agent|aliyun|aegis|nezha|monitor'
排查 IDC 塞的"agent 类"服务(typhoon agent、阿里云盾、监控蜘蛛等),有则查清楚是什么 → 决定保留 / disable / mask。
看 unit 文件本身
systemctl cat sshd # 显示完整 unit 配置(含所有 drop-in override)
systemctl show sshd # 显示所有属性(含 systemd 内部解析后的值)
systemctl show sshd -p ExecStart # 看特定属性
cat 显示文件原文,show 显示 systemd 实际生效的解析后属性。排查"为什么这个服务用了某参数"用 cat。
改 unit 配置:drop-in 而不是改原文件
发行版自带的 unit 文件在 /lib/systemd/system/(或 /usr/lib/systemd/system/)。不要直接改——apt upgrade 会覆盖。
正确做法:drop-in(覆盖片段)。
systemctl edit sshd # 自动开个空 drop-in 文件让你编辑
# 写入要 override 的部分:
[Service]
Environment="SSHD_OPTS=-d"
这会在 /etc/systemd/system/ssh.service.d/override.conf 创建一个片段。systemd 把原文件 + 这个片段合并使用,apt 升级不会动它。
完成后:
systemctl daemon-reload # 必须做:让 systemd 重读 unit 文件
systemctl restart sshd
daemon-reload 时机
任何时候改了 unit 文件 / drop-in / 新增自定义 unit → 必须 systemctl daemon-reload。否则 systemd 用的还是旧配置。
Warning: The unit file, source configuration file or drop-ins of sshd.service changed on disk.
Run 'systemctl daemon-reload' to reload units.
看到这个 warning 就 daemon-reload 一下。
写一个最小自定义服务
cat > /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp --config /etc/myapp.conf
Restart=on-failure
RestartSec=5s
User=myapp
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp
三个段:
[Unit]—— 元信息、依赖关系(After、Requires、Wants)[Service]—— 进程行为(ExecStart、Type、Restart、User)[Install]——enable时挂哪个 target(通常multi-user.target)
Type=simple 是最常用的(ExecStart 不 daemonize、systemd 直接 fork 跑)。其它常见:
Type=forking—— ExecStart 会自己 daemonize(老式 daemon)Type=oneshot—— 跑完就退(一次性脚本)Type=notify—— 进程会主动通知 systemd ready(高级用法)
target —— 系统的"运行级别"
systemd 的 target 类似 SysV 的 runlevel:
| target | 含义 | 旧 runlevel |
|---|---|---|
poweroff.target | 关机 | 0 |
rescue.target | 单用户 | 1 |
multi-user.target | 多用户文字模式(服务器默认) | 3 |
graphical.target | 图形界面 | 5 |
reboot.target | 重启 | 6 |
systemctl get-default # 看当前默认 target
systemctl set-default multi-user.target # 改默认(服务器不要图形)
systemctl isolate rescue.target # 立即切到 rescue(小心)
常见踩坑
坑 1:start 之后没 enable,重启后服务没了
systemctl start kubelet # 这次开机内跑着
reboot # 重启
systemctl status kubelet # inactive (dead)
start 只管这次开机。重启后要靠 enable 的 symlink 来拉起。永远用 enable --now。
坑 2:改了 unit 文件但不 daemon-reload
systemd 把 unit 文件解析后缓存在内存。改文件后必须 systemctl daemon-reload。不做的话:
vim /etc/systemd/system/myapp.service # 改了
systemctl restart myapp # 重启了
systemctl show myapp -p ExecStart # ExecStart 还是旧值!
坑 3:改了 /lib/systemd/system/... 被 apt 覆盖
vim /lib/systemd/system/ssh.service # ❌ 不要改这里
正确:systemctl edit ssh 用 drop-in。
坑 4:status 显示 failed,不知道为啥
systemctl status myapp
# Active: failed (Result: exit-code)
直接跟 journalctl(详见 journalctl.md):
journalctl -u myapp -n 100 --no-pager
journalctl -u myapp --since "10 min ago"
或者用 --full 看完整错误:
systemctl status myapp -l # -l 不截断长行
坑 5:服务自动 restart 把日志刷爆
[Service]
Restart=always # 任何退出都重启
RestartSec=1s # 1 秒间隔
挂了 → 1 秒后重启 → 又挂 → 又重启 ... 日志爆炸。
防御:
Restart=on-failure # 只有失败才重启(exit 0 不重启)
RestartSec=10s # 间隔大点
StartLimitInterval=60s # 60 秒内
StartLimitBurst=3 # 最多 3 次,再失败就不重启了
坑 6:reload 当 restart 用,对不支持 reload 的服务无效果
systemctl reload myapp # 静默"成功",但啥都没做
如果 unit 里没定义 ExecReload,systemctl reload 通常报错 / 没动作。改用 restart,或在 unit 里加 ExecReload=/bin/kill -HUP $MAINPID(如果你的应用响应 HUP)。
坑 7:用 kill 干进程,被 systemd 自动拉起
kill 1234 # 杀了 myapp
ps aux | grep myapp # 还在跑(PID 变了)
Restart= 让 systemd 监控进程退出并重启。要真停:systemctl stop myapp。
关联命令
- journalctl —— 看日志(status 给你尾巴,journalctl 给你全部)
systemd-cgls/systemd-cgtop—— cgroup 视角看服务资源占用loginctl—— 登录会话管理- hostnamectl —— hostname 设置
timedatectl—— 时区 / NTP 设置