PHP 容器化引发线上 502 错误状态码的修复

简介: 公司的业务逐步推广容器化,其中 PHP 业务的 Pod,由以下 5 个容器组成:nginx、php-fpm、metric(监控)、jaeger(链路追踪)、log(日志收集)。线上滚动部署的时候报错!
最后更新时间 2023-01-24.

背景

笔者所在公司技术栈为 Golang + PHP,目前部分项目已经逐步转 Go 语言重构,部分 PHP 业务短时间无法用 Go 重写。

相比 Go 语言,互联网公司常见的 Nginx + PHP-FPM 模式,经常会出现性能问题——

特别是我们的活动业务,尽管底层用了鸟哥的 Yaf 框架,

但由于业务逻辑繁重,即使框架层面上完全零损耗,常常支撑不了流量高峰。

一旦某个时间段开启活动,虚拟机的扩容真的非常痛苦。

SRE、开发、QA 三方经常需要因为某个运营活动的进行,提前压测预估容量。

目前活动业务已经逐步用 Go 语言改造,此处不具体展开,防止跑题哈哈。

正因为 PHP 虚拟机模式,每次扩容需要流量剔除、克隆、操作负载均衡、验证流量等等,

推进 PHP 容器化就显得格外重要。

公司在去年年中,已经开始进行 PHP 容器化,不过由于项目优先级以及人力原因,进度较为迟缓。

事情经过

  1. 某项目进行 PHP 容器化改造,切换少许流量到容器中
  2. 逐步加大灰度流量
  3. 某一天开发上线新功能,发现滚动部署过程中存在 502 错误

分析原因

nginx 发生了 502,很多时候是后端,也就是 php-fpm 不在工作。

我们的 PHP 业务的 Pod,由以下 5 个容器组成:

  • nginx
  • php-fpm
  • metric(监控)
  • jaeger(链路追踪)
  • log(日志收集)

滚动时存在关闭旧 Pod 启动新 Pod 的过程,借助 K8s 官方文档 的描述,我们看看 Pod 结束的一个例子:

  1. 你使用 kubectl 工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期是默认值(30 秒)。
  2. API 服务器中的 Pod 对象被更新,记录涵盖体面终止限期在内 Pod 的最终死期,超出所计算时间点则认为 Pod 已死(dead)。 如果你使用 kubectl describe 来查验你正在删除的 Pod,该 Pod 会显示为 "Terminating" (正在终止)。 在 Pod 运行所在的节点上:kubelet 一旦看到 Pod 被标记为正在终止(已经设置了体面终止限期),kubelet 即开始本地的 Pod 关闭过程。
  3. 在 kubelet 启动体面关闭逻辑的同时,控制面会将关闭的 Pod 从对应的 EndpointSlice(和 Endpoints)对象中移除,过滤条件是 Pod 被对应的服务以某 选择算符选定。 ReplicaSet 和其他工作负载资源不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。 关闭动作很慢的 Pod 也无法继续处理请求数据, 因为负载均衡器(例如服务代理)已经在终止宽限期开始的时候将其从端点列表中移除。
  4. 超出终止宽限期限时,kubelet 会触发强制关闭过程。容器运行时会向 Pod 中所有容器内仍在运行的进程发送 SIGKILL 信号。 kubelet 也会清理隐藏的 pause 容器,如果容器运行时使用了这种容器的话。
  5. kubelet 触发强制从 API 服务器上删除 Pod 对象的逻辑,并将体面终止限期设置为 0 (这意味着马上删除)。
  6. API 服务器删除 Pod 的 API 对象,从任何客户端都无法再看到该对象。

通常情况下,容器运行时会发送一个 TERM 信号到每个容器中的主进程。很多容器运行时都能够注意到容器镜像中 STOPSIGNAL 的值,并发送该信号而不是 TERM。一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后 Pod 就会被从 API 服务器上移除。 如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启,集群会从头开始重试,赋予 Pod 完整的体面终止限期。

所以,我们可以发现:

  1. nginx、php-fpm 收到 TERM 信号后,不做请求的优雅处理,直接强制退出了!强制退出的原因,可以移步这俩文档:nginx - http://nginx.org/en/docs/control.htmlphp-fpm - https://linux.die.net/man/8/php-fpm
  2. 参考上面的第 3 点,在容器运行时发送 TERM 信号后,也同时移除 endpoint,此处不是串行的。一旦 endpoint 移除的时间晚了,流量就会剔不干净,到达了 nginx 后,php-fpm 进程已经退出从而导致 502 的产生。

