首页 / 内容指南 / 当前文章

systemd服务卡在deactivating或stop-sigterm超时:停止排查指南

发布于 2026-08-26 · Content Fleet 编辑部

先运行 systemctl status 服务 -l --no-pager、systemctl show 和 journalctl -u 服务 -b,保存 ActiveState、SubState、Result、MainPID、ControlPID、TimeoutStopUSec、KillMode 与当前 job。用 systemd-cgls 或 systemctl status 列出 unit cgroup 内所有 PID,再用 ps 查看状态、等待通道和父子关系。如果 ExecStop= 在运行,先查 ControlPID;若主进程收到 SIGTERM 但不退出,检查信号处理、线程栈、锁、外部依赖和 I/O。只有确认优雅停止不可能且风险可控时才升级信号,并在事后修复根因。

直接答案

先运行 systemctl status 服务 -l --no-pagersystemctl showjournalctl -u 服务 -b,保存 ActiveState、SubState、Result、MainPID、ControlPID、TimeoutStopUSec、KillMode 与当前 job。用 systemd-cglssystemctl status 列出 unit cgroup 内所有 PID,再用 ps 查看状态、等待通道和父子关系。如果 ExecStop= 在运行,先查 ControlPID;若主进程收到 SIGTERM 但不退出,检查信号处理、线程栈、锁、外部依赖和 I/O。只有确认优雅停止不可能且风险可控时才升级信号,并在事后修复根因。

一、保存当前unit与job状态

执行:

systemctl status example.service --no-pager -l
systemctl show example.service -p ActiveState -p SubState -p Result \
  -p MainPID -p ControlPID -p ExecMainStatus -p TimeoutStopUSec -p KillMode
systemctl list-jobs
journalctl -u example.service -b --no-pager -n 300

MainPID 是服务主进程,ControlPID 常用于当前执行的控制命令,例如 ExecStop。若 ControlPID 非零,停止脚本本身可能卡住;若 MainPID 仍活着,则查主程序退出路径。不要把两者混为一个 PID。

二、读取生效配置而不是手边文件

使用 systemctl cat example.service 查看主 unit 和所有 drop-in,再用 systemctl show -p FragmentPath -p DropInPaths -p ExecStop -p TimeoutStopUSec -p KillMode 确认运行配置。管理员修改后未 daemon-reload,磁盘文件与 systemd 内存状态可能不同。

检查 Type=PIDFile=GuessMainPID= 和服务是否自行 daemonize。MainPID 识别错误会让 systemd 给错误进程发信号,真正工作进程仍在。现代服务优先以前台模式运行,避免不必要的 fork 与 PID 文件竞态。

三、列出整个cgroup内的进程

systemd 管理的是 unit cgroup,不只是一个 PID。使用:

systemd-cgls --unit example.service
systemctl status example.service --no-pager -l
ps -eo pid,ppid,stat,wchan:32,etimes,cmd --forest

查明主进程、worker、辅助脚本和残留 shell。服务主进程已退出但 cgroup 仍有子进程时,unit 可能继续 deactivating。子进程若被错误放到其他 scope 或双重 fork 逃逸,则应修正进程管理方式,而不是靠名称匹配杀进程。

四、确认ExecStop是否阻塞

ExecStop= 应执行明确、有界且可重复的停止操作。如果脚本等待网络 API、无超时轮询 PID、读取 FIFO,或调用一个永不返回的管理命令,systemd 会等待它。根据 ControlPID 查看脚本的系统调用、子进程与日志。

不要在 ExecStop 中实现“等待到永远”。给外部调用和循环设置内部截止时间,退出码和日志明确。若应用本身能正确处理 SIGTERM,可能无需复杂停止脚本;让 systemd 直接发信号通常更可靠。

五、理解TimeoutStopSec

TimeoutStopSec= 同时约束停止命令以及服务终止阶段,具体行为按 unit 配置和 systemd 版本解释。超时后 systemd 可按 KillMode 和发送信号策略继续处理。过短会打断正常刷盘,过长则延迟发布和主机关闭。

先测正常停止的中位数与高分位,再按数据安全和恢复目标设置。不要把超时无限放大;若停止时间持续增长,应查积压、锁、外部依赖和存储延迟。

六、检查程序是否处理SIGTERM

优雅停止通常从 SIGTERM 开始。应用应停止接收新请求、等待有界的在途任务、刷新必要状态并退出。若代码忽略信号、信号只到达父 shell、主线程被锁住或 handler 做了不安全阻塞操作,就无法正常退出。

在测试环境以与 systemd 相同的用户和参数发送 TERM,观察应用日志、线程和退出时间。容器或 shell 包装脚本必须使用 exec 把主程序替换为 PID,避免 shell 吞掉或不转发信号。

七、KillMode决定信号发给谁

KillMode=control-groupmixedprocessnone 会影响停止时信号范围。默认控制组语义通常最安全,能避免子进程遗留。设置 process 只处理主进程,worker 可能继续占端口和文件;none 则把清理责任完全交给服务。

