Linux systemd 服务管理
大约 11 分钟约 3229 字
Linux systemd 服务管理
简介
systemd 是现代 Linux 系统(CentOS 7+、Ubuntu 16.04+ 等)的初始化系统和服务管理器,由 Lennart Poettering 发起开发,旨在替代传统的 SysVinit。它不仅是系统的第一个进程(PID 1),还负责管理系统服务的启动、停止、依赖关系和日志收集。
systemd 通过 Unit 文件(.service、.timer、.socket 等)来定义和管理系统资源。掌握 systemd 的使用是 Linux 运维的基础技能,它能帮助你高效地管理应用服务的生命周期、排查启动故障、设置定时任务和优化系统性能。
本文将从 Unit 文件编写、服务管理命令、日志查看、定时器配置到高级特性,全面介绍 systemd 的使用方法。
systemd 核心架构
systemd 的组成
systemd
├── systemd (PID 1) -- 系统初始化和服务管理
├── systemctl -- 服务管理命令行工具
├── journalctl -- 日志查看工具
├── systemd-analyze -- 启动性能分析工具
├── systemd-run -- 临时服务运行工具
└── systemd-nspawn -- 轻量级容器管理工具Unit 类型
systemd 使用 Unit 文件来管理不同类型的系统资源:
| Unit 类型 | 文件扩展名 | 说明 |
|---|---|---|
| Service | .service | 系统服务(最常用) |
| Target | .target | 服务组(类似运行级别) |
| Timer | .timer | 定时器(替代 crontab) |
| Socket | .socket | 套接字激活 |
| Mount | .mount | 文件系统挂载点 |
| Automount | .automount | 自动挂载点 |
| Device | .device | 内核设备 |
| Swap | .swap | 交换分区 |
| Path | .path | 文件路径监控 |
| Slice | .slice | 资源隔离组(cgroup) |
Unit 文件详解
Unit 文件结构
一个标准的 .service 文件由三个主要部分组成:
# /etc/systemd/system/myapp.service
[Unit]
# 描述信息
Description=My .NET Application
Documentation=https://example.com/docs
# 依赖关系
After=network.target mysql.service
Wants=mysql.service
Requires=network.target
[Service]
# 服务类型
Type=notify
# 运行用户和组
User=www-data
Group=www-data
# 工作目录
WorkingDirectory=/opt/myapp
# 启动命令
ExecStart=/usr/bin/dotnet /opt/myapp/MyApp.dll
# 重载命令
ExecReload=/bin/kill -HUP $MAINPID
# 停止命令
ExecStop=/bin/kill -QUIT $MAINPID
# 重启策略
Restart=on-failure
RestartSec=10
# 环境变量
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://0.0.0.0:5000
EnvironmentFile=/opt/myapp/.env
# 资源限制
LimitNOFILE=65536
LimitNPROC=65535
# 安全加固
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp/data
[Install]
# 安装目标
WantedBy=multi-user.targetService Type 说明
| Type | 说明 | 适用场景 |
|---|---|---|
simple(默认) | 启动后即认为服务就绪 | 普通后台服务 |
notify | 服务通过 sd_notify 通知就绪 | 支持 sd_notify 的应用 |
forking | 服务 fork 子进程后父进程退出 | 传统守护进程 |
oneshot | 执行一次后退出 | 初始化脚本、备份脚本 |
dbus | 通过 D-Bus 信号通知就绪 | D-Bus 服务 |
idle | 等待所有任务完成后启动 | 避免与启动日志混在一起 |
exec | ExecStart 进程退出前认为未就绪 | 需要精确控制启动时序 |
依赖关系管理
[Unit]
# After:控制启动顺序(本服务在 target 之后启动)
After=network.target mysql.service
# Wants:软依赖(服务失败不影响本服务)
Wants=mysql.service
# Requires:硬依赖(服务失败则本服务也失败)
Requires=network.target
# Before:在指定服务之前启动
Before=httpd.service
# Conflicts:冲突关系(不能同时运行)
Conflicts=sendmail.serviceAfter 与 Requires 的区别
After 仅控制启动顺序,不建立依赖关系。Requires 建立硬依赖关系。通常两者配合使用:
After=mysql.service
Requires=mysql.service如果只是希望某个服务在另一个之后启动,但不要求必须存在,可以只用 After 配合 Wants:
After=redis.service
Wants=redis.service服务管理命令
基本服务管理
# 重新加载 Unit 文件(修改 .service 文件后必须执行)
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start myapp
# 停止服务
sudo systemctl stop myapp
# 重启服务
sudo systemctl restart myapp
# 重新加载配置(不中断服务)
sudo systemctl reload myapp
# 查看服务状态
sudo systemctl status myapp
# 检查服务是否活跃
sudo systemctl is-active myapp
# 检查服务是否已启用
sudo systemctl is-enabled myapp
# 开机自启
sudo systemctl enable myapp
# 禁用开机自启
sudo systemctl disable myapp
# 启用并立即启动
sudo systemctl enable --now myapp
# 禁用并立即停止
sudo systemctl disable --now myapp服务状态查看
# 查看详细服务状态
systemctl status myapp -l
# 查看服务属性
systemctl show myapp
# 查看指定属性
systemctl show myapp -p MainPID
systemctl show myapp -p ExecStart
systemctl show myapp -p MemoryCurrent
# 列出所有失败的服务
systemctl --failed
# 列出所有服务
systemctl list-units --type=service
# 列出所有已启用的服务
systemctl list-unit-files --type=service --state=enabled日志管理(journalctl)
基本日志查看
# 查看指定服务的日志
journalctl -u myapp
# 实时跟踪日志(类似 tail -f)
journalctl -u myapp -f
# 查看最近 100 行日志
journalctl -u myapp -n 100
# 查看指定时间范围的日志
journalctl -u myapp --since "2024-01-01 00:00:00"
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp --since "yesterday"
journalctl -u myapp --until "2024-01-02 00:00:00"
# 查看本次启动的日志
journalctl -u myapp -b
# 查看上次启动的日志(排查上次启动失败)
journalctl -u myapp -b -1日志过滤与格式
# 按优先级过滤(emerg/alert/crit/err/warning/notice/info/debug)
journalctl -u myapp -p err
journalctl -u myapp -p warning
# 按 PID 过滤
journalctl _PID=12345
# 按 UNIT 过滤
journalctl UNIT=myapp.service
# 输出为 JSON 格式
journalctl -u myapp -o json
# 输出为精简格式
journalctl -u myapp -o short-precise
# 输出为 cat 格式(只显示消息内容)
journalctl -u myapp -o cat日志持久化与大小限制
# /etc/systemd/journald.conf
[Journal]
# 持久化存储日志
Storage=persistent
# 日志目录
SystemMaxUse=500M
SystemMaxFileSize=100M
# 单个服务日志最大大小
SystemMaxFiles=10
# 压缩日志
Compress=yes
# 保留时间(默认不过期)
MaxRetentionSec=30day
# 修改后重启 journald
systemctl restart systemd-journald
# 清理旧日志
journalctl --vacuum-size=500M
journalctl --vacuum-time=7d
journalctl --vacuum-files=10systemd 定时器
创建定时任务
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
# 每天凌晨 2 点执行
OnCalendar=*-*-* 02:00:00
# 系统关机期间错过的任务,启动后补执行
Persistent=true
# 允许的时间误差(避免所有定时任务同时启动)
AccuracySec=1h
# 随机延迟(0-60 秒),避免同时触发
RandomizedDelaySec=60
[Install]
WantedBy=timers.target# /etc/systemd/system/backup.service
[Unit]
Description=Run Daily Backup
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
WorkingDirectory=/opt/scripts
User=root
# 设置超时时间(避免脚本卡死)
TimeoutStartSec=3600常用 Timer 时间格式
# 每天凌晨 2 点
OnCalendar=*-*-* 02:00:00
# 每小时
OnCalendar=hourly
# 每天
OnCalendar=daily
# 每周
OnCalendar=weekly
# 每月
OnCalendar=monthly
# 每 5 分钟
OnCalendar=*-*-* *:0/5:00
# 工作日每天 9 点
OnCalendar=Mon..Fri *-*-* 09:00:00
# 每季度第一天
OnCalendar=*-01,04,07,10-01 00:00:00Timer 管理命令
# 启用定时器
sudo systemctl enable backup.timer
# 启动定时器
sudo systemctl start backup.timer
# 查看所有定时器
systemctl list-timers
# 查看所有定时器(包括未启用的)
systemctl list-timers --all
# 查看定时器状态
systemctl status backup.timer
# 查看定时器下次执行时间
systemctl show backup.timer -p NextElapseUSecRealtime启动性能分析
# 查看系统启动耗时
systemd-analyze
# 查看每个服务的启动耗时
systemd-analyze blame
# 生成启动时间线图表(SVG 文件)
systemd-analyze plot > /tmp/boot_timeline.svg
# 查看关键链(影响启动速度的服务链)
systemd-analyze critical-chain
# 查看指定服务的关键链
systemd-analyze critical-chain myapp.service服务资源限制
# /etc/systemd/system/myapp.service
[Service]
# CPU 限制(最多使用 50% CPU)
CPUQuota=50%
# 内存限制(最大 512MB)
MemoryMax=512M
MemoryHigh=400M
# 任务限制
TasksMax=100
# 文件描述符限制
LimitNOFILE=65536
# 进程数限制
LimitNPROC=65535
# 打开文件数限制
LimitFSIZE=infinity常见问题排查
服务启动失败
# 1. 检查 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 2. 查看详细启动日志
journalctl -u myapp -b -p err
# 3. 检查 ExecStart 路径和权限
ls -la /opt/myapp/MyApp.dll
# 4. 检查 WorkingDirectory
ls -ld /opt/myapp
# 5. 手动执行启动命令测试
sudo -u www-data /usr/bin/dotnet /opt/myapp/MyApp.dllType=notify 超时问题
问题:使用 Type=notify 搭配不支持 sd_notify 的应用,导致服务启动超时
解决:
1. 改用 Type=simple
2. 或使用 Type=forking
3. 或添加 TimeoutStartSec=60 增加超时时间修改 Unit 文件后不生效
# 修改 .service 文件后必须重新加载
sudo systemctl daemon-reload
sudo systemctl restart myappsystemd vs crontab
| 特性 | systemd-timer | crontab |
|---|---|---|
| 日志集成 | journalctl 原生支持 | 需要手动重定向 |
| 依赖管理 | 可以依赖其他服务 | 不支持 |
| 错过执行 | Persistent 自动补执行 | 不支持 |
| 环境管理 | 完整的系统环境 | 最小化环境 |
| 权限管理 | systemd 权限模型 | 用户级 |
| 学习成本 | 较高 | 较低 |
systemd Socket 激活
Socket 激活是 systemd 的独特功能,可以在收到连接时才启动对应的服务,从而节省系统资源。
Socket 单元配置
# /etc/systemd/system/myapp.socket
[Unit]
Description=My App Socket
[Socket]
# 监听 TCP 8080 端口
ListenStream=8080
# 监听多个端口
# ListenStream=8080
# ListenStream=9090
# 监听 Unix Socket
# ListenStream=/run/myapp.sock
# 背压队列长度
Backlog=511
# 连接超时
Accept=no
[Install]
WantedBy=sockets.target配合 Socket 的 Service
# /etc/systemd/system/myapp.service
[Unit]
Description=My App Service
Requires=myapp.socket
After=myapp.socket
[Service]
Type=notify
ExecStart=/usr/bin/dotnet /opt/myapp/MyApp.dll
# 标准输入来自 Socket
StandardInput=socket
StandardOutput=journal
StandardError=journal
# 非阻塞模式
NonBlocking=trueSocket 激活管理命令
# 启用并启动 Socket(服务会在收到连接时自动启动)
sudo systemctl enable myapp.socket
sudo systemctl start myapp.socket
# 查看 Socket 状态
systemctl status myapp.socket
# 列出所有活跃的 Socket
systemctl list-sockets
# 测试 Socket 连接
curl http://localhost:8080systemd Path 单元 — 文件监控
Path 单元可以监控文件系统的变化,自动触发对应的服务。
# /etc/systemd/system/watch-upload.path
[Unit]
Description=Watch Upload Directory
[Path]
# 目录中有新文件写入时触发
PathModified=/opt/uploads/
# 目录中有新文件创建时触发(更精确)
# PathExists=/opt/uploads/new_file.flag
# 目录变化时触发
# PathChanged=/opt/uploads/
# 目录中有文件关闭写入时触发
# PathClosed=/opt/uploads/
# 检查间隔(纳秒)
MakeDirectory=yes
DirectoryMode=0755
[Install]
WantedBy=paths.target# /etc/systemd/system/watch-upload.service
[Unit]
Description=Process Uploaded Files
[Service]
Type=oneshot
ExecStart=/opt/scripts/process-upload.shsystemd Slice 与资源隔离
Slice 单元用于组织和管理系统资源(基于 cgroup),适合多租户或多服务器的资源分配。
# /etc/systemd/system/app.slice
[Unit]
Description=Application Slice
Before=slices.target
[Slice]
# 该 Slice 下所有服务的总内存限制
MemoryMax=2G
MemoryHigh=1.5G
# CPU 权重(相对值,默认 100)
CPUWeight=200
# IO 权重
IOWeight=150# /etc/systemd/system/myapp.service
[Unit]
Description=My App
[Service]
ExecStart=/usr/bin/dotnet /opt/myapp/MyApp.dll
# 将服务分配到 app.slice
Slice=app.slice
# 服务自身的资源限制(不能超过 Slice 的上限)
MemoryMax=512M
CPUQuota=50%查看资源使用情况
# 查看所有 Slice
systemctl list-units --type=slice
# 查看 cgroup 层次结构
systemd-cgls
# 查看 cgroup 资源使用
systemd-cgtop
# 查看指定服务的资源统计
systemctl show myapp.service -p MemoryCurrent -p CPUUsageNSecsystemd-run 临时服务
systemd-run 可以临时创建并运行一个服务,无需编写 Unit 文件。
# 运行一次性任务
sudo systemd-run --unit=backup-task /opt/scripts/backup.sh
# 在临时 Slice 中运行
sudo systemd-run --slice=app.slice --property=MemoryMax=512M /opt/scripts/heavy-task.sh
# 以指定用户运行
sudo systemd-run --uid=www-data --gid=www-data /opt/scripts/cleanup.sh
# 运行并跟踪输出
sudo systemd-run --pty /opt/scripts/interactive-task.sh
# 查看临时服务的输出
journalctl -u backup-tasksystemd 安全加固详解
完整的安全加固配置
[Service]
# ===== 文件系统保护 =====
# 保护 /usr、/boot 等只读目录
ProtectSystem=strict
# 允许写入的目录
ReadWritePaths=/opt/myapp/data /var/log/myapp
# 保护 /home 目录
ProtectHome=read-only
# 保护内核变量
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectKernelLogs=yes
# 保护控制组
ProtectControlGroups=yes
# ===== 网络限制 =====
# 限制网络访问(private-network 或 restrict-network)
# PrivateNetwork=yes -- 完全禁止网络
# ===== 用户命名空间 =====
# PrivateUsers=yes -- 启用用户命名空间隔离
# ===== 能力限制 =====
# 移除所有 Linux 能力,只保留必要的
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# ===== 其他安全选项 =====
# 禁止获取新权限
NoNewPrivileges=true
# 锁定内存
LockPersonality=yes
# 禁止创建临时设备节点
PrivateTmp=yes
# 随机化 /tmp 目录
# TemporaryFileSystem=/tmp:rw
# 系统调用过滤
SystemCallFilter=@system-service
SystemCallFilter=~@mount @swap
# 地址空间布局随机化
RandomizedDelaySec=5安全配置验证
# 验证 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
# 查看服务的安全状态
systemd-analyze security myapp.service
# 输出示例:
# NAME DESCRIPTION
# PrivateNetwork Service has access to the host's network
# User=/RunAs= Service runs as root
# ProtectHome= Service has access to home directories
# Overall exposure: 6.5 (exposure 0.0 is best)systemd 模板化服务(Instantiated Services)
模板化服务允许使用一个 Unit 文件管理多个实例。
# /etc/systemd/system/myapp@.service
[Unit]
Description=My App Instance %i
After=network.target
[Service]
Type=notify
User=www-data
WorkingDirectory=/opt/myapp/%i
ExecStart=/usr/bin/dotnet /opt/myapp/%i/MyApp.dll
Environment=ASPNETCORE_URLS=http://0.0.0.0:50%i
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target使用模板化服务
# 启动实例 tenant1
sudo systemctl start myapp@tenant1
# 启动实例 tenant2
sudo systemctl start myapp@tenant2
# 同时启动多个实例
sudo systemctl start myapp@tenant1 myapp@tenant2 myapp@tenant3
# 查看所有实例
systemctl list-units 'myapp@*'
# 启用所有实例开机自启
sudo systemctl enable myapp@tenant1 myapp@tenant2常见故障排查进阶
服务循环重启问题
# 查看服务的重启计数和频率
systemctl show myapp -p NRestarts
journalctl -u myapp --since "10 minutes ago" | grep -i "start\|stop\|fail"
# 检查是否因 OOM 被杀
dmesg | grep -i oom
journalctl -k | grep -i oom
# 设置启动限制(防止循环重启)
# 在 [Service] 中添加:
# StartLimitIntervalSec=60
# StartLimitBurst=5
# StartLimitAction=reboot -- 达到限制后的动作环境变量加载失败
# 检查 EnvironmentFile 是否存在且有权限
ls -la /opt/myapp/.env
# 验证 .env 文件格式(不支持变量展开,不支持引号内空格)
# 正确格式:
# KEY=value
# ANOTHER_KEY="simple_value"
# 错误格式(不支持):
# export KEY=value
# KEY=$OTHER_VAR
# 使用 Environment 而非 EnvironmentFile 的替代方案
# Environment=KEY=value ANOTHER_KEY=value