我有一台让我头疼的服务器。它托管了几个网站:这些网站要么基于 php,要么基于 java。我的设置是使用 apache2 + suPhp 来管理 php 网站,使用 apache2 + mod_proxy + apache tomcat 来管理 java 应用程序。
过去几周,我发现了一些奇怪的行为。有时,我发现一个 httpd 进程的 CPU 占用率会达到 30-40%,内存占用率会超过 70%。我没有看到任何 php 或 java 进程占用额外的资源,所以我(天真地)认为问题与 php 或 java 代码无关。这些峰值似乎发生在随机时间,并且间隔随机;有时一天发生多次,有时一整周都没有发生任何情况。
我注意到的另一件奇怪的事情是,当我手动kill -9
关闭正在运行的 httpd 进程时,另一个 httpd 进程会在几秒钟内弹出,并开始占用大量内存和 CPU。我可以重复几次,直到它自动停止运行 :/
所以,我实际上有几个问题:
- 有人能告诉我如何追踪这种行为的原因吗?最终,我想看看是哪种请求导致了这些问题。我查看了 httpd 访问和错误日志,但并没有发现任何异常。我对研究这种类型的问题并不熟悉,所以即使是对你来说可能非常明显的事情也可能有所帮助。
- 有没有办法限制单个 httpd 子进程可能消耗的资源量?或者当它超过一定内存量时直接将其终止?
- 与上一个问题相关,我是否可以将 oom-daemon 配置为对 httpd 进程更敏感,而对其他进程的敏感度更低?我之所以问这个问题,是因为在找到合适的解决方案之前,我想确保当 httpd 进程再次开始出现故障时,它不会终止我的 java 进程。
更新
lsof
最近我发现导致此问题出现的请求来自 googlebot。以下是消耗所有可用内存和 CPU 的进程的输出摘录:
httpd 18588 nobody 37u IPv6 96675092 TCP myhost.com:http->crawl-66-249-76-96.googlebot.com:56730 (ESTABLISHED)
我设置了 mod_security 来记录来自 googlebots 似乎使用此规则的 IP 范围的所有请求<VirtualHost>
:
SecRule REMOTE_ADDR "@ipMatch 66.249.76.0/24" phase:1,id:1,auditlog,allow
我让服务器保持这种状态一段时间。在此期间,httpd 进程多次出现峰值,甚至 OOM 守护进程开始终止进程(httpd、java,现在它甚至不时关闭 mysql)。然后我提取了所有被 googlebot 命中的 URL,并创建了一个小脚本来卷曲所有这些 URL,希望我可以让 httpd 进程出现峰值,从而找到导致这些问题的请求。
不幸的是,这并没有发生——所有的请求都很快返回,并且 CPU 和内存使用率远不及 googlebot 访问服务器时的水平。
所以我认为有两种可能性:
问题是由于特定的 HTTP 标头造成的。我的脚本不会复制这些标头,它只是使用普通的 curl,没有其他标头。
导致问题的请求没有被记录。据我所知,情况不应该如此,因为我告诉 mod_security 在第 1 阶段记录请求,即在 apache 实际处理请求之前。
有人还有其他想法吗?
更新2:
该进程的 strace 输出:
brk(0x3568c000) = 0x3568c000
brk(0x356ca000) = 0x356ca000
brk(0x35708000) = 0x35708000
brk(0x35746000) = 0x35746000
brk(0x35784000) = 0x35784000
brk(0x357c2000) = 0x357c2000
brk(0x35800000) = 0x35800000
brk(0x3583e000) = 0x3583e000
...
brk(0x3587c000) = 0x3587c000
brk(0x358ba000) = 0x358ba000
brk(0x358f8000) = 0x358f8000
brk(0x35936000) = 0x35936000
brk(0x35974000) = 0x35974000
brk(0x359b2000) = 0x359b2000
brk(0x359f0000) = 0x359f0000
brk(0x35a2e000) = 0x35a2e000
brk(0x35a6c000) = 0x35a6c000
brk(0x35aaa000) = 0x35aaa000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f2028000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f2005000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1fe2000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1fbf000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1f9c000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1f79000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1f56000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1f33000
...
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1f10000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1eed000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1eca000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1ea7000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1e84000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1e61000
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f72f1e3e000
+++ killed by SIGKILL +++
提前致谢。
答案1
我没有看到任何 php 或 java 进程占用额外的资源
您认为这完全值得检查,这表明您要么对 CGI 漏洞了解很多,要么对 Web 服务/流程管理了解甚少。
最后,我想看看是哪种请求导致了这些问题
这是一个明智的起点。最简单的解决方案是安装 mod_security 并将其配置为记录传入请求(Apache 仅在响应分派时记录)。还有其他方法,例如嗅探流量(pastMon、Wireshark)或在反向代理上记录。
有没有办法限制单个 httpd 子进程可能消耗的资源量
不是直接的,但您应该将 LimitInternalRecursion、LimitRequestBody、LimitRequestFields、LimitRequestFieldSize、LimitRequestLine、MaxKeepAliveRequests、MaxRequestsPerChild 和 Timeout 设置为合理的值。
我是否可以将 oom-daemon 配置为对 httpd 进程更具触发性
搞乱 OOM Killer 几乎总是一个坏主意。即使您认为自己知道自己在做什么。在没有网络服务器的情况下,您的 Java 能做任何有用的事情吗?如果是,那么也许您应该考虑在单独的机器上运行它们。
答案2
首先:您必须限制可以启动的 Apache 进程的最大数量。这可以通过降低来实现MaxSpareServers
。从低开始(3-10,然后稍微增加一点,直到性能开始下降)。MinSpareServers
应该是 2。
PHP 通常作为 apache 模块 (mod_php) 运行,这意味着它与 apache 运行在相同的地址空间中,我的意思是您将只看到 apache 进程,并且内部也会运行 php。您可以gdb
在这些进程上运行并backtrace
在内部运行gdb
以查看它们在做什么。您也可以使用pstack
它。如果您注意到哪个 URL 发生了这种情况(检查 access.log 以找出 URL),那么您可以开始调试 php 代码。搜索 php 调试器或/和 php 分析器。
答案3
终于找到答案了。原来我们托管的一个网站有一个可疑的 .htaccess 隐藏在某个地方,里面有一个 RewriteRule,在某些情况下会触发无限循环。