215 lines
8.9 KiB
YAML
215 lines
8.9 KiB
YAML
services:
|
||
# ── 프론트 (React/Vite → 정적 빌드 → nginx) ──────────────────────────────
|
||
negodata-front:
|
||
build:
|
||
context: ./negodata/front
|
||
dockerfile: Dockerfile.prod
|
||
container_name: negodata-front
|
||
ports:
|
||
- "${NEGODATA_FRONT_PORT:-30012}:80"
|
||
depends_on:
|
||
- negodata-backend # nginx 가 시작 시 upstream(negodata-backend) 이름을 해석해야 함
|
||
restart: unless-stopped
|
||
|
||
negosium-front:
|
||
build:
|
||
context: ./frontend
|
||
dockerfile: Dockerfile.prod
|
||
container_name: negosium-front
|
||
ports:
|
||
- "${NEGOSIUM_FRONT_PORT:-30010}:80"
|
||
depends_on:
|
||
- negosium-backend
|
||
restart: unless-stopped
|
||
|
||
# ── 솔루션 랜딩 (React Router SSG → 정적 빌드 → nginx) ───────────────────
|
||
landing:
|
||
build:
|
||
context: ./landing
|
||
dockerfile: Dockerfile.prod
|
||
container_name: negosium-landing
|
||
ports:
|
||
- "${LANDING_PORT:-30013}:80"
|
||
restart: unless-stopped
|
||
|
||
# ── 백엔드 (FastAPI, 기존 Dockerfile) ────────────────────────────────────
|
||
negodata-backend:
|
||
build: ./negodata/backend
|
||
container_name: negodata-backend
|
||
environment:
|
||
APP_ENV: prod
|
||
SCHEDULER_ENABLED: "1" # 견적 자동마감 크론(process_count=1 단일 워커라 중복 없음)
|
||
PYTHONUNBUFFERED: "1"
|
||
LPS_BASE_URL: http://lps-api:9600 # 최저가 검색요청 enqueue — 같은 compose 망의 lps-api 컨테이너(미설정 시 localhost:9600 → 연결실패)
|
||
# LPS 읽기 DB(lps_db)는 config.prod.toml 의 [LpsDBConfig](172.30.1.36/o2o_db_admin) 이 담당 → 별도 env 불필요
|
||
DB_HOST: ${DB_HOST} # toml 의 127.0.0.1 을 외부 DB 로 override
|
||
DB_PORT: ${DB_PORT:-5432}
|
||
DB_USER: ${DB_USER}
|
||
DB_PASSWORD: ${DB_PASSWORD}
|
||
DB_NAME: ${DB_NAME:-negosium_db}
|
||
volumes:
|
||
- ./negodata/backend/config/config.prod.toml:/app/config/config.prod.toml:ro
|
||
# 외부 비노출 — negodata-front 의 nginx 가 /v1 을 내부망으로 프록시.
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway" # DB 가 이 서버 호스트면 DB_HOST=host.docker.internal
|
||
restart: unless-stopped
|
||
|
||
negosium-backend:
|
||
build: ./backend
|
||
container_name: negosium-backend
|
||
environment:
|
||
APP_ENV: prod
|
||
AGENT_BASE_URL: http://agent:9500 # 내부망으로 agent 호출
|
||
PYTHONUNBUFFERED: "1"
|
||
DB_HOST: ${DB_HOST}
|
||
DB_PORT: ${DB_PORT:-5432}
|
||
DB_USER: ${DB_USER}
|
||
DB_PASSWORD: ${DB_PASSWORD}
|
||
DB_NAME: ${DB_NAME:-negosium_db}
|
||
volumes:
|
||
- ./backend/config/config.prod.toml:/app/config/config.prod.toml:ro
|
||
# 외부 비노출 — negosium-front 의 nginx 가 /v1 을 내부망으로 프록시.
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
|
||
# ── 협상 에이전트 (Python, 내부 전용) ────────────────────────────────────
|
||
agent:
|
||
build: ./agent
|
||
container_name: negosium-agent
|
||
environment:
|
||
APP_ENV: prod
|
||
PYTHONUNBUFFERED: "1"
|
||
DB_HOST: ${DB_HOST}
|
||
DB_PORT: ${DB_PORT:-5432}
|
||
DB_USER: ${DB_USER}
|
||
DB_PASSWORD: ${DB_PASSWORD}
|
||
DB_NAME: ${DB_NAME:-negosium_db}
|
||
volumes:
|
||
- ./agent/config/config.prod.toml:/app/config/config.prod.toml:ro
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
|
||
# ── 앵커링 값 자동 조정 배치 (negosium_db 공유, 포트 없음 — 상주 스케줄러) ──
|
||
# 이미지는 config.{APP_ENV}.toml 을 읽는다(dev/prod 는 파일 없으면 기동 중단). DB/시크릿은
|
||
# config.prod.toml(172.30.1.36) 에서 온다 — env(DB_*/REDIS_*) 를 설정하면 파일값을 override.
|
||
anchoring:
|
||
build: ./schedules/anchoring
|
||
container_name: anchoring
|
||
environment:
|
||
APP_ENV: prod # → config.prod.toml 선택 (local 로 두면 빈 config.local 로 기동 실패)
|
||
REDIS_HOST: anchoring-redis # 전용 캐시 컨테이너 사용(config 의 redis host 를 override)
|
||
TZ: Asia/Seoul
|
||
volumes:
|
||
- ./schedules/anchoring/config.prod.toml:/app/config.prod.toml:ro # DB 접속·시크릿은 파일에서(마운트 필수)
|
||
depends_on:
|
||
- anchoring-redis
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
logging: # 상주 배치 — 장기 운영 디스크 보호
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "5"
|
||
|
||
# 앵커링 조회 캐시 (anchoring 전용)
|
||
anchoring-redis:
|
||
image: redis:7-alpine
|
||
container_name: anchoring-redis
|
||
ports:
|
||
- "127.0.0.1:6380:6379" # 호스트 로컬만 개방 (무인증 Redis). 6380 = 호스트 redis(6379)와 충돌 회피
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "5"
|
||
|
||
# ── LPS (인터넷 최저가 검색) ──────────────────────────────────
|
||
# API(요청 접수) + 워커(크롤, Chromium+Xvfb).
|
||
# 이미지는 APP_ENV=local 고정 → config.local.toml 을 읽는다. prod 실값(DB 172.30.1.36 /
|
||
# o2o_db_admin / negosium_db + OpenAI·Naver·Decodo 시크릿)은 config.prod.toml 을 그 경로에
|
||
# 마운트해 주입한다(다른 prod 서비스와 동일한 파일 기반 방식). DB_* env 는 주지 않는다 —
|
||
# 주면 파일값을 빈 값/오타로 덮어써 인증 실패(예: 사용자 o2oadmin)한다.
|
||
lps-api:
|
||
build:
|
||
context: ./lps
|
||
dockerfile: Dockerfile
|
||
container_name: lps-api
|
||
environment:
|
||
APP_ENV: local # 이미지 고정값 — 변경 금지(config.prod.toml 을 직접 읽지 않음)
|
||
PYTHONUNBUFFERED: "1"
|
||
PROCESS_COUNT: ${LPS_API_PROCESS_COUNT:-1}
|
||
DB_CONNECTION_BUDGET: ${LPS_DB_CONNECTION_BUDGET:-40} # 전용 PG(max_connections≈100)면 90 근처로 상향
|
||
volumes:
|
||
- ./lps/config/config.prod.toml:/app/config/config.local.toml:ro # prod DB·시크릿 주입(APP_ENV=local 이 읽는 경로 덮어씀)
|
||
ports:
|
||
- "9600:9600"
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
labels:
|
||
autoheal: "true" # HEALTHCHECK 실패 시 autoheal 이 재시작
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options: { max-size: "10m", max-file: "5" }
|
||
|
||
lps-worker:
|
||
build:
|
||
context: ./lps
|
||
dockerfile: Dockerfile.worker # Chromium + Xvfb (headless 는 안티봇에 탐지됨)
|
||
container_name: lps-worker
|
||
environment:
|
||
APP_ENV: local # 이미지 고정값 — 변경 금지
|
||
PYTHONUNBUFFERED: "1"
|
||
WORKER_CONCURRENCY: "1" # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 4×N)
|
||
LPS_PROFILE_DIR: /profiles # Chrome 프로필을 영속 볼륨에 → 재시작해도 cf_clearance 유지
|
||
# DB·시크릿(OpenAI/Naver/Decodo)은 아래 config.prod.toml 마운트에서 온다. env override 는 주지 않는다.
|
||
volumes:
|
||
- ./lps/config/config.prod.toml:/app/config/config.local.toml:ro # prod DB·시크릿 주입(APP_ENV=local 이 읽는 경로 덮어씀)
|
||
- lps-profiles:/profiles # Chrome 프로필(쿠키) 영속
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시
|
||
stop_grace_period: 75s # graceful 종료 유예 — 기본 10s 면 하던 잡 마무리 전에 SIGKILL
|
||
labels:
|
||
autoheal: "true" # 하트비트 HEALTHCHECK 실패(행/좀비) 시 autoheal 이 재시작
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options: { max-size: "10m", max-file: "5" }
|
||
|
||
# LPS 관리자 UI (정적 React → nginx). /v1·/healthz·/readyz 는 nginx 가 lps-api:9600 으로 프록시(동일 compose 망).
|
||
lps-admin:
|
||
build:
|
||
context: ./lps-admin
|
||
dockerfile: Dockerfile
|
||
container_name: lps-admin
|
||
ports:
|
||
- "${LPS_ADMIN_PORT:-30014}:80"
|
||
depends_on:
|
||
- lps-api
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options: { max-size: "10m", max-file: "5" }
|
||
|
||
# HEALTHCHECK 실패 컨테이너 자동 재시작 — autoheal 라벨 붙은 컨테이너(lps-api/lps-worker)를 감시해 재시작.
|
||
# docker.sock 마운트 = 도커 제어 권한이므로 신뢰 환경에서만 사용.
|
||
autoheal:
|
||
image: willfarrell/autoheal:latest
|
||
container_name: autoheal
|
||
environment:
|
||
AUTOHEAL_CONTAINER_LABEL: autoheal
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options: { max-size: "10m", max-file: "5" }
|
||
|
||
volumes:
|
||
lps-profiles:
|