大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
systemd 服务
这一课你将能做到
- 用 systemctl 完成服务的启动、停止、重启、状态查看与开机自启管理
- 用 journalctl 查询服务日志,按时间、优先级、关键字过滤,快速定位服务异常原因
- 为本站 Spring Boot 后端编写一个生产可用的 systemd unit 文件,实现开机自启、崩溃自动重启
为什么要学
现代 Linux(Ubuntu 16.04+)的服务管理全部由 systemd 接管。本站服务器上的 Nginx、MySQL、Redis 都是 systemd 服务——systemctl status nginx 看到的绿色 active (running) 就是它在管。你的 Spring Boot 应用部署到服务器后,也不能再靠 nohup java -jar & 这种土办法,而应该注册为 systemd 服务:开机自动启动、崩溃自动拉起、日志统一收集、资源限制可配。
面试中「如何排查服务启动失败」「unit 文件的 [Service] 段有哪些关键配置」「journalctl 怎么看昨天的日志」是运维方向必考题。
先建立图像
systemd 的世界:
┌─────────────────────────────────────────────────┐
│ systemd (PID 1) │ ← 系统第一个进程,所有服务的「总管家」
├─────────────────────────────────────────────────┤
│ unit 类型: │
│ .service 服务(nginx、mysql、你的 Java 应用) │
│ .timer 定时任务(替代 cron) │
│ .mount 挂载点 │
│ .socket socket 激活 │
└─────────────────────────────────────────────────┘
unit 文件的位置:
/etc/systemd/system/ ← 你自己写的服务放这里(优先级最高)
/lib/systemd/system/ ← apt 装的软件自带的服务放这里
服务生命周期命令:
start → stop → restart → reload → enable(开机自启)→ disable
跟着做
步骤 1:服务状态管理六连招
# 查看状态:最常用,排障第一步
systemctl status nginx
# 输出解读:
# ● nginx.service - A high performance web server
# Loaded: loaded (...; enabled; ...) ← enabled 表示开机自启
# Active: active (running) since ... ← 绿色 running 表示正常
# Main PID: 1234 (nginx)
# Memory: 8.5M
# 启动 / 停止 / 重启
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx # 先停再启,进程会换 PID
# 重载配置:不中断连接地加载新配置(改完 nginx.conf 用它)
sudo systemctl reload nginx
# 开机自启 / 取消自启
sudo systemctl enable nginx
sudo systemctl disable nginx
# 一步到位:启动 + 设置自启
sudo systemctl enable --now nginx
步骤 2:排查服务启动失败
# 服务红了(failed)之后的三板斧:
# 1. 看状态摘要(里面有最关键的一行错误)
systemctl status mysql
# 2. 看完整日志(最近的 50 行)
sudo journalctl -u mysql -n 50 --no-pager
# 3. 看本次启动以来的日志
sudo journalctl -u mysql -b
步骤 3:journalctl 日志查询进阶
# 实时跟踪日志(相当于 tail -f)
sudo journalctl -u nginx -f
# 按时间过滤
sudo journalctl -u mysql --since "2026-10-07 09:00" --until "2026-10-07 10:00"
sudo journalctl -u mysql --since "1 hour ago"
sudo journalctl -u mysql --since yesterday
# 按优先级过滤(err 及以上:err/crit/alert/emerg)
sudo journalctl -u redis -p err -b
# 反向输出:最新的日志在最上面(排查刚发生的故障好用)
sudo journalctl -u mysql -r
# 看内核日志(OOM Killer 杀进程的记录在这里)
sudo journalctl -k | grep -i "killed process"
# 全系统所有服务日志
sudo journalctl -b --no-pager | tail -100
步骤 4:为 Spring Boot 应用编写 unit 文件(本站实战)
创建服务文件:
sudo nano /etc/systemd/system/codeforge.service
写入以下内容:
[Unit]
Description=CodeForge Spring Boot Backend
After=network.target mysql.service redis-server.service
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/codeforge
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /opt/codeforge/codeforge.jar --spring.profiles.active=prod
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
StandardOutput=append:/var/log/codeforge/app.log
StandardError=append:/var/log/codeforge/error.log
[Install]
WantedBy=multi-user.target
逐段解释:
- [Unit]:
Description服务描述;After声明启动顺序——等网络、MySQL、Redis 就绪后再启动本服务(本站后端依赖它们)。 - [Service]:
User/Group以 app 用户运行(最小权限,别用 root);ExecStart启动命令,JVM 参数直接写在这里;Restart=on-failure崩溃后自动拉起,RestartSec=10等 10 秒再拉;StandardOutput=append:...把日志追加到文件(systemd 240+ 支持)。 - [Install]:
WantedBy=multi-user.target表示在多用户模式下开机自启(相当于「注册到开机流程」)。
步骤 5:启用并验证服务
# 准备运行环境
sudo useradd -r -s /bin/false app
sudo mkdir -p /opt/codeforge /var/log/codeforge
sudo chown -R app:app /opt/codeforge /var/log/codeforge
# (此处假设已上传 codeforge.jar 到 /opt/codeforge/)
# 改完 unit 文件必须 reload,让 systemd 重新加载配置
sudo systemctl daemon-reload
# 启动并设置自启
sudo systemctl enable --now codeforge
# 验证
systemctl status codeforge
curl http://localhost:8080/api/health # 验证应用真的起来了
sudo journalctl -u codeforge -f # 实时看启动日志
步骤 6:日常运维操作
# 部署新版本的标准流程
sudo systemctl stop codeforge
cp codeforge-v2.jar /opt/codeforge/codeforge.jar
sudo systemctl start codeforge
sudo journalctl -u codeforge -n 30 # 确认启动无报错
# 查看所有失败的服务(系统健康巡检)
systemctl --failed
# 查看服务耗时(系统启动慢排查)
systemd-analyze blame | head
# 看服务的完整配置(包括默认值)
systemctl cat nginx
步骤 7:资源限制(防止应用吃光内存)
在 [Service] 段追加:
MemoryMax=1G # 内存硬上限,超过会被 OOM Kill
CPUQuota=150% # 最多用 1.5 个 CPU 核
LimitNOFILE=65535 # 文件句柄上限(Java 应用常需要调大)
改完记得 sudo systemctl daemon-reload && sudo systemctl restart codeforge。
讲清楚要点
- systemd 是 PID 1:系统启动的第一个用户态进程,所有服务都是它的子孙。
pstree能看到这棵进程树。 - unit 文件改完必须
daemon-reload:systemd 缓存了配置,不 reload 改动不生效。这是初学者最常忘的一步。 restartvsreload:restart 是真重启(进程换 PID,有秒级中断);reload 是让进程重读配置(不断连接)。Nginx 改配置用 reload,Java 应用改 jar 必须 restart。enable不改当前状态:它只创建开机自启的符号链接(/etc/systemd/system/multi-user.target.wants/下)。想立即启动用enable --now。- 日志双通道:systemd 服务的 stdout/stderr 默认进 journal(journalctl 查),也可以用
StandardOutput=append:写文件。两者可共存。 After=只是启动顺序,不是依赖:它保证本服务在 MySQL 之后启动,但 MySQL 挂了不会自动拉它。真正的依赖用Requires=或Wants=。- 崩溃自动重启三件套:
Restart=on-failure+RestartSec=10+ 可选StartLimitBurst。注意「反复崩溃」场景 systemd 会放弃并标记 failed,需要systemctl reset-failed后才能再 start。
你可能会懵的地方
-
systemctl status显示 active (exited) 是什么意思? 服务是「一次性任务」(Type=oneshot),执行完就退出,这是正常状态。常驻服务应该是 active (running)。 -
为什么我的服务 start 成功但立刻 failed? 常见原因:
ExecStart路径错了;User 指定的用户不存在;程序本身报错(端口被占用、配置文件错误)。journalctl -u 服务名 -n 50看具体报错。 -
enable 之后重启服务器,服务没自启? 检查
[Install]段的WantedBy是否写了 multi-user.target;systemctl is-enabled 服务名确认状态;云服务器检查安全组/启动脚本是否干扰。 -
journalctl 的日志会丢吗?重启后还在吗? Ubuntu 默认持久化(
/var/log/journal/),重启后-b -1能看上一次启动的日志。但有些精简系统配置为内存存储,重启即丢。 -
为什么弃用
service nginx start老命令?service是 SysV init 时代的兼容命令,Ubuntu 里它实际转发给 systemctl。新脚本统一用 systemctl。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 改完 unit 文件直接 restart | systemd 还在用旧配置 | 先 systemctl daemon-reload |
| 用 root 跑 Java 应用 | 安全红线,被入侵即全盘沦陷 | 建专用用户 User=app |
生产用 nohup java -jar & 跑应用 | 服务器重启就没了,崩溃没人管 | 注册 systemd 服务 |
ExecStart=java -jar app.jar 相对路径 | systemd 工作目录不确定,找不到 jar | 全部用绝对路径 |
| journalctl 翻不动大日志 | 默认进 less 分页器 | 加 --no-pager 直接输出 |
| 服务反复重启刷爆日志 | 没有重启间隔限制 | RestartSec=10 + StartLimitBurst=5 |
卡点急救
-
现象:
Failed to start xxx.service: Unit not foundunit 文件名打错,或文件没放在正确目录,或没 daemon-reload。解决:systemctl list-unit-files | grep xxx确认注册状态。 -
现象:服务启动超时(
start operation timed out) Java 应用启动慢,默认 90 秒不够。解决:[Service]段加TimeoutStartSec=300。 -
现象:Java 应用日志在 journalctl 里却没有写进文件
StandardOutput=append:路径写法要求 systemd ≥ 240(Ubuntu 20.04+ 满足);且日志目录必须存在且服务用户有写权限。解决:systemd --version确认版本,chown app:app /var/log/codeforge。
面试常问
-
如何排查一个 systemd 服务启动失败? 三板斧:
systemctl status xxx看错误摘要 →journalctl -u xxx -n 50 --no-pager看详细日志 → 检查 unit 文件(systemctl cat xxx)的路径、用户、依赖配置。如果是端口占用,ss -tlnp | grep 端口找占用者。 -
unit 文件的 [Service] 段关键配置有哪些? ExecStart(启动命令)、User/Group(运行身份)、Restart(重启策略)、WorkingDirectory(工作目录)、Environment(环境变量)、TimeoutStartSec(启动超时)、StandardOutput(日志去向)。能说出 Restart 的几种取值(no/on-failure/always)更加分。
-
journalctl 如何查看昨天 MySQL 的错误日志?
journalctl -u mysql --since yesterday --until today -p err --no-pager。要点:-u 指定服务,--since/--until 框时间,-p 过滤优先级。
自测三问
- 修改了
/etc/systemd/system/codeforge.service后,正确的生效流程是什么? systemctl restart和systemctl reload的区别?改完 nginx.conf 应该用哪个?- 服务器重启后,如何用 journalctl 查看上一次启动期间 Redis 的日志?
小结一句话
systemd 是 Linux 服务的总管家:systemctl 管生命周期(启停+自启),journalctl 管日志,unit 文件三段落(Unit/Service/Install)定义服务的一切。
延伸
- systemd 官方文档
- Ubuntu systemd 服务管理指南
- 下一课:进程管理——ps、top、kill 与负载解读,看懂服务器在忙什么。