배포서버 쿠팡 크롤 조사(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>
86 lines
3.1 KiB
Plaintext
86 lines
3.1 KiB
Plaintext
# 배포서버용. 복사해서 사용: cp config.prod.toml.example config.prod.toml
|
|
# 실제 config.prod.toml 은 시크릿 포함이라 커밋하지 않는다.
|
|
#
|
|
# ⚠️ 로드 경로 주의 — docker-compose.prod.yml 이 이 파일을 config.local.toml 자리에 마운트한다:
|
|
# ./lps/config/config.prod.toml : /app/config/config.local.toml : ro
|
|
# 컨테이너는 APP_ENV=local 로 뜨므로 코드가 읽는 파일명은 config.local.toml 이지만
|
|
# 내용은 이 파일이다. 파일명만 보고 "prod 설정이 안 읽힌다"고 오해하기 쉽다.
|
|
#
|
|
# 로컬(config.local.toml.example)과 다른 항목만 ★ 로 표시했다.
|
|
|
|
[WebServerConfig]
|
|
server_name = "LpsServer"
|
|
port = 9600
|
|
process_count = 1
|
|
is_ssl = false # 리버스프록시 뒤 — TLS 는 프록시가 종단
|
|
is_test = false # ★ prod
|
|
cors_origins = [] # ★ 브라우저가 직접 호출하지 않으면 비움
|
|
api_keys = ["<API_KEY>"] # ★ prod 는 반드시 채운다(비면 무인증 개방). openssl rand -hex 32
|
|
|
|
[LogConfig]
|
|
print_console = true
|
|
log_level = "info" # ★ prod
|
|
|
|
[MainDBConfig]
|
|
db_type = "postgresql"
|
|
name = "lps_db"
|
|
write_host = "host.docker.internal" # ★ 컨테이너 → 호스트 DB (compose extra_hosts)
|
|
write_port = 5432
|
|
write_id = "<DB_USER>"
|
|
write_pw = "<DB_PASSWORD>"
|
|
read_host = "host.docker.internal" # ★
|
|
read_port = 5432
|
|
read_id = "<DB_USER>"
|
|
read_pw = "<DB_PASSWORD>"
|
|
show_log = false
|
|
pool_size = 10
|
|
max_overflow = 20
|
|
connection_budget = 40
|
|
sslmode = "" # 관리형 DB 면 "require"
|
|
|
|
[WorkerConfig]
|
|
concurrency = 1 # ★ 동시성을 올리면 차단 시 IP 소모도 비례해 늘어난다
|
|
fallbacks = []
|
|
profile_dir = "/profiles" # ★ 필수 — compose 가 lps-profiles 볼륨을 여기에 마운트한다.
|
|
# ".profiles" 로 두면 컨테이너 레이어에 쌓여 재생성마다 쿠키가 날아간다.
|
|
job_deadline_sec = 300
|
|
shutdown_grace_sec = 60
|
|
chrome_channel = "chrome"
|
|
chrome_executable = "/opt/google/chrome/chrome" # ★ 컨테이너 필수(설정 시 --no-sandbox 가 함께 붙음)
|
|
heartbeat_file = "/tmp/lps_worker_heartbeat"
|
|
|
|
[AlertConfig]
|
|
webhook = "" # ★ Slack 호환 웹훅. 비우면 로그로만 알림
|
|
cooldown_min = 30
|
|
dead_1h = 20
|
|
blocks_1h = 80
|
|
queue_lag_sec = 300
|
|
pool_pct = 90
|
|
source_fail_30m = 5
|
|
deadline_1h = 5
|
|
cost_1h_usd = 1.0
|
|
ports_low_pct = 30
|
|
block_sessions_6h = 1
|
|
|
|
# ── 시크릿 ──
|
|
|
|
[NaverConfig]
|
|
[[NaverConfig.keys]]
|
|
id = "<NAVER_CLIENT_ID>"
|
|
secret = "<NAVER_CLIENT_SECRET>"
|
|
|
|
[OpenAIConfig]
|
|
api_key = "<OPENAI_API_KEY>" # 소진되면 크롤이 성공해도 AI 판정 실패로 잡이 DEAD 된다
|
|
model = "gpt-4o-mini"
|
|
|
|
[DecodoConfig]
|
|
host = "gate.decodo.com"
|
|
username = "<DECODO_USERNAME>"
|
|
password = "<DECODO_PASSWORD>"
|
|
port_start = 10001
|
|
port_end = 10100
|
|
session_minutes = 10
|
|
cost_per_gb = 3.0
|
|
ip_request_budget = 3 # IP당 요청 예산. 차단이 ip_req#1 에 몰리면 이 값과 무관한 문제다
|
|
port_cooldown_sec = 0 # 0=자동 max(sticky, 30분)
|