From 3a8d6699704625034383992071528f6da33e8819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Thu, 9 Jul 2026 23:04:37 +0900 Subject: [PATCH] =?UTF-8?q?feat(lps):=20P2=20=EC=8B=9C=ED=81=AC=EB=A6=BF?= =?UTF-8?q?=20env=20=EC=A3=BC=EC=9E=85=20+=20Chrome=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=98=81=EC=86=8D=20=EB=B3=BC=EB=A5=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로덕션에서 시크릿을 이미지에 굽지 않고 env 로 주입, 재시작 시 챌린지 쿠키 유지. - server_configs: 시크릿 env override 추가(house 의 DB override 패턴 확장) — OPENAI_API_KEY/MODEL, DECODO_HOST/USERNAME/PASSWORD/COST_PER_GB, NAVER_KEYS="id:sec,...". 로컬은 env 미설정 → config.local.toml 그대로. - worker_main: Chrome 프로필 경로를 LPS_PROFILE_DIR(기본 /tmp)로 env 화 → 영속 볼륨 마운트 가능. - docker-compose: lps-worker 에 lps-profiles 볼륨(/profiles) + 시크릿 env 주입 주석 예시. - config.example: 프로덕션 env 주입 목록 문서화. 검증: env override 동작(OpenAI/DECODO/Naver), 프로필이 /profiles 볼륨에 Cookies 포함 저장(cf_clearance 유지). 89 tests. Co-Authored-By: Claude Fable 5 --- docker-compose.yml | 12 ++++++++++++ lps/config/config.local.toml.example | 8 ++++++++ lps/config/server_configs.py | 23 ++++++++++++++++++++++- lps/worker_main.py | 4 +++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8f2d376..0639251 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -143,6 +143,15 @@ services: DB_HOST: host.docker.internal PYTHONUNBUFFERED: "1" WORKER_CONCURRENCY: "1" # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 4×N) + LPS_PROFILE_DIR: /profiles # Chrome 프로필을 영속 볼륨에 → 재시작해도 cf_clearance 유지(재웜업 회피) + # ── 프로덕션 시크릿 주입(이미지에 안 굽고 env 로). 아래 주석 해제 후 값 채우거나 secrets 매니저 연동 ── + # OPENAI_API_KEY: ${OPENAI_API_KEY} + # DECODO_USERNAME: ${DECODO_USERNAME} + # DECODO_PASSWORD: ${DECODO_PASSWORD} + # DECODO_HOST: ${DECODO_HOST} + # NAVER_KEYS: ${NAVER_KEYS} # "id1:secret1,id2:secret2" + volumes: + - lps-profiles:/profiles # Chrome 프로필(쿠키) 영속 extra_hosts: - "host.docker.internal:host-gateway" shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시 @@ -150,3 +159,6 @@ services: logging: driver: json-file options: { max-size: "10m", max-file: "5" } + +volumes: + lps-profiles: diff --git a/lps/config/config.local.toml.example b/lps/config/config.local.toml.example index de6f428..bef8dba 100644 --- a/lps/config/config.local.toml.example +++ b/lps/config/config.local.toml.example @@ -1,6 +1,14 @@ # 복사해서 사용: cp config.local.toml.example config.local.toml # 실제 config.local.toml 은 시크릿 포함이라 커밋하지 않는다(.gitignore: *.toml). # 모든 서버는 APP_ENV=local 로 띄우며 이 파일을 읽는다. +# +# ── 프로덕션: 시크릿을 이미지에 굽지 말고 env 로 주입(server_configs 가 override) ── +# DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME +# OPENAI_API_KEY / OPENAI_MODEL +# DECODO_HOST / DECODO_USERNAME / DECODO_PASSWORD / DECODO_COST_PER_GB +# NAVER_KEYS="id1:secret1,id2:secret2" +# LPS_PROFILE_DIR=/profiles (Chrome 프로필 영속 볼륨), LPS_CHROME_EXECUTABLE=/usr/bin/chromium +# → 배포 시엔 아래 시크릿 값을 비워두고 위 env 로 채우면 이미지에 시크릿이 안 남는다. [WebServerConfig] server_name = "LpsServer" port = 9600 diff --git a/lps/config/server_configs.py b/lps/config/server_configs.py index e93b65b..6c1405a 100644 --- a/lps/config/server_configs.py +++ b/lps/config/server_configs.py @@ -2,7 +2,7 @@ import os from config.config_loader import Configs from config.config_models import ( - WebServerConfig, LogConfig, MainDBConfig, NaverConfig, OpenAIConfig, DecodoConfig, + WebServerConfig, LogConfig, MainDBConfig, NaverConfig, NaverKey, OpenAIConfig, DecodoConfig, ) # 실행 환경 결정 (기본 local). 환경변수 APP_ENV 로 변경. @@ -41,4 +41,25 @@ def _apply_db_env_override(cfg: MainDBConfig): cfg.name = os.environ["DB_NAME"] +# 시크릿 env override — 프로덕션에선 API 키를 이미지에 굽지 않고 env(또는 시크릿매니저)로 주입한다. +# 로컬은 env 미설정 → config.local.toml 값 그대로. (배포 시 toml 의 시크릿은 비워두고 아래 env 로 주입 권장) +def _apply_secret_env_override(): + if os.environ.get("OPENAI_API_KEY"): + openai_config.api_key = os.environ["OPENAI_API_KEY"] + if os.environ.get("OPENAI_MODEL"): + openai_config.model = os.environ["OPENAI_MODEL"] + for k in ("host", "username", "password"): + v = os.environ.get(f"DECODO_{k.upper()}") + if v: + setattr(decodo_config, k, v) + if os.environ.get("DECODO_COST_PER_GB"): + decodo_config.cost_per_gb = float(os.environ["DECODO_COST_PER_GB"]) + # NAVER_KEYS="id1:secret1,id2:secret2" 형식으로 키 로테이션 주입 + nk = os.environ.get("NAVER_KEYS") + if nk: + naver_config.keys = [NaverKey(id=i, secret=s) + for i, s in (p.split(":", 1) for p in nk.split(",") if ":" in p)] + + _apply_db_env_override(main_db_config) +_apply_secret_env_override() diff --git a/lps/worker_main.py b/lps/worker_main.py index 7da5b3f..5b0b990 100644 --- a/lps/worker_main.py +++ b/lps/worker_main.py @@ -40,7 +40,9 @@ def _build_worker(i: int, concurrency: int, has_openai: bool, neg_cache, history suffix = f"_w{i}" if concurrency > 1 else "" def _pf(source): # 워커별 Chrome 프로필 경로(중복 실행 시 ProcessSingleton 충돌 방지) - return f"/tmp/lps_{source}{suffix}" + # LPS_PROFILE_DIR 를 영속 볼륨으로 마운트하면 재시작해도 cf_clearance 등 쿠키 유지(재웜업 회피). + base = os.environ.get("LPS_PROFILE_DIR", "/tmp") + return f"{base}/lps_{source}{suffix}" adapters = { "coupang": CoupangAdapter(headless=False, user_data_dir=_pf("coupang"), proxy=proxy, on_detect=bot_log.record),