在Docker中使用Xdebug

首發於 樊浩柏科學院

我們經常會使用 PhpStorm 結合 Xdebug 進行代碼斷點調試,這樣能追蹤程序執行流程,方便調試代碼和發現潛在問題。博主將開發環境遷入 Docker 後,Xdebug 調試遇到了些問題,在這裏整理出 Docker 中使用 Xdebug 的方法和注意事項。

說明:開發和調試環境爲本地 Docker 中的 LNMP,IDE 環境爲本地 Win10 下的 PhpStorm。這種情況下 Xdebug 屬於遠程調試模式,IDE 和本地 IP 爲 192.168.1.101,Docker 中 LNMP 容器 IP 爲 172.17.0.2。

問題描述

在 Docker 中安裝並配置完 Xdebug ,並設置 PhpStorm 中對應的 Debug 參數後,但是 Debug 並不能正常工作。

此時,php.ini中 Xdebug 配置如下:

xdebug.idekey = phpstorm
xdebug.remote_enable = on
xdebug.remote_connect_back = on
xdebug.remote_port = 9001        //PhpStorm監聽本地9001端口
xdebug.remote_handler = dbgp
xdebug.remote_log = /home/tmp/xdebug.log

開始收集問題詳細表述。首先,觀察到 PhpStorm 的 Debug 控制檯出現狀態:

Waiting for incoming connection with ide key ***

然後查看 Xdebug 調試日誌xdebug.log,存在如下錯誤:

