# LPS 워커 이미지 — 크롤용 **헤드풀 Chromium + 가상 디스플레이(Xvfb)**. # headless Chrome 은 Akamai(쿠팡)·Cloudflare Turnstile(G마켓/옥션)에 탐지돼 통과 못 함(실측 확인). # → 컨테이너에선 Xvfb(가상 프레임버퍼)로 headful Chromium 을 실행한다. # # 빌드/실행: # docker compose build lps-worker && docker compose up -d lps-worker # 스텔스 참고: 로컬 Mac 은 실제 Chrome(channel=chrome), 컨테이너도 실제 google-chrome-stable(executable_path). # 오픈소스 chromium 은 Akamai(쿠팡)가 지문으로 차단(실측: errors.edgesuite.net, IP 첫 요청부터) → 실제 Chrome 필수. FROM python:3.14-slim # 실제 Chrome(구글 apt 저장소, amd64 전용) + Xvfb + 한글 폰트 RUN apt-get update && apt-get install -y --no-install-recommends wget gnupg \ && wget -qO- https://dl.google.com/linux/linux_signing_key.pub \ | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \ && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" \ > /etc/apt/sources.list.d/google-chrome.list \ && apt-get update && apt-get install -y --no-install-recommends \ google-chrome-stable xvfb xauth fonts-nanum ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 시크릿 든 config.local.toml 은 .dockerignore 로 제외됨 → example(플레이스홀더)로 대체. # 실값은 compose env 로 주입: DB_*, OPENAI_API_KEY, DECODO_*(포트 포함), NAVER_KEYS. RUN cp config/config.local.toml.example config/config.local.toml # Chrome 경로는 [WorkerConfig].chrome_executable(설정 파일)이 유일한 소스다. # 예전에 LPS_CHROME_EXECUTABLE env 를 뒀지만 읽는 코드가 없어 '설정한 줄 알았는데 아니었다'는 # 오진을 만들었다(2026-07-28 배포서버 크롤 조사) — 죽은 env 는 두지 않는다. # 컨테이너에서는 config 에 "/opt/google/chrome/chrome" 을 넣어야 --no-sandbox 가 함께 붙는다. ENV APP_ENV=local \ PYTHONUNBUFFERED=1 \ DISPLAY=:99 \ LPS_HEARTBEAT_FILE=/tmp/lps_worker_heartbeat # 하트비트 신선도(<120s)로 행/좀비 워커 감지. start-period 는 웜업(브라우저 기동) 여유. HEALTHCHECK --interval=30s --timeout=8s --start-period=120s --retries=3 \ CMD python -c "import os,time,sys; p=os.environ['LPS_HEARTBEAT_FILE']; sys.exit(0 if os.path.exists(p) and time.time()-os.path.getmtime(p)<120 else 1)" # Xvfb(가상 디스플레이)를 백그라운드로 띄우고 python 을 exec 로 승계 실행. # → 헤드풀 Chromium 이 :99 에 뜨고, 워커 로그는 그대로 docker logs 로 나온다(xvfb-run 은 로그를 삼킴). # # stale lock 제거가 먼저다(2026-08-04): `docker compose start`(재시작)는 컨테이너 파일시스템을 # 그대로 재사용하므로 지난 실행의 /tmp/.X99-lock·/tmp/.X11-unix/X99 가 남는다. Xvfb 는 이걸 # '이미 켜진 디스플레이'로 보고 죽고 → DISPLAY 없이 headful Chrome 이 못 떠 **모든 크롤이 조용히 실패**한다 # (증상: 워커는 살아있는데 launch_persistent_context 가 'Missing X server or $DISPLAY' 로 실패). CMD ["bash", "-c", "rm -f /tmp/.X99-lock /tmp/.X11-unix/X99; Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/dev/null 2>&1 & sleep 1; exec python worker_main.py"]