Files
sundol/docs/incident-2026-05-18-chrome-cdp-crawling.md
joungmin 5700449bfd docs: Chrome CDP 크롤링 장애 인시던트 보고서 추가
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>
2026-05-18 01:17:11 +00:00

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는 Playwright connectOverCDP(HTTP/WS endpoint 필요)와 호환되지 않아 우회 불가

부가 발견

  1. 자동 복구 불가 구조: Chrome을 PM2가 관리하지 않아, 한번 죽거나 잘못 뜨면 영구 장애가 됨
  2. IPv6 함정: 에러가 ::1:9222 (IPv6 localhost). localhost가 IPv6로 resolve되는데 Chrome은 기본적으로 IPv4 127.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-chromepm2 save
  • pm2-opc systemd 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 연결 완료 확인
  • 커밋 9569309git push origin main 완료
  • 커밋 범위: start-chrome.sh, ecosystem.config.cjs, PlaywrightBrowserService.java 3개 파일만 (무관한 프론트엔드 변경 제외)

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