systemd

什么是systemd

systemd 的定位

systemd 是 Linux 的系统与服务管理器,负责:

systemd = init + service manager + log + timer + more

systemd的核心概念

特性 说明
并行启动 提高开机速度
依赖管理 精确控制启动顺序
按需启动 socket / path 激活
统一接口 systemctl / journalctl
可控生命周期 Restart / Timeout

Unit(单元)基础概念

systemd中,一切都是Unit

Unit的类型

Unit 类型 后缀 作用
Service .service 服务
Socket .socket 套接字激活
Timer .timer 定时任务
Target .target 启动目标
Mount .mount 挂载点
Path .path 文件变动触发
Device .device 设备

最常用的时 .service.timer

Unit文件搜索路径(优先级)

/etc/systemd/system/        # 管理员自定义(最高)
/run/systemd/system/        # 运行时生成
/usr/lib/systemd/system/    # 软件包自带(最低)

systemctl常用命令

服务管理

systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx
systemctl status nginx

开机自启

systemctl enable nginx
systemctl disable nginx
systemctl is-enabled nginx

查看所有服务

systemctl list-units
systemctl list-units --type=service
systemctl list-unit-files

重新加载配置

systemctl daemon-reload

修改unit文件后必须执行。

Service文件结构

一个 .service 文件由三大段组成:

[Unit]
[Service]
[Install]

一个最简单的Service示例

[Unit]
Description=My Simple Service
After=network.target

[Service]
ExecStart=/usr/local/bin/my_app
Restart=always

[Install]
WantedBy=multi-user.target

启用并启动

systemctl daemon-reload
systemctl enable my_app
systemctl start my_app

Unit段详解

[Unit]
Description=描述信息
After=network.target
Before=xxx.service
Requires=mysql.service
Wants=redis.service

常用指令

指令 含义
Description 描述
After 启动顺序(不保证存在)
Requires 强依赖(失败即失败)
Wants 弱依赖
Conflicts 冲突

After 不等价于 Requires

Service段详解

Type

Type 用途
simple 默认,前台进程
forking 后台守护进程
oneshot 执行一次
notify systemd 通知
idle 延后启动
Type=simple

ExecStart ExecStop

ExecStart=/usr/bin/python3 app.py
ExecStop=/bin/kill -TERM $MAINPID

Restart策略

Restart=always
RestartSec=5
说明
no 不重启
on-failure 失败重启
always 总是重启

用户与权限

User=www-data
Group=www-data

工作目录与环境变量

WorkingDirectory=/opt/app
Environment="ENV=prod"
EnvironmentFile=/etc/myapp.env

资源限制

MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65535

Install段详解

[Install]
WantedBy=multi-user.target
Target 类似
multi-user.target 多用户(无 GUI)
graphical.target 图形界面
reboot.target 重启

Timer(systemd定时任务)

Timer vs cron

cron systemd timer
无依赖 支持依赖
无日志 journal
精度低 高精度

Timer示例

myjob.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

myjob.timer

[Unit]
Description=Daily Backup

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

启用

systemctl enable myjob.timer
systemctl start myjob.timer

日志管理(journalctl)

常用命令

journalctl -u nginx
journalctl -u my_app -f
journalctl --since today
journalctl -p err

持久化日志

mkdir -p /var/log/journal
systemctl restart systemd-journald

高级功能

Socket激活(懒启动)

myapp.socket
myapp.service

systemd在端口有访问时才启动服务

Path激活(监听文件变化)

[Path]
PathModified=/etc/my.conf

Watchdog

WatchdogSec=30

应用定期 sd_notify

调试与排错技巧

查看失败原因

systemctl status my_app
journalctl -xe

验证unit文件

systemd-analyze verify my_app.service

启动时间分析

systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain

实战模板

后端服务模板

[Unit]
Description=My Backend Service
After=network.target

[Service]
Type=simple
User=app
WorkingDirectory=/opt/app
ExecStart=/opt/app/server
Restart=on-failure
RestartSec=3
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target

systemd的发展历史与设计动机

systemd出现之前的世界

传统SysVinit最早期

核心特征:

/etc/init.d/sshd start

启动流程

boot → init → rcS → rc3.d → 服务逐个启动

存在问题,串行 启动慢、脚本不可分析、依赖靠命名顺序、没有状态管理。

init只会执行、不会管理。

Upstart(Ubuntu)

特点:

问题:

Upstart是一次过渡失败。

systemd的诞生(2010)

核心目标,不是替代 init,systemd 的目标是

