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),