Linux 服务管理实战:systemd、日志与时间同步的完整闭环
上一期我们讲了进程管理,能看清每个进程、能给它发信号。但手动
kill+nohup管不了一台服务器上的几十个服务——谁负责开机自启?谁负责挂了自动拉起?谁来统一收集日志?答案都是 systemd。本文以 Ubuntu 24.04 / Rocky 9 为演示环境,覆盖四块内容:systemd 到底管了什么、systemctl 全家桶、手写 service unit 并按四层验证、三大日志入口(journalctl / rsyslog / dmesg)逐层缩小排障窗口,最后是时间同步与 Chrony。
文章目录
- Linux 服务管理实战:systemd、日志与时间同步的完整闭环
- 一、systemd 到底管理了什么
- 二、systemctl:服务管理的瑞士军刀
- 2.1 生命周期五连
- 2.2 开机自启三连
- 2.3 status 输出逐行读
- 2.4 排障常用的几个
- 三、daemon-reload:最容易漏的一步
- 四、手写一个 service unit
- 4.1 完整模板(逐字段注释)
- 4.2 Type 怎么选
- 4.3 Restart 策略
- 4.4 四层验证:Unit → 进程 → 端口 → 应用
- 五、故障证据去哪找:三个日志入口
- 六、tail:文件日志的入门神器
- 七、journalctl:把排障窗口逐层缩小
- 7.1 基础用法
- 7.2 verbose 模式:看到日志的全部字段
- 7.3 核心思路:五层条件逐层缩小窗口
- 八、dmesg:内核在说什么
- 九、系统时间、时区与 Chrony
- 9.1 先看清现状
- 9.2 时区设置
- 9.3 为什么必须做时间同步
- 9.4 Chrony:现代时间同步方案
- 十、总结
一、systemd 到底管理了什么
systemd 是现代 Linux 的1 号进程(PID 1)——内核启动后运行的第一个用户态程序,其他所有进程都直接或间接由它拉起。它管的东西远不止"服务":
ps-p1-opid,comm# PID COMMAND# 1 systemd它管理的基本单位叫 Unit(单元),常见类型:
| 类型 | 后缀 | 管什么 | 例子 |
|---|---|---|---|
| service | .service | 服务进程(最常用) | nginx.service |
| target | .target | 一组 unit 的集合(类似"运行级别") | multi-user.target |
| socket | .socket | 按需启动(收到连接才拉起服务) | sshd.socket |
| timer | .timer | 定时任务(cron 的替代品) | logrotate.timer |
| mount | .mount | 文件系统挂载点 | -.mount(根分区) |
两个容易忽略但很值钱的知识点:
- 开机自启:以前的
/etc/rc.local、chkconfig 全被 systemd 收编,自启就是"把服务挂到某个 target 下"。 - 资源控制:systemd 依托 cgroups 给每个服务记账(CPU、内存、进程数),
systemd-cgtop可以像 top 一样按服务看资源——这是按进程看资源的 ps/top 之外的另一个维度。
二、systemctl:服务管理的瑞士军刀
2.1 生命周期五连
sudosystemctl start nginx# 启动sudosystemctl stop nginx# 停止sudosystemctl restart nginx# 重启(先停再起,有瞬断)sudosystemctl reload nginx# 重读配置(不断服务,需服务支持)sudosystemctl status nginx# 看状态(最常用)restart 和 reload 的区别必须分清:restart 是"杀掉重来",服务会短暂不可用;reload 是"让进程重读配置文件",进程不换、连接不断——线上改配置优先 reload。
2.2 开机自启三连
sudosystemctlenablenginx# 设开机自启sudosystemctl disable nginx# 取消开机自启systemctl is-enabled nginx# 查询是否自启高频易错点:enable只管开机自启,不会立刻启动服务;start只管现在启动,不管开机。两个都要?一条命令搞定:
sudosystemctlenable--nownginx# 自启 + 立刻启动sudosystemctl disable--nownginx# 取消自启 + 立刻停止2.3 status 输出逐行读
systemctl status nginx● nginx.service - A high performance web server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) since Sat 2026-10-10 09:00:01 CST; 2h ago Main PID: 1000 (nginx) Tasks: 3 (limit: 4561) Memory: 5.2M CPU: 120ms CGroup: /system.slice/nginx.service ├─1000 nginx: master process └─1001 nginx: worker process- Loaded:unit 文件路径;
enabled说明设了自启 - Active:
active (running)正常运行;failed出事了;inactive (dead)没在跑 - Main PID:主进程号
- CGroup:该服务名下的所有进程——僵尸进程的爹、被服务拉起的子进程,全在这一览无余
2.4 排障常用的几个
# 只看挂了的服务(宕机排查第一步)systemctl--failed# 服务为什么起不来?看它自己的报错systemctl status nginx# 看服务的标准错误输出journalctl-unginx-e# -e 直接跳到末尾(下一章详讲)# 禁止一个服务被任何东西(包括依赖)拉起sudosystemctl mask nginxsudosystemctl unmask nginxmask是"钉死":disable 之后其他服务仍可能把它拉起来,mask 直接把 unit 文件软链到/dev/null,谁也启动不了它——处理流氓服务的终极手段。
三、daemon-reload:最容易漏的一步
sudosystemctl daemon-reload它做什么:让 systemd 重新读取磁盘上的 unit 配置文件。
为什么需要:你直接vim修改了/etc/systemd/system/xxx.service,systemd并不知道——它内存里还是旧配置。此时执行restart会出现经典警告:
Warning: The unit file, source configuration file or drop-ins of xxx.service changed on disk. Run 'systemctl daemon-reload' to reload units.标准动作顺序(改配置的肌肉记忆):
sudovim/etc/systemd/system/myapp.service# 1. 改 unit 文件sudosystemctl daemon-reload# 2. 让 systemd 重新读sudosystemctl restart myapp# 3. 按新配置重启systemctl status myapp# 4. 验证反过来,daemon-reload只重读配置,不重启服务——它管"菜单",不管"菜"。漏掉第 2 步是新手部署自己写的服务时"改了配置不生效"的第一大原因。
四、手写一个 service unit
4.1 完整模板(逐字段注释)
假设我们有个自己写的 SpringBoot 应用/opt/myapp/app.jar,要交给 systemd 托管:
sudovim/etc/systemd/system/myapp.service[Unit] Description=My SpringBoot App # 描述,status 里显示的标题 After=network-online.target # 排序:网络就绪之后再启动我 Wants=network-online.target # 弱依赖:尽量把网络拉起来(拉不起来也不拦着我) [Service] Type=simple # 主进程就是前台进程(java 不 fork 时用 simple) ExecStart=/usr/bin/java -jar /opt/myapp/app.jar # 怎么启动(必须绝对路径) ExecReload=/bin/kill -HUP $MAINPID # reload 时执行什么 Restart=on-failure # 崩溃自动拉起(核心!) RestartSec=5 # 拉起前等 5 秒,防止疯狂重启 User=appuser # 用哪个用户跑(别用 root) WorkingDirectory=/opt/myapp # 工作目录 Environment=JAVA_OPTS=-Xmx512m # 环境变量 [Install] WantedBy=multi-user.target # 挂到"多用户模式"下 → 开机自启三个区块的职责一句话:[Unit] 讲关系(我是谁、排在谁后面)、[Service] 讲怎么跑(启动命令、重启策略)、[Install] 讲挂哪(挂到哪个 target 实现自启)。
4.2 Type 怎么选
| Type | 含义 | 适用 |
|---|---|---|
simple | ExecStart 的进程就是主进程,前台跑 | java -jar、python app.py |
forking | 进程会 fork 出子进程然后父进程退出 | nginx(传统 daemon 行为) |
notify | 服务自己通知 systemd"我就绪了" | 支持 sd_notify 的现代服务 |
4.3 Restart 策略
no(默认):挂了就挂了on-failure:非正常退出就自动拉起(推荐)always:不管怎么退出都拉起(连正常 stop 后也会被拉回,慎用)
这就是上一期预告的"崩溃自愈":半夜 3 点服务崩了,systemd 在你睡觉时已经把它拉起来了,你早上只在日志里看到一次重启记录。
4.4 四层验证:Unit → 进程 → 端口 → 应用
服务部署完,别只看 status 绿了就走人,按四层逐级验证:
第 1 层:Unit 层(systemd 眼中)
systemctl status myapp# Active: active (running)?systemctl is-enabled myapp# enabled(开机自启)?第 2 层:进程层(内核眼中)
pgrep-a-fapp.jar# 进程在跑?PID 和 Main PID 一致?第 3 层:端口层(网络眼中)
ss-tlnp|grep8080# 端口在监听?进程名对得上?第 4 层:应用层(用户眼中)
curl-Ihttp://localhost:8080/actuator/health# HTTP/1.1 200 —— 这才是"服务真的能用了"四层各回答一个问题:systemd 认为它活着吗?进程真的存在吗?网络真的通了吗?用户真的能访问吗?——任何一层断了,故障范围立刻锁定。
五、故障证据去哪找:三个日志入口
服务挂了别瞎猜,Linux 的日志分三个入口,按故障类型选对入口,排障效率翻倍:
| 入口 | 看什么 | 工具 |
|---|---|---|
| journalctl | systemd 管的所有服务日志(含开机过程) | journalctl -u xxx |
| /var/log/ | rsyslog 落盘的传统日志文件 | tail -f、grep |
| dmesg | 内核与硬件(磁盘报错、OOM 杀进程) | dmesg -T |
选路口诀:
- 服务起不来 / 应用报错→
journalctl -u 服务名 - 登录失败 / 认证问题→
/var/log/auth.log(Rocky 为/var/log/secure) - 机器卡死 / 磁盘报错 / 进程被杀→
dmesg,重点搜OOM、I/O error
六、tail:文件日志的入门神器
# 看文件末尾 100 行tail-n100/var/log/syslog# 持续盯着日志滚动(排障时最常用)tail-f/var/log/nginx/error.log# 文件被轮转切割后依然盯着新文件(比 -f 更稳)tail-F/var/log/nginx/access.log# 同时盯多个文件(用 ==> 分隔)tail-fapp.log error.log# 配合 grep 只看关心的行tail-f/var/log/nginx/access.log|grep" 500 "-f和-F的区别:日志按天/大小切割时(access.log→access.log.1+ 新的access.log),-f还死盯着旧文件的句柄,新日志一条也看不到;-F会自动跟到新文件。盯被 logrotate 管理的日志,用-F。
七、journalctl:把排障窗口逐层缩小
journalctl 读的是 systemd 的日志(journal),它最大的价值是结构化——每条日志都带时间、Unit、PID、优先级等字段,可以像查数据库一样过滤。
7.1 基础用法
journalctl# 全部日志(一般不这么干,量太大)journalctl-unginx# 只看 nginx 这个服务journalctl-unginx-f# 持续盯着滚动(类似 tail -f)journalctl-unginx-n50# 只看最近 50 行journalctl-unginx-e# 直接跳到末尾,看最新报错7.2 verbose 模式:看到日志的全部字段
journalctl-unginx-overbose输出会展开每条日志的所有元数据字段:
Sat 2026-10-10 09:00:01 CST ... _PID=1000 _UID=0 _COMM=nginx _SYSTEMD_UNIT=nginx.service PRIORITY=6 ...这些下划线开头的字段(_PID、_UID、_SYSTEMD_UNIT)都可以作为后续的过滤条件——这就是下一节"字段过滤"的素材来源。
7.3 核心思路:五层条件逐层缩小窗口
故障排查的本质是缩小搜索范围。journalctl 按这五层从粗到细切:
① boot(哪次开机)→ ② 时间(哪段时间)→ ③ Unit(哪个服务) → ④ 级别(多严重)→ ⑤ 字段(哪个进程/用户)第 ① 层:按开机次序
journalctl-b# 本次开机以来的日志journalctl-b-1# 上一次开机的日志(重启后查"重启前发生了什么")journalctl --list-boots# 列出所有开机记录,带编号和时间机器莫名重启了?-b -1看上一次生命周期的最后几十行,死因经常就在里面。
第 ② 层:按时间
journalctl--sincetoday# 今天以来journalctl--since"09:00"--until"09:30"# 今早 9 点到 9 点半journalctl--since"2026-10-10 09:00:00"# 精确时间点journalctl--since"-1h"# 最近 1 小时第 ③ 层:按 Unit
journalctl-unginx# 单个服务journalctl-unginx-ussh# 多个服务一起看第 ④ 层:按级别
日志共 8 个级别,排查故障从err往上找:
| 级别 | 含义 |
|---|---|
| 0 emerg | 系统不可用 |
| 1 alert | 必须立即处理 |
| 2 crit | 严重 |
| 3 err | 错误(排障起点) |
| 4 warning | 警告 |
| 5 notice | 正常但重要 |
| 6 info | 常规信息 |
| 7 debug | 调试 |
journalctl-perr# 只看 err 及以上journalctl-pwarning-unginx# nginx 的警告及以上第 ⑤ 层:按字段(精确定位)
journalctl_PID=1000# 只看这个 PID 发出的journalctl_UID=1001# 只看这个用户的journalctl-g"timeout"# 全文搜索关键字(像 grep)组合拳示例:今早 9 点 nginx 502,一条命令锁定现场:
journalctl-b--since"09:00"--until"09:15"-unginx-perr这次开机 + 9:00-9:15 + nginx 服务 + err 级别——四层过滤叠加,几万行日志瞬间只剩肇事的那几条。
默认很多发行版 journal 存内存、重启即失;想持久化保存,建目录后重启:
sudomkdir-p/var/log/journalsudosystemctl restart systemd-journald八、dmesg:内核在说什么
dmesg读的是内核环形缓冲区——开机自检、驱动加载、硬件报错、内核杀进程,全在这里。它和 journalctl 的分工:dmesg 只管内核层,journalctl 管应用层。
dmesg# 全量(时间戳是秒数,不友好)dmesg-T# 转成人类可读的时间(必加)dmesg-lerr,warn# 只看错误和警告dmesg-w# 持续盯着(像 tail -f)三大经典场景:
① 进程被 OOM Killer 干掉了——服务莫名其妙消失,日志里还没有应用报错?查这个:
dmesg-T|grep-i"killed process"# Out of memory: Killed process 1234 (java) total-vm:...内存不足时内核会挑"最肥"的进程杀掉保命——Java 应用是头号受害者。
② 磁盘要坏了:
dmesg-T|grep-iE"i/o error|bad sector"③ 网卡/硬盘掉线重连:
dmesg-T|tail-50# 看最近的内核事件内存口诀:进程离奇失踪查 dmesg 的 OOM,服务报错查 journalctl,文件日志查 /var/log。
九、系统时间、时区与 Chrony
9.1 先看清现状
date# 当前时间date+"%F %T"# 2026-10-10 09:00:01timedatectl# 时间、时区、NTP 同步状态一览timedatectl输出重点看两行:
Time zone: Asia/Shanghai (CST, +0800) System clock synchronized: yes ← 时间已和 NTP 源同步 NTP service: active ← 时间同步服务在跑9.2 时区设置
timedatectl list-timezones|grepShanghaisudotimedatectl set-timezone Asia/Shanghai服务器建议一律用Asia/Shanghai;跨国的多机器环境甚至可以统一用 UTC 避免换算——关键不是用哪个,是全部机器保持一致,不然比对多机日志时差 8 小时,能把人逼疯。
9.3 为什么必须做时间同步
- 日志取证:两台机器时间差 3 分钟,攻击链的先后顺序就对不上
- 证书校验:HTTPS/TLS 证书有有效期,时间飘了直接握手失败
- 分布式系统:数据库主从、Hadoop 集群对时间偏差极敏感,超限直接拒绝工作
时间不同步不是"表不准"的小事,是生产事故的隐形来源。
9.4 Chrony:现代时间同步方案
主流发行版已用 chrony 取代老的 ntpd:同步更快、断网恢复后修正更猛,还能间歇联网工作。
sudoaptinstallchrony# Ubuntu(Rocky 一般自带)systemctl status chronyd# Rocky 服务名叫 chronyd配置公网时间源——编辑配置文件(Ubuntu 在/etc/chrony/chrony.conf,Rocky 在/etc/chrony.conf):
# 用 pool 指定时间服务器(可写多行做冗余) pool ntp.aliyun.com iburst pool time1.cloud.tencent.com iburst server ntp.tuna.tsinghua.edu.cn iburst # 允许本机时间慢慢校准(不猛跳) makestep 1.0 3国内常用公网时间源:
| 提供方 | 地址 |
|---|---|
| 阿里云 | ntp.aliyun.com |
| 腾讯云 | time1.cloud.tencent.com |
| 清华 | ntp.tuna.tsinghua.edu.cn |
| NTP Pool 中国区 | cn.pool.ntp.org |
改完重启并验证:
sudosystemctl restart chrony# 看时间源状态(^* 开头的就是当前正在用的源)chronyc sources-v# ^* 203.107.6.88 2 10 377 133 -1234us[ -567us] +/- 12ms# 看同步详情chronyc tracking# 时间差太多?立刻硬校准(跳变对齐,不用慢慢磨)sudochronyc makestepchronyc sources -v输出解读:行首^*表示已同步并选定使用的源,^+是备选,^?是还没连上的——全是^?说明同步有问题,查网络或防火墙(NTP 用 UDP 123 端口)。
最后回到timedatectl确认闭环:
timedatectl# System clock synchronized: yes ← 到这里,时间这条线就稳了十、总结
这一期把"服务怎么被管起来"讲完了:
- systemd 是 PID 1,管理的基本单位是 unit(service/target/timer/socket…),开机自启和崩溃自愈都归它管。
- systemctl 五连(start/stop/restart/reload/status)+ 自启三连(enable/disable/is-enabled),改配置用 reload 不用 restart;
enable --now一条顶两条。 - 改了 unit 文件必须
daemon-reload,这是"配置不生效"的头号原因。 - 手写 service unit:[Unit] 讲关系、[Service] 讲怎么跑、[Install] 讲挂哪;
Restart=on-failure实现崩溃自愈;部署完按Unit → 进程 → 端口 → 应用四层验证。 - 日志三入口:服务问题 journalctl、认证看 /var/log、内核硬件看 dmesg——进程离奇失踪先查 OOM。
- journalctl 五层缩小窗口:boot → 时间 → Unit → 级别 → 字段,多层条件叠加就是"数据库查询"。
- tail -F 优于 tail -f:日志轮转后不会跟丢。
- 时间同步用 Chrony:配国内源、
chronyc sources -v看^*、makestep 硬校准;多机日志比对的前提是时间一致。
练习环境:Ubuntu 24.04 LTS / Rocky Linux 9 虚拟机