Xdebug Timeout Question

AI 摘要: 本文讨论了从反向代理Nginx请求简单的PHP脚本时,出现“Broken pipe”错误的问题,并提供了解决方案。

1. 问题描述

如图所示,从反向代理Nginx请求一个简单的PHP脚本,告警提示:There was a problem sending 193 bytes on socket 7: Broken pipe

2. 检查端口连通性

由于Nginx和FPM都是基于Alpine自行构建的,在Nginx容器中,没有telnet工具,可以使用nc工具替代;

nc的安装,可以基于https://pkgs.alpinelinux.org/contents查询命令,其他Alpine包也可以通过检索安装;

fpm为fpm所在主机号,Docker中可以直接用服务名作为主机号

1
2
3
4
5
6
7
8
// 安装strace和nc
apk add strace netcat-openbsd

-z  Zero-I/O mode (scanning)
-v  Verbose

/etc/nginx # nc -zv fpm 9000
Connection to fpm 9000 port [tcp/*] succeeded!

3. 启用strace调试准备

由于strace在容器中默认是禁用的,如果单独的Doker命令run起来的,可以加上--privileged Give extended privileges to this container

docker-composer.yml配置中,通过加入SYS_PTRACE能力支持,更多可以参考:https://docs.docker.com/compose/compose-file/#cap_add-cap_drop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
version: "3.7"
services:
    nginx:
        ports:
          - 86:86
        networks:
          phpfpm-net:
          proxy-net:
              aliases:
                - phpfpm-nginx
                - phpngx
        volumes:
          - /data/www:/data/www
          - /data/learn:/data/learn
        depends_on:
          - fpm
        restart: always
    fpm:
        cap_add:
            - SYS_PTRACE
        security_opt:
            - seccomp:unconfined
        build:
            context: ./fpm
        image: tkstorm/phpfpm
        networks:
          phpfpm-net:
        volumes:
          - /data/www:/data/www
          - /data/learn:/data/learn
        restart: always
networks:
    phpfpm-net:
        name: phpfpm-net
    proxy-net:
        external: true
        name: proxy-net

4. 进入fpm容器中跟踪调试

1
2
// 调试www相关进程
strace -s 1024 -p $(pgrep www|tr '\n' ','|sed 's/,$//')

通过curl请求phpinfo.php脚本页面时候,直接终止在recvfrom(7这个系统调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[pid    15] writev(6, [{iov_base="[15] <- eval -i 12 -- KHN0cmluZy"..., iov_len=67}, {iov_base=NULL, iov_len=0}], 2) = 67
[pid    15] getpid()                    = 15
[pid    15] writev(6, [{iov_base="[15] -> <response xmlns=\"urn:deb"..., iov_len=223}, {iov_base=NULL, iov_len=0}], 2) = 223
[pid    15] write(7, "257\0<?xml version=\"1.0\" encoding"..., 262) = 262
[pid    15] recvfrom(7, "eval -i 13 -- KHN0cmluZykoJF9TRV"..., 128, 0, NULL, NULL) = 59
[pid    15] getpid()                    = 15
[pid    15] writev(6, [{iov_base="[15] <- eval -i 13 -- KHN0cmluZy"..., iov_len=67}, {iov_base=NULL, iov_len=0}], 2) = 67
[pid    15] getpid()                    = 15
[pid    15] writev(6, [{iov_base="[15] -> <response xmlns=\"urn:deb"..., iov_len=236}, {iov_base=NULL, iov_len=0}], 2) = 236
[pid    15] write(7, "270\0<?xml version=\"1.0\" encoding"..., 275) = 275
[pid    15] recvfrom(7, strace: Process 6 detached

再往上找这个文件描述符7,会发现是由于打开了一个192.168.65.2:9801的套接字

1
2
3
4
5
6
7
8
[pid    15] socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 7
[pid    15] fcntl(7, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
[pid    15] fcntl(7, F_SETFD, FD_CLOEXEC) = 0
[pid    15] connect(7, {sa_family=AF_INET, sin_port=htons(9801), sin_addr=inet_addr("192.168.65.2")}, 16) = -1 EINPROGRESS (Operation in progress)
[pid    15] poll([{fd=7, events=POLLIN|POLLPRI|POLLOUT}], 1, 200) = 1 ([{fd=7, revents=POLLOUT}])
[pid    15] getpeername(7, {sa_family=AF_INET, sin_port=htons(9801), sin_addr=inet_addr("192.168.65.2")}, [28->16]) = 0
[pid    15] fcntl(7, F_SETFL, O_RDONLY) = 0
[pid    15] setsockopt(7, SOL_TCP, TCP_NODELAY, "\1\0\0\0\0\0\0\0", 8) = 0

这个配置的端口其实就是xdebug扩展模块,配置的与jebrain应用的tcp通信调试端口,xdebug基于DBGp协议,所以才会往套接字中写入与协议相关的内容,参考,https://xdebug.org/docs/remote

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Connect
<?xml version="1.0" encoding="iso-8859-1"?>
<init xmlns="urn:debugger_protocol_v1"
      xmlns:xdebug="http://xdebug.org/dbgp/xdebug"
      fileuri="file:///home/httpd/www.xdebug.org/html/docs/index.php"
      language="PHP"
      protocol_version="1.0"
      appid="13202"
      idekey="derick">
  <engine version="2.0.0RC4-dev"><![CDATA[Xdebug]]></engine>
  <author><![CDATA[Derick Rethans]]></author>
  <url><![CDATA[http://xdebug.org]]></url>
  <copyright><![CDATA[Copyright (c) 2002-2007 by Derick Rethans]]></copyright>
</init>
(cmd)

5. 排查xdebug配置

1
2
3
4
5
6
7
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
xdebug.remote_autostart=1
xdebug.idekey=PHPSTORM
xdebug.remote_enable=on
xdebug.remote_host=host.docker.internal
xdebug.remote_port=9801
xdebug.remote_log=/tmp/xdebug.log

默认的xdebug的使用过程中,通常需要通过COOKIE或者GET,携带上相关的标识信息(或者利用相关插件,比如firebug之类的);

我不想使用这类插件,通信由希望与容器中的PHP进程进行调试,所以配置的默认远端调试自动启动;

1
2
3
4
xdebug.remote_autostart
Type: boolean, Default value: false

Normally you need to use a specific HTTP GET/POST variable to start remote debugging (see Remote Debugging). When this setting is set to 1, Xdebug will always attempt to start a remote debugging session and try to connect to a client, even if the GET/POST/COOKIE variable was not present.

到此,我才发现我开了的PHPSTORM已经默默阻塞了多个请求!!