解决办法

有了上面的分析,解决起来就方便多了!

查看上面文档,我们可以了解到,nginx 和 php-fpm 喜欢 QUIT 信号,均可做到 graceful shutdown。

只需要在 Dockerfile 指定 STOPSIGNAL SIGQUIT 即可。

但我记得之前封装的 php-fpm 镜像使用的是社区维护版本,应该加上了才对。

而我看了线上的 Dockerfile,nginx 使用的是社区维护的,已经配置了 STOPSIGNAL SIGQUIT,没问题!

但是 PHP 由于之前的 alpine linux 因为监控扩展、链路追踪扩展编译环境的原因,使用了 CentOS 镜像。

镜像的来源都是自己打包的,并没有指定退出信号!

加上了之后,发现不会有 502 了!

至此,问题解决。

延伸思考

本来文章到此结束,突然想到线上的 Go 服务会不会有同样的问题?

想了一下,也好办!

要么自行处理 TERM 信号,做好优雅退出的姿势!

要么学 nginx,也用 QUIT 信号并做好优雅退出处理,Dockerfile 指定 STOPSIGNAL。

至于怎么处理信号,Go 实现起来非常舒服:

ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT)
<-ch
// 收到信号了(还可以根据信号类型做不同的处理逻辑),自行处理剩余任务实现优雅退出。

嗯,channel 大法好。


文章来源于本人博客,发布于 2022-09-03,原文链接: https://imlht.com/archives/398/
目录
相关文章
|
14天前
|
Cloud Native 持续交付 Docker
云原生之旅:Docker容器化实战指南
【8月更文挑战第29天】本文将引领你进入云原生技术的世界,以Docker容器化为切入点,深入浅出地介绍如何利用Docker进行应用的打包、部署及管理。我们将通过实际代码示例,一步步展示Docker镜像的构建过程,以及如何运行和管理这些容器。无论你是初学者还是有一定经验的开发者,都能从中获得宝贵的知识和实操经验。
|
9天前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
9天前
|
应用服务中间件 nginx Docker
Docker同一台宿主机容器通信-通过容器名称互联
本文详细介绍了如何通过容器名称实现同一宿主机上容器间的互联,并提供了实战案例。首先,文章解释了容器间通过自定义名称访问的原理,随后演示了创建并连接Tomcat与Nginx容器的具体步骤。此外,还讨论了配置中可能出现的问题及解决方案,包括避免硬编码IP地址和使用自定义容器别名来增强系统的灵活性与可维护性。通过这些实践,展示了如何高效地配置容器间通信,确保服务稳定可靠。
16 1
Docker同一台宿主机容器通信-通过容器名称互联
|
6天前
|
Cloud Native 持续交付 Docker
云原生技术实践:Docker容器化部署教程
【9月更文挑战第4天】本文将引导你了解如何利用Docker这一云原生技术的核心工具,实现应用的容器化部署。文章不仅提供了详细的步骤和代码示例,还深入探讨了云原生技术背后的哲学,帮助你理解为何容器化在现代软件开发中变得如此重要,并指导你如何在实际操作中运用这些知识。
|
9天前
|
存储 Unix 虚拟化
Docker容器简介
Docker是一种轻量级的虚拟化技术,它通过容器化应用,提高了硬件资源利用率,简化了应用的部署、运输和运行,且与虚拟机相比,具有更快的交付速度和更低的资源消耗。
24 2
|
21天前
|
关系型数据库 MySQL 应用服务中间件
[Docker]容器内文件修改
【8月更文挑战第21天】[Docker]容器内文件修改
42 13
|
15天前
|
存储 安全 Ubuntu
Docker 镜像与 Docker 容器的区别
【8月更文挑战第27天】
56 5
|
15天前
|
存储 Kubernetes 安全
如何与不同节点共享 Docker 容器
【8月更文挑战第27天】
22 5
|
14天前
|
Shell Docker 容器
docker给容器分配固定ip
docker给容器分配固定ip
24 3
|
15天前
|
运维 Ubuntu Shell
掌握Docker容器的创建:从镜像到实例
【8月更文挑战第27天】
74 4