为 Linux 提供一个“可描述、可依赖、可验证”的系统状态模型

systemd的哲学变化

旧世界 systemd
shell 脚本 声明式
顺序启动 依赖图
进程存在即服务 unit 状态机
stdout 随意 统一日志
cron timer
rc.local oneshot

systemd是“系统状态管理器”,不是脚本执行器。

Unit=状态机(核心思想)

systemd的最底层抽象不是“脚本”,而是:

Unit=一个有限状态机。

Service的状态流转

inactive
   ↓ start
activating
   ↓ ExecStart 成功
active
   ↓ 进程退出
failed / inactive

systemd随时知道你在哪里。

为什么systemd要接管这么多

功能 原因
journald 统一日志上下文
logind 用户会话感知
timers 可依赖 + 持久
mount 系统状态一致性
socket 按需启动

systemd并不是“做多”,而是“做闭环”。

真实样例

下面写一个 接近真实线上后端服务的systemd方案。

场景

你有一个后端服务:

准备运行用户

useradd -r -s /sbin/nologin game
mkdir -p /opt/game
chown -R game:game /opt/game

完整Service文件

/etc/systemd/system/game-server.service

[Unit]
Description=Game Backend Server
# 启动顺序
After=network-online.target mysql.service redis.service
# 网络尽量可用
Wants=network-online.target
# 数据库挂了->服务失败
Requires=mysql.service

[Service]
# 进程模型 后端自己管理主循环 systemd直接跟踪PID
Type=simple

# 运行身份
User=game
Group=game

# 工作目录
WorkingDirectory=/opt/game

# 启动命令(必须前台)
ExecStart=/opt/game/server --config /etc/game/config.yaml

# 平滑关闭 给服务器30秒首位 SIGTERM优雅下线
ExecStop=/bin/kill -TERM $MAINPID
TimeoutStopSec=30

# 自动重启策略 crash才重启 手动stop不会拉起
Restart=on-failure
RestartSec=5

# 资源限制
LimitNOFILE=200000
MemoryMax=2G
CPUQuota=300%

# 安全增强
# 禁止提权
NoNewPrivileges=true
# 私有/tmp system自带“轻量sandbox”
PrivateTmp=true
# 只读系统
ProtectSystem=full
# 禁止访问家目录
ProtectHome=true

# 日志
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

启动验证流程

systemctl daemon-reload
systemctl enable game-server
systemctl start game-server
systemctl status game-server

日志分析(生产必会)

journalctl -u game-server -f
journalctl -u game-server --since "10 min ago"

为什么不用nohup与screen

nohup systemd
无状态 状态机
无依赖 精确依赖
无日志 journal
不可恢复 自动恢复

设计的本质

systemd=把“运维经验”写进内核级工具。

它假设:

systemd与容器(Docker K8s)的哲学冲突

很多人“会用Docker 与 K8s”,但真正理解它们为什么和systemd天然冲突的人并不多。

哲学冲突

一句话总结:

systemd试图“管理一台机器的全部状态”,而容器试图“否认机器的存在”。

根本哲学冲突

systemd的世界观:我负责这台机器上的一切

systemd是“宿主机中心论”

Docker和K8s的世界观:不存在机器,只有workload

K8s是“去机器化”设计。

第一性冲突总结

systemd 容器
管理系统状态 抽象系统
稳定长期运行 短生命周期
本机依赖 网络依赖
机器唯一 节点可丢

PID1之争

systemd的要求

PID 1 = 系统生命中枢

Docker容器的现实

PID 1 = 你的程序

失败语义完全不同

systemd的失败模型

失败 意义
进程退出 异常
非 0 错误
需要重启 恢复
Restart=on-failure

Kubernetes的失败模型

失败 意义
容器退出 正常
Pod 被杀 调度
节点消失 常态
restartPolicy: Always

K8s把失败当作控制信号。

systemd: 你死了?我拉你起来! K8s: 你死了?我换一个你。

依赖管理

systemd依赖

Requires=mysql.service
After=mysql.service

明确、强约束、本地。

K8s依赖

通过Service、DNS、readiness、liveness、最终一致性

mysql 不存在 ≠ 启动失败

K8s不允许强依赖。

日志与可观测性冲突

systemd/journald

journalctl -u nginx

容器日志

kubectl logs pod

journald 在容器内是负资产。

重启

场景 systemd K8s
崩溃 重启进程 重建容器
内存超限 OOMKill Pod Evict
节点异常 无概念 重调度

为什么K8s明确返回systemd in container

官方共识:一个容器=一个进程

原因:

systemd破坏了这些假设