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.html(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/

目录
相关文章
|
2月前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
275 78
|
23天前
|
Ubuntu NoSQL Linux
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
108 6
《docker基础篇:3.Docker常用命令》包括帮助启动类命令、镜像命令、有镜像才能创建容器,这是根本前提(下载一个CentOS或者ubuntu镜像演示)、容器命令、小总结
|
2月前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序
|
2月前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
|
2月前
|
存储 Kubernetes 开发者
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
Docker 是一种开源的应用容器引擎,允许开发者将应用程序及其依赖打包成可移植的镜像,并在任何支持 Docker 的平台上运行。其核心概念包括镜像、容器和仓库。镜像是只读的文件系统,容器是镜像的运行实例,仓库用于存储和分发镜像。Kubernetes(k8s)则是容器集群管理系统,提供自动化部署、扩展和维护等功能,支持服务发现、负载均衡、自动伸缩等特性。两者结合使用,可以实现高效的容器化应用管理和运维。Docker 主要用于单主机上的容器管理,而 Kubernetes 则专注于跨多主机的容器编排与调度。尽管 k8s 逐渐减少了对 Docker 作为容器运行时的支持,但 Doc
154 5
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
|
23天前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
97 11
|
2月前
|
关系型数据库 应用服务中间件 PHP
实战~如何组织一个多容器项目docker-compose
本文介绍了如何使用Docker搭建Nginx、PHP和MySQL的环境。首先启动Nginx容器并查看IP地址,接着启动Alpine容器并安装curl测试连通性。通过`--link`方式或`docker-compose`配置文件实现服务间的通信。最后展示了Nginx配置文件和PHP代码示例,验证了各服务的正常运行。
68 3
实战~如何组织一个多容器项目docker-compose
|
3月前
|
运维 Kubernetes Docker
深入理解容器化技术:Docker与Kubernetes的协同工作
深入理解容器化技术:Docker与Kubernetes的协同工作
91 14
|
2月前
|
数据建模 应用服务中间件 nginx
docker替换宿主与容器的映射端口和文件路径
通过正确配置 Docker 的端口和文件路径映射,可以有效地管理容器化应用程序,确保其高效运行和数据持久性。在生产环境中,动态替换映射配置有助于灵活应对各种需求变化。以上方法和步骤提供了一种可靠且易于操作的方案,帮助您轻松管理 Docker 容器的端口和路径映射。
159 3
|
2月前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
191 7

热门文章

最新文章