I: Checking remote connect back address.
I: Checking header 'HTTP_X_FORWARDED_FOR'.
I: Checking header 'REMOTE_ADDR'.
I: Remote address found, connecting to 172.17.0.1:9001.
W: Creating socket for '172.17.0.1:9001', poll success, but error: Operation now in progress (29).
E: Could not connect to client. :-(

分析問題

查看這些問題表述,基本上可以定位爲 Xdebug 和 PhpStorm 之間的 網絡通信 問題,接下來一步步定位具體問題。

排查本地9001端口

Win 下執行 netstat -ant命令:

協議    本地地址       外部地址        狀態           卸載狀態
TCP  0.0.0.0:9001   0.0.0.0:0     LISTENING       InHost

端口 9001 監聽正常,然後在容器中使用 telnet 嘗試同本地 9001 端口建立 TCP 連接:

$ telnet 192.168.1.101 9001

Trying 192.168.1.101...
Connected to 192.168.1.101.
Escape character is '^]'.

說明容器同本地 9001 建立 TCP 連接正常,但是 Xdebug 爲什麼會報連接失敗呢?此時,至少可以排除不會是因爲 PhpStorm 端配置的問題。

排查Xdebug問題

回過頭來看看 Xdebug 的錯誤日誌,注意觀察到失敗時的連接信息:

I: Remote address found, connecting to 172.17.0.1:9001.
W: Creating socket for '172.17.0.1:9001', poll success, but error: Operation now in progress (29).
E: Could not connect to client. :-(

此時,在容器中使用 tcpdump 截獲的數據包如下:

$ tcpdump -nnA port 9001
# 嘗試建立連接,但是失敗了
12:20:34.318080 IP 172.17.0.2.40720 > 172.17.0.1.9001: Flags [S], seq 2365657644, win 29200, options [mss 1460,sackOK,TS val 833443 ecr 0,nop,wscale 7], length 0
E..<..@.@.=...........#)...,......r.XT.........
............
12:20:34.318123 IP 172.17.0.1.9001 > 172.17.0.2.40720: Flags [R.], seq 0, ack 2365657645, win 0, length 0
E..(.]@[email protected]........#).........-P....B..

可以確定的是, Xdebug 是向 IP 爲 172.17.0.1 且端口爲 9001 的目標機器嘗試建立 TCP 連接,而非正確的 192.168.1.101 本地 IP。到底發生了什麼?

首先,爲了搞懂 Xdebug 和 PhpStorm 的交互過程,查了 官方手冊 得知,Xdebug 工作在遠程調試模式時,有兩種工作方式:

1、IDE 所在機器 IP 確定/單人開發

圖中,由於 IDE 的 IP 和監聽端口都已知,所以 Xdebug 端可以很明確知道 DBGP 交互時 IDE 目標機器信息,所以 Xdebug 只需配置 xdebug.remote_hostxdebug.remote_port 即可。

2、IDE 所在機器 IP 未知/團隊開發

由於 IDE 的 IP 未知或者 IDE 存在多個 ,那麼 Xdebug 無法提前預知 DBGP 交互時的目標 IP,所以不能直接配置 xdebug.remote_host 項(remote_port 項可以確定),必須設置 xdebug.remote_connect_back 爲 On 標識(會忽略 xdebug.remote_host 項)。這時,Xdebug 會優先獲取 HTTP_X_FORWARDED_FORREMOTE_ADDR 中的一個值作爲通信時 IDE 端的目標 IP,通過Xdebug.log記錄可以確認。

I: Checking remote connect back address.
I: Checking header 'HTTP_X_FORWARDED_FOR'.
I: Checking header 'REMOTE_ADDR'.
I: Remote address found

接下來,可以知道 Xdebug 端是工作在遠程調試的模式 2 上,Xdebug 會通過 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR 項獲取目標機 IP。Docker 啓動容器時已經做了 80 端口映射,忽略宿主機同 Docker 容器複雜的數據包轉發規則,先截取容器 80 端口數據包:

$ tcpdump -nnA port 80
# 請求信息
13:30:07.017770 IP 172.17.0.1.33976 > 172.17.0.2.80: Flags [P.], seq 1:208, ack 1, win 229, options [nop,nop,TS val 1250713 ecr 1250713], length 207
E....=@[email protected]..    .+.......Y......
........GET /v2/room/list.json HTTP/1.1
Accept: */*
Cache-Control: no-cache
Host: localhost
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_152-release)
Accept-Encoding: gzip,deflate

可以看出,數據包的源地址爲 172.17.0.1,並非真正的源地址 192.168.1.101,HTTP 請求頭中也無 HTTP_X_FORWARDED_FOR 項。

說明:172.17.0.1 實際爲 Docker 創建的虛擬網橋 docker0 的地址 ,也是所有容器的默認網關。Docker 網絡通信方式默認爲 Bridge 模式,通信時宿主機會對數據包進行 SNAT 轉換,進而源地址變爲 docker0,那麼,怎麼在 Docker 裏獲取客戶端真正 IP 呢?

定位根源

最後,可以確定由於 HTTP_X_FORWARDED_FOR 未定義,因此 Xdebug 會取 REMOTE_ADDR 爲 IDE 的 IP,同時由於 Docker 特殊的網絡轉發規則,導致 REMOTE_ADDR 變更爲網關 IP,所以 Xdebug 同 PhpStorm 進行 DBGP 交互會失敗。

解決問題

由於 Docker 容器裏獲取真正客戶端 IP 比較複雜,這裏使用 Xdebug 的 遠程模式 1 明確 IDE 端 IP 來規避源 IP 被修改的情況,最終解決 Xdebug 調試問題。

模式 1 的 Xdebug 主要配置爲:

//並沒有xdebug.remote_connect_back項
xdebug.idekey = phpstorm
xdebug.remote_enable = on
xdebug.remote_host = 192.168.1.101
xdebug.remote_port = 9001
xdebug.remote_handler = dbgp

重啓 php-fpm,使用php --ri xdebug確定無誤,使用 PhpStorm 重新進行調試。

再次在容器中 tcpdump 抓取 9001 端口數據包:

# 連接的源地址已經正確
14:05:27.379783 IP 172.17.0.2.44668 > 192.168.1.101.9001: Flags [S], seq 3444466556, win 29200, options [mss 1460,sackOK,TS val 1462749 ecr 0,nop,wscale 7], length 0
E..<2.@[email protected].|#).Nc|......r.nO.........
..Q.........

再次使用 PhpStorm 的 REST Client 斷點調試 API 時, Debug 控制檯如下:

所以,使用 Xdebug 進行遠程調試時,需要選擇合適的調試模式,在 Docker 下建議使用遠程模式 1。

其他注意事項

  • Xdebug 版本和 PHP 版本一致

並不是每個 Xdebug 版本都適配 PHP 每個版本,可以直接使用 官方工具,選擇合適的 Xdebug 版本。

  • 本地文件和遠端文件映射關係

如上圖,在使用 PhpStorm 時進行遠程調試時,需要配置本地文件和遠端文件的目錄映射關係,這樣 IDE 才能根據 Xdebug 傳遞的當前執行文件路徑與本地文件做匹配,實現斷點調試和單步調試等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章