From 5700449bfd64903b35dee50ce4fd7333b16dc628 Mon Sep 17 00:00:00 2001 From: joungmin Date: Mon, 18 May 2026 01:17:11 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20Chrome=20CDP=20=ED=81=AC=EB=A1=A4?= =?UTF-8?q?=EB=A7=81=20=EC=9E=A5=EC=95=A0=20=EC=9D=B8=EC=8B=9C=EB=8D=98?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-05-18 웹크롤링/유튜브 자막 장애의 증상·진단·근본원인 (Chrome 136+ 기본 프로필 CDP 거부)·조치·검증·재발 점검 체크리스트를 docs/incident-2026-05-18-chrome-cdp-crawling.md 로 정리. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...incident-2026-05-18-chrome-cdp-crawling.md | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/incident-2026-05-18-chrome-cdp-crawling.md diff --git a/docs/incident-2026-05-18-chrome-cdp-crawling.md b/docs/incident-2026-05-18-chrome-cdp-crawling.md new file mode 100644 index 0000000..23f9b15 --- /dev/null +++ b/docs/incident-2026-05-18-chrome-cdp-crawling.md @@ -0,0 +1,203 @@ +# 인시던트 보고서: 웹크롤링/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)**: + +```bash +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가 관리·자동 재기동: + +```js +{ + 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 save` +- `pm2-opc` systemd unit **enabled** 확인 → 재부팅 시 자동 복원 + +### 5-3. IPv6 함정 제거 (`PlaywrightBrowserService.java`) + +```diff +- 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.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. 재발 시 점검 체크리스트 + +```bash +# 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` | + +