hyperf 服务在 kubernetes 中无法优雅关闭的根本原因是 sigterm 未传至 php 主进程或未被正确捕获;需确保 php 为 pid 1 或显式转发信号,并在应用内注册 sigterm 处理器调用 server::shutdown(),同时合理配置 prestop 和 terminationgraceperiodseconds。

Hyperf 服务在 Kubernetes 中无法优雅关闭,根本原因不是“没配 preStop”,而是 SIGTERM 信号根本没传到 PHP 主进程,或者传到了但没被正确捕获 —— 这两点必须同时解决,否则任何钩子都只是障眼法。
为什么 Hyperf 容器收不到 SIGTERM
绝大多数 Hyperf 镜像使用 supervisord 或 shell 脚本(如 start.sh)启动,导致 PHP-FPM 或 php bin/hyperf.php start 成为子进程。而 Linux 下,SIGTERM 默认只发给 PID 1 进程(即 supervisord 或 sh),不会自动转发给子进程。
- 现象:执行
kubectl delete pod后,容器瞬间退出,日志里看不到 “收到终止信号” 类提示 - 验证方式:进容器执行
ps auxf,确认主进程是否为php;如果不是,信号大概率被拦截 - 修复方向:要么让 PHP 进程直接作为 PID 1(推荐),要么在启动脚本中显式转发信号(需
trap+kill -TERM "$PID")
preStop 钩子该写什么命令
preStop 不是万能补丁,它不能替代信号处理,但可以做三件事:等 kube-proxy 同步、触发应用层主动下线、或兜底发送信号。对 Hyperf 来说,最稳妥的组合是:
- 先 sleep 3–5 秒,确保 Service Endpoint 已从 kube-proxy 规则中移除(避免新请求打进来)
- 再用
curl或kill向 Hyperf 的管理端点或主进程发通知(如果启用了 Admin Server) - 不建议只写
sleep—— 单纯延迟不能保证业务逻辑真正退出;也不建议只写kill -TERM 1—— 如果 PID 1 是 shell,可能无效
示例配置片段:
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
sleep 3
kill -TERM $(cat /var/run/php-fpm.pid 2>/dev/null || pgrep -f "hyperf.php start" | head -n1) 2>/dev/null || true
Hyperf 应用内必须注册 SIGTERM 处理器
即使信号能到达 PHP 进程,Hyperf 默认也不会响应 SIGTERM。你得在启动逻辑中显式监听并调用 Server::shutdown()。
- 关键位置:在
config/autoload/server.php的callbacks中加入WorkerStart回调,或在bin/hyperf.php的start命令后注入 - 必须调用
$server->shutdown(),而不是仅exit—— 否则协程、连接池、定时器等资源不会释放 - 注意:PHP 的
pcntl_signal在多进程模式下需配合pcntl_async_signals(true)(PHP 7.1+)
最小可行代码段(放入 ServerProvider 的 beforeStart):
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function ($signal) {
\Hyperf\Utils\Coroutine::create(function () {
\Hyperf\Contract\ServerInterface::class::shutdown();
// 可选:等待 DB 连接池清空、未完成协程结束等
while (\Hyperf\Utils\Coroutine::stats()->running > 0) {
usleep(10000);
}
exit(0);
});
});
terminationGracePeriodSeconds 设置多少才够用
默认 30 秒经常不够 —— Hyperf 的 shutdown 流程包含连接池归还、协程等待、Redis 发布订阅断开、数据库事务提交等,实测常需 45–90 秒。
- 不要盲目设成 120 —— 过长会拖慢滚动更新节奏,且宽限期一过仍会被
SIGKILL强杀 - 合理值 =
preStop 执行时间+Hyperf shutdown 实际耗时+ 5 秒缓冲(网络同步误差) - 上线前务必压测:模拟高并发请求下,从发
kubectl delete到容器真正退出的时间 - 可在 Pod spec 中直接设置:
terminationGracePeriodSeconds: 60
最容易被忽略的一点:这个字段是 Pod 级别的,不是 Deployment 级别,改了 Deployment yaml 后必须重新 apply,旧 Pod 不会自动继承新值。











