上一篇把管理员、服务账号和 SSH 登录边界分开,本篇继续把应用进程从人的终端中剥离。我们用 systemd 描述启动顺序、运行身份、失败重启、资源边界和日志,再用 timer 取代难审计的后台循环。
一、痛点:nohup 只解决断线
nohup command &不知道进程何时就绪、失败后是否重启、应该在哪个目录运行,也不会主动收紧权限。把它写进 rc.local 又缺少依赖、超时和一致的日志入口。生产服务需要“期望状态”:系统启动后运行一个指定版本的进程;进程异常退出时按策略恢复;停止时整个进程组都被回收。
systemd 的 unit 是声明,不是顺序执行脚本。After=network-online.target只规定排序,不会自动拉起目标,因此通常还需Wants=network-online.target;即便网络在线,也不保证外部数据库可用,应用仍须实现带抖动的重试。Requires=会形成更强的失败传播关系,不应因为“看起来保险”就到处使用。
二、原理:前台进程、监管与退出码
服务应以前台模式运行,让 systemd 持有主进程。双重守护化会让 PID 跟踪和停止语义变复杂。Type=simple适合启动后立即运行的进程;能发送 readiness 通知的程序可用Type=notify;一次性准备任务用Type=oneshot。不要用固定睡眠冒充就绪,应让应用暴露健康端点或通知机制。
Restart=on-failure会在非零退出、信号或超时后重启,正常停止不重启。配置错误若快速失败,盲目重启会形成日志风暴,因此同时设置StartLimitIntervalSec与StartLimitBurst。退出码必须有语义:不可恢复的配置错误应尽快失败,由告警召回人,而不是永远重试。
安全选项是纵深防御。NoNewPrivileges阻止获得新特权,PrivateTmp隔离临时目录,ProtectSystem=strict将系统树只读,再用ReadWritePaths精确放行。它们可能影响旧应用,先用systemd-analyze security评估,再在预发布验证。
三、实现:托管一个独立的健康检查服务
下面脚本创建不可登录账号、一个持续写心跳的前台程序,以及完整 unit。程序和配置由 root 管理,运行用户只能写状态目录,避免服务被攻破后修改自己的可执行文件。
#!/usr/bin/env bashset-euopipefail[["$EUID"-eq0]]||{echo'请使用 root 运行'>&2;exit1;}idops-heartbeat>/dev/null2>&1||useradd--system--home/var/lib/ops-heartbeat--shell/usr/sbin/nologin ops-heartbeatinstall-d-m0750-oops-heartbeat-gops-heartbeat /var/lib/ops-heartbeatinstall-d-m0755 /usr/local/lib/ops-heartbeatcat>/usr/local/lib/ops-heartbeat/run<<'EOF' #!/usr/bin/env bash set -euo pipefail trap 'exit 0' TERM INT while true; do tmp=/var/lib/ops-heartbeat/.last.tmp date -u +%FT%TZ > "$tmp" mv "$tmp" /var/lib/ops-heartbeat/last sleep 30 & wait $! done EOFchmod0755 /usr/local/lib/ops-heartbeat/runcat>/etc/systemd/system/ops-heartbeat.service<<'EOF' [Unit] Description=Operations heartbeat example After=local-fs.target [Service] Type=simple User=ops-heartbeat Group=ops-heartbeat ExecStart=/usr/local/lib/ops-heartbeat/run Restart=on-failure RestartSec=5s NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/var/lib/ops-heartbeat [Install] WantedBy=multi-user.target EOFsystemctl daemon-reload systemctlenable--nowops-heartbeat.service systemctl is-active ops-heartbeat.service运行输出:
active定时任务适合用 timer。下面每小时执行一次验收,Persistent=true会在机器关机错过计划后补跑;RandomizedDelaySec分散机群同时启动造成的尖峰。服务仍应幂等,因为补跑、人工触发和重试都可能重复执行。
#!/usr/bin/env bashset-euopipefail[["$EUID"-eq0]]||exit1cat>/etc/systemd/system/ops-verify.service<<'EOF' [Unit] Description=Verify heartbeat freshness [Service] Type=oneshot ExecStart=/usr/bin/test -s /var/lib/ops-heartbeat/last NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes EOFcat>/etc/systemd/system/ops-verify.timer<<'EOF' [Unit] Description=Hourly heartbeat verification [Timer] OnCalendar=hourly Persistent=true RandomizedDelaySec=5m AccuracySec=1m [Install] WantedBy=timers.target EOFsystemctl daemon-reload systemctlenable--nowops-verify.timer systemctl start ops-verify.service systemctl list-timers ops-verify.timer --no-pager运行输出:
NEXT LEFT LAST PASSED UNIT ACTIVATES Tue 2026-08-18 15:03:00 UTC 21m - - ops-verify.timer ops-verify.service四、踩坑:reload、restart 与 daemon-reload
修改 unit 文件后执行daemon-reload让管理器重读定义;它不会重启服务。reload要求应用自己支持重载,restart会重建进程。部署时先systemd-analyze verify,再重启并做健康检查。把秘密直接写在Environment=会出现在 unit 属性中;应使用权限受控的凭据机制,并评估 systemd credentials。
journal 默认收集标准输出和错误。日志要结构化、限量且不含令牌;查看本次启动用journalctl -u UNIT -b,跟随用-f。不要在服务脚本中自行无限追加文件,轮转和磁盘上限应由统一日志层处理。
五、验证:故意制造失败
用systemctl kill --signal=KILL ops-heartbeat验证自动恢复,用只读目录和无效配置验证失败信息,用systemctl show检查实际用户、重启计数与限制。确认停止后没有遗留子进程,重启机器后服务和 timer 都恢复,错过的 timer 任务能补跑且重复执行无害。
进程托管稳定后,下一风险来自磁盘:日志、缓存和删除但仍被占用的文件会悄悄耗尽空间。下一篇将建立容量、inode、文件描述符与日志轮转的排查路径。
参考来源
- systemd.service 手册
- systemd.timer 手册
- systemd.exec 手册
- systemd-analyze 手册
👍 觉得有用就点个赞 + 收藏,方便回头查阅;有疑问直接在评论区留言,我看到都会回。
🚀 本文属于《Linux 服务器运维实战》系列,持续更新,关注不迷路。
📌 文章里的代码都能直接跑。想要可直接 clone 的完整工程 + 配套部署脚本 / 踩坑清单?评论一声或发邮件到cj2664@qq.com,我免费发你。
如果你正好在做类似系统、或有工程化难题想找人做,也欢迎邮件聊一句——我按实际情况评估,能落地的就接单或出方案。评论和邮件都能直接找到我,不用跳别的平台。