콘텐츠로 이동

운영 배포 가이드

LLM Search Service를 프로덕션 환경에 배포하기 위한 가이드입니다.

구성특징권장 환경
StandaloneFastAPI 단독 실행개발, 소규모
Docker컨테이너 기반중규모, 클라우드
DDEV 통합기존 DDEV 환경에 추가si-data 프로젝트

  • Python 3.11+
  • OpenAI API 키 (프로덕션)
  • Elasticsearch 7.x (RAG Extended 사용 시)
  • Ollama (로컬 LLM 사용 시)
  • Docker (컨테이너 배포 시)

Terminal window
cd services/llm-search
# 가상환경 생성
python3 -m venv .venv
source .venv/bin/activate
# 의존성 설치
pip install -r requirements.txt
Terminal window
# .env 파일 생성
cp .env.example .env
# 필수 설정 편집
nano .env

.env 설정 예시:

Terminal window
# LLM Provider (openai 권장)
LLM_PROVIDER=openai
# OpenAI 설정
OPENAI_API_KEY=sk-proj-your-api-key-here
OPENAI_MODEL=gpt-4o
OPENAI_TIMEOUT=30
OPENAI_MAX_RETRIES=3
# Elasticsearch (RAG Extended 사용 시)
ES_HOST=localhost
ES_PORT=9200
ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# 서비스 설정
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8000
DEBUG=false
LOG_LEVEL=INFO
Terminal window
# 개발 모드
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# 프로덕션 모드 (Gunicorn)
pip install gunicorn
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
/etc/systemd/system/llm-search.service
[Unit]
Description=LLM Search Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/llm-search
Environment="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:8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Terminal window
sudo systemctl daemon-reload
sudo systemctl enable llm-search
sudo systemctl start llm-search

# services/llm-search/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 복사
COPY app/ ./app/
# 환경변수
ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8000
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
services/llm-search/docker-compose.yml
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:
Terminal window
# 빌드 및 실행
docker-compose up -d
# 로그 확인
docker-compose logs -f llm-search
# 상태 확인
curl http://localhost:8000/health

.ddev/docker-compose.llm-search.yaml
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.site
Terminal window
# .ddev/.env 또는 .ddev/config.yaml의 web_environment
ddev config --web-environment-add="OPENAI_API_KEY=sk-proj-your-key"
web/modules/custom/si_data/src/Service/LLMSearchService.php
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' => []];
}
}
}
web/modules/custom/si_data/si_data.services.yml
services:
si_data.llm_search:
class: Drupal\si_data\Service\LLMSearchService
arguments: ['@http_client']

Terminal window
# 기본 헬스 체크
curl http://localhost:8000/health
# 상세 설정 확인
curl http://localhost:8000/config
# Provider 연결 테스트
curl http://localhost:8000/api/keywords/test/all
Terminal window
# OpenAI 사용량 확인
curl http://localhost:8000/api/benchmark/usage-stats
# PageIndex 캐시 현황
curl http://localhost:8000/api/pageindex/cache/list
# 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()
]
)
# 비용 임계값 알림 예시
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 등)
pass

Terminal window
# .env 파일 권한 설정
chmod 600 .env
# Git에서 제외 확인
cat .gitignore | grep ".env"
app/config.py
cors_origins: List[str] = [
"https://si-data.seoul.go.kr",
"https://data.seoul.go.kr"
]
# pip install slowapi
from slowapi import Limiter
from 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, ...):
...

문제원인해결책
429 Too Many RequestsOpenAI rate limitdelay 증가, API 키 확인
Connection refusedES 미실행ES 서비스 시작
Timeout네트워크/서버 지연timeout 값 증가
Import error의존성 누락pip install -r requirements.txt
Terminal window
# 서비스 로그
tail -f /tmp/llm-search.log
# Docker 로그
docker-compose logs -f llm-search
# systemd 로그
journalctl -u llm-search -f
Terminal window
# .env에서 설정
DEBUG=true
LOG_LEVEL=DEBUG
# 또는 실행 시
uvicorn app.main:app --reload --log-level debug

Terminal window
# Provider
LLM_PROVIDER=openai
# OpenAI
OPENAI_API_KEY=sk-proj-xxx
OPENAI_MODEL=gpt-4o
OPENAI_TIMEOUT=30
OPENAI_MAX_RETRIES=3
# Elasticsearch
ES_HOST=elasticsearch
ES_PORT=9200
ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# Service
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8000
DEBUG=false
LOG_LEVEL=INFO
# CORS (프로덕션 도메인만)
CORS_ORIGINS=["https://si-data.seoul.go.kr"]
사용량OpenAI BasicOpenAI RAG
일 100 쿼리~$0.07~$0.17
일 1,000 쿼리~$0.73~$1.70
월 10,000 쿼리~$7.30~$17.00

버전날짜내용
1.02026-01-26초기 작성