배포서버 쿠팡 크롤 조사(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>
106 lines
5.9 KiB
Plaintext
106 lines
5.9 KiB
Plaintext
# 복사해서 사용: cp config.local.toml.example config.local.toml
|
||
# 실제 config.local.toml 은 시크릿 포함이라 커밋하지 않는다(.gitignore: *.toml).
|
||
#
|
||
# ── 설정 소스는 TOML 하나다(2026-07-13 협의 — env/.env 이중 관리 제거) ──
|
||
# 호스트 실행: APP_ENV=local(기본) → 이 파일
|
||
# Docker : APP_ENV=dev|prod → config.dev.toml / config.prod.toml 을 컨테이너에 마운트
|
||
# (각 example 참고, 실행은 ../run_docker.sh 대화형 권장)
|
||
# env 는 APP_ENV·PROCESS_COUNT/WORKER_CONCURRENCY(실행 스크립트 대화형 입력)·LPS_LIVE(테스트)만 남는다.
|
||
[WebServerConfig]
|
||
server_name = "LpsServer"
|
||
port = 9600
|
||
process_count = 1
|
||
is_ssl = false
|
||
is_test = true
|
||
# CORS 허용 오리진(프론트). 비우면 [] (CORS 미적용). 5173=vite dev.
|
||
cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"]
|
||
# API guard 키 — 비우면 개방 모드(개발). prod 는 채운다(복수 등록 = 무중단 키 교체).
|
||
# 생성 예: openssl rand -hex 32. 호출자(negodata)도 같은 키를 설정해야 한다.
|
||
api_keys = []
|
||
|
||
[LogConfig]
|
||
print_console = true
|
||
log_level = "debug"
|
||
|
||
# DB Read/Write 분리. 관리형 DB(RDS/Aurora/Azure)는 host 에 엔드포인트, sslmode="require".
|
||
# LPS 도메인 로직/테이블이 생기기 전까지는 접속하지 않으므로(엔진 lazy) placeholder 여도 부팅된다.
|
||
[MainDBConfig]
|
||
db_type = "postgresql"
|
||
name = "lps_db"
|
||
write_host = "127.0.0.1"
|
||
write_port = 5432
|
||
write_id = "<DB_USER>"
|
||
write_pw = "<DB_PASSWORD>"
|
||
read_host = "127.0.0.1"
|
||
read_port = 5432
|
||
read_id = "<DB_USER>"
|
||
read_pw = "<DB_PASSWORD>"
|
||
show_log = false
|
||
pool_size = 10 # connection_budget>0 이면 무시(자동 산정). budget=0 일 때만 이 값 사용.
|
||
max_overflow = 20 # 〃
|
||
# 커넥션 예산(자동 산정). process_count 에 맞춰 pool_size/max_overflow 를 자동 계산:
|
||
# (pool+overflow) × 2엔진 × process_count ≤ connection_budget.
|
||
# 'lps API 가 쓸 총 커넥션 상한' — 공유 PG(max_connections)·동거 서비스(worker 등)를 고려한 값.
|
||
# 예) 전용 PG(max_connections=100)면 90 근처, 공유 PG면 40 권장. 0 이면 자동 끔(위 pool 값 사용).
|
||
connection_budget = 40
|
||
sslmode = "" # 로컬: "" / 관리형 DB: "require"|"verify-ca"|"verify-full"
|
||
|
||
# 워커 런타임 (worker_main.py). 동시성만 실행 시 WORKER_CONCURRENCY env 로 임시 override 가능.
|
||
[WorkerConfig]
|
||
concurrency = 1 # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 최대 4×N). 로컬 권장 2~3
|
||
fallbacks = [] # 오픈마켓 폴백(기본 OFF). 예: ["gmarket", "auction", "st11"] — 켜기 전 라이브 스모크
|
||
# Chrome 프로필 베이스. 영속 경로면 재시작에도 쿠키 유지(재웜업 회피).
|
||
# ⚠️ 컨테이너로 띄우면 반드시 "/profiles" — compose 가 lps-profiles 볼륨을 그 경로에 마운트한다.
|
||
# ".profiles" 로 두면 /app/.profiles(컨테이너 레이어)에 쌓여 재생성 때마다 쿠키가 전부 날아가고,
|
||
# 볼륨은 붙어만 있고 아무 일도 하지 않는다(2026-07-28 로컬·배포서버 양쪽에서 실측).
|
||
# 호스트 직접 실행(run_local_worker.sh)일 때만 ".profiles" 를 쓴다.
|
||
profile_dir = ".profiles"
|
||
job_deadline_sec = 300 # 잡 1건 처리 상한(크롤 행 방어). 0=무제한(테스트용)
|
||
shutdown_grace_sec = 60 # graceful 종료 유예 — docker stop_grace_period 를 이보다 길게
|
||
chrome_channel = "chrome" # 로컬: 실제 Chrome
|
||
chrome_executable = "" # 컨테이너: "/opt/google/chrome/chrome" (설정 시 channel 무시)
|
||
heartbeat_file = "/tmp/lps_worker_heartbeat" # Docker HEALTHCHECK 가 신선도 확인
|
||
|
||
# 임계 알림 (AlertManager — 룰 의미는 docs/operations.md 표)
|
||
[AlertConfig]
|
||
webhook = "" # Slack 호환 웹훅 URL. 비우면 로그로만 알림
|
||
cooldown_min = 30 # 같은 룰 재발송 억제(분). 해소 알림은 즉시
|
||
dead_1h = 20 # 최근 1h DEAD 잡 수
|
||
blocks_1h = 80 # 최근 1h 봇 감지 수
|
||
queue_lag_sec = 300 # 가장 오래된 PENDING 대기 초
|
||
pool_pct = 90 # DB 커넥션 풀 포화율(%)
|
||
source_fail_30m = 5 # 소스별 30분 내 시도 N회 이상 & 성공 0건
|
||
deadline_1h = 5 # 최근 1h 잡 데드라인 강제종료 수
|
||
cost_1h_usd = 1.0 # 최근 1h 검색원가 합($)
|
||
ports_low_pct = 30 # 가용 프록시 포트 비율(%)
|
||
block_sessions_6h = 1 # 최근 6h '예산 회전에도 차단된' IP 세션 수
|
||
|
||
# ── 시크릿(API 키 등)도 이 파일에서 통합 관리 (미커밋). ──
|
||
|
||
# 네이버 쇼핑 오픈API (https://developers.naver.com/apps). 여러 개면 429/403 로테이션 자동 포함.
|
||
[NaverConfig]
|
||
[[NaverConfig.keys]]
|
||
id = "<NAVER_CLIENT_ID>"
|
||
secret = "<NAVER_CLIENT_SECRET>"
|
||
# 추가 키는 아래처럼 블록을 더 넣으면 됨:
|
||
# [[NaverConfig.keys]]
|
||
# id = "..."
|
||
# secret = "..."
|
||
|
||
# AI 유사도 판정/검색어 생성 (OpenAI)
|
||
[OpenAIConfig]
|
||
api_key = "<OPENAI_API_KEY>"
|
||
model = "gpt-4o-mini"
|
||
|
||
# DECODO residential 프록시 (쿠팡 전용, 포트기반 sticky). 값 다 채우면 활성(비면 프록시 미사용).
|
||
[DecodoConfig]
|
||
host = "" # 예: gate.decodo.com
|
||
username = "" # 대시보드 USERNAME (예: sppd6a3ze3)
|
||
password = "" # 대시보드 PASSWORD
|
||
port_start = 0 # 예: 10001
|
||
port_end = 0 # 예: 10010 — 포트를 늘리면(계약 변경) 이 범위만 넓히면 됨(코드 무변경)
|
||
session_minutes = 10 # 대시보드 Sticky 지속시간(분)과 일치
|
||
cost_per_gb = 0.0 # DECODO 요금($/GB) — 검색 원가의 대역폭 비용 산정용(플랜에 맞게, 예 3.0)
|
||
ip_request_budget = 3 # IP당 요청 예산 — 도달 시 차단 전 선제 회전(0=비활성). 튜닝은 docs/database.md
|
||
port_cooldown_sec = 0 # 차단 감지 포트 격리 초. 0=자동 max(sticky, 30분)
|