systemd 教程 / 生产环境最佳实践
生产环境最佳实践
在生产环境中使用 systemd 需要遵循一系列最佳实践,以确保服务的可靠性、安全性和可维护性。
1. Unit 文件编写规范
1.1 文件结构规范
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
Documentation=https://myapp.example.com/docs
After=network.target postgresql.service
Requires=postgresql.service
Wants=redis.service
ConditionPathExists=/opt/myapp/bin/myapp
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=-/etc/sysconfig/myapp
ExecStartPre=/opt/myapp/bin/pre-start.sh
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/opt/myapp/bin/stop.sh
Restart=on-failure
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=300
TimeoutStartSec=300
TimeoutStopSec=30
KillMode=mixed
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
1.2 文件放置规范
| 路径 | 用途 | 是否可编辑 |
|---|
/etc/systemd/system/ | 管理员自定义服务 | ✅ |
/usr/lib/systemd/system/ | 发行版提供的服务 | ❌(使用 override) |
/run/systemd/system/ | 运行时生成的服务 | ❌(临时) |
⚠️ 注意:永远不要直接修改 /usr/lib/systemd/system/ 下的文件,使用 systemctl edit 创建 override。
2. 服务依赖设计
2.1 After/Requires/Wants 选择
| 依赖类型 | 含义 | 使用场景 |
|---|
After= | 启动顺序 | 必须在之后启动 |
Requires= | 强依赖 | 依赖服务失败则本服务也失败 |
Wants= | 弱依赖 | 依赖服务失败不影响本服务 |
Requisite= | 必需依赖 | 依赖服务未运行则拒绝启动 |
Conflicts= | 冲突 | 互斥服务 |
2.2 依赖设计示例
# 场景 1:Web 应用依赖数据库(强依赖)
[Unit]
After=network.target postgresql.service
Requires=postgresql.service # 数据库必须运行
Wants=redis.service # Redis 可选
# 场景 2:微服务架构(弱依赖避免级联失败)
[Unit]
After=network.target
Wants=auth-service.service inventory-service.service
3. 重启策略设计
3.1 重启参数配置
[Service]
# 重启条件
Restart=on-failure # 失败时重启
Restart=on-abnormal # 异常退出时重启
Restart=always # 总是重启(不推荐)
Restart=no # 不重启
# 重启间隔
RestartSec=10
# 启动限制(防止重启风暴)
StartLimitBurst=5 # 时间窗口内最多启动 5 次
StartLimitIntervalSec=300 # 5 分钟时间窗口
3.2 重启策略选择
| 场景 | 推荐策略 | 说明 |
|---|
| Web 服务器 | on-failure | 正常退出不重启 |
| 后台守护进程 | on-failure | 正常退出不重启 |
| 一次性任务 | no | 执行一次即完成 |
| 关键服务 | on-abnormal | 仅异常退出时重启 |
3.3 看门狗(Watchdog)
[Service]
Type=notify
WatchdogSec=30
NotifyAccess=main
# 应用需要定期发送 WATCHDOG=1 通知 systemd
4. 日志策略
4.1 journald 配置
# /etc/systemd/journald.conf
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=100M
MaxRetentionSec=30day
Compress=yes
ForwardToSyslog=yes
4.2 日志轮转
# 手动清理旧日志
sudo journalctl --vacuum-time=30d
sudo journalctl --vacuum-size=2G
# 查看日志占用空间
journalctl --disk-usage
# 立即应用配置
sudo systemctl restart systemd-journald
5. 安全加固
5.1 最小权限原则
[Service]
User=myapp
Group=myapp
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ReadOnlyPaths=/etc/myapp
ReadWritePaths=/var/lib/myapp /var/log/myapp /run/myapp
5.2 Capabilities 限制
[Service]
# 仅授予必要权限
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH
# 或完全移除
CapabilityBoundingSet=
5.3 系统调用过滤
[Service]
SystemCallFilter=@system-service @network-io @file-system
SystemCallFilter=~@mount @reboot @swap @debug
5.4 网络限制
[Service]
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictSUIDSGID=true
RestrictRealtime=true
5.5 安全评分检查
# 评估服务安全等级
systemd-analyze security myapp.service
# 目标:< 2.0 (OK)
6. 资源限制(cgroups)
6.1 CPU 和内存
[Service]
CPUQuota=50%
CPUWeight=100
CPUAffinity=0 1 2 3
MemoryMax=1G
MemoryHigh=512M
OOMPolicy=continue
6.2 I/O 和进程
[Service]
IOWeight=500
IOReadBandwidthMax=/dev/sda 50M
TasksMax=100
LimitNOFILE=65535
LimitNPROC=65535
6.3 Slice 管理
# /etc/systemd/system/myapp.slice
[Unit]
Description=MyApp Slice
[Slice]
CPUQuota=80%
MemoryMax=2G
TasksMax=200
7. 监控集成
7.1 Prometheus node_exporter
# /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
Type=simple
User=prometheus
ExecStart=/usr/local/bin/node_exporter \
--collector.systemd \
--web.listen-address=:9100
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
[Install]
WantedBy=multi-user.target
7.2 自定义健康检查
#!/bin/bash
# /opt/myapp/bin/healthcheck.sh
# 检查进程
if ! pgrep -x myapp > /dev/null; then
echo "Process not running"; exit 1
fi
# 检查端口
if ! ss -tlnp | grep -q ":8080"; then
echo "Port 8080 not listening"; exit 1
fi
# 检查 HTTP
if ! curl -sf http://localhost:8080/health > /dev/null; then
echo "Health check failed"; exit 1
fi
echo "OK"
8. 配置管理集成
8.1 Ansible 示例
# deploy-service.yml
---
- name: Deploy systemd service
hosts: webservers
become: yes
tasks:
- name: Deploy systemd unit file
template:
src: templates/myapp.service.j2
dest: "/etc/systemd/system/myapp.service"
notify: Reload systemd
- name: Enable and start service
systemd:
name: myapp
enabled: yes
state: started
handlers:
- name: Reload systemd
systemd:
daemon_reload: yes
8.2 Puppet 示例
file { '/etc/systemd/system/myapp.service':
ensure => file,
content => template('myapp/myapp.service.erb'),
notify => Exec['systemctl-daemon-reload'],
}
exec { 'systemctl-daemon-reload':
command => '/bin/systemctl daemon-reload',
refreshonly => true,
}
service { 'myapp':
ensure => 'running',
enable => true,
}
9. 生产 Checklist
9.1 部署前检查
| 检查项 | 命令 | 期望结果 |
|---|
| Unit 文件语法 | systemd-analyze verify myapp.service | 无错误 |
| 安全评分 | systemd-analyze security myapp.service | < 2.0 |
| 依赖关系 | systemctl list-dependencies myapp.service | 所有依赖正常 |
| 用户存在 | id myapp | 用户存在 |
| 端口可用 | ss -tlnp | grep :8080 | 端口未被占用 |
9.2 部署后检查
| 检查项 | 命令 | 期望结果 |
|---|
| 服务状态 | systemctl status myapp.service | active (running) |
| 日志无错误 | journalctl -u myapp.service -p err | 无错误 |
| 端口监听 | ss -tlnp | grep :8080 | 端口已监听 |
| 健康检查 | curl http://localhost:8080/health | 返回 OK |
9.3 运维 Checklist
| 检查项 | 频率 | 说明 |
|---|
| 服务状态 | 每日 | 检查所有关键服务 |
| 日志检查 | 每日 | 检查错误和警告 |
| 资源使用 | 每周 | CPU、内存、磁盘 |
| 安全更新 | 每月 | 更新 systemd |
| 配置备份 | 每月 | 备份 Unit 文件 |
10. 故障恢复
10.1 快速回滚
# 使用版本化的 Unit 文件
# /etc/systemd/system/myapp.service -> myapp.service.v1.0
sudo cp /etc/systemd/system/myapp.service.v1.0 /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
⚠️ 注意事项
- 永远不要修改
/usr/lib/systemd/system/:使用 systemctl edit 创建 override - 测试环境先验证:所有配置变更先在测试环境验证
- 配置版本控制:Unit 文件应纳入版本控制
- 监控告警:配置服务异常告警
- 定期审查:定期审查服务配置和安全设置
💡 提示
- 使用
systemctl edit 创建 override 配置,保持原始文件不变 systemd-analyze security 可以快速评估服务安全等级- 使用
systemctl show 查看服务的所有配置属性 - 使用 Slice 组织相关服务,便于资源管理
- 配置
StartLimitBurst 和 StartLimitIntervalSec 防止重启风暴
扩展阅读