必须同时满足三件事:readinessProbe正确配置、SIGUSR1热更信号被Master进程捕获、Worker进程退出前不主动清理协程上下文;否则滚动更新会丢请求、扩容后状态串、日志指标错乱。

Hyperf 应用在 Kubernetes 上做集群部署,关键不是“能不能跑”,而是“重启时会不会丢请求”“扩容后状态会不会串”“日志和指标能不能对得上人”。直接结论:必须同时满足三件事——readinessProbe 正确配置、SIGUSR1 热更信号被 Master 进程捕获、Worker 进程退出前不主动清理协程上下文。
如何让 K8s 滚动更新不中断流量
K8s 的滚动更新本身不保证平滑,它只保证旧 Pod 逐个终止、新 Pod 逐个就绪。Hyperf 要不丢请求,得靠自己“拖住”旧 Worker 直到当前请求结束。
-
readinessProbe必须指向一个真实反映“已加载完、能接请求”的端点,比如/health,且该接口不能只是返回{"status":"up"},而要检查 Swoole Server 是否已启动、协程池是否 ready - Pod 终止前,K8s 会先发
SIGTERM;Hyperf 必须有WorkerStopHandler捕获它,并设置Context::set('worker.stopping', true),否则 Worker 会立刻退出,正在处理的请求被强杀 - Deployment 中的
terminationGracePeriodSeconds建议设为 30+,给 Worker 留出足够时间处理完长尾请求(如上传、下游 HTTP 调用) - 避免在
__destruct或finally块里做阻塞操作(如同步写 Redis),它们会在协程退出时同步执行,拖慢整个 Worker 退出流程
为什么 SIGUSR1 热更在 K8s 里容易失效
本地 kill -USR1 {master_pid} 能触发滚动重启,但在 K8s 里常失败,根本原因是信号没发到 Master 进程,而是被容器 init 进程或 sidecar 吞了。
- Hyperf 的
ReloadHandler注解只监听SignalHandlerInterface::MASTER,所以必须确保php bin/hyperf.php start启动的是 Master 进程(即未加--daemon),且容器 entrypoint 没用sh -c包裹导致进程树错乱 - K8s 无法直接向 Pod 内指定进程发信号,
kubectl exec -it pod -- kill -USR1 1只在 PID 1 是 Hyperf Master 时有效;更可靠的做法是用 readinessProbe + 镜像版本更新触发滚动更新,而非手动发信号 - 如果用了
hyperf/process启动了额外子进程(如定时任务),它们不会响应SIGUSR1,需单独注册信号监听或改用消息通知机制
Hyperf 多实例共享状态的典型翻车点
Hyperf 常驻内存模型下,多个 Pod 实例看似隔离,但一旦引入外部共享资源,就极易出现竞态或脏数据。
- Redis 缓存键未带 Pod ID 或 Worker ID 前缀,导致 A Pod 清缓存误删 B Pod 正在用的数据
- 使用
AtomicCounter或Channel等进程内原子结构做限流/计数,它们只在单个 Pod 内有效,集群维度完全失效 - 配置中心(如 Nacos)监听变更后,用
Config::set()动态覆盖配置,但不同 Pod 可能收到不同顺序的推送,造成短暂不一致 - 日志写入文件时没加
process_id或pod_name字段,排查问题时分不清哪条日志来自哪个实例
最易被忽略的其实是健康探针路径与实际业务逻辑的耦合程度——/health 返回 200,不代表所有协程都 ready,也不代表数据库连接池已 warm up。真正在意平滑性的团队,都会在 readiness 探针里加一层“业务就绪”判断,比如查一次 Redis 并验证连接池可用性。这步省不得。


















