4. 编写第一个Systemd服务

说实话,很多运维兄弟干了三五年,还在用 /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 的核心逻辑:

Systemd Service Unit 核心结构 [Unit] • Description:服务描述 • After:启动顺序 • Requires:依赖关系 • Wants:弱依赖 • Documentation:文档 [Service] • Type:服务类型 • ExecStart:启动命令 • ExecStop:停止命令 • User/Group:运行用户 • WorkingDirectory:工作目录 • Restart:重启策略 • RestartSec:重启间隔 • Environment:环境变量 [Install] • WantedBy:被谁启用 • RequiredBy:强依赖 • Alias:别名 三段式结构:声明元信息 → 定义行为 → 注册到系统

4.5 常见问题与排查

写 Service Unit 文件,新手最容易踩的坑我列一下:

  1. 路径写错:ExecStart 里的路径必须是绝对路径。我曾经见过有人写 python app.py,结果 systemd 找不到 python 解释器。
  2. 权限问题:如果指定了 User=,要确保这个用户对工作目录和日志目录有读写权限。
  3. Type 选错:前面说过了,常驻进程用 simple,一次性任务用 oneshot,fork 型用 forking。
  4. 忘记 daemon-reload:改完文件不重载,systemd 根本不认你的修改。

排查技巧: 服务起不来?先 systemctl status 服务名 看错误信息。如果信息不够,用 journalctl -u 服务名 -n 50 --no-pager 看最近 50 条日志。这个命令我一天能用几十次。

好了,第一个 Systemd 服务就写完了。从 Unit 文件的结构,到启停查的命令,再到一个完整的实战例子,这套流程你走一遍,后面再写其他服务就轻车熟路了。