服务管理:systemd unit 文件编写 | Service Management: systemd Unit Files

章节概述

核心理念:systemd 是现代 Linux 的初始化系统和服务管理器,掌握 unit 文件编写是系统管理员的必备技能。理解 [Unit]、[Service]、[Install] 三个部分,就像理解 C 语言的程序结构一样重要。


第1节:unit 文件结构

1.1 基本模板

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
Documentation=https://myapp.readthedocs.io
After=network.target
Wants=network-online.target
 
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
PrivateTmp=true
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp /var/log/myapp
 
[Install]
WantedBy=multi-user.target

1.2 三个部分详解

部分用途
[Unit]服务描述、依赖关系、启动顺序
[Service]服务运行方式、权限、资源控制
[Install]安装时的配置(启用/禁用行为)

第2节:服务类型

2.1 Type 类型

[Service]
# simple - 主进程就是服务进程(默认)
Type=simple
ExecStart=/usr/bin/myapp
 
# forking - 服务会 fork 后退出(传统守护进程)
Type=forking
ExecStart=/usr/sbin/nginx
PIDFile=/run/nginx.pid
 
# oneshot - 执行一次就退出(初始化脚本)
Type=oneshot
ExecStart=/usr/local/bin/setup.sh
RemainAfterExit=yes
 
# notify - 类似 simple,但支持通知 systemd 就绪
Type=notify
ExecStart=/usr/bin/myapp
 
# dbus - 通过 D-Bus 获取名字后就绪
Type=dbus
BusName=com.example.MyApp

2.2 Type 选择指南

场景推荐 Type
现代应用程序simplenotify
传统守护进程forking
初始化脚本oneshot
D-Bus 服务dbus
容器服务simple

第3节:依赖关系

3.1 After/Before

[Unit]
# After: 在这些目标启动之后启动
After=network.target mysql.target redis.target
 
# Before: 在这些目标之前启动
Before=nginx.target

3.2 Wants/Requires/Requisite

[Unit]
# Wants: 弱依赖(失败不影响启动)
Wants=redis.service
Wants=postgresql.service
 
# Requires: 强依赖(失败则停止)
Requires=mysql.service
 
# Requisite: 强依赖(必须已启动,否则立即失败)
Requisite=network-online.target
 
# BindsTo: 绑定(依赖停止则自己也停止)
BindsTo=docker.service
 
# PartOf: 部分(重启/停止会传播)
PartOf=myapp.target

3.3 依赖关系图

网络就绪 (network-online.target)
    │
    ├─ Wants → redis.service
    ├─ Wants → postgresql.service
    │
    └─ After → myapp.service
                │
                ├─ Requires → myapp-worker.service
                │
                └─ Wants → myapp-scheduler.service

第4节:journal 日志

4.1 日志配置

[Service]
# 日志输出到 journal
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
 
# 日志输出到文件
StandardOutput=append:/var/log/myapp/stdout.log
StandardError=append:/var/log/myapp/stderr.log
 
# 日志级别
LogLevelMax=info

4.2 查看日志

# 查看服务日志
sudo journalctl -u myapp.service
 
# 实时查看日志
sudo journalctl -u myapp.service -f
 
# 查看最近 100 行
sudo journalctl -u myapp.service -n 100
 
# 按时间范围查看
sudo journalctl -u myapp.service --since "2026-08-22 10:00"
sudo journalctl -u myapp.service --since "1 hour ago"
 
# 查看内核日志
sudo journalctl -k
 
# 按优先级过滤
sudo journalctl -u myapp.service -p err
 
# 输出为文件
sudo journalctl -u myapp.service --since today > today.log

第5节:systemctl 操作

5.1 基本操作

# 启动服务
sudo systemctl start myapp.service
 
# 停止服务
sudo systemctl stop myapp.service
 
# 重启服务
sudo systemctl restart myapp.service
 
# 重新加载配置
sudo systemctl reload myapp.service
 
# 查看状态
sudo systemctl status myapp.service
 
# 启用开机自启
sudo systemctl enable myapp.service
 
# 禁用开机自启
sudo systemctl disable myapp.service
 
# 查看是否启用
sudo systemctl is-enabled myapp.service
 
# 查看是否运行
sudo systemctl is-active myapp.service

5.2 高级操作

# 查看服务的依赖关系
sudo systemctl list-dependencies myapp.service
 
# 查看服务的反向依赖
sudo systemctl list-dependencies --reverse myapp.service
 
# 重新加载 systemd 配置
sudo systemctl daemon-reload
 
# 重载失败的服务
sudo systemctl reset-failed myapp.service
 
# 杀死服务的所有进程
sudo systemctl kill myapp.service
 
# 查看服务的属性
sudo systemctl show myapp.service
 
# 设置服务属性
sudo systemctl set-property myapp.service CPUQuota=50%

5.3 Target 管理

# 查看所有 target
sudo systemctl list-units --type=target
 
# 切换到 multi-user.target(命令行模式)
sudo systemctl isolate multi-user.target
 
# 切换到 graphical.target(图形界面模式)
sudo systemctl isolate graphical.target
 
# 设置默认 target
sudo systemctl set-default multi-user.target
 
# 查看默认 target
systemctl get-default

第6节:完整示例

6.1 Web 应用服务

# /etc/systemd/system/webapp.service
[Unit]
Description=Web Application
Documentation=https://webapp.readthedocs.io
After=network-online.target
Wants=network-online.target
Requires=postgresql.service redis.service
 
[Service]
Type=simple
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp
 
Environment=NODE_ENV=production
Environment=PORT=8080
Environment=DATABASE_URL=postgresql://localhost:5432/webapp
Environment=REDIS_URL=redis://localhost:6379
 
ExecStart=/usr/bin/node server.js
ExecReload=/bin/kill -HUP $MAINPID
 
Restart=on-failure
RestartSec=10
StartLimitBurst=3
StartLimitIntervalSec=60
 
StandardOutput=journal
StandardError=journal
SyslogIdentifier=webapp
 
# 安全加固
PrivateTmp=true
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/webapp /var/log/webapp
CapabilityBoundingSet=
SystemCallFilter=@system-service
SystemCallArchitectures=native
 
# 资源限制
LimitNOFILE=65536
LimitNPROC=4096
MemoryMax=2G
CPUQuota=200%
 
[Install]
WantedBy=multi-user.target

6.2 定时任务服务

# /etc/systemd/system/cleanup.service
[Unit]
Description=System Cleanup Service
 
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
 
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run cleanup daily
 
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1800
 
[Install]
WantedBy=timers.target

6.3 Docker 容器服务

# /etc/systemd/system/mycontainer.service
[Unit]
Description=My Docker Container
After=docker.service
Requires=docker.service
 
[Service]
Type=simple
Restart=on-failure
RestartSec=30
 
ExecStartPre=-/usr/bin/docker stop mycontainer
ExecStartPre=-/usr/bin/docker rm mycontainer
ExecStart=/usr/bin/docker run --name mycontainer \
    --network host \
    -v /data/mycontainer:/data \
    -e TZ=Asia/Shanghai \
    myapp:latest
 
ExecStop=/usr/bin/docker stop mycontainer
 
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mycontainer
 
[Install]
WantedBy=multi-user.target