o2o-negosium-original/lps/config/server_configs.py
민헌 dc8c288f68 feat(lps): docker-compose 에 lps-admin 추가 + 환경설정 config.local.toml 단일화
- lps-admin 컨테이너화: Vite 정적 빌드 → nginx, /v1·/healthz·/readyz 를
  lps-api:9600 으로 동일출처 프록시(빌드 타임 API URL 주입 불필요)
- lps-api/worker: APP_ENV=local 고정 + DB_HOST override(도메인 backend·negodata·agent
  와 동일 패턴), config.<env>.toml 마운트 방식 폐기
- server_configs 에 DB 접속 env override(_apply_db_env_override) 복원
- config.dev/prod.toml.example 제거 — 환경 구분 없이 config.local.toml 하나
  (prod 서버도 그 서버의 config.local.toml + docker compose up -d)
- run_docker.sh 환경 선택 제거·lps-admin 포함, README·operations 문서 갱신

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:54:32 +09:00

95 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from config.config_loader import Configs
from config.config_models import (
WebServerConfig, LogConfig, MainDBConfig, NaverConfig, OpenAIConfig, DecodoConfig,
WorkerConfig, AlertConfig,
)
# ── 설정 소스는 config.local.toml 하나다(도메인 backend 와 동일 — 서버마다 그 서버의 값, 미커밋). ──
# 운영 전제: 항상 APP_ENV=local 로 띄운다 → config.local.toml (호스트 실행·pytest·docker 모두 동일).
# prod 서버도 그 서버의 config.local.toml 에 prod 값(시크릿·guard키·스케일)을 채우고 `docker compose up -d`.
# env 는 실행 입력과 '환경별로 바뀌는 접속점'만 담당한다:
# DB_HOST/PORT/… : DB 접속 override — config.local.toml 은 127.0.0.1(호스트 실행·pytest) 유지하고,
# 컨테이너에선 compose 가 DB_HOST=host.docker.internal 로 호스트 DB 를 가리킨다(_apply_db_env_override).
# PROCESS_COUNT : 실행 스크립트·부하벤치의 대화형 입력(uvicorn 워커 수 임시 override)
# WORKER_CONCURRENCY : 워커 실행 스크립트의 대화형 입력(worker_main 이 읽음)
# LPS_LIVE : 라이브 스모크 테스트 옵트인(설정이 아니라 실행 스위치)
# 시크릿(네이버·OpenAI·DECODO)은 env 로 넣지 않는다 — config.local.toml 에 두고 컨테이너엔 마운트한다.
APP_ENV = os.environ.get("APP_ENV", "local")
_config_dir = os.path.dirname(__file__)
_config_file = os.path.join(_config_dir, f"config.{APP_ENV}.toml")
if not os.path.exists(_config_file):
raise FileNotFoundError(
f"설정 파일이 없습니다: {_config_file} (APP_ENV={APP_ENV}). "
"config.local.toml 을 준비하세요(config.local.toml.example 복사). 컨테이너는 compose 가 이 파일을 마운트합니다."
)
configs = Configs(_config_file)
web_server_config: WebServerConfig = configs.get(WebServerConfig)
log_config: LogConfig = configs.get(LogConfig)
main_db_config: MainDBConfig = configs.get(MainDBConfig)
# 섹션이 없으면 기본값(빈/비활성)으로 동작.
naver_config: NaverConfig = configs.get(NaverConfig) or NaverConfig()
openai_config: OpenAIConfig = configs.get(OpenAIConfig) or OpenAIConfig()
decodo_config: DecodoConfig = configs.get(DecodoConfig) or DecodoConfig()
worker_config: WorkerConfig = configs.get(WorkerConfig) or WorkerConfig()
alert_config: AlertConfig = configs.get(AlertConfig) or AlertConfig()
# DB 접속 env override — config.local.toml 의 접속 정보를 '환경별로 바뀌는 값만' 덮는다.
# 로컬(호스트 uvicorn·pytest)은 env 미설정 → toml(127.0.0.1) 그대로. 컨테이너는 compose 가
# DB_HOST=host.docker.internal 을 넣어 호스트 DB 를 가리킨다(도메인 backend 와 동일한 패턴).
# 값이 빈 문자열이면 override 하지 않는다 → 관리형 DB 는 LPS_DB_HOST= 로 비워 config.local.toml 의 호스트를 쓴다.
def _apply_db_env_override(cfg: MainDBConfig):
if os.environ.get("DB_HOST"):
cfg.write_host = cfg.read_host = os.environ["DB_HOST"]
if os.environ.get("DB_PORT"):
cfg.write_port = cfg.read_port = int(os.environ["DB_PORT"])
if os.environ.get("DB_USER"):
cfg.write_id = cfg.read_id = os.environ["DB_USER"]
if os.environ.get("DB_PASSWORD"):
cfg.write_pw = cfg.read_pw = os.environ["DB_PASSWORD"]
if os.environ.get("DB_NAME"):
cfg.name = os.environ["DB_NAME"]
# 커넥션 예산도 env 로 조정 가능(자동 산정 입력) — 전용/공유 PG 에 맞춰 서버별로.
if os.environ.get("DB_CONNECTION_BUDGET"):
cfg.connection_budget = int(os.environ["DB_CONNECTION_BUDGET"])
def _autosize_pool(cfg: MainDBConfig, process_count: int):
"""process_count 기반 커넥션 풀 자동 산정.
실제 동시 커넥션 = (pool_size + max_overflow) × 2엔진(R/W) × process_count.
이 값이 connection_budget 를 넘지 않도록 pool_size/max_overflow 를 역산한다.
budget<=0 이면 비활성(toml 의 pool_size/max_overflow 그대로 사용).
반환: 자동 산정을 수행했으면 (pool_size, max_overflow), 아니면 None.
"""
budget = cfg.connection_budget
if budget <= 0:
return None
pc = max(1, process_count)
# 워커·엔진당 커넥션 = 예산 / (2엔진 × process_count). 최소 1(pool_size>=1) 보장.
# (예산 준수가 최우선 — process_count 가 너무 크면 엔진당 1커넥션까지 줄여서라도 예산을 넘지 않는다)
per_engine = max(1, budget // (2 * pc))
# 정상(pool) 60% + 버스트(overflow) 40% 로 분할.
cfg.pool_size = max(1, round(per_engine * 0.6))
cfg.max_overflow = max(0, per_engine - cfg.pool_size)
return cfg.pool_size, cfg.max_overflow
# DB 접속점 override(호스트↔컨테이너) — 자동 산정(예산) 전에 적용해야 budget override 가 반영된다.
_apply_db_env_override(main_db_config)
# uvicorn 워커 수 — 실행 스크립트/부하벤치의 대화형 입력만 env 로 임시 override(설정은 toml 이 소스).
if os.environ.get("PROCESS_COUNT"):
web_server_config.process_count = int(os.environ["PROCESS_COUNT"])
# 커넥션 풀 자동 산정: process_count(위에서 확정) 기준으로 예산 안에 맞춘다.
# → 멀티워커 배포 시 풀 오버서브스크립션(→커넥션 고갈)을 config 가 스스로 방지.
_pool_autosized = _autosize_pool(main_db_config, web_server_config.process_count)