Python 프로그램을 디버깅하실 때 어떤 툴을 사용하시나요? 아무래도 가장 많이 사용하고 계신 툴은 PyCharm이 아닐까 싶습니다. PyCharm은 JetBrains에서 만든 GUI 환경에서 사용할 수 있는 Python IDE입니다.
PyCharm은:
로컬 컴퓨터에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
로컬 컴퓨터에서 실행 중인 Python 프로세스에 Attach해 디버깅할 수 있습니다.
원격 서버에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
하지만 원격에서 동작하고 있는 프로세스에 디버거를 Attach하는 기능은 제공하지 않고 있습니다. 또한 Python 디버깅 모듈인 pdb를 사용하여도 동작하고 있는 프로세스에 Attach하는 것은 지원하고 있지 않습니다.
로컬 환경에서는 문제 없이 돌아갔던 Python 프로그램이 원격 서버에서는 아무런 로그 없이 멈춰 버리는 경우 어디서부터 손을 대야 할지 정말 막막합니다. 이 때 GDB와 strace를 이용하면 어디에서 문제가 발생했는지 진단할 수 있습니다. 이 세션에서는 GDB와 strace를 이용해 디버깅하여 원격 리눅스 서버에서 Python Process가 Hang 되어 버리는 문제를 진단하고 해결했던 경험을 공유하려고 합니다.
1. GDB와 strace로 Hang 걸린
Python Process 원격 디버깅
구영민 <youngminz.kr@gmail.com>
2. whoami
• 구영민 / youngminz
• 고등학교 때부터 Python으로 외주 프로그램 개발
• 대학교 2학년 휴학생
• 에서 소프트웨어 엔지니어로 일하는 중 (2018.01-)
• Python과 Django, Linux를 좋아함
• 초등학교 5학년부터 프로그래밍 시작
• PyCon Korea 2014, 2016, 2017, 2018 참여
3. 우리 회사의 시스템
• 우리 회사는 P2P 금융 서비스
• 투자자들이 채권에 투자하고
• 돈이 입금되면 투자자들에게 원금과 이자를 상환해줌
4. 시스템이 하는 일
• 친구에게 연 4.94% 이자율로 100만원을 빌려 주고 6개월 후 받기로 함
• Case 1) 1개월 후, “내가 목돈이 갑자기 생겨서 지금 다 갚을게”
• Case 2) 6개월 후, “돈이 하나도 없어서 못 갚겠고, 1달 뒤에 다 갚을게”
• Case 3) 6개월 후, “50만원만 갚고 나머지는 6개월에 걸쳐서 갚을게”
• ……
• 이런 돈과 관련된 민감한 경우들을 시스템이 늘 잘 처리해줄까?
5. 시스템 테스트: 수동으로
>>> 인터프리터를 열어서 이자를 계산해주는 함수를 호출해본다
• 새로운 기능을 만들 때마다 수동 테스트를 계속 반복해야 하나?
• 코딩 중 실수로 오타를 낸다면?
• 손으로 테스트하다가 빼먹는 부분이 생기면?
• 개발만 해도 바쁜데 언제 매번 테스트한다고 콘솔에 타이핑하고 있나?
• 동료가 개발한 기능을 내가 실수로 망가트리면?
• 내가 개발한 기능을 동료가 실수로 망가트리면?
• …
6. 시스템 테스트: 자동으로??
• 손으로 매번 테스트하는 작업은 시간이 많이 들고 루틴함
• 자동으로 시스템이 정상 작동하는지 알 수는 없을까? 있다!
• 자동화 된 테스트: Django Test Case
• 손으로 하던 일련의 작업들을 자동화할 수 있게 소스 코드로 옮긴 것!
7. Test Case를 만들면
• 테스트를 통해 시간을 절약할 수 있음
• 배포하기 전에 문제를 확인하고 수정할 수 있음
• 테스트가 코드를 더 매력적으로 보이게 만듬
• 팀이 함께 일하는 것을 도와줌
[2] Django Documentation, https://docs.djangoproject.com/
8. Test Case
• 우리 회사의 시스템에는 2,000개 이상의 테스트 케이스 존재
• 이를 모두 통과한 후 운영 서버에 배포
• 시스템을 수정하고 배포하는 데 자신감이 붙음!
• 별도의 Jenkins 서버에서 테스트가 돌아가고 있었음
9. 테스트가 안 돌아감
• “영민님, 테스트가 안 돌아가는데 확인 좀 해주세요 ㅠㅠ”
• 확인해 보니 한 번 테스트가 도는 데 20시간 걸림
• ????????????????
• 테스트를 못 하니 긴급한 수정 건도 반영하지 못함
• 개발 팀 파업
10. 문제 분석
1. 로컬에서는 잘 돌아가는데 테스트 서버에서는 안 됨
2. 테스트 로그가 업데이트되지 않음
3. CPU 사용량은 0%
어디에서 문제가 발생했는지 디버그해 보자!
11. 디버그란?
• 프로그램의 결함이나 문제를 찾는 행위
def add(x, y):
value = (x - y) / 0
return value
• 손코딩 뇌컴파일 눈디버깅 할 수도 있지만…
• 세상은 넓고 툴은 많음
12.
13. Traceback / Call Stack
Traceback (most recent call last):
File "main.py", line 6, in <module>
print(add(10, 5))
File "main.py", line 3, in add
value = (x - y) / 0
ZeroDivisionError: division by zero
def add(x, y):
value = (x - y) / 0
return value
print(add(10, 5))
• 어떤 함수를 실행하고 있는지 확인 가능
• 처리되지 않은 예외 발생 시 자동으로 출력됨
• 디버깅할 때 꼭!! 필요한 정보
14. 디버깅: 이상 편
• 버그가 났을 때 Traceback이 나며 크래시됨
• 문제가 있는 로직 앞뒤로 logger.debug 로그를 찍어 둠
• 로그를 나중에 찾아볼 수 있게 되어 있음
• 로컬 환경에서도 재현이 가능함 <<< 중요!
15. 디버깅: 현실 편
• segmentation fault (core dumped)
• 프로그램이 동작 도중 Traceback도 안 나오고 그냥 얼어버림
• 확률적으로 오류 발생
• 디버깅 모드만 켜면 언제 그랬냐는 듯 잘 돌아감
• 실행하고 있는 프로세스에서 문제가 발생했을 때 디버거를 붙여야 함
17. PyCharm
• 할 수 있는 것
• 로컬에서 디버깅 모드로 실행하기
• 로컬에서 실행 중인 Python Process에 붙어서 디버깅하기
• 원격에서 디버깅 모드로 실행하기
• 그러나
• 원격에서 실행 중인 프로세스에 붙어서 디버깅 불가
18. pdb: The Python Debugger
• 좋은 점
• Python 기본 디버거
• 언제 어디서나 디버깅 모드로 실행할 수 있음
• 그러나
• 실행 중인 프로세스에 붙어서 디버깅 불가
19. GDB: The GNU Project Debugger
• 실행되고 있는 프로세스도 디버깅 가능!!!
• C / C++은 물론이고
• Python 인터프리터
• 심지어 Java Virtual Machine / Go 등등등
• 다른 프로세스에 접근하는 행위이기 때문에 sudo 사용해야 함
20. 삼단 논법 (소크라테스)
• 모든 사람은 죽는다.
• 소크라테스는 사람이다.
• 그러므로, 소크라테스는 죽는다.
21. 삼단 논법 (CPython 버전)
• Python의 표준 구현체인 CPython은 C로 만들어졌다.
• C 프로그램의 디버깅은 GDB로 할 수 있다.
• 그러므로, CPython 프로그램 디버깅은 GDB로 할 수 있다.
• + Python이 추상화해주는 부가적인 정보를 함께 볼 수 있음
23. 간단한 GDB 사용법
• PID를 통하여 동작하고 있는 프로세스에 연결: gdb –p <pid>
• bt 명령은 C Level Call Stack를 보여줌
• C 키를 이용하여 continue
• Ctrl + C로 일시 중지
• Ctrl + D로 디버거 연결을 끊고 프로그램 continue
24. hanging.py
$ sudo gdb -p 20457
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
Attaching to process 20457
Reading symbols from /usr/bin/python3.5...(no debugging symbols
found)...done.
...
(gdb) bt
25. hanging.py
(gdb) bt
#0 0x00007f2214c025b3 in __select_nocancel () at ../sysdeps/u
#1 0x00000000005fa30d in ?? ()
#2 0x000000000053b9c6 in PyEval_EvalFrameEx ()
#3 0x000000000053b7e4 in PyEval_EvalFrameEx ()
#4 0x0000000000540199 in ?? ()
#5 0x0000000000540e4f in PyEval_EvalCode ()
#6 0x000000000060c272 in ?? ()
#7 0x000000000060e71a in PyRun_FileExFlags ()
#8 0x000000000060ef0c in PyRun_SimpleFileExFlags ()
#9 0x000000000063fb26 in Py_Main ()
#10 0x00000000004cfeb1 in main ()
import os, time
print('current pid', os.getpid())
def hang():
time.sleep(30000)
hang()
27. 디버깅 심볼
• 디버깅을 위하여 컴파일 시에 C 컴파일러가 만들어 주는 파일
• GDB는 마법이 아님
• 프로그램 실행 시에는 없어도 되나 디버깅할 때에는 없으면 안 됨
• 소스 코드가 변경될 때마다 심볼 파일도 변경됨
• 디버깅을 위해서는
• 실행 파일 (바이너리)
• CPython 소스 코드
• 디버깅 심볼 파일
• 이 있어야 함!
37. 평범한 HTTP 통신
import requests
response = requests.get(‘http://private-api.example.com/’)
print(response)
아무런 문제 없어 보이지만, 드물게 코드가 멈출 수 있음
38. py-bt: Pythonic Traceback
(gdb) py-bt
Traceback (most recent call first):
File "/urllib3/connection.py", line 81, increate_connection
sock.connect(sa)
...
File ”/requests/api.py", line 67, in get
return request('get', url, params=params, **kwargs)
File "test.py", line 7, in <module>
• 왜 오류가 발생하는지 명확히 알기 쉽지 않음
39. 마법의 도구, strace
• 디버깅 시 사용할 수 있는 명령어
• 프로세스가 어떤 시스템 콜을 사용하는지 알 수 있음
• 예를 들면 어떤 IP로 TCP 통신을 시도하는지
• 다른 프로세스에 접근해야 하므로 sudo 권한 필요
• sudo strace -p <pid>
44. HTTP 통신은 실패할 수 있음
• HTTP 통신은 Timeout를 지정하자!
• Best Effort: 연결에 실패할 수 있음
• 인터넷 연결이 끊길 수도 있고
• DNS 질의에 실패할 수도 있고
• 패킷이 방화벽에 막혀서 Drop 될 수도 있음
[3] Requests Documentation, http://docs.python-requests.org/en/master/user/quickstart/#timeouts
45. HTTP 통신은 실패할 수 있음
• Requests 문서에 따르면,
• If no timeout is specified explicitly, requests do not time out.
• Nearly all production code should use this parameter in nearly all
requests.
• 번역하면,
• Timeout를 명시적으로 지정하지 않으면, requests는 타임아웃 처리 안 함.
• 대부분의 프로덕션 코드는 매 요청마다 이 파라미터를 넣어야 함.
[3] Requests Documentation, http://docs.python-requests.org/en/master/user/quickstart/#timeouts
46. HTTP 통신은 실패할 수 있음
• 프로덕션에서는 timeout 파라미터를 항상 사용할 것을 강력히 권장
• Response = requests.get(…, timeout=5)
[3] Requests Documentation, http://docs.python-requests.org/en/master/user/quickstart/#timeouts
47. 문제 파악 / 해결
• 환경 변수에 지정된 API 서버의 접근이 방화벽에 막힘
• timeout을 지정하지 않아 접근이 불가능한 상태로 멈춤
• API 서버를 로컬에 띄우고 환경 변수 변경
• Redis 서버도 상기 문제와 동일한 문제 있었음
• 서드파티 Lint 플러그인에서는 무한 루프가 도는 버그
• 세 가지 문제를 해결한 후 테스트 서버가 정상으로 돌아옴!!!
48. GDB / strace
• Use Case
• Segmentation Fault (core dumped) / Hang / Deadlock
• GDB
• 프로그램의 Traceback를 보기 위하여 사용
• strace
• 이 프로그램이 현재 어떤 동작을 하고 있는지 확인할 때 사용