1. 为什么需要systemd服务
每次重启服务器后都要手动启动一堆服务?作为一名运维老兵,我见过太多人用nohup、screen这些土办法跑守护进程,结果服务器一重启服务全挂了。其实Linux早就给我们准备好了更专业的解决方案——systemd服务管理。
想象一下这样的场景:你花了三天三夜写了个数据备份脚本,结果某天机房断电重启后,发现上周的数据库全量备份都没执行。这时候要是当初配置了systemd服务,就不会出现这种人间惨剧。systemd不仅能实现开机自启,还能自动重启崩溃的服务、记录日志、限制资源占用,堪称服务管理的瑞士军刀。
2. 准备你的可执行程序
2.1 脚本编写规范
先给大家看看我最近写的一个真实案例——网站日志清理脚本:
#!/bin/bash # 日志目录 LOG_DIR="/var/log/nginx" # 保留最近7天的日志 DAYS_TO_KEEP=7 function cleanup() { echo "$(date) - 开始清理日志..." >> /var/log/log_cleaner.log find "$LOG_DIR" -type f -name "*.log" -mtime +$DAYS_TO_KEEP -delete echo "$(date) - 日志清理完成" >> /var/log/log_cleaner.log } # 主循环 while true; do cleanup # 每天凌晨3点执行 sleep 86400 done这个脚本有几个关键点:
- 使用完整路径而非相对路径
- 所有操作都有日志记录
- 用函数封装核心逻辑
- 包含必要的注释
2.2 测试脚本可靠性
写完脚本别急着配置服务,先手动测试几轮:
# 给执行权限 chmod +x /opt/scripts/log_cleaner.sh # 第一次试运行 /opt/scripts/log_cleaner.sh # 查看执行日志 tail -f /var/log/log_cleaner.log # 模拟异常退出 kill -9 $(pgrep -f log_cleaner.sh)我建议至少测试以下场景:
- 正常启动/停止
- 手动kill进程
- 修改系统时间测试定时逻辑
- 磁盘空间不足的情况
3. 创建systemd服务单元文件
3.1 服务文件存放位置
系统级服务建议放在/etc/systemd/system/,比如我们的日志清理服务:
sudo vim /etc/systemd/system/log-cleaner.service3.2 完整服务配置示例
这是我优化过的生产级配置:
[Unit] Description=NGINX日志清理服务 After=nginx.service Requires=nginx.service StartLimitIntervalSec=500 StartLimitBurst=5 [Service] Type=simple User=root Group=root ExecStart=/opt/scripts/log_cleaner.sh Restart=on-failure RestartSec=30s StandardOutput=syslog StandardError=syslog SyslogIdentifier=log_cleaner Environment="LOG_LEVEL=verbose" [Install] WantedBy=multi-user.target3.3 关键参数详解
[Unit] 部分
After:表示在nginx服务启动后再启动Requires:强依赖关系,nginx挂了本服务也会停止StartLimit*:防止服务崩溃时无限重启
[Service] 部分
Restart=on-failure:只有异常退出时才重启RestartSec:重启间隔避免频繁重启SyslogIdentifier:方便在日志中过滤
[Install] 部分
WantedBy:多用户模式启动,适合大多数服务器场景
4. 服务生命周期管理
4.1 注册并启动服务
# 重载systemd配置 sudo systemctl daemon-reload # 设置开机自启 sudo systemctl enable log-cleaner # 立即启动服务 sudo systemctl start log-cleaner # 查看状态 sudo systemctl status log-cleaner4.2 日常维护命令
# 查看完整日志 journalctl -u log-cleaner -f # 平滑重启服务 sudo systemctl restart log-cleaner # 临时停止 sudo systemctl stop log-cleaner # 禁用开机启动 sudo systemctl disable log-cleaner4.3 服务故障排查
当服务启动失败时,我常用的排查步骤:
- 检查服务状态
systemctl status log-cleaner -l- 查看详细日志
journalctl -xe -u log-cleaner --no-pager- 手动执行命令
sudo -u root /opt/scripts/log_cleaner.sh- 检查依赖服务
systemctl list-dependencies log-cleaner5. 高级配置技巧
5.1 资源限制
防止服务占用过多资源:
[Service] ... MemoryLimit=512M CPUQuota=80% IOWeight=1005.2 环境变量管理
推荐使用单独的环境文件:
# /etc/sysconfig/log-cleaner LOG_DIR="/var/log/nginx" DAYS_TO_KEEP=7然后在服务文件中引用:
EnvironmentFile=/etc/sysconfig/log-cleaner ExecStart=/opt/scripts/log_cleaner.sh ${LOG_DIR} ${DAYS_TO_KEEP}5.3 多命令服务
需要执行多个命令时:
ExecStartPre=/bin/mkdir -p /var/run/myservice ExecStart=/usr/bin/myservice --daemonize ExecStop=/usr/bin/myservice --shutdown ExecReload=/bin/kill -HUP $MAINPID6. 真实案例:Python应用服务化
以Flask应用为例,用gunicorn启动:
[Unit] Description=Flask电商平台API服务 After=network.target redis.service mysql.service [Service] User=appuser Group=appuser WorkingDirectory=/opt/apps/ecommerce Environment="PATH=/opt/apps/venv/bin" ExecStart=/opt/apps/venv/bin/gunicorn -w 4 -b 0.0.0.0:8000 app:app Restart=always [Install] WantedBy=multi-user.target关键点:
- 指定虚拟环境路径
- 设置工作目录
- 使用非root用户运行
- 配置正确的进程数(-w参数)
7. 常见问题解决方案
7.1 服务启动超时
如果服务启动较慢,需要调整超时时间:
[Service] ... TimeoutStartSec=3007.2 日志文件切割
对于长期运行的服务,建议配置日志轮转:
# /etc/logrotate.d/log-cleaner /var/log/log_cleaner.log { daily rotate 30 missingok notifempty compress delaycompress sharedscripts postrotate systemctl kill -s HUP log-cleaner.service endscript }7.3 权限问题处理
遇到权限问题时,可以检查:
- 脚本是否有执行权限
- 服务User/Group配置是否正确
- 文件目录所属用户和权限
- SELinux上下文配置
8. 服务监控与告警
8.1 健康检查配置
[Service] ... ExecStartPost=/bin/bash -c 'echo $MAINPID > /var/run/log-cleaner.pid' ExecStopPost=/bin/rm -f /var/run/log-cleaner.pid8.2 集成Prometheus监控
对于支持metrics的服务:
Environment="PROMETHEUS_MULTIPROC_DIR=/tmp/metrics" ExecStartPre=/bin/mkdir -p /tmp/metrics8.3 邮件告警设置
通过systemd的OnFailure触发告警:
[Unit] OnFailure=status-email@%n.service [Service] ... FailureAction=exit配套的邮件服务模板:
# /etc/systemd/system/status-email@.service [Unit] Description=Send email notification for %i failure [Service] Type=oneshot ExecStart=/usr/local/bin/send-failure-alert.sh %i