운영 배포 가이드
운영 배포 가이드
섹션 제목: “운영 배포 가이드”LLM Search Service를 프로덕션 환경에 배포하기 위한 가이드입니다.
배포 구성 옵션
섹션 제목: “배포 구성 옵션”| 구성 | 특징 | 권장 환경 |
|---|---|---|
| Standalone | FastAPI 단독 실행 | 개발, 소규모 |
| Docker | 컨테이너 기반 | 중규모, 클라우드 |
| DDEV 통합 | 기존 DDEV 환경에 추가 | si-data 프로젝트 |
1. 사전 요구사항
섹션 제목: “1. 사전 요구사항”필수 요구사항
섹션 제목: “필수 요구사항”- Python 3.11+
- OpenAI API 키 (프로덕션)
- Elasticsearch 7.x (RAG Extended 사용 시)
선택 요구사항
섹션 제목: “선택 요구사항”- Ollama (로컬 LLM 사용 시)
- Docker (컨테이너 배포 시)
2. Standalone 배포
섹션 제목: “2. Standalone 배포”2.1 설치
섹션 제목: “2.1 설치”cd services/llm-search
# 가상환경 생성python3 -m venv .venvsource .venv/bin/activate
# 의존성 설치pip install -r requirements.txt2.2 환경 설정
섹션 제목: “2.2 환경 설정”# .env 파일 생성cp .env.example .env
# 필수 설정 편집nano .env.env 설정 예시:
# LLM Provider (openai 권장)LLM_PROVIDER=openai
# OpenAI 설정OPENAI_API_KEY=sk-proj-your-api-key-hereOPENAI_MODEL=gpt-4oOPENAI_TIMEOUT=30OPENAI_MAX_RETRIES=3
# Elasticsearch (RAG Extended 사용 시)ES_HOST=localhostES_PORT=9200ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# 서비스 설정SERVICE_HOST=0.0.0.0SERVICE_PORT=8000DEBUG=falseLOG_LEVEL=INFO2.3 실행
섹션 제목: “2.3 실행”# 개발 모드uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# 프로덕션 모드 (Gunicorn)pip install gunicorngunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:80002.4 서비스 등록 (systemd)
섹션 제목: “2.4 서비스 등록 (systemd)”[Unit]Description=LLM Search ServiceAfter=network.target
[Service]Type=simpleUser=www-dataWorkingDirectory=/opt/llm-searchEnvironment="PATH=/opt/llm-search/.venv/bin"ExecStart=/opt/llm-search/.venv/bin/gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000Restart=alwaysRestartSec=5
[Install]WantedBy=multi-user.targetsudo systemctl daemon-reloadsudo systemctl enable llm-searchsudo systemctl start llm-search3. Docker 배포
섹션 제목: “3. Docker 배포”3.1 Dockerfile
섹션 제목: “3.1 Dockerfile”# services/llm-search/DockerfileFROM python:3.11-slim
WORKDIR /app
# 의존성 설치COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 복사COPY app/ ./app/
# 환경변수ENV PYTHONUNBUFFERED=1ENV SERVICE_HOST=0.0.0.0ENV SERVICE_PORT=8000
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]3.2 Docker Compose
섹션 제목: “3.2 Docker Compose”version: '3.8'
services: llm-search: build: . ports: - "8000:8000" environment: - LLM_PROVIDER=openai - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=gpt-4o - ES_HOST=elasticsearch - ES_PORT=9200 depends_on: - elasticsearch restart: unless-stopped
elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.14 environment: - discovery.type=single-node - "ES_JAVA_OPTS=-Xms512m -Xmx512m" volumes: - es_data:/usr/share/elasticsearch/data ports: - "9200:9200"
volumes: es_data:3.3 실행
섹션 제목: “3.3 실행”# 빌드 및 실행docker-compose up -d
# 로그 확인docker-compose logs -f llm-search
# 상태 확인curl http://localhost:8000/health4. DDEV 통합 배포
섹션 제목: “4. DDEV 통합 배포”4.1 DDEV 커스텀 서비스 추가
섹션 제목: “4.1 DDEV 커스텀 서비스 추가”version: '3.6'
services: llm-search: build: context: ../services/llm-search dockerfile: Dockerfile container_name: ddev-${DDEV_SITENAME}-llm-search labels: com.ddev.site-name: ${DDEV_SITENAME} com.ddev.approot: $DDEV_APPROOT environment: - LLM_PROVIDER=openai - OPENAI_API_KEY=${OPENAI_API_KEY} - ES_HOST=elasticsearch - ES_PORT=9200 - ES_DATA_INDEX=elasticsearch_index_datasi_new_si expose: - "8000" external_links: - ddev-router:${DDEV_SITENAME}.ddev.site4.2 DDEV 환경변수 설정
섹션 제목: “4.2 DDEV 환경변수 설정”# .ddev/.env 또는 .ddev/config.yaml의 web_environmentddev config --web-environment-add="OPENAI_API_KEY=sk-proj-your-key"4.3 Drupal 모듈 통합
섹션 제목: “4.3 Drupal 모듈 통합”namespace Drupal\si_data\Service;
use GuzzleHttp\ClientInterface;
class LLMSearchService {
protected $httpClient; protected $baseUrl;
public function __construct(ClientInterface $http_client) { $this->httpClient = $http_client; $this->baseUrl = 'http://llm-search:8000'; }
public function expandKeywords(string $query, int $maxKeywords = 5): array { try { $response = $this->httpClient->post($this->baseUrl . '/api/keywords/expand', [ 'json' => [ 'query' => $query, 'max_keywords' => $maxKeywords, 'provider' => 'openai', ], 'timeout' => 30, ]);
return json_decode($response->getBody(), TRUE); } catch (\Exception $e) { \Drupal::logger('si_data')->error('LLM Search error: @error', [ '@error' => $e->getMessage(), ]); return ['expanded_keywords' => []]; } }}services: si_data.llm_search: class: Drupal\si_data\Service\LLMSearchService arguments: ['@http_client']5. 모니터링 및 운영
섹션 제목: “5. 모니터링 및 운영”5.1 헬스 체크
섹션 제목: “5.1 헬스 체크”# 기본 헬스 체크curl http://localhost:8000/health
# 상세 설정 확인curl http://localhost:8000/config
# Provider 연결 테스트curl http://localhost:8000/api/keywords/test/all5.2 사용량 모니터링
섹션 제목: “5.2 사용량 모니터링”# OpenAI 사용량 확인curl http://localhost:8000/api/benchmark/usage-stats
# PageIndex 캐시 현황curl http://localhost:8000/api/pageindex/cache/list5.3 로깅 설정
섹션 제목: “5.3 로깅 설정”# app/main.py에 추가import logging
logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("/var/log/llm-search/app.log"), logging.StreamHandler() ])5.4 알림 설정 (선택)
섹션 제목: “5.4 알림 설정 (선택)”# 비용 임계값 알림 예시async def check_usage_threshold(): from app.services.openai_client import OpenAIClient client = OpenAIClient() stats = client.usage_stats
if stats['total_cost_usd'] > 10.0: # $10 초과 시 # 알림 전송 (Slack, Email 등) pass6. 보안 설정
섹션 제목: “6. 보안 설정”6.1 API 키 보호
섹션 제목: “6.1 API 키 보호”# .env 파일 권한 설정chmod 600 .env
# Git에서 제외 확인cat .gitignore | grep ".env"6.2 CORS 설정 (프로덕션)
섹션 제목: “6.2 CORS 설정 (프로덕션)”cors_origins: List[str] = [ "https://si-data.seoul.go.kr", "https://data.seoul.go.kr"]6.3 Rate Limiting (선택)
섹션 제목: “6.3 Rate Limiting (선택)”# pip install slowapifrom slowapi import Limiterfrom slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)app.state.limiter = limiter
@router.post("/expand")@limiter.limit("10/minute")async def expand_keywords(request: Request, ...): ...7. 트러블슈팅
섹션 제목: “7. 트러블슈팅”7.1 일반적인 문제
섹션 제목: “7.1 일반적인 문제”| 문제 | 원인 | 해결책 |
|---|---|---|
| 429 Too Many Requests | OpenAI rate limit | delay 증가, API 키 확인 |
| Connection refused | ES 미실행 | ES 서비스 시작 |
| Timeout | 네트워크/서버 지연 | timeout 값 증가 |
| Import error | 의존성 누락 | pip install -r requirements.txt |
7.2 로그 확인
섹션 제목: “7.2 로그 확인”# 서비스 로그tail -f /tmp/llm-search.log
# Docker 로그docker-compose logs -f llm-search
# systemd 로그journalctl -u llm-search -f7.3 디버그 모드
섹션 제목: “7.3 디버그 모드”# .env에서 설정DEBUG=trueLOG_LEVEL=DEBUG
# 또는 실행 시uvicorn app.main:app --reload --log-level debug8. 권장 설정 요약
섹션 제목: “8. 권장 설정 요약”프로덕션 환경 (.env)
섹션 제목: “프로덕션 환경 (.env)”# ProviderLLM_PROVIDER=openai
# OpenAIOPENAI_API_KEY=sk-proj-xxxOPENAI_MODEL=gpt-4oOPENAI_TIMEOUT=30OPENAI_MAX_RETRIES=3
# ElasticsearchES_HOST=elasticsearchES_PORT=9200ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# ServiceSERVICE_HOST=0.0.0.0SERVICE_PORT=8000DEBUG=falseLOG_LEVEL=INFO
# CORS (프로덕션 도메인만)CORS_ORIGINS=["https://si-data.seoul.go.kr"]비용 예상
섹션 제목: “비용 예상”| 사용량 | OpenAI Basic | OpenAI RAG |
|---|---|---|
| 일 100 쿼리 | ~$0.07 | ~$0.17 |
| 일 1,000 쿼리 | ~$0.73 | ~$1.70 |
| 월 10,000 쿼리 | ~$7.30 | ~$17.00 |
변경 이력
섹션 제목: “변경 이력”| 버전 | 날짜 | 내용 |
|---|---|---|
| 1.0 | 2026-01-26 | 초기 작성 |