#!/bin/bash set -e echo "================================================" echo "RAG 시스템 경량 오프라인 패키지 생성" echo "================================================" # 현재 디렉토리 확인 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PROJECT_DIR="$SCRIPT_DIR" echo "프로젝트 디렉토리: $PROJECT_DIR" echo "⚡ 경량 버전 (외부 API 전용 + 로컬 MongoDB): ~500MB" # 운영 배포 기본값 (Qdrant + 어드민 포함) PACKAGE_NAME="${PACKAGE_NAME:-rag-offline-package-slim}" PACKAGE_TAR="${PACKAGE_TAR:-$PACKAGE_NAME.tar.gz}" API_HOST_PORT="${API_HOST_PORT:-28012}" ADMIN_PORT="${ADMIN_PORT:-28013}" COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-rag-project}" RAG_API_IMAGE="${RAG_API_IMAGE:-rag-api-slim:latest}" RAG_BATCH_IMAGE="${RAG_BATCH_IMAGE:-rag-batch-slim:latest}" QDRANT_IMAGE="${QDRANT_IMAGE:-qdrant/qdrant:v1.18.2}" MONGO_COLLECTION="${MONGO_COLLECTION:-rag_conversations}" QDRANT_COLLECTION="${QDRANT_COLLECTION:-qa_vectors}" # 패키지 디렉토리 생성 PACKAGE_DIR="$HOME/$PACKAGE_NAME" echo "[1/6] 패키지 디렉토리 생성: $PACKAGE_DIR" rm -rf "$PACKAGE_DIR" mkdir -p "$PACKAGE_DIR" echo "테스트 API 포트: $API_HOST_PORT" echo "Compose 프로젝트명: $COMPOSE_PROJECT_NAME" echo "API 이미지: $RAG_API_IMAGE" echo "Batch 이미지: $RAG_BATCH_IMAGE" echo "Mongo 컬렉션: $MONGO_COLLECTION" echo "Qdrant 컬렉션: $QDRANT_COLLECTION" # Docker 이미지 빌드 (경량 버전) echo "[2/6] Docker 이미지 빌드 중... (2-3분 소요)" cd "$PROJECT_DIR" # 경량 docker-compose 사용 # ⚠️ 중요: x86_64(amd64) 아키텍처로 빌드 (CentOS 7 호환) echo " ⚠️ 플랫폼: linux/amd64 (Intel/AMD x86_64용)" echo "" # Docker Buildx로 멀티플랫폼 빌드 설정 export DOCKER_BUILDKIT=1 export COMPOSE_DOCKER_CLI_BUILD=1 # 1단계: 배치 이미지 빌드 echo " [2-1] 배치 이미지 빌드 중 (linux/amd64)..." docker buildx build --platform linux/amd64 -t "$RAG_BATCH_IMAGE" -f docker/batch-slim.Dockerfile . --load if [ $? -ne 0 ]; then echo " ❌ 배치 이미지 빌드 실패" exit 1 fi echo " ✅ $RAG_BATCH_IMAGE 빌드 완료" # 2단계: API 이미지 빌드 echo " [2-2] API 이미지 빌드 중 (linux/amd64)..." docker buildx build --platform linux/amd64 -t "$RAG_API_IMAGE" -f docker/service-slim.Dockerfile . --load if [ $? -ne 0 ]; then echo " ❌ API 이미지 빌드 실패" exit 1 fi echo " ✅ $RAG_API_IMAGE 빌드 완료" # 이미지 확인 echo "[3/6] 빌드된 이미지 확인 중..." if docker images | grep -q "${RAG_API_IMAGE%%:*}"; then echo " ✅ $RAG_API_IMAGE" else echo " ❌ rag-api-slim 이미지 없음" exit 1 fi if docker images | grep -q "${RAG_BATCH_IMAGE%%:*}"; then echo " ✅ $RAG_BATCH_IMAGE" else echo " ❌ rag-batch-slim 이미지 없음" exit 1 fi # Docker 이미지 저장 echo "[4/6] Docker 이미지를 tar 파일로 저장 중..." cd "$PACKAGE_DIR" echo " - rag-api-slim.tar 저장 중..." docker save -o rag-api-slim.tar "$RAG_API_IMAGE" SIZE_API=$(ls -lh rag-api-slim.tar | awk '{print $5}') echo " 완료: $SIZE_API" echo " - rag-batch-slim.tar 저장 중..." docker save -o rag-batch-slim.tar "$RAG_BATCH_IMAGE" SIZE_BATCH=$(ls -lh rag-batch-slim.tar | awk '{print $5}') echo " 완료: $SIZE_BATCH" # Qdrant 서버 이미지 포함 (로컬에 없으면 pull) echo " - $QDRANT_IMAGE 확인/저장 중..." if ! docker image inspect "$QDRANT_IMAGE" >/dev/null 2>&1; then echo " 로컬에 없음 → docker pull $QDRANT_IMAGE" docker pull "$QDRANT_IMAGE" fi docker save -o qdrant.tar "$QDRANT_IMAGE" SIZE_QDRANT=$(ls -lh qdrant.tar | awk '{print $5}') echo " 완료: $SIZE_QDRANT" # 프로젝트 파일 복사 echo "[5/6] 프로젝트 파일 복사 중..." cp -r "$PROJECT_DIR" "$PACKAGE_DIR/rag-project" # 불필요한 파일 제거 (이미지 tar/Qdrant 저장소/캐시 등은 패키지에서 제외) cd "$PACKAGE_DIR/rag-project" rm -rf __pycache__ scripts/__pycache__ .git .gitignore images qdrant_data rm -f .env *.zip *.tar *.tar.gz # data 디렉토리 초기화 (빈 디렉토리만 유지) rm -rf data/* touch data/.gitkeep echo " - data 디렉토리 초기화 완료 (배치 작업 시 자동 생성됨)" # 기존 docker-compose.yml을 slim 버전으로 교체 mv docker-compose-slim.yml docker-compose.yml # force-reindex.sh는 유지 (재임베딩용) chmod +x force-reindex.sh 2>/dev/null || true echo " 복사된 파일: $(du -sh . | awk '{print $1}')" # 배포 스크립트 생성 echo "[6/6] 배포 스크립트 생성 중..." cd "$PACKAGE_DIR" cat > deploy.sh << 'DEPLOY_SCRIPT' #!/bin/bash set -e echo "================================================" echo "RAG 시스템 경량 오프라인 배포" echo "================================================" GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' # 운영 배포 기본값 (Qdrant + 어드민 포함) API_HOST_PORT="${API_HOST_PORT:-28012}" ADMIN_PORT="${ADMIN_PORT:-28013}" COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-rag-project}" RAG_API_IMAGE="${RAG_API_IMAGE:-rag-api-slim:latest}" RAG_BATCH_IMAGE="${RAG_BATCH_IMAGE:-rag-batch-slim:latest}" MONGO_COLLECTION="${MONGO_COLLECTION:-rag_conversations}" QDRANT_COLLECTION="${QDRANT_COLLECTION:-qa_vectors}" VECTOR_STORE_VALUE="${VECTOR_STORE:-qdrant}" QDRANT_HOST_VALUE="${QDRANT_HOST:-127.0.0.1}" set_env_value() { local key=$1 local value=$2 if grep -q "^${key}=" .env; then sed -i "s|^${key}=.*|${key}=${value}|" .env else echo "${key}=${value}" >> .env fi } # Docker 이미지 로드 (rag-api, rag-batch, qdrant) echo -e "${GREEN}[1/5] Docker 이미지 로딩 중...${NC}" for tarfile in rag-api-slim.tar rag-batch-slim.tar qdrant.tar; do if [ -f "$tarfile" ]; then docker load -i "$tarfile" echo "✅ $tarfile 로드 완료" else echo -e "${RED}❌ $tarfile 파일이 없습니다${NC}" exit 1 fi done # 이미지 확인 echo "" echo "로드된 이미지:" docker images | grep -E "rag-.*-slim|qdrant" # 프로젝트 디렉토리 이동 echo -e "${GREEN}[2/5] 프로젝트 디렉토리 설정 중...${NC}" cd rag-project # .env 파일 확인 echo -e "${GREEN}[3/5] 환경 설정 확인 중...${NC}" if [ ! -f ".env" ]; then echo -e "${YELLOW}⚠️ .env 파일이 없습니다. env.template을 복사합니다.${NC}" cp env.template .env set_env_value "API_HOST_PORT" "$API_HOST_PORT" set_env_value "ADMIN_PORT" "$ADMIN_PORT" set_env_value "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_NAME" set_env_value "RAG_API_IMAGE" "$RAG_API_IMAGE" set_env_value "RAG_BATCH_IMAGE" "$RAG_BATCH_IMAGE" set_env_value "MONGO_COLLECTION" "$MONGO_COLLECTION" set_env_value "QDRANT_COLLECTION" "$QDRANT_COLLECTION" set_env_value "VECTOR_STORE" "$VECTOR_STORE_VALUE" set_env_value "QDRANT_HOST" "$QDRANT_HOST_VALUE" echo "" echo -e "${RED}❗ 중요: .env 파일을 수정해주세요:${NC}" echo " vi .env" echo "" echo "필수 수정 항목:" echo " - LLM_HOST=" echo " - TEI_EMBED_HOST=" echo " - TEI_RERANK_HOST=" echo "" read -p "지금 수정하시겠습니까? (y/n) " answer if [ "$answer" = "y" ]; then ${EDITOR:-vi} .env else echo "나중에 수정하세요: cd rag-project && vi .env" exit 0 fi else set_env_value "API_HOST_PORT" "$API_HOST_PORT" set_env_value "ADMIN_PORT" "$ADMIN_PORT" set_env_value "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_NAME" set_env_value "RAG_API_IMAGE" "$RAG_API_IMAGE" set_env_value "RAG_BATCH_IMAGE" "$RAG_BATCH_IMAGE" set_env_value "MONGO_COLLECTION" "$MONGO_COLLECTION" set_env_value "QDRANT_COLLECTION" "$QDRANT_COLLECTION" set_env_value "VECTOR_STORE" "$VECTOR_STORE_VALUE" set_env_value "QDRANT_HOST" "$QDRANT_HOST_VALUE" fi # 외부 API 연결 테스트 echo -e "${GREEN}[4/5] 외부 API 연결 테스트 중...${NC}" source .env export API_HOST_PORT COMPOSE_PROJECT_NAME RAG_API_IMAGE RAG_BATCH_IMAGE MONGO_COLLECTION QDRANT_COLLECTION test_api() { local name=$1 local url=$2 echo -n " - $name: " if curl -s --connect-timeout 5 "$url/health" > /dev/null 2>&1; then echo -e "${GREEN}✅ OK${NC}" return 0 else echo -e "${RED}❌ FAIL${NC}" return 1 fi } ALL_OK=true test_api "LLM" "$LLM_BASE_URL" || ALL_OK=false test_api "TEI Embed" "$TEI_EMBED_URL" || ALL_OK=false test_api "TEI Rerank" "$TEI_RERANK_URL" || ALL_OK=false if [ "$ALL_OK" = false ]; then echo -e "${YELLOW}⚠️ 일부 API 연결 실패. 계속하시겠습니까? (y/n)${NC}" read -p "> " answer if [ "$answer" != "y" ]; then exit 1 fi fi # 5) Qdrant 기동 → 임베딩/인덱싱 → 서비스 시작 echo -e "${GREEN}[5/5] Qdrant 기동 → 임베딩/인덱싱 실행 중...${NC}" # 5-1) Qdrant 먼저 기동 (embed가 의존) docker compose up -d qdrant echo "Qdrant 준비 대기 중..." QDRANT_PORT_VALUE="${QDRANT_PORT:-6333}" for i in $(seq 1 30); do if curl -s "http://localhost:${QDRANT_PORT_VALUE}/readyz" > /dev/null 2>&1 \ || curl -s "http://localhost:${QDRANT_PORT_VALUE}/healthz" > /dev/null 2>&1; then echo "✅ Qdrant 준비 완료" break fi sleep 1 done # 5-2) 임베딩 → 인덱싱 (Qdrant로 적재; qdrant 모드에서 index는 자동 스킵) echo "⚠️ LLM 요약 단계 생략 (qa_raw.jsonl 직접 임베딩)" if docker compose up embed index; then echo -e "${GREEN}✅ 배치 작업 완료!${NC}" else echo -e "${RED}❌ 배치 작업 실패${NC}" exit 1 fi # 6) API + 어드민 서비스 시작 echo "" echo "================================================" echo "[6/6] API + 어드민 서비스 시작 중..." echo "================================================" docker compose up -d api admin echo "서비스 시작 대기 중... (5초)" sleep 5 # 헬스체크 API_BASE_URL="http://localhost:${API_HOST_PORT}" ADMIN_BASE_URL="http://localhost:${ADMIN_PORT}" if curl -s "${API_BASE_URL}/health" > /dev/null 2>&1; then echo -e "${GREEN}✅ API 서비스 정상 동작!${NC}" echo "" echo "🔗 엔드포인트:" echo " - 챗봇 헬스체크: ${API_BASE_URL}/health" echo " - 챗봇 질의응답: ${API_BASE_URL}/ask" echo " - 큐레이션 어드민: ${ADMIN_BASE_URL}/ (검색/조회/수정/삭제/추가)" echo " - Qdrant 대시보드: http://localhost:${QDRANT_PORT_VALUE}/dashboard" echo "" echo "📝 테스트 명령어:" echo " curl -X POST ${API_BASE_URL}/ask -H \"Content-Type: application/json\" -d '{\"query\":\"테스트 질문\"}'" echo " curl ${ADMIN_BASE_URL}/api/stats" else echo -e "${YELLOW}⚠️ 헬스체크 실패. 로그 확인 필요:${NC}" echo " docker compose logs -f api" echo " docker compose logs -f admin" echo " docker compose logs -f qdrant" fi echo "" echo "================================================" echo -e "${GREEN}배포 완료!${NC}" echo "================================================" DEPLOY_SCRIPT chmod +x deploy.sh # README 생성 cat > README.txt << 'README' RAG 시스템 경량 오프라인 운영 패키지 (Qdrant + 어드민) ================================ 외부 API(LLM/임베딩/리랭커)를 사용하고, 벡터DB는 Qdrant로 구성하는 운영 배포 패키지입니다. (외부 API 전용 + 로컬 MongoDB + 로컬 Qdrant) 패키지 크기: ~1GB 패키지 내용: - rag-api-slim.tar api(챗봇) + admin(어드민) 이미지 - rag-batch-slim.tar embed/index 배치 이미지 - qdrant.tar Qdrant 벡터DB 서버 이미지 - deploy.sh 자동 배포 스크립트 - rag-project/ 프로젝트 파일 (scripts, docker-compose.yml 등) 핵심 특징: - GPU 불필요 (모든 AI 모델은 외부 API 사용) - 로컬 MongoDB(27017) + 로컬 Qdrant(6333) 사용 - 완전한 오프라인 배포 가능 (3개 이미지 모두 포함) - 챗봇 API + 큐레이션 어드민 동시 기동 - 기본 챗봇 API 포트: 28012 - 기본 어드민 포트: 28013 - 기본 Compose 프로젝트명: rag-project - 기본 이미지 태그: rag-api-slim:latest, rag-batch-slim:latest, qdrant/qdrant:v1.18.2 - 기본 Mongo 컬렉션: rag_conversations - 기본 Qdrant 컬렉션: qa_vectors 시스템 요구사항: - OS: Linux (Ubuntu 20.04+, CentOS 7+) - Docker: 20.10+ - Docker Compose: 1.29+ - MongoDB: localhost:27017 (사전 설치 필요) * Database: chat_history * User: exlink / Password: !wkcproqkf1 - 디스크: 2GB 이상 - RAM: 4GB 이상 배포 순서: 1. tar -xzf rag-offline-package-slim.tar.gz 2. cd rag-offline-package-slim 3. rag-project/data/ 디렉토리에 qa_raw.jsonl 파일 배치 (Excel을 JSONL로 변환한 원본 QA 데이터) 4. ./deploy.sh 중요: - data 디렉토리는 빈 상태로 제공됩니다 - qa_raw.jsonl 파일을 배치해야 배치 작업이 실행됩니다 - 나머지 파일(qa_vecs.jsonl, qa.index 등)은 자동 생성됩니다 - 챗봇 API는 http://localhost:28012, 어드민은 http://localhost:28013 에서 열립니다 - Qdrant 대시보드는 http://localhost:6333/dashboard - 대화 로그는 rag_conversations 컬렉션에 저장됩니다 - 로컬에 MongoDB가 localhost:27017에서 실행 중이어야 합니다 - 기존에 떠 있던 컨테이너는 배포 전 정리하세요: docker compose down --remove-orphans 재임베딩이 필요한 경우: - ./force-reindex.sh 실행 (기존 임베딩/인덱스 삭제 후 재생성) 생성일: $(date +%Y-%m-%d) README # 전체 패키지 압축 cd "$HOME" echo "압축 중... (잠시만 기다려주세요)" tar -czf "$PACKAGE_TAR" "$PACKAGE_NAME/" # 결과 출력 echo "" echo "================================================" echo "경량 패키지 생성 완료!" echo "================================================" echo "패키지 위치: $HOME/$PACKAGE_TAR" FINAL_SIZE=$(ls -lh "$HOME/$PACKAGE_TAR" | awk '{print $5}') echo "패키지 크기: $FINAL_SIZE" echo "" echo "포함된 이미지:" echo " - $RAG_API_IMAGE (api + admin)" echo " - $RAG_BATCH_IMAGE (임베딩/인덱싱)" echo " - $QDRANT_IMAGE (벡터DB)" echo "" echo "주의: 로컬 MongoDB (localhost:27017) 사전 설치 필요" echo " Database: chat_history" echo " User: exlink / Password: !wkcproqkf1" echo "" echo "운영 실행 정보:" echo " - 챗봇 API 포트: $API_HOST_PORT" echo " - 어드민 포트: $ADMIN_PORT" echo " - Compose 프로젝트명: $COMPOSE_PROJECT_NAME" echo " - Mongo 컬렉션: $MONGO_COLLECTION" echo " - Qdrant 컬렉션: $QDRANT_COLLECTION" echo "" echo "다음 단계:" echo "1. USB로 전송: cp ~/$PACKAGE_TAR /Volumes/USB/" echo "2. 서버에서 압축 해제: tar -xzf $PACKAGE_TAR" echo "3. 원본 데이터 배치: $PACKAGE_NAME/rag-project/data/qa_raw.jsonl" echo " (Excel을 JSONL로 변환한 QA 데이터 파일)" echo "4. 배포 실행: cd $PACKAGE_NAME && ./deploy.sh" echo "================================================"