AI 서버 아키텍처 구축 보고서 - 100-hours-a-week/2-hertz-wiki GitHub Wiki
본 문서는 AI 서버의 아키텍처 및 배포 방식을 Docker 기반으로 전환한 과정과 그에 따른 운영 전략, 그리고 CI/CD 구축 방식에 대해 설명합니다.
- 초기에는 CPU 서버에서 FastAPI + 모델 추론 코드가 직접 실행되는 구조로 운영됨
- 인스턴스에 직접 배포 하기 때문에 사전에 인스턴스에 배포 환경을 구축해야 한다.
- 수평 확장을 하거나 개발 환경을 통일하는데 문제가 있을 수 있다.
- CPU 서버:
- Docker 기반으로 전환
- 이미지 경량화를 위해 필요 모듈만 포함
- 호스트 볼륨을 통해 모델 파일, 로그 파일 공유
- GPU 서버:
- 여전히 VM 직접 실행 방식 유지
- VLLM 서버를 따로 두어 mcp나 llm 모델을 서빙한다.
- 도커 미사용 (→ GPU 디바이스/드라이버 관리, PyTorch 호환성 문제 고려)
GPU AI 서버 주요 패키지 및 런타임 환경
구분 | 패키지/런타임 | 버전(권장/테스트) | 비고 |
---|---|---|---|
Python | python3.10 | >=3.10.0 | 3.9 이상 권장 |
패키지 매니저 | pip/uv | 최신 | uv 권장 (패키지 캐싱/속도) |
가상환경 | venv (uv venv) | Python3.10 호환 | 프로젝트 루트에 .venv |
AI 프레임워크 | torch | 최신 (--torch-backend=auto) | GPU 최적화 |
LLM 추론 서버 | vllm | 최신 | OpenAI 호환 API |
LLM 런타임 | Ollama | 최신 | Qwen 등 다양한 LLM 다운로드 |
API 서버 | FastAPI | >=0.100 | uvicorn 연동 |
운영툴 | PM2 | >=5.3.0 | node.js 기반 프로세스 매니저 |
NVIDIA 드라이버 | nvidia-driver-XXX | 570 이상 | 서버 GPU 사양 맞춰 선택 |
CUDA Toolkit | cuda-12.8 | 12.x 권장 | cuDNN 호환 필수 |
cuDNN | cudnn-8.9.7+cuda12 | CUDA 버전에 맞춤 | 버전 불일치 주의 |
-
호스트 볼륨 활용: 모델은 호스트에 저장되고 Docker는 마운트 방식으로 접근
-
공유 라이브러리 디렉터리
- 호스트에
/home/deploy/app-pylibs
생성 -
sentence-transformers
,torch
등 대용량 패키지 설치 - 컨테이너 실행 시
v /home/deploy/app-pylibs:/app/extlibs
로 마운트
- 호스트에
-
모델 캐시 디렉터리
- 호스트에
/home/deploy/models
에 모델 다운로드(스크립트로 관리) - 컨테이너에
v /home/deploy/models:/app/model-cache
로 마운트
- 호스트에
volumes: - /home/deploy/app-pylibs:/app/extlibs - /home/deploy/models:/app/model-cache
-
공유 라이브러리 디렉터리
-
이미지 경량화: 미사용 의존성 제거, Python Slim 베이스 이미지 사용
AI CPU 모델 서버의 경우 모델과 라이브러리가 무거워 이미지를 경량화 화는 것이 중요했습니다. 따라서 모델과 핵심 라이브러리를 제외한 나머지를 CI에서 ECR에 푸시하고, CD 시 모델과 핵심 라이브러리를 mount 하거나 설치하도록 구성하였습니다.
-
Base 이미지 최소화
-
python:3.10-slim
같은 최소 크기의 공식 이미지를 사용
-
-
의존성 분리
-
requirements-base.txt
에 핵심 의존성만 명시 →pip install --no-cache-dir -r requirements-base.txt
- 대용량 AI 모델·라이브러리는 컨테이너 이미지가 아닌 호스트 볼륨에 별도 설치
-
-
빌드 캐시 최적화
- Dockerfile에서 의존성 설치를 소스 복사 이전 단계에 배치
- 코드 변경 시 캐시를 최대한 활용하여 빠른 재빌드 가능
-
Base 이미지 최소화
- 기존처럼 VM에 개발 환경을 구축하고 PM2와 배포 스크립트를 활용하여 관리
- 추론 모델과 llm을 돌릴 vllm 서버와 fastapi 서버로 구성
- vllm 서버가 켜지는데 시간이 오래 걸리기 때문에 충분히 기다린 후 fastapi 서버가 실행될 수 있도록 설정 sleep 설정
PM2 프로세스 관리 파일
module.exports = {
apps: [
{
name: "vllm-server",
script: "/home/deploy/2-hertz-ai/.venv/bin/python",
args: "-m vllm.entrypoints.openai.api_server --model Qwen/Qwen2.5-7B-Instruct-AWQ",
interpreter: "none",
cwd: "/home/deploy/2-hertz-ai",
env: { ENV: "prod", PYTHONIOENCODING: "utf-8" },
out_file: "./logs/vllm.log",
error_file: "./logs/vllm.log",
log_date_format: "YYYY-MM-DD HH:mm:ss",
watch: true,
restart_delay: 5000
},
{
name: "tuning-report",
script: "/home/deploy/2-hertz-ai/.venv/bin/python",
args: "-m uvicorn app-report.main:app --host 0.0.0.0 --port 8001",
interpreter: "none",
cwd: "/home/deploy/2-hertz-ai",
env: {
ENV: "prod",
PYTHONIOENCODING: "utf-8",
PYTHONPATH: "."
},
out_file: "./logs/fastapi-ai.log",
error_file: "./logs/fastapi-ai.log",
log_date_format: "YYYY-MM-DD HH:mm:ss",
watch: true,
restart_delay: 10000,
max_restarts: 1000,
autorestart: true
}
]
};
배포 성공 여부는 각 애플리케이션의 Health Check API를 통해서 확인 후, Discord Webhook을 통한 알림으로 처리됨
- GitHub Actions 기반
- PR 발생 시 Lint, 유닛 테스트 수행
- main 또는 develop 브랜치 병합 시 Docker 이미지 빌드 및 푸시
GitHub Actions를 사용하여 main/develop 브랜치의 PR이 Merge 될 때 자동으로 CI 파이프라인이 실행됩니다. 또한 workflow_dispatch
를 통해 수동/자동으로 트리거됩니다.
-
Build: Docker 이미지를 빌드하는 단계
1.1 환경변수를 SSM을 통해 가져오고 민감한 정보는 마스킹 처리 하도록 구현하였습니다.
#### 4. SSM에서 환경 변수/시크릿 불러오기 - name: Set environment variables from SSM id: ssm run: | load_param() { VALUE=$(aws ssm get-parameter --name "$1" --with-decryption --query "Parameter.Value" --output text) echo "::add-mask::$VALUE" echo "$2=$VALUE" >> $GITHUB_ENV } load_secret_to_file() { VALUE=$(aws ssm get-parameter --name "$1" --with-decryption --output json | jq -r .Parameter.Value) echo "$VALUE" | while IFS= read -r line; do echo "::add-mask::$line"; done echo "$VALUE" > "$2" }
1.2 Docker 이미지 생성
1.3 branch-SHA
기반 태그(
develop-`) 생성 -
Push: ECR에 환경에 맞는 태그와 SHA 태그로 이미시 푸시
- 환경 별 태그는 다음과 같습니다.
- main:
main-latest
/ develop:develop-latest
- main:
- 환경 별 태그는 다음과 같습니다.
-
Notify: 성공/실패 여부에 따라 Discord Webhook 알림
- CPU 서버에 SSH 접속 후
deploy.sh
스크립트 실행 - health check (
/ping
,/docs
) 통과 시 정상 배포 판단 - 실패 시 이전 커밋으로 롤백 자동 수행
- Discord로 배포 결과 알림 전송
CD 파이프라인은 CI가 정상적으로 완료 됐을 시에 동작합니다. 이때 AI는 각 서버의 배포 스크립트를 배치시켜놓아, 배포스크립트를 실행하여 배포를 수행합니다.
- 타켓 인스턴스 실행: 배포할 서버가 켜져 있는지 확인하고 꺼져 있다면 서버를 가동 시키고 진행
- SSH 접속 후 배포환경 체크
- 라이브러리와 모델이 설치되어 있는지 확인하고, 되어있지 않다면 설치를 진행합니다.
# 필요한 라이브러리 없으면 설치
if [ ! -d "/home/deploy/app-pylibs/sentence_transformers" ]; then
pip install --target="/home/deploy/app-pylibs" sentence-transformers==4.1.0
fi
# 모델 캐시 자동 다운로드
if [ ! -d "/home/deploy/models/jhgan-ko-sbert-nli" ]; then
docker compose run --rm tuning python scripts/download_model.py
fi
- 컨테이너 배포: docker-compose를 통한 컨테이너를 배포합니다.
- chromadb와 함께 docker-compose로 관리하여 배포 수행
docker compose -f docker-compose-tuning.yml stop tuning
docker compose -f docker-compose-tuning.yml rm -f tuning
docker compose -f docker-compose-tuning.yml up -d tuning
- 헬스체크
- 서버를 가동 시키고 헬스체크 수행하며 실패하면 롤백 스크립트 작동
# ================== Health Check + 태그 기록 + 롤백 (Prod만) ==================
# 최초 롤백 대비: 파일이 없으면 빈 파일 생성(최초 배포, 또는 수동 복구 대비)
[ -f last-successful-tag.txt ] || touch last-successful-tag.txt
echo "🔍 API 서비스 헬스체크 중..."
for i in {1..5}; do
if curl -sf http://localhost:8000/api/v1/health; then
echo "✅ API 서비스가 정상적으로 시작되었습니다."
echo "$IMAGE_TAG" > last-successful-tag.txt
# ChromaDB 연결 확인(경고만)
echo "🔍 ChromaDB 연결 상태 확인 중..."
if curl -sf http://localhost:8000/api/v1/health/chromadb; then
echo "✅ ChromaDB가 정상적으로 작동 중입니다."
else
echo "⚠️ 경고: ChromaDB 연결을 확인할 수 없습니다."
fi
exit 0
fi
echo "⏱️ API 서비스 준비 대기 중... ($i/5)"
sleep 10
done
- Notify
- 성공 시 Slack/Webhook 알림 ( 이미지 태그 , 성공 여부)
- 플랫폼: GitHub Actions 기반
-
트리거:
- PR이 develop/main 브랜치에 merge될 때
- workflow_dispatch로 수동 실행 가능
-
핵심 기능:
-
SSM으로부터 환경변수·시크릿을 불러와 민감정보 마스킹
load_param() { VALUE=$(aws ssm get-parameter --name "$1" --with-decryption --query "Parameter.Value" --output text) echo "::add-mask::$VALUE" echo "$2=$VALUE" >> $GITHUB_ENV }
-
배포 타겟 인스턴스 상태 확인
- GCP(Google Cloud) CLI를 사용해 대상 VM이 Running 상태인지 확인
- 실행 중이 아니면 중단/Slack 알림(불필요한 배포 방지)
STATUS=$(gcloud compute instances describe "$INSTANCE" --zone="$ZONE" --format='get(status)') if [[ "$STATUS" != "RUNNING" ]]; then exit 1; fi
-
SSH로 배포 대상(GPU 서버) 접속 후 배포 스크립트(ai_deploy.sh) 실행
-
헬스체크(/ping, /docs) 결과로 성공/실패 판단
- vllm 서버가 실행되는데 오래 걸리기 때문에 헬스체크 시간을 여유롭게 설정
- name: Health check with retries vllm run: | echo "🔍 AI 서버 헬스체크 시작 ... 최대 3회 시도합니다." for i in {1..5}; do echo "⏱️ 시도 $i: http://${{ env.HOST }}:8001/ping" if curl -sf http://${{ env.HOST }}:8001/ping; then echo "✅ 헬스체크 성공 🎉" exit 0 else echo "⚠️ 헬스체크 실패. 60초 후 재시도..." sleep 60 fi done
-
실패 시 자동 롤백 수행
- 롤백 스크립트를 수행하여 이전에 정상적으로 빌드된 버전의 commit 해쉬를 기반으로 롤백
- name: SSH into server and rollback uses: appleboy/ssh-action@master with: host: ${{ env.HOST }} username: ${{ needs.ai-cd.outputs.SSH_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /home/deploy **./ai_deploy.sh --rollback** || exit 1
-
결과(성공/실패/롤백)를 Discord Webhook으로 즉시 알림
-
-
.env
기반 환경 변수 관리 (SSM으로 암호화 저장) - 각 인스턴스는 내부 IP 기반 통신
- 외부 접속은 Load Balancer 또는 OpenVPN을 통해 제어
- GPU 서버도 Docker로 통합할 수 있도록 테스트 환경 구성 예정
- CPU 서버 서버 리스 도입
- 모델 서빙에 TensorRT, ONNXRuntime 등 성능 최적화 프레임워크 도입 검토
- Redis 기반 캐싱 전략 추가 검토