4. 编写第一个Systemd服务:Service Unit文件结构、启动/停止/重启/状态查询
说实话,很多运维兄弟干了三五年,还在用 /etc/init.d/ 那套老脚本。不是不能用,但时代变了。Systemd 现在几乎是所有主流 Linux 发行版的标配,你躲不开它。
我个人习惯,每接手一台新服务器,第一件事就是看看它的 init 系统是什么。如果是 Systemd,那好办,后面所有服务的启停管理都按它的规矩来。今天我们就手把手写第一个 Service Unit 文件,把启动、停止、重启、查状态这些基本功练扎实。
核心要点:Systemd 的 Service Unit 文件,本质上就是一个声明式的配置文件。你告诉它“我的程序是谁、怎么启动、怎么停止”,剩下的交给 systemd 去调度。
4.1 Service Unit 文件长什么样?
一个标准的 Service Unit 文件,通常放在 /etc/systemd/system/ 目录下,后缀是 .service。它的结构分三个段:[Unit]、[Service]、[Install]。
我直接给你看一个我项目中用过的例子。这是一个简单的 Python Web 服务的 Unit 文件:
[Unit]
Description=My Python Web Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
嗯,这里要注意几个关键点:
[Unit]:描述这个服务的基本信息。Description就是给人看的说明。After表示启动顺序——等网络就绪了再启动我的服务。[Service]:核心配置段。定义怎么启动、怎么停止、以什么用户跑、工作目录在哪。[Install]:定义这个服务在哪个 target 下被启用。multi-user.target是最常用的,相当于传统的运行级别 3。
我的经验: Type=simple 是最常用的,但如果你遇到那种启动后马上 fork 出子进程的程序(比如 Nginx),就要用 Type=forking。我曾经因为 Type 选错,导致 systemd 认为服务已经退出,反复重启,差点把线上搞崩。
4.2 启动、停止、重启、查状态
文件写好了,怎么用?Systemd 的命令行工具是 systemctl。说白了,你以后跟服务打交道,就靠它了。
| 操作 | 命令 | 说明 |
|---|---|---|
| 启动 | systemctl start myapp.service |
立即启动服务 |
| 停止 | systemctl stop myapp.service |
立即停止服务 |
| 重启 | systemctl restart myapp.service |
先停止再启动 |
| 重载 | systemctl reload myapp.service |
不中断服务,重新加载配置(需要服务支持) |
| 状态 | systemctl status myapp.service |
查看服务运行状态、日志、PID 等 |
| 启用开机自启 | systemctl enable myapp.service |
创建符号链接,让服务随系统启动 |
| 禁用开机自启 | systemctl disable myapp.service |
移除符号链接 |
你想想看,是不是比传统脚本清晰多了?每个命令只做一件事,而且输出信息非常详细。
避坑指南: 每次修改了 .service 文件后,一定要执行 systemctl daemon-reload 重新加载配置。我曾经忘了这步,改了半天发现服务还是老样子,白白浪费了半小时。
4.3 实战:写一个定时清理日志的服务
光说不练假把式。我们写一个实际能用的服务——定时清理 7 天前的日志文件。
先写一个简单的脚本 /opt/cleanup/clean_logs.sh:
#!/bin/bash
find /var/log/myapp -type f -mtime +7 -delete
echo "Cleaned logs older than 7 days"
然后创建 Service Unit 文件 /etc/systemd/system/clean-logs.service:
[Unit]
Description=Clean up old application logs
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/cleanup/clean_logs.sh
User=root
[Install]
WantedBy=multi-user.target
这里我用了 Type=oneshot。为什么?因为这个脚本执行完就退出了,不是常驻进程。 oneshot 就是为这种一次性任务准备的。
再配合一个 Timer 文件 /etc/systemd/system/clean-logs.timer:
[Unit]
Description=Run clean-logs daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
最后启用 Timer:
systemctl daemon-reload
systemctl enable clean-logs.timer
systemctl start clean-logs.timer
你看,一个定时任务就搞定了。比 crontab 更规范,而且日志、状态管理都统一走 systemd 的体系。
4.4 知识体系结构图
下面这张图帮你理清 Service Unit 的核心逻辑:
4.5 常见问题与排查
写 Service Unit 文件,新手最容易踩的坑我列一下:
- 路径写错:ExecStart 里的路径必须是绝对路径。我曾经见过有人写
python app.py,结果 systemd 找不到 python 解释器。 - 权限问题:如果指定了
User=,要确保这个用户对工作目录和日志目录有读写权限。 - Type 选错:前面说过了,常驻进程用
simple,一次性任务用oneshot,fork 型用forking。 - 忘记 daemon-reload:改完文件不重载,systemd 根本不认你的修改。
排查技巧: 服务起不来?先 systemctl status 服务名 看错误信息。如果信息不够,用 journalctl -u 服务名 -n 50 --no-pager 看最近 50 条日志。这个命令我一天能用几十次。
好了,第一个 Systemd 服务就写完了。从 Unit 文件的结构,到启停查的命令,再到一个完整的实战例子,这套流程你走一遍,后面再写其他服务就轻车熟路了。