🛠 서버에서 Playwright 좀비 프로세스(headless_shell) 확인 및 삭제 방법
FastAPI + Playwright 기반 스크래핑 환경에서는 브라우저 컨텍스트 종료 누락이나 부모 프로세스 미회수로 인해 headless_shell <defunct> 프로세스가 남아 서버 메모리/CPU를 잠식하는 경우가 발생할 수 있습니다.
이 글에서는 좀비 프로세스 확인 → 종료 → 재발 방지 → 모니터링까지 정리합니다.
1. 현재 좀비 프로세스 확인
좀비 프로세스는 상태 코드(STAT)가 Z로 표시됩니다.
ps -eo pid,ppid,stat,cmd | grep 'Z' | grep -v grep
예시 출력:
170750 169780 Z [headless_shell] <defunct>
170751 169780 Z [headless_shell] <defunct>
- PID : 좀비 프로세스 ID (kill 불가능, 부모 종료 시 정리됨)
- PPID: 부모 프로세스 ID
- [headless_shell] <defunct> : Playwright 브라우저 잔여 프로세스
2. 부모 프로세스 확인
ps -p <PPID> -o pid,ppid,stat,cmd
예:
ps -p 169780 -o pid,ppid,stat,cmd
PID PPID STAT CMD
169780 169758 Ssl /usr/local/bin/python3.12 /usr/local/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
여기서 부모가 FastAPI(Uvicorn) 프로세스임을 확인할 수 있습니다.
3. 좀비 프로세스 제거 방법
(1) FastAPI 컨테이너 재시작
docker compose restart fastapi
또는
docker restart fastapi
→ 부모 프로세스 재시작 시 하위 좀비들이 정리됩니다.
(2) 전체 프로세스 종료 후 재기동
docker compose down
docker compose up -d
4. 메모리/CPU 상태 확인
실시간 모니터링
htop
또는 간단 확인:
free -h
출력 예:
Mem: 15Gi 4.0Gi 10Gi 0.5Gi 1.2Gi 12Gi
Swap: 0B 0B 0B
CPU 사용량 상위 프로세스 확인
ps -eo pid,ppid,%cpu,%mem,stat,cmd --sort=-%cpu | head -n 20
5. 좀비 프로세스 재발 방지
- 브라우저 컨텍스트 종료 철저→ 키워드 처리 후 반드시 컨텍스트 닫기
- from app.features.internal.fetch_article.scraper.playwright_browser import close_all_contexts await close_all_contexts()
- 브라우저 주기적 재가동→ N회 처리마다 브라우저 자체를 재시작
- from app.features.internal.fetch_article.scraper.playwright_browser import recycle_browser await recycle_browser()
- FastAPI 종료 이벤트에서 정리
- @app.on_event("shutdown") async def _on_shutdown(): await recycle_browser()
6. 좀비 감시 스크립트 예시
#!/bin/bash
while true; do
echo "==== $(date) ===="
ps -eo pid,ppid,stat,cmd | grep 'Z' | grep -v grep
free -h
sleep 60
done
- /root/check_zombies.sh 로 저장
- 실행: bash /root/check_zombies.sh
✅ 정리
- Z 상태 프로세스 = 좀비 (kill 불가, 부모 종료 필요)
- 부모 확인 후 재시작으로 제거
- 컨텍스트/브라우저 종료 로직 철저하게 유지
- 정기적인 모니터링으로 재발 방지