不要为了保留某个辅助进程随意改成 process/none。若组件生命周期不同,应拆成独立 unit,用依赖关系管理,而不是让一个服务退出后遗留无人管理的进程。

八、检查KillSignal与最终信号

KillSignal= 指定初始停止信号,通常保持 SIGTERM;FinalKillSignal= 可用于最终阶段和故障转储场景。修改为应用自定义信号前,确认程序文档与实际 handler。错误信号可能触发 reload 而不是退出。

SendSIGKILL=no 会让超时进程继续存在,可能阻塞后续启动或关机。只有明确理解恢复策略时才使用。最终强制信号无法让程序保存状态,数据库和队列服务尤其需要评估一致性。

九、D状态进程不能靠SIGKILL解决

ps 的 STAT 为 D 通常表示不可中断睡眠,常见于磁盘、NFS、块设备或内核驱动等待。信号包括 SIGKILL 要等进程返回可中断状态后才能生效。此时继续发送信号不会改善问题。

查看 wchan/proc/PID/stack(需权限)、内核日志、存储和挂载状态。若是 NFS 或块设备故障,先恢复底层 I/O 或按基础设施恢复流程处理。不要在未确认影响时强制卸载或重启生产主机。

十、线程锁和在途请求

进程为 S/R 状态但不退出时,采集线程栈、应用诊断和连接状态。常见原因包括线程池等待永不完成的任务、数据库事务、死锁、无限重试和长连接没有截止时间。检查停止流程是否先拒绝新流量,再等待有界 drain。

为每个关闭阶段记录开始、完成和剩余任务数。没有分阶段日志时,只能看到一个总超时,难以判断卡在 HTTP、队列、数据库还是日志刷新。

十一、发布期间的正确停止顺序

负载均衡环境应先把实例从服务发现或流量入口摘除,等待连接排空,再执行 stop。若 stop 开始后仍有新请求进入,优雅期限可能永远用尽。readiness 失败与进程退出之间应有明确传播等待。

滚动发布一次只处理受控实例,设置最大不可用数与回滚条件。单实例停止超时只隔离该实例,不要同时强杀整个集群。部署工具应保存 systemd job 和服务日志。

十二、避免restart制造停止风暴

systemctl restart 本质上包含 stop 和 start。旧进程未完全退出时,新进程可能抢占同一端口、PID 文件或数据目录。不要在 restart 卡住后反复执行 restart;这会叠加 job 或让操作员误判状态。

先观察 systemctl list-jobs,等待、取消明确的管理 job需谨慎。解决旧进程后确认 cgroup 清空、端口释放和文件锁解除,再启动新实例。

十三、强制终止前的风险清单

确认服务是否正在写数据库、文件、索引、WAL 或队列;是否有可恢复副本和一致性检查;是否能先隔离流量;是否保存了堆栈、日志和进程信息。得到明确变更授权后,才选择逐级信号或 systemctl kill 的目标范围。

强杀后必须验证数据恢复、端口、残留进程和下一次正常停止。不要把“服务重新起来”当作根因解决;记录为什么 TERM 无效、超时是否合理以及代码/配置修复项。

十四、修复后的验收清单

在测试和 canary 环境覆盖空闲停止、满载停止、长连接、队列积压、外部依赖不可用和存储变慢。确认每阶段有截止时间,MainPID 与子进程全部退出,cgroup 清空,端口释放,Result 正确且没有 SIGKILL。

再执行 restart、滚动发布和主机关闭,测量停止高分位。监控 deactivating 持续时间、stop timeout、强制信号、残留 cgroup、D 状态进程和部署耗时;当趋势变差时在到达硬超时前告警。

常见问题 FAQ

systemctl stop卡住可以直接Ctrl+C吗?

Ctrl+C通常只中断当前客户端等待,不一定取消 systemd 中的 job。先查看 systemctl list-jobs 和 unit 状态,避免误以为停止已取消。

kill -9为什么没有作用?

进程处于不可中断 D 状态时,要等底层 I/O 返回后才处理信号。应查存储、NFS、驱动和内核,而不是重复发送 SIGKILL。

TimeoutStopSec越大越安全吗?

不一定。它能给正常清理更多时间,也会延长故障和发布。应依据停止耗时分布与数据安全设置,并修复无界等待。

主进程退出了为什么仍是deactivating?

cgroup 中可能还有子进程,或 ExecStop 的 ControlPID 仍运行。查看整个 unit cgroup 和 ControlPID。

KillMode=process能否避免误杀worker?

它可能留下无人管理的 worker。生命周期不同的组件更适合拆成独立 unit,而不是依赖遗留进程。

总结

systemd 停止卡住的排查顺序是:保存 job 和生效配置,区分 MainPID 与 ControlPID,查看整个 cgroup,再分析 ExecStop、信号处理、KillMode、线程锁和底层 I/O。强制终止只能作为受控恢复动作,不能替代根因修复。通过有界关闭阶段、正确流量摘除和停止回归测试,才能让发布和关机稳定可预测。

官方资料

  • systemd.service:https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
  • systemd.kill:https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html
  • systemd.exec:https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
  • systemctl:https://www.freedesktop.org/software/systemd/man/latest/systemctl.html