systemd 是 Linux 的系统与服务管理器,负责:
systemd = init + service manager + log + timer + more
| 特性 | 说明 |
|---|---|
| 并行启动 | 提高开机速度 |
| 依赖管理 | 精确控制启动顺序 |
| 按需启动 | socket / path 激活 |
| 统一接口 | systemctl / journalctl |
| 可控生命周期 | Restart / Timeout |
systemd中,一切都是Unit
Unit的类型
| Unit 类型 | 后缀 | 作用 |
|---|---|---|
| Service | .service |
服务 |
| Socket | .socket |
套接字激活 |
| Timer | .timer |
定时任务 |
| Target | .target |
启动目标 |
| Mount | .mount |
挂载点 |
| Path | .path |
文件变动触发 |
| Device | .device |
设备 |
最常用的时 .service 和 .timer。
/etc/systemd/system/ # 管理员自定义(最高)
/run/systemd/system/ # 运行时生成
/usr/lib/systemd/system/ # 软件包自带(最低)systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx
systemctl status nginxsystemctl enable nginx
systemctl disable nginx
systemctl is-enabled nginxsystemctl list-units
systemctl list-units --type=service
systemctl list-unit-filessystemctl daemon-reload修改unit文件后必须执行。
一个 .service 文件由三大段组成:
[Unit]
[Service]
[Install][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]
Description=描述信息
After=network.target
Before=xxx.service
Requires=mysql.service
Wants=redis.service常用指令
| 指令 | 含义 |
|---|---|
| Description | 描述 |
| After | 启动顺序(不保证存在) |
| Requires | 强依赖(失败即失败) |
| Wants | 弱依赖 |
| Conflicts | 冲突 |
After 不等价于 Requires
Type
| Type | 用途 |
|---|---|
| simple | 默认,前台进程 |
| forking | 后台守护进程 |
| oneshot | 执行一次 |
| notify | systemd 通知 |
| idle | 延后启动 |
Type=simpleExecStart ExecStop
ExecStart=/usr/bin/python3 app.py
ExecStop=/bin/kill -TERM $MAINPIDRestart策略
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]
WantedBy=multi-user.target| Target | 类似 |
|---|---|
| multi-user.target | 多用户(无 GUI) |
| graphical.target | 图形界面 |
| reboot.target | 重启 |
Timer vs cron
| cron | systemd timer |
|---|---|
| 无依赖 | 支持依赖 |
| 无日志 | journal |
| 精度低 | 高精度 |
myjob.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shmyjob.timer
[Unit]
Description=Daily Backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target启用
systemctl enable myjob.timer
systemctl start myjob.timer常用命令
journalctl -u nginx
journalctl -u my_app -f
journalctl --since today
journalctl -p err持久化日志
mkdir -p /var/log/journal
systemctl restart systemd-journaldmyapp.socket
myapp.servicesystemd在端口有访问时才启动服务
[Path]
PathModified=/etc/my.confWatchdogSec=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传统SysVinit最早期
核心特征:
/etc/init.d/*/etc/init.d/sshd start启动流程
boot → init → rcS → rc3.d → 服务逐个启动存在问题,串行 启动慢、脚本不可分析、依赖靠命名顺序、没有状态管理。
init只会执行、不会管理。
特点:
start on filesystem问题:
Upstart是一次过渡失败。
核心目标,不是替代 init,systemd 的目标是
为 Linux 提供一个“可描述、可依赖、可验证”的系统状态模型
systemd的哲学变化
| 旧世界 | systemd |
|---|---|
| shell 脚本 | 声明式 |
| 顺序启动 | 依赖图 |
| 进程存在即服务 | unit 状态机 |
| stdout 随意 | 统一日志 |
| cron | timer |
| rc.local | oneshot |
systemd是“系统状态管理器”,不是脚本执行器。
systemd的最底层抽象不是“脚本”,而是:
Unit=一个有限状态机。
Service的状态流转
inactive
↓ start
activating
↓ ExecStart 成功
active
↓ 进程退出
failed / inactive
systemd随时知道你在哪里。
| 功能 | 原因 |
|---|---|
| journald | 统一日志上下文 |
| logind | 用户会话感知 |
| timers | 可依赖 + 持久 |
| mount | 系统状态一致性 |
| socket | 按需启动 |
systemd并不是“做多”,而是“做闭环”。
下面写一个 接近真实线上后端服务的systemd方案。
场景
你有一个后端服务:
/opt/game/server准备运行用户
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.targetsystemctl daemon-reload
systemctl enable game-server
systemctl start game-server
systemctl status game-serverjournalctl -u game-server -f
journalctl -u game-server --since "10 min ago"| nohup | systemd |
|---|---|
| 无状态 | 状态机 |
| 无依赖 | 精确依赖 |
| 无日志 | journal |
| 不可恢复 | 自动恢复 |
systemd=把“运维经验”写进内核级工具。
它假设:
很多人“会用Docker 与 K8s”,但真正理解它们为什么和systemd天然冲突的人并不多。
一句话总结:
systemd试图“管理一台机器的全部状态”,而容器试图“否认机器的存在”。
systemd的世界观:我负责这台机器上的一切
systemd是“宿主机中心论”
Docker和K8s的世界观:不存在机器,只有workload
K8s是“去机器化”设计。
第一性冲突总结
| systemd | 容器 |
|---|---|
| 管理系统状态 | 抽象系统 |
| 稳定长期运行 | 短生命周期 |
| 本机依赖 | 网络依赖 |
| 机器唯一 | 节点可丢 |
systemd的要求
PID 1 = 系统生命中枢Docker容器的现实
PID 1 = 你的程序systemd的失败模型
| 失败 | 意义 |
|---|---|
| 进程退出 | 异常 |
| 非 0 | 错误 |
| 需要重启 | 恢复 |
Restart=on-failureKubernetes的失败模型
| 失败 | 意义 |
|---|---|
| 容器退出 | 正常 |
| Pod 被杀 | 调度 |
| 节点消失 | 常态 |
restartPolicy: AlwaysK8s把失败当作控制信号。
systemd: 你死了?我拉你起来! K8s: 你死了?我换一个你。
systemd依赖
Requires=mysql.service
After=mysql.service明确、强约束、本地。
K8s依赖
通过Service、DNS、readiness、liveness、最终一致性
mysql 不存在 ≠ 启动失败K8s不允许强依赖。
systemd/journald
journalctl -u nginx容器日志
kubectl logs podjournald 在容器内是负资产。
| 场景 | systemd | K8s |
|---|---|---|
| 崩溃 | 重启进程 | 重建容器 |
| 内存超限 | OOMKill | Pod Evict |
| 节点异常 | 无概念 | 重调度 |
官方共识:一个容器=一个进程
原因:
systemd破坏了这些假设