2026-05-18 웹크롤링/유튜브 자막 장애의 증상·진단·근본원인 (Chrome 136+ 기본 프로필 CDP 거부)·조치·검증·재발 점검 체크리스트를 docs/incident-2026-05-18-chrome-cdp-crawling.md 로 정리. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.2 KiB
인시던트 보고서: 웹크롤링/YouTube 자막 장애 (Chrome CDP)
- 발생 인지일: 2026-05-18
- 장애 시작 추정일: 2026-04-13 이후 (Chrome 자동 업데이트 시점)
- 영향 범위: 웹크롤링 3차 폴백(Playwright), YouTube 자막 추출 전면 실패
- 상태: 해결 완료 (배포 커밋
9569309)
1. 증상
웹크롤링이 예전에는 정상 동작했으나 어느 시점부터 "이상"해짐.
- 일반 웹페이지(Jsoup / Jina Reader로 처리 가능한 사이트)는 정상 동작 → "되긴 되는데"
- 봇 차단 사이트 및 YouTube 자막 추출은 전부 실패 → "이상하네"
- 백엔드 자체는 정상 기동 (PM2 online, 장애 인지 시점 기준 21h+ uptime)
2. 시스템 구조 (배경)
웹크롤링은 3단계 폴백 구조입니다.
crawl(url)
├─ 1차: Jsoup (정적 HTML 파싱)
├─ 2차: Jina Reader API (https://r.jina.ai/)
└─ 3차: Playwright → PlaywrightBrowserService → 사용자 Chrome (CDP, 9222)
WebCrawlerService.crawl(): 3단계 폴백 오케스트레이션PlaywrightBrowserService:connectOverCDP("http://...:9222")로 사용자 Chrome에 연결YouTubeTranscriptService.fetchWithPlaywright(): 자막 추출 시 동일 CDP 경로 사용
3차 폴백(Playwright)은 봇 판정 우회를 위해 사용자가 로그인한 Chrome 세션에 CDP로 붙는 것이 핵심 설계입니다. 따라서 Chrome이 --remote-debugging-port=9222로 떠 있고, 거기에 로그인 세션(쿠키)이 살아 있어야 합니다.
3. 진단 과정
| 확인 항목 | 결과 |
|---|---|
curl http://localhost:9222/json/version |
연결 실패 |
pgrep -af remote-debugging-port |
디버깅 포트로 뜬 Chrome 없음 |
| 백엔드 프로세스 | 정상 (PM2 online) |
| 백엔드 로그 | connect ECONNREFUSED ::1:9222, Chrome CDP 재연결 실패 반복 |
| 마지막 정상 CDP 연결 로그 | 2026-04-13 이후 없음 |
| 실제 Chrome 프로세스 | 떠 있음 (단, --remote-debugging-port 인자 없이 일반 실행) |
| 재기동 시도 시 Chrome 출력 | DevTools remote debugging requires a non-default data directory. Specify this using --user-data-dir. |
진단 중 1차 가설(Chrome 프로세스 죽음, IPv6 ::1 resolve 문제)을 거쳐, 최종적으로 Chrome 콘솔 출력에서 결정적 메시지를 확인.
4. 근본 원인
Chrome 136+ (장애 시점 147) 의 보안 정책: 기본 프로필 디렉토리(~/.config/google-chrome)에서는 원격 디버깅(CDP)을 거부한다.
--user-data-dir에 기본 경로를 그대로 지정해도 거부됨 → 반드시 non-default(별도) 디렉토리여야 함- 2026-04-13 마지막 정상 동작 이후 Chrome 자동 업데이트로 이 정책이 적용됨
- Chrome 프로세스가 죽은 것이 아니라, 버전업으로 기본 프로필 + 원격 디버깅 조합이 영구 차단된 것
--remote-debugging-pipe는 PlaywrightconnectOverCDP(HTTP/WS endpoint 필요)와 호환되지 않아 우회 불가
부가 발견
- 자동 복구 불가 구조: Chrome을 PM2가 관리하지 않아, 한번 죽거나 잘못 뜨면 영구 장애가 됨
- IPv6 함정: 에러가
::1:9222(IPv6 localhost).localhost가 IPv6로 resolve되는데 Chrome은 기본적으로 IPv4127.0.0.1에만 바인딩 → 표기 불일치 잠재 위험
5. 조치 내역
세 가지를 모두 적용했습니다.
5-1. 프로필 이동 + 기동 스크립트 (start-chrome.sh 신규)
봇 우회용 로그인 세션을 보존하기 위해 기존 프로필을 이동(mv):
mv ~/.config/google-chrome ~/.config/google-chrome-cdp
/home/opc/sundol/start-chrome.sh 신규 작성:
DISPLAY=:1(VNC 세션)- 기존 동일 프로필 Chrome 종료 (graceful → 강제)
- 비정상 종료로 남은 stale 싱글톤 락(
SingletonLock/SingletonCookie/SingletonSocket) 정리 exec로 foreground 유지 → PM2 fork 모드가 프로세스 추적- 기동 인자:
--user-data-dir=/home/opc/.config/google-chrome-cdp --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --no-first-run --no-default-browser-check --start-maximized
5-2. PM2 상시화 (ecosystem.config.cjs)
sundol-chrome 앱 추가하여 PM2가 관리·자동 재기동:
{
name: "sundol-chrome",
script: "./start-chrome.sh",
interpreter: "/bin/bash",
cwd: "/home/opc/sundol",
env: { DISPLAY: ":1" },
}
pm2 start ecosystem.config.cjs --only sundol-chrome→pm2 savepm2-opcsystemd unit enabled 확인 → 재부팅 시 자동 복원
5-3. IPv6 함정 제거 (PlaywrightBrowserService.java)
- private static final String CDP_URL = "http://localhost:9222";
+ private static final String CDP_URL = "http://127.0.0.1:9222";
Chrome 측도 --remote-debugging-address=127.0.0.1로 IPv4 명시 바인딩 → 양쪽을 IPv4로 통일하여 localhost의 IPv6 해석 변수 자체를 제거.
비고: Chrome
--remote-debugging-address는 단일 주소만 지원하여 IPv4/IPv6 동시 바인딩 불가. 따라서 "양쪽 통일"은 IPv4(127.0.0.1)로 일치시키는 방식으로 달성.
6. 배포 (CLAUDE.md 배포 전 필수 절차 준수)
| 단계 | 결과 |
|---|---|
| 1. 컴파일 | 통과 (종료코드 0) |
| 2. PMD 정적 분석 | 6개 위반 검출, 전부 기존 코드 (변경 파일 PlaywrightBrowserService.java 위반 0). 규칙상 기존 위반은 보고만, 미수정 |
| 3. 코드 리뷰 | 변경은 상수 1줄. 재시도/중복적재/null/카운터 등 로직 영향 없음 |
| 4. 사용자 승인 | 승인 후 배포 진행 |
- 백엔드 재시작 (
pm2 restart sundol-backend) → 부팅 로그사용자 Chrome에 CDP 연결 완료확인 - 커밋
9569309→git push origin main완료 - 커밋 범위:
start-chrome.sh,ecosystem.config.cjs,PlaywrightBrowserService.java3개 파일만 (무관한 프론트엔드 변경 제외)
ecosystem.config.cjs에는 이전 작업분(frontend script: node → /usr/local/bin/node)이 한 파일에 섞여 분리 불가하여 함께 커밋, 커밋 메시지에 명시함.
7. 검증 결과
| 검증 | 결과 |
|---|---|
curl http://127.0.0.1:9222/json/version |
Chrome/147.0.7727.55 정상 응답 |
| 포트 바인딩 | LISTEN 127.0.0.1:9222 — IPv4 명시 바인딩 확인 |
| 백엔드 부팅 로그 | CDP 연결: 1 contexts, 1 pages / 사용자 Chrome에 CDP 연결 완료 |
PM2 sundol-chrome |
status=online, restarts=0 (재시작 루프 없음), PPID=PM2 데몬 |
| CDP 페이지 제어 스모크 | example.com 새 탭 생성(type=page) → 제어 → 종료 성공 (Playwright openPage와 동일 메커니즘) |
/api/knowledge/youtube-transcript (무인증) |
HTTP 401 → 엔드포인트 정상 가동(인증만 필요) |
미완 검증 (사용자 액션 필요): 실제 end-to-end(로그인 인증이 필요한 유튜브 자막/봇차단 페이지 본문 추출)는 인증 장벽으로 자동 수행 불가. 프론트엔드에서 1건 시도 시 백엔드 로그로 최종 확정 가능.
8. 운영 규칙 (재발 방지)
Chrome은 PM2
sundol-chrome만 관리한다. 수동으로google-chrome를 실행하지 말 것.
수동 인스턴스가 ~/.config/google-chrome-cdp 프로필 락을 먼저 잡으면, PM2가 띄우는 Chrome이 CDP 포트를 열지 못해 동일 장애가 재발한다. 사용자가 평소 VNC에서 사용하는 Chrome도 PM2가 띄운 그 창을 그대로 사용한다.
9. 재발 시 점검 체크리스트
# 1) CDP 포트 응답 확인
curl -s http://127.0.0.1:9222/json/version
# 2) 디버깅 포트로 뜬 Chrome 프로세스 확인
pgrep -af 'remote-debugging-port=9222'
# 3) PM2 sundol-chrome 상태 (재시작 루프 여부)
npx pm2 list | grep sundol-chrome # status=online, ↺(restart) 비정상 증가 여부
# 4) 백엔드 로그에서 CDP 연결 상태
grep -aE 'CDP 연결 완료|CDP 연결 실패|ECONNREFUSED.*9222' ~/.pm2/logs/sundol-backend-out.log | tail
# 5) Chrome 콘솔 출력 (프로필 정책 위반 메시지 확인)
grep -a 'non-default data directory' ~/.pm2/logs/sundol-chrome-*.log
# 6) 수동 Chrome 인스턴스가 프로필 락을 잡고 있지 않은지
ls -la ~/.config/google-chrome-cdp/SingletonLock
pgrep -af '/opt/google/chrome/chrome' | grep -v -E 'crashpad|--type='
복구 절차: 수동 Chrome 전부 종료 → npx pm2 restart sundol-chrome → 위 1~4번 재확인.
10. 관련 파일·위치
| 항목 | 경로 |
|---|---|
| Chrome 기동 스크립트 | /home/opc/sundol/start-chrome.sh |
| PM2 설정 | /home/opc/sundol/ecosystem.config.cjs (sundol-chrome 앱) |
| CDP 연결 코드 | sundol-backend/src/main/java/com/sundol/service/PlaywrightBrowserService.java (CDP_URL) |
| 크롤링 폴백 | sundol-backend/.../service/WebCrawlerService.java |
| YouTube 자막 | sundol-backend/.../service/YouTubeTranscriptService.java |
| CDP 전용 프로필 | /home/opc/.config/google-chrome-cdp (로그인 세션 포함, 824MB) |
| 백엔드 로그 | ~/.pm2/logs/sundol-backend-out.log |
| Chrome 로그 | ~/.pm2/logs/sundol-chrome-out.log / -error.log |
| 백엔드 포트 | 8080 |
| VNC 디스플레이 | :1 |