Docker for Mac에서 Xdebug 연동

updated

이 글이 의외로 인기(인기란 상대적인 것)가 있어서 여기저기 공유가 되고 있는데, 다시 읽어보니 메쉬코리아의 다른 분들은 어떻게 테스트를 하고 있었다는 건지 궁금할 수도 있겠다. 나는 분명 dd 찍는 게 위험하다고 했는데 말이다.

일단 dd를 찍어 놓고 production에 배포할 일은 쉽게 일어나진 않는다. 다수의 리뷰어가 지켜보고 있기도 하고, (커버리지에 속해 있다면) 유닛 테스트가 돌아가기도 하고, QA를 거치기도 하고, 그렇게 막 배포하지 않을 사람을 뽑기도 한다.

개발 환경은 통일되어 있지 않아서(굳이 다 docker로 맞출 필요가 없다) 각자 마음대로 해도 되고, 이미 xdebug같은 툴을 쓰시는 분도 계실 것이다. 나는 이걸 표준 개발 환경 안에 넣으려고 시도했던 것이고.

내가 이전에 거쳐간 회사에서도 별다른 도구 없이 그냥 코드를 이해하고 개발 잘하시는 분이 많았다. 가끔 찍어볼 일이야 생기긴 하지만 나처럼 자주 breakpoint 걸어 확인하는 스타일은 아니었다. 디버깅을 편하게(편하다는 것은 상대적인 것) 한다고 좋은 코드를 만들어 내는 건 아니다.

그럼에도 불구하고 나처럼 덤벙거리고 부족한 사람(여기서 내가 PHP 개발 경력이 제일 많은데, 제일 못한다!)은 팀에서는 여러 안전장치 안에서 개발하는 습관을 둬야 한다고 생각한다.


새로운 환경에 적응하기

새 직장(아…그 사람 구한다는 메쉬코리아?)에서의 개발환경은 나로서는 꽤 도전적이었는데, 뭐하나 익숙한 게 없었다.

Backend만 개발하며, PHP7.0, AWS, Mac…

UI가 없는 순수 백엔드(API)만 개발 하다보니 Postman이 주 테스트 도구(혹은 진입점)가 된다.

윈도나 리눅스 remote 서버에 올려 하던 식으로 해봤더니 뭐가 잘 안되곤 해서 매번 dd, var_dump, Log::xxxx() 식으로 코드를 망가뜨리며 테스트를 했었다. 위험하고 불편하다. 그럼 그럴 시간에 잠시 짬을 내서 Xdebug 설정을 완료하는데 노력을 했어야 하지 않았냐는 의문도 들 수 있다. 그런데 일단 대안(코드에 찍어 확인하기)이 있긴 했고, 다른 많은 것을 익혀야 했기 때문에 우선 순위가 밀리고 밀렸다.

그런데 도저히 안되겠다 싶어서 최근에 다시 후벼 파기 시작했고 성과가 있었다.

회사 위키에 적은 것을 조금 바꿔 공유해본다.

Xdebug 설정하기

원리

IDE에서 XDebug 모듈로부터 들어오는 연결 요청을 받기 위해 특정 port를 띄우고 기다린다

Client(브라우저, Postman 등)에서 HTTP request를 할 때 헤더로 XDEBUG_SESSION이라는 쿠키를 실어 보낸다.

PHP 엔진(+XDebug module)에서 XDEBUG_SESSION을 감지하면 config에 설정된 remote host와 port를 찾아 연결을 시도한다

DBGp 프로토콜로 두 모듈 간 통신을 하게 된다(break point로 라인 단위 실행 등)

Communication Set-up

With a static IP/single developer

With remote debugging, Xdebug embedded in PHP acts like the client, and the IDE as the server. The following animation shows how the communication channel is set-up:

comm. With a static IP/single developer image - the IP of the server is 10.0.1.2 with HTTP on port 80 - the IDE is on IP 10.0.1.42, so xdebug.remote_host is set to 10.0.1.42 - the IDE listens on port 9000, so xdebug.remote_port is set to 9000 - the HTTP request is started on the machine running the IDE - Xdebug connects to 10.0.1.42:9000 - Debugging runs, HTTP Response provided comm. With an unknown IP/multiple developers image - The IP of the server is 10.0.1.2 with HTTP on port 80 - The IDE is on an unknown IP, so xdebug.remote_connect_back is set to 1 - The IDE listens on port 9000, so xdebug.remote_port is set to 9000 - The HTTP request is made, Xdebug detects the IP addres from the HTTP headers - Xdebug connects to the detected IP (10.0.1.42) on port 9000 - Debugging runs, HTTP Response provided

기대 효과

  1. dd(dump and die) 등의 프로세스 종료 코드나 로그 확인을 위한 코드 변경을 하지 않고 많은 context 정보를 확인할 수 있다.

  2. 제시된 context 정보를 통해 실시간 evaluate/watch도 가능하다.

  3. 코드를 완벽히 이해하지 않아도 차례차례 순서대로 한줄씩 실행해보면서 전체 프로세스를 익힐 수 있다.

Docker for mac에서의 설정 방법

회사에서는 표준 컨테이너 하나를 띄우고 쓴다

설정 확인

