운영 실측으로 범위가 좁혀졌다: 같은 컨테이너에서 **쿠팡은 60건 정상, 네이버만** 매 IP
첫 요청부터 wtm_captcha. kr_host 도 정상이다(ensure_ports 에 kr.decodo.com, 마커가 해외 IP
하드차단 '비정상적인 접근'(2.6KB)이 아니라 wtm_captcha(43~57KB)). IP·게이트웨이가 아니라
브라우저 지문이 남은 후보다.
**먼저 확인한 것 — 지문 위장은 이 스택에서 불가능하다(중요)**
WebGL 렌더러(컨테이너=SwiftShader)를 실기기 이름으로 덮으려 했으나 주입 경로가 둘 다 막혔다:
context.add_init_script() 무반응. 단순 마커(window.__M__) 주입조차 undefined 다.
patchright 가 무력화한다 — 이 API 는 CDP
addScriptToEvaluateOnNewDocument 로 구현되고 그 흔적이 탐지
신호라, 걷어내는 게 patchright 의 존재 이유다.
확장(MV3, world:MAIN, document_start) 로드는 되지만 값이 바뀌지 않았다.
→ 동작하지 않는 코드를 남기지 않는다. 스푸핑은 넣지 않고, 대신 **판정**만 한다.
재시도 방지를 위해 이 실측을 fingerprint.py docstring 에 남겼다.
**넣은 것**
- services/search/fingerprint.py: 지문 판정(항목·정상여부·관측값·왜 문제인가). 진단이 곧
'무엇을 고칠까'로 이어지도록 이유를 함께 낸다.
- net_meter 토글: 네이버에 계측용 CDP(Network.enable)를 붙이지 않는다(기본 off).
네이버는 '요청 가로채기 자체가 탐지 신호'라 리소스 차단을 끈 이력이 있는데 계측 CDP 는
그대로 붙고 있었다 — 같은 계열이다. [WorkerConfig].naver_net_meter 로 재빌드 없이 A/B
(config 는 마운트). 끄면 대역폭이 DOM 크기 근사로 떨어질 뿐 크롤엔 영향 없다.
쿠팡은 잘 통과하므로 기본값을 바꾸지 않는다.
- Dockerfile.worker: fonts-noto-cjk·fonts-liberation 추가(나눔 하나뿐이면 폰트 지문이 부자연스럽다).
- diag_naver.py 가 같은 판정 모듈을 쓰도록 정리.
통과 환경 기준값(맥 실측): 의심 항목 0개 · HTTP 200 · 6건 · 1.26MB.
테스트 6건 추가(통과 지문을 문제로 몰지 않는지·컨테이너 지문을 놓치지 않는지), 전체 262 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docker compose start/restart 는 컨테이너 파일시스템을 재사용해 지난 실행의
/tmp/.X99-lock 과 /tmp/.X11-unix/X99 가 남는다. Xvfb 가 이걸 '이미 켜진 디스플레이'로
보고 종료하면 DISPLAY 가 없어 headful Chrome 이 못 뜨고, 워커는 살아있는 채로
launch_persistent_context 가 'Missing X server or $DISPLAY' 로 실패한다.
→ CMD 에서 Xvfb 기동 전에 stale lock/소켓을 제거한다.
재현·검증: restart 2회 연속 Xvfb 정상 기동 확인.
배포서버 쿠팡 크롤 조사(2026-07-28)에서 드러난 '설정한 줄 알았는데 아니었던' 것들 정리.
크롤 동작 자체를 바꾸는 변경은 없다.
- Dockerfile.worker: ENV LPS_CHROME_EXECUTABLE 제거.
읽는 코드가 없는데 값이 박혀 있어 "컨테이너 Chrome 경로가 설정돼 있다"는 오해를 만들었다.
경로의 유일한 소스는 [WorkerConfig].chrome_executable 이다(설정 시 --no-sandbox 동반).
- config.local.toml.example: profile_dir 경고 추가.
컨테이너에서 ".profiles" 로 두면 /app/.profiles(컨테이너 레이어)에 쌓여 재생성마다 쿠키가
날아가고, compose 가 마운트한 lps-profiles 볼륨은 붙어만 있고 아무 일도 하지 않는다.
로컬·배포서버 양쪽에서 실측(/profiles 는 7/9·7/16 잔재, /app/.profiles 에 9MB 최신).
- config.prod.toml.example 신규: 배포서버 설정 템플릿.
로컬과 다른 값만 ★ 표시. 헤더에 로드 경로의 함정을 명시했다 —
docker-compose.prod.yml 이 이 파일을 config.local.toml 자리에 마운트하므로
APP_ENV=local 인데도 내용은 prod 설정이다(파일명만 보면 오해한다).
- worker_main.py: budget_leak 알림이 "ip_request_budget 하향 검토"를 단정하던 것을 수정.
차단이 ip_req#1 에 몰리면 새 IP 첫 요청부터 막히는 것이라 예산과 무관하다.
bot_detection.ip_request_no 분포를 보고 처방을 고르도록 문구를 바꿨다.
⚠️ Dockerfile.worker 가 바뀌었으므로 배포 시 lps-worker 재빌드 필요.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
시크릿 유출 차단(핵심):
- config.local.toml(OpenAI·DECODO·네이버 키)이 COPY . . 로 이미지에
구워지던 문제 — .dockerignore 제외 + 빌드 시 example(플레이스홀더)
복사로 대체. 실값은 compose env 주입(리포 루트 .env, 템플릿 .env.example)
- DECODO_PORT_START/END/SESSION_MINUTES env override 추가 — 포트가
toml(플레이스홀더 0)에만 있으면 자격증명을 넣어도 프록시가 조용히
꺼지는 구멍 봉합
배포 견고화:
- API Dockerfile 에 HEALTHCHECK(/healthz) 추가
- autoheal 컨테이너 추가 — compose restart 는 unhealthy 를 재시작하지
않으므로 라벨(autoheal=true) 기반 자동 재시작 담당
- 이미지 python 3.12→3.14 정렬(로컬 개발·테스트 환경과 일치)
- API 이미지 경량화: requirements-api.txt 분리(크롤 의존성 제거, 330MB)
검증: 양 이미지 빌드 성공, 이미지 내 시크릿·.profiles 부재 확인,
무시크릿 API 이미지 스모크(healthz/readyz/HEALTHCHECK healthy) 통과
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>