- SIGINT/SIGTERM 핸들러 등록: cancel 대신 stop 이벤트 set → 새 잡 클레임 중단, 하던 잡은 마무리 후 자연 종료(트레이스백 없이 exit 0). 신호 재수신 시 강제 종료 - 종료 유예 LPS_SHUTDOWN_GRACE_SEC(기본 60s) 초과 시 강제 취소(잡은 lease 만료 후 재큐) - 워커/리퍼가 예외로 죽으면 기존처럼 전파하되, finally에서 남은 태스크 취소·완주 대기 후 정리 - 리스너·어댑터 정리를 항목별 try/except로 격리 — 하나 실패해도 나머지 Chrome 정리 - 웜업(bg) 태스크는 종료 신호 즉시 취소해 어댑터 락 해제 - compose lps-worker에 stop_grace_period: 75s (기본 10s면 드레인 전 SIGKILL) - 운영 가이드에 워커 종료 절차 문서화 검증: SIGTERM/SIGINT 실기동 테스트 — graceful 로그 후 exit 0, 기존 테스트 26개 통과 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
170 lines
7.0 KiB
YAML
170 lines
7.0 KiB
YAML
# Negosium + Negodata 백엔드.
|
||
# DB 는 compose 에서 관리하지 않는다 — 각 backend 는 config.docker.toml 의 접속 정보대로
|
||
# 외부 PostgreSQL 에 연결한다(호스트에 떠 있는 로컬 postgres, 또는 따로 실행 중인 docker postgres).
|
||
# 컨테이너에서 호스트의 DB 에 접속할 때는 host.docker.internal 을 쓴다.
|
||
#
|
||
# docker compose up -d
|
||
# negosium 서버: http://localhost:9300/docs
|
||
# negosium 프론트: http://localhost:3300
|
||
# negodata 서버: http://localhost:9400/docs
|
||
# agent 서버: http://localhost:9500/docs
|
||
# anchoring 배치: 포트 없음 — 상주 스케줄러(격주 토 00:00 KST), docker logs anchoring 으로 확인
|
||
#
|
||
# DB 준비(최초 1회): postgres-init 의 SQL 을 대상 DB 에 적용한다.
|
||
# psql -h <host> -p <port> -U <user> -f postgres-init/00-init.sql (스키마 전체: negosium_db + 도메인·learning·anchoring schema)
|
||
# psql -h <host> -p <port> -U <user> -f postgres-init/temp-data.sql (임시 데이터 시드: admin / admin1234, company.users)
|
||
|
||
services:
|
||
negosium-backend:
|
||
build: ./backend
|
||
container_name: negosium-backend
|
||
environment:
|
||
APP_ENV: local
|
||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
|
||
AGENT_BASE_URL: http://host.docker.internal:9500 # 컨테이너→호스트 agent (로컬 uvicorn :9500)
|
||
ports:
|
||
- "9300:9300"
|
||
# 컨테이너에서 호스트의 DB 로 접근 (config.docker.toml 의 host.docker.internal)
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
|
||
negodata-backend:
|
||
build: ./negodata/backend
|
||
container_name: negodata-backend
|
||
environment:
|
||
APP_ENV: local
|
||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
|
||
RELOAD: "1" # uvicorn --reload 활성 → 소스 저장 시 자동 재기동(재빌드 불필요)
|
||
SCHEDULER_ENABLED: "1" # 마감 크론 활성(단일 워커라 중복 없음). 운영 다중 워커면 1개 프로세스에서만 1
|
||
PYTHONUNBUFFERED: "1" # 컨테이너 로그 실시간 출력(stdout 버퍼링 끔)
|
||
volumes:
|
||
- ./negodata/backend:/app # 호스트 소스 = 컨테이너 코드. 이게 있어야 수정이 즉시 반영됨
|
||
ports:
|
||
- "9400:9400"
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
|
||
# negodata 프론트 (Vite dev 서버).
|
||
negodata-front:
|
||
build: ./negodata/front
|
||
container_name: negodata-front
|
||
ports:
|
||
- "3000:3000"
|
||
volumes:
|
||
- ./negodata/front:/app
|
||
- /app/node_modules
|
||
restart: unless-stopped
|
||
|
||
# 협상 에이전트 (negosium_db 공유, learning 스키마 사용).
|
||
agent:
|
||
build: ./agent
|
||
container_name: negosium-agent
|
||
environment:
|
||
APP_ENV: local
|
||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
|
||
ports:
|
||
- "9500:9500"
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
restart: unless-stopped
|
||
|
||
# negosium 공급사 프론트 (프로덕션 빌드 정적 서빙, :3300). 브라우저가 backend(:9300)를 직접 호출.
|
||
negosium-front:
|
||
build: ./frontend
|
||
container_name: negosium-front
|
||
ports:
|
||
- "3300:3300"
|
||
restart: unless-stopped
|
||
|
||
# 앵커링 값 자동 조정 배치 (negosium_db 공유, 포트 없음 — 상주 스케줄러).
|
||
anchoring:
|
||
build: ./schedules/anchoring
|
||
container_name: anchoring
|
||
environment:
|
||
APP_ENV: local # config.{APP_ENV}.toml 선택 (local/dev/prod)
|
||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB
|
||
REDIS_HOST: anchoring-redis
|
||
TZ: Asia/Seoul
|
||
volumes:
|
||
- ./schedules/anchoring/config.local.toml:/app/config.local.toml:ro # 시크릿은 마운트 — up 전에 파일 필요
|
||
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(요청 접수, lean) + 워커(크롤, 헤드풀 Chromium+Xvfb). DB 는 외부(host.docker.internal).
|
||
lps-api:
|
||
build:
|
||
context: ./lps
|
||
dockerfile: Dockerfile
|
||
container_name: lps-api
|
||
environment:
|
||
APP_ENV: local
|
||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
|
||
PYTHONUNBUFFERED: "1"
|
||
# 멀티코어: PROCESS_COUNT(uvicorn 워커=코어수)를 올리면 커넥션 풀은 자동 산정된다.
|
||
# (pool+overflow)×2엔진×PROCESS_COUNT ≤ DB_CONNECTION_BUDGET 를 config 가 스스로 보장.
|
||
# PROCESS_COUNT: "4"
|
||
# DB_CONNECTION_BUDGET: "40" # 전용 PG(max_connections≈100)면 90 근처로 상향
|
||
ports:
|
||
- "9600:9600"
|
||
extra_hosts:
|
||
- "host.docker.internal:host-gateway"
|
||
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
|
||
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 을 많이 씀 — 부족하면 탭 크래시
|
||
stop_grace_period: 75s # graceful 종료 유예(LPS_SHUTDOWN_GRACE_SEC=60 + 정리 여유) — 기본 10s 면 하던 잡 마무리 전에 SIGKILL
|
||
restart: unless-stopped
|
||
logging:
|
||
driver: json-file
|
||
options: { max-size: "10m", max-file: "5" }
|
||
|
||
volumes:
|
||
lps-profiles:
|