host 환경에도 php가 설치되어 있을텐데, 이건 IDE가 사용하는 cli이고 container 안의 php로 확인해야 한다

❯ docker exec -it 컨테이너명 bash
root@0829de89a35f:/var/www/html#

xdebug 설치 여부 확인

root@0829de89a35f:/var/www/html# php -v
PHP 7.0.22-0ubuntu0.16.04.1 (cli) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.0.22-0ubuntu0.16.04.1, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.4.0, Copyright (c) 2002-2016, by Derick Rethans

xdebug 설치 여부 및 설정 파일(xdebug.ini, php.ini 등) 위치 확인

root@0829de89a35f:/var/www/html# php -i | grep xdebug
/etc/php/7.0/cli/conf.d/20-xdebug.ini,

XDebug 옵션 설정

zend_extension=xdebug.so # to load extension
xdebug.remote_log=/tmp/xdebug.log # 로그 파일 위치. 첫 연동이라면 실패를 대비해서 로그 파일 위치를 지정하자
xdebug.idekey=PHPSTORM # XDEBUG_SESSION 쿠키에 value로 들어갈 문자열
xdebug.remote_enable=1 # remote host에서의 연결을 허용할지 여부
xdebug.remote_host=host.docker.internal # remote host의 주소. container 안에서 호스트로 접근할 때는 이대로 입력하자
xdebug.remote_port=9003 # remote host에서 어떤 port로 연동할지

저장 후 apache 재시동

root@0829de89a35f:/var/www/html# supervisorctl restart apache2

(자기 환경에 맞춰 알아서 재시작 하자)

IDE 설정

IDE - Xdebug 설정 이미지

IDE - DBGp proxy 설정 이미지

Preferences > Languages & Frameworks > PHP > Debug

  • debug port 입력. 기본 9000번. 충돌이 나는지 확인하고 다른 port를 입력해도 된다
  • 단, 위에서 설정한 옵션 xdebug.remote_port=xxxx 와 맞춰야 한다
  • Force break at the first line when no path mapping specified 옵션
    • path mapping이 되지 않으면 이 옵션을 켜야만 한다
    • 매번 최초의 php script 위치부터 시작하므로 매우 귀찮다
    • 아래 server 설정을 추가하자

Preferences > Languages & Frameworks > PHP > Server

Server - path mapping 설정 이미지

Client 쿠키 설정

브라우저

Postman

  • host별 cookie에 설정
    • XDEBUG_SESSION=PHPSTORM; path=/; domain=localhost; Expires=Tue, 19 Jan 2038 03:14:07 GMT;

Postman - cookie 설정 이미지

  • 일부 request에만 추가하려면 header에 설정

Postman - header 설정 이미지

테스트 중인 모습

테스트 중인 모습 이미지

삽질

나는 Mac도 잘 몰랐고, Docker도 잘 몰랐으므로 도대체 어디서 문제가 생긴 건지 알 수가 없었다. 여러가지 검색을 통해 힌트를 얻을 수도 있었지만, 나는 영어도 잘 모르지 않는가! 꽤 초기에 발견했던 커뮤니티 글을 다시 한번 찬찬히 읽어보고 결정적인 힌트를 얻게 되는데…

linux나 windows에선 xdebug.remote_connect_back=1을 설정하면 remote_host를 지정할 필요도 없이 쉽게 연동되나 Docker for Mac에서는 이 방식이 동작하지 않는다. 이건 Docker for Mac의 구조적인 문제라고 한다.

결국 remote host를 지정해야 하는데 그 방법으로 IP alias를 생성하라는 글이 아주 많은데,

sudo ifconfig en0 alias 10.254.254.254 255.255.255.0

이렇게 하고 remote_host의 값을 10.254.254.254을 지정하면 동작은 잘된다.

그런데 이때 Mac에서 인터넷 연결이 끊기는 문제가 발생했다. 이유는 도저히 모르겠다. 뭔가 충돌이 나는 IP일 수도…

그러다 Docker for Mac의 공식 문서를 보고 해결책을 찾았는데,

Dcker에서는 container 안에서 호스트로 접근할 수 있는 host.docker.internal라는 특별한 DNS name을 제공한다는 것이다. 따라서 위에 설정한 대로 xdebug.remote_host=host.docker.internal을 입력하면 호스트(IDE를 띄워놓은)에 접근 가능하다.

이 과정에서 문제가 무엇인지는 xdebug.remote_log에서 지정된 로그 파일로부터 얻었다. 연동이 잘 안된다면 맨 위에 설명한 것처럼 DBGp로 연결이 이뤄지기까지 어느 단계에서 문제가 생겼는지 확인해보는 게 좋다.

  • XDEBUG_SESSION이 잘 세팅되지 않았다면 쿠키/헤더 설정을 확인해보자
  • PHP(+Xdebug) 엔진 단에서 XDEBUG_SESSION을 인식을 못한다면, Xdebug 모듈이 로드 안됐을 수 있으니 php -vphpinfo()로 확인하자
  • XDEBUG_SESSION을 인식은 했는데 request 들어온 곳을 찾아가지 못하는 경우라면,
    • IDE에서 Xdebug connection을 listen하지 않거나
    • container 내부에서 호스트에 연결할 수 없는 상황…은 다양하긴 하다