338 lines
11 KiB
Bash
Executable File
338 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
||
# build.sh - Poetry 기반 Vector DB API Service Image 빌드 스크립트
|
||
|
||
set -e
|
||
|
||
# 변수 설정
|
||
IMAGE_NAME="vector-api"
|
||
IMAGE_TAG="${1:-latest}"
|
||
ACR_NAME="${2:-acrdigitalgarage03}"
|
||
RESOURCE_GROUP="${3:-rg-digitalgarage-03}"
|
||
BASE_IMAGE_TAG="${4:-latest}"
|
||
|
||
# ACR URL 자동 구성
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
REGISTRY="${ACR_NAME}.azurecr.io"
|
||
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||
BASE_IMAGE="${REGISTRY}/vector-api-base:${BASE_IMAGE_TAG}"
|
||
else
|
||
FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}"
|
||
BASE_IMAGE="vector-api-base:${BASE_IMAGE_TAG}"
|
||
fi
|
||
|
||
# 고정된 파일 경로
|
||
DOCKERFILE_PATH="deployment/container/Dockerfile"
|
||
BUILD_CONTEXT="."
|
||
|
||
# 색상 설정
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m'
|
||
|
||
log_info() { echo -e "${CYAN}ℹ️ $1${NC}"; }
|
||
log_success() { echo -e "${GREEN}✅ $1${NC}"; }
|
||
log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
||
log_error() { echo -e "${RED}❌ $1${NC}"; }
|
||
|
||
echo "========================================================"
|
||
echo "🚀 Poetry 기반 Vector DB API Service Image 빌드"
|
||
echo "========================================================"
|
||
echo "Service 이미지명: ${FULL_IMAGE_NAME}"
|
||
echo "Base 이미지: ${BASE_IMAGE}"
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
echo "ACR 이름: ${ACR_NAME}"
|
||
echo "리소스 그룹: ${RESOURCE_GROUP}"
|
||
fi
|
||
echo "빌드 시작: $(date)"
|
||
echo ""
|
||
|
||
# 시작 시간 기록
|
||
BUILD_START=$(date +%s)
|
||
|
||
# 사용법 표시 함수
|
||
show_usage() {
|
||
echo "사용법:"
|
||
echo " $0 [IMAGE_TAG] [ACR_NAME] [RESOURCE_GROUP] [BASE_IMAGE_TAG]"
|
||
echo ""
|
||
echo "파라미터:"
|
||
echo " IMAGE_TAG : Service 이미지 태그 (기본값: latest)"
|
||
echo " ACR_NAME : Azure Container Registry 이름"
|
||
echo " RESOURCE_GROUP: Azure 리소스 그룹"
|
||
echo " BASE_IMAGE_TAG: Base 이미지 태그 (기본값: latest)"
|
||
echo ""
|
||
echo "예시:"
|
||
echo " $0 v1.0.0 # 로컬 빌드만"
|
||
echo " $0 v1.0.0 acrdigitalgarage01 rg-digitalgarage-03 # ACR 빌드 + 푸시"
|
||
echo " $0 v1.0.0 acrdigitalgarage01 rg-digitalgarage-03 v2.0.0 # 특정 Base Image 사용"
|
||
echo ""
|
||
echo "전제조건:"
|
||
echo " Poetry 기반 Base Image가 먼저 빌드되어 있어야 합니다:"
|
||
echo " ./build-base.sh ${BASE_IMAGE_TAG} [ACR_NAME] [RESOURCE_GROUP]"
|
||
echo ""
|
||
echo "💡 장점:"
|
||
echo " - 빠른 빌드: 앱 코드만 복사 (30초~2분)"
|
||
echo " - Poetry 환경: 의존성 관리 최적화"
|
||
echo " - 캐시 활용: Base Image 재사용으로 효율성 극대화"
|
||
}
|
||
|
||
# ACR 로그인 함수
|
||
acr_login() {
|
||
local acr_name="$1"
|
||
local resource_group="$2"
|
||
|
||
log_info "Azure Container Registry 로그인 중..."
|
||
|
||
if ! command -v az &> /dev/null; then
|
||
log_error "Azure CLI (az)가 설치되지 않았습니다."
|
||
exit 1
|
||
fi
|
||
|
||
if ! command -v jq &> /dev/null; then
|
||
log_error "jq가 설치되지 않았습니다."
|
||
exit 1
|
||
fi
|
||
|
||
if ! az account show &> /dev/null; then
|
||
log_error "Azure에 로그인되지 않았습니다."
|
||
echo "로그인 명령: az login"
|
||
exit 1
|
||
fi
|
||
|
||
local credential_json
|
||
credential_json=$(az acr credential show --name "${acr_name}" --resource-group "${resource_group}" 2>/dev/null)
|
||
|
||
if [ $? -ne 0 ]; then
|
||
log_error "ACR credential 조회 실패"
|
||
exit 1
|
||
fi
|
||
|
||
local username
|
||
local password
|
||
|
||
username=$(echo "${credential_json}" | jq -r '.username')
|
||
password=$(echo "${credential_json}" | jq -r '.passwords[0].value')
|
||
|
||
if [ -z "${username}" ] || [ -z "${password}" ] || [ "${username}" == "null" ] || [ "${password}" == "null" ]; then
|
||
log_error "ACR credential 파싱 실패"
|
||
exit 1
|
||
fi
|
||
|
||
echo "${password}" | docker login "${REGISTRY}" -u "${username}" --password-stdin
|
||
|
||
if [ $? -eq 0 ]; then
|
||
log_success "ACR 로그인 성공!"
|
||
return 0
|
||
else
|
||
log_error "ACR 로그인 실패"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 파라미터 검증
|
||
if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
|
||
show_usage
|
||
exit 0
|
||
fi
|
||
|
||
if [ -n "${ACR_NAME}" ] && [ -z "${RESOURCE_GROUP}" ]; then
|
||
log_error "ACR_NAME이 제공된 경우 RESOURCE_GROUP도 필요합니다."
|
||
echo ""
|
||
show_usage
|
||
exit 1
|
||
fi
|
||
|
||
# 필수 파일 확인
|
||
log_info "필수 파일 확인 중..."
|
||
|
||
if [ ! -f "app/main.py" ]; then
|
||
log_error "app/main.py 파일을 찾을 수 없습니다."
|
||
exit 1
|
||
fi
|
||
|
||
if [ ! -f "${DOCKERFILE_PATH}" ]; then
|
||
log_error "${DOCKERFILE_PATH} 파일을 찾을 수 없습니다."
|
||
exit 1
|
||
fi
|
||
|
||
log_success "모든 필수 파일이 확인되었습니다."
|
||
echo "📄 Service Dockerfile: ${DOCKERFILE_PATH}"
|
||
echo "📄 메인 애플리케이션: app/main.py"
|
||
echo "🏗️ 빌드 컨텍스트: ${BUILD_CONTEXT}"
|
||
|
||
# Base Image 존재 확인
|
||
echo ""
|
||
log_info "Poetry 기반 Base Image 확인 중..."
|
||
if docker image inspect "${BASE_IMAGE}" >/dev/null 2>&1; then
|
||
log_success "Base Image 확인됨: ${BASE_IMAGE}"
|
||
|
||
# Base Image에서 Poetry 환경 확인
|
||
log_info "Base Image Poetry 환경 검증 중..."
|
||
if docker run --rm "${BASE_IMAGE}" poetry --version >/dev/null 2>&1; then
|
||
POETRY_VERSION=$(docker run --rm "${BASE_IMAGE}" poetry --version 2>/dev/null)
|
||
log_success "Poetry 환경 확인됨: ${POETRY_VERSION}"
|
||
else
|
||
log_warning "Base Image에서 Poetry를 찾을 수 없습니다"
|
||
fi
|
||
|
||
# Base Image 크기 확인
|
||
BASE_SIZE=$(docker images "${BASE_IMAGE}" --format "{{.Size}}")
|
||
echo "📊 Base Image 크기: ${BASE_SIZE}"
|
||
|
||
else
|
||
log_error "Base Image를 찾을 수 없습니다: ${BASE_IMAGE}"
|
||
echo ""
|
||
echo "📋 해결 방법:"
|
||
echo " Base Image를 먼저 빌드하세요:"
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
echo " ./build-base.sh ${BASE_IMAGE_TAG} ${ACR_NAME} ${RESOURCE_GROUP}"
|
||
else
|
||
echo " ./build-base.sh ${BASE_IMAGE_TAG}"
|
||
fi
|
||
echo ""
|
||
echo " 또는 ACR에서 Base Image를 풀하세요:"
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
echo " docker pull ${BASE_IMAGE}"
|
||
fi
|
||
exit 1
|
||
fi
|
||
|
||
# ACR 로그인 수행
|
||
if [ -n "${ACR_NAME}" ] && [ -n "${RESOURCE_GROUP}" ]; then
|
||
echo ""
|
||
acr_login "${ACR_NAME}" "${RESOURCE_GROUP}"
|
||
echo ""
|
||
fi
|
||
|
||
# Docker 빌드
|
||
log_info "Poetry 기반 Service Image 빌드 시작..."
|
||
echo " - 예상 빌드 시간: 30초~2분 (앱 코드만 복사)"
|
||
echo " - Base Image 재사용으로 빠른 빌드"
|
||
echo " - Poetry 환경 상속"
|
||
echo ""
|
||
echo "🔨 빌드 명령어:"
|
||
echo "docker build --build-arg BASE_IMAGE=\"${BASE_IMAGE}\" -t \"${FULL_IMAGE_NAME}\" -f \"${DOCKERFILE_PATH}\" \"${BUILD_CONTEXT}\""
|
||
echo ""
|
||
|
||
docker build --build-arg BASE_IMAGE="${BASE_IMAGE}" -t "${FULL_IMAGE_NAME}" -f "${DOCKERFILE_PATH}" "${BUILD_CONTEXT}"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
# 빌드 종료 시간 계산
|
||
BUILD_END=$(date +%s)
|
||
BUILD_TIME=$((BUILD_END - BUILD_START))
|
||
BUILD_MINUTES=$((BUILD_TIME / 60))
|
||
BUILD_SECONDS=$((BUILD_TIME % 60))
|
||
|
||
log_success "Service Image 빌드 완료!"
|
||
echo ""
|
||
echo "⏱️ 총 빌드 시간: ${BUILD_MINUTES}분 ${BUILD_SECONDS}초"
|
||
|
||
# 이미지 정보 표시
|
||
echo ""
|
||
log_info "Service Image 정보:"
|
||
docker images "${FULL_IMAGE_NAME}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
|
||
|
||
# 🧪 빌드된 이미지 검증
|
||
log_info "Service Image 검증 중..."
|
||
echo "🔍 Poetry 환경 및 앱 코드 확인:"
|
||
|
||
docker run --rm "${FULL_IMAGE_NAME}" sh -c "
|
||
echo '✅ Poetry 버전:' && poetry --version &&
|
||
echo '✅ Python 버전:' && poetry run python --version &&
|
||
echo '✅ 설치된 패키지 수:' && poetry show | wc -l &&
|
||
echo '✅ 앱 코드 확인:' && ls -la /app/app/ 2>/dev/null | head -5
|
||
" 2>/dev/null || log_warning "일부 검증에 실패했습니다."
|
||
|
||
# latest 태그 추가 생성
|
||
if [ "${IMAGE_TAG}" != "latest" ] && [ -n "${REGISTRY}" ]; then
|
||
echo ""
|
||
log_info "latest 태그 생성 중..."
|
||
docker tag "${FULL_IMAGE_NAME}" "${REGISTRY}/${IMAGE_NAME}:latest"
|
||
log_success "latest 태그 생성 완료: ${REGISTRY}/${IMAGE_NAME}:latest"
|
||
fi
|
||
|
||
# ACR 푸시
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
echo ""
|
||
log_info "ACR에 Service Image 푸시 중..."
|
||
|
||
echo "📤 푸시 중: ${FULL_IMAGE_NAME}"
|
||
docker push "${FULL_IMAGE_NAME}"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
log_success "Service Image 푸시 성공"
|
||
|
||
if [ "${IMAGE_TAG}" != "latest" ]; then
|
||
echo "📤 푸시 중: ${REGISTRY}/${IMAGE_NAME}:latest"
|
||
docker push "${REGISTRY}/${IMAGE_NAME}:latest"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
log_success "latest 태그 푸시 성공"
|
||
fi
|
||
fi
|
||
else
|
||
log_error "Service Image 푸시 실패"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
log_success "🎉 Poetry 기반 Service Image 빌드 완료!"
|
||
echo ""
|
||
echo "📋 완료된 작업:"
|
||
echo " ✅ Service Image 빌드: ${FULL_IMAGE_NAME}"
|
||
echo " ✅ Base Image 활용: ${BASE_IMAGE}"
|
||
if [ "${IMAGE_TAG}" != "latest" ] && [ -n "${REGISTRY}" ]; then
|
||
echo " ✅ latest 태그: ${REGISTRY}/${IMAGE_NAME}:latest"
|
||
fi
|
||
if [ -n "${ACR_NAME}" ]; then
|
||
echo " ✅ ACR 푸시 완료"
|
||
fi
|
||
echo " ✅ Poetry 환경 상속"
|
||
|
||
echo ""
|
||
echo "🧪 테스트 명령어:"
|
||
echo " # 로컬 실행 테스트"
|
||
echo " docker run --rm -p 8000:8000 ${FULL_IMAGE_NAME}"
|
||
echo ""
|
||
echo " # Poetry 환경 확인"
|
||
echo " docker run --rm ${FULL_IMAGE_NAME} poetry show"
|
||
echo ""
|
||
echo " # 앱 헬스체크"
|
||
echo " docker run --rm ${FULL_IMAGE_NAME} poetry run python -c \"import app.main; print('✅ 앱 로드 성공')\""
|
||
|
||
echo ""
|
||
echo "📝 다음 단계:"
|
||
echo " 1. 로컬 테스트:"
|
||
echo " docker run --rm -p 8000:8000 ${FULL_IMAGE_NAME}"
|
||
echo ""
|
||
echo " 2. Kubernetes 배포:"
|
||
echo " kubectl apply -f deployment/manifests/"
|
||
echo ""
|
||
echo " 3. 스케일링:"
|
||
echo " kubectl scale deployment vector-api --replicas=3"
|
||
|
||
echo ""
|
||
echo "💡 성능 최적화:"
|
||
echo " ✅ 빠른 빌드: Base Image 재사용으로 빌드 시간 95% 단축"
|
||
echo " ✅ 효율적 캐시: Poetry 환경은 Base Image에서 관리"
|
||
echo " ✅ 작은 이미지: 앱 코드만 포함하여 배포 이미지 최소화"
|
||
echo " ✅ 개발 친화적: Poetry로 일관된 의존성 관리"
|
||
|
||
else
|
||
log_error "Service Image 빌드 실패!"
|
||
echo ""
|
||
echo "🔍 문제 해결:"
|
||
echo " 1. Base Image 확인: docker images | grep vector-api-base"
|
||
echo " 2. Dockerfile 확인: cat ${DOCKERFILE_PATH}"
|
||
echo " 3. 앱 코드 확인: ls -la app/"
|
||
echo " 4. 빌드 로그 확인: 위의 에러 메시지 참조"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "🏁 Service Image 빌드 프로세스 완료 - $(date)"
|
||
|