Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Linux(EC2/CI/컨테이너)에서 실행되는 파일은 LF로 고정한다.
# (Windows에서 편집해도 CRLF가 섞여 sh 스크립트/Dockerfile이 깨지지 않도록)
Dockerfile text eol=lf
*.sh text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.conf text eol=lf
Modelfile text eol=lf
.dockerignore text eol=lf

# 바이너리(정규화 제외)
*.gguf binary
*.safetensors binary
*.bin binary
*.png binary
*.jpg binary
38 changes: 38 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 백엔드 Docker 이미지가 실제로 빌드되는지 CI에서 검증한다.
# (배포 산출물이 항상 빌드 가능한 상태임을 보장 — 포트폴리오용 "Docker build passing" 근거)
name: docker-build

on:
push:
branches: [main]
paths:
- 'backend/**'
- 'docker-compose.yml'
- '.github/workflows/docker-build.yml'
pull_request:
paths:
- 'backend/**'
- 'docker-compose.yml'
- '.github/workflows/docker-build.yml'
workflow_dispatch:

jobs:
build-backend-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build backend image
uses: docker/build-push-action@v6
with:
context: ./backend
push: false
tags: wtgmate-backend:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Validate docker-compose.yml
run: docker compose -f docker-compose.yml config >/dev/null && echo "compose config OK"
62 changes: 53 additions & 9 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ ALB(월 ~$16)와 API Gateway(요청 29초 하드 리밋)를 모두 피하기 위
| Ollama 호스트 | ✅ | `OLLAMA_HOST`/`OLLAMA_MODEL` env | `backend/main.py` |
| 의존성 고정 | ✅ | `backend/requirements.txt` 생성(fastapi·uvicorn[standard]·httpx·python-dotenv·pydantic·ortools) | `backend/requirements.txt` |
| env 템플릿 | ✅ | `backend/.env.example`, `frontend/.env.example` 추가 | - |
| 컨테이너화 | ⬜ | (선택) `Dockerfile` + `docker-compose.yml`. bare-metal(venv+systemd)로도 가능 | - |
| 컨테이너화 | ✅ | `backend/Dockerfile`(+`.dockerignore`), 루트 `docker-compose.yml`(ollama + model-loader + backend) | - |
| 모델 등록 | ✅ | `backend/finetune/Modelfile`(raw passthrough) 커밋 → `ollama create`로 재현 | - |
| nginx | ✅ | `deploy/nginx-wtgmate.conf`(정적 프론트 + /api 프록시, certbot용) | - |

**→ 배포 산출물이 모두 준비됨. EC2에 올려 아래 "배포 순서 요약"만 실행하면 기동된다.**

**배포 시 주입할 env 요약**
- 백엔드: `KAKAO_REST_API_KEY`, `TMAP_APP_KEY`, `ODSAY_API_KEY`, `ALLOWED_ORIGINS`(배포 도메인), `OLLAMA_HOST`/`OLLAMA_MODEL`
Expand Down Expand Up @@ -75,12 +79,52 @@ ALB(월 ~$16)와 API Gateway(요청 29초 하드 리밋)를 모두 피하기 위

---

## 배포 순서 요약
## 배포 순서 요약 (산출물 준비 완료 → EC2에서 실행만)

**로컬(완료됨):** Phase 1 코드 정리 + Dockerfile/compose/nginx/Modelfile 산출물. GGUF는 Colab 노트북으로 생성.

**EC2(Ubuntu, t3.medium+)에서:**
```bash
# 0) 도구
sudo apt update && sudo apt install -y docker.io docker-compose-plugin nginx certbot python3-certbot-nginx
sudo usermod -aG docker $USER # 재로그인

# 1) 코드 + 모델
git clone https://github.com/ysb2152/WTGMate.git && cd WTGMate
mkdir -p model
aws s3 cp s3://<버킷>/wtgmate-parser.gguf model/wtgmate-parser.gguf # GGUF 전달(S3)
cp backend/finetune/Modelfile model/Modelfile

# 2) 키 주입(.env 또는 SSM). ALLOWED_ORIGINS는 배포 도메인.
cat > .env <<EOF
KAKAO_REST_API_KEY=...
TMAP_APP_KEY=...
ODSAY_API_KEY=...
ALLOWED_ORIGINS=https://YOUR_DOMAIN
EOF

# 3) 백엔드 + Ollama 기동(모델 자동 등록) → 헬스체크
docker compose up -d --build
curl http://localhost:8000/api/health # 3개 키 configured 확인

# 4) 프론트 빌드 → nginx 정적 서빙
cd frontend && VITE_API_BASE_URL="" VITE_KAKAO_JAVASCRIPT_KEY=... npm ci && npm run build
sudo mkdir -p /var/www/wtgmate && sudo cp -r dist/* /var/www/wtgmate/ && cd ..
sudo cp deploy/nginx-wtgmate.conf /etc/nginx/sites-available/wtgmate
sudo sed -i 's/YOUR_DOMAIN/실제도메인/' /etc/nginx/sites-available/wtgmate
sudo ln -sf /etc/nginx/sites-available/wtgmate /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# 5) HTTPS
sudo certbot --nginx -d YOUR_DOMAIN
```

**콘솔 작업:** 보안그룹 80/443 오픈, Elastic IP + 도메인 A레코드, **Kakao JS 키 플랫폼에 배포 도메인 등록**, **ODsay '서버' 앱에 EC2 공인 IP 등록**.

---

## 실제 라이브 데모는 Cloudflare Tunnel로 운영 (비용 절감)

1. Phase 1 코드 정리 (로컬에서 완료 가능)
2. GGUF S3 업로드
3. EC2(t3.medium) 프로비저닝 + 보안 그룹(80/443) 설정
4. Docker/의존성 설치 → backend + Ollama 기동 → `ollama create`
5. nginx 설정(정적 서빙 + /api 프록시) + certbot HTTPS
6. 도메인 연결 + Kakao 콘솔에 도메인 등록
7. 헬스체크(`/api/health`)로 확인
위 AWS 구성은 **배포 즉시 가능한 상태로 완비**해 두었으나(t3.medium 상시 ~$30/월), 포트폴리오 데모는
비용 0의 **Cloudflare Tunnel + Vercel** 로 운영한다. 백엔드/Ollama는 개발 PC에서 그대로 돌고
(모든 API 키가 이미 그 IP로 동작), Cloudflare Tunnel이 HTTPS로 노출한다. 상세는 `deploy/CLOUDFLARE.md`.
1 change: 1 addition & 0 deletions DEVELOPMENT_JOURNEY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

| 시점 | 구분 | 한 일 | 관련 |
|---|---|---|---|
| 2026-08-27 | chore | AWS 배포 산출물 완비(EC2에 올리면 바로 기동): backend Dockerfile + docker-compose(ollama+model-loader+backend) + nginx + Modelfile 커밋, GitHub Actions로 Docker 이미지 빌드 CI 추가, DEPLOY.md 구체 런북화 | `chore/deploy-artifacts` / `DEPLOY.md` |
| 2026-08-27 | chore | 배포 Phase 1(코드 준비): requirements.txt 생성, 프론트 API주소·백엔드 CORS를 env화, .env.example 2종 추가 | `chore/deploy-phase1` / `DEPLOY.md` |
| 2026-08-27 | fix | 실사용 브라우저 검증 중 발견: Kakao 자동차 API가 실패해도 200으로 result_code만 주는 짧은 구간에서 `summary` KeyError → result_code 확인해 명확히 폴백 | `fix/kakao-car-resultcode` |
| 2026-08-27 | perf/test | 배포용 정확도·성능 측정(최적화 최적성 PASS·LLM 추출 6/6·추정행렬 regret 평균 0.5분) 후 OR-Tools time_limit 5s→2s, 검증 하네스 경로 하드코딩 제거 | `chore/perf-timelimit-harness` |
Expand Down
14 changes: 14 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 이미지에 넣지 않을 것들(용량·비밀 제외).
venv/
.venv/
__pycache__/
*.pyc
.env
.env.*
!.env.example
tests/
finetune/data/
finetune/__pycache__/
*.gguf
*.safetensors
*.bin
22 changes: 22 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# WTGMate FastAPI 백엔드 이미지.
# Ollama는 이 이미지에 넣지 않고 별도 컨테이너로 띄운다(docker-compose.yml 참고).
FROM python:3.11-slim

WORKDIR /app

# httpx의 TLS 검증용 CA 등 최소 런타임 의존성.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# 의존성 먼저 설치(레이어 캐시 활용).
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 애플리케이션 코드(.dockerignore로 venv/데이터/gguf/.env 제외).
COPY . .

EXPOSE 8000

# 프로덕션 기동. OLLAMA_HOST/키 등은 compose가 환경변수로 주입한다.
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
17 changes: 17 additions & 0 deletions backend/finetune/Modelfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# WTGMate 장소추출 파인튜닝 모델(wtgmate-parser)을 Ollama에 등록하기 위한 Modelfile.
#
# 배포 시:
# 1) 파인튜닝 산출물 GGUF(wtgmate-parser.gguf, ~1.9GB, git 제외)를 이 파일과 같은 폴더에 둔다
# (Colab 노트북으로 생성하거나 S3에서 내려받는다 — DEPLOY.md 참고).
# 2) ollama create wtgmate-parser -f Modelfile
#
# TEMPLATE는 채팅 래핑 없이 프롬프트를 그대로 넘기는 raw passthrough다. 학습을 순수 텍스트
# 이어쓰기(alpaca_prompt) 형식으로 했기 때문에, 추론 때도 동일 형식이어야 성능이 유지된다.
# (main.py의 call_ollama가 raw=True로 호출하는 것과 짝을 이룬다.)

FROM ./wtgmate-parser.gguf
TEMPLATE {{ .Prompt }}
PARAMETER num_ctx 2048
PARAMETER stop <|im_end|>
PARAMETER stop <|endoftext|>
PARAMETER temperature 0.1
35 changes: 35 additions & 0 deletions deploy/nginx-wtgmate.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# WTGMate nginx 설정 (단일 EC2: 정적 프론트 서빙 + /api 리버스 프록시 + HTTPS).
#
# 배포:
# 1) 프론트 빌드: cd frontend && VITE_API_BASE_URL="" npm run build (동일 오리진이라 상대 /api)
# => frontend/dist 를 /var/www/wtgmate 로 복사.
# 2) 이 파일을 /etc/nginx/sites-available/wtgmate 로 복사하고 YOUR_DOMAIN 치환, sites-enabled 링크.
# 3) sudo nginx -t && sudo systemctl reload nginx
# 4) sudo certbot --nginx -d YOUR_DOMAIN (443 서버블록 + HTTP→HTTPS 리다이렉트 자동 삽입)
#
# 프론트와 /api가 같은 도메인이라 CORS도 필요 없다(백엔드 ALLOWED_ORIGINS는 그대로 둬도 무방).

server {
listen 80;
server_name YOUR_DOMAIN; # 예: wtgmate.example.com

# vite build 산출물(frontend/dist) 배포 위치.
root /var/www/wtgmate;
index index.html;

# SPA: 정적 파일 없으면 index.html로 폴백.
location / {
try_files $uri $uri/ /index.html;
}

# 백엔드(FastAPI, docker-compose가 8000 노출) 리버스 프록시.
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# LLM 파싱/최적화 대기 여유(브루트포스·OR-Tools 포함).
proxy_read_timeout 120s;
}
}
59 changes: 59 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# WTGMate 백엔드 + Ollama 오케스트레이션 (단일 EC2 등에서 `docker compose up -d --build`).
#
# 준비물:
# 1) 파인튜닝 GGUF와 Modelfile을 ./model/ 에 둔다:
# ./model/wtgmate-parser.gguf (git 제외 ~1.9GB, S3에서 내려받거나 Colab로 생성)
# ./model/Modelfile (backend/finetune/Modelfile 복사)
# 2) 루트 .env 또는 셸 환경변수로 키 주입:
# KAKAO_REST_API_KEY / TMAP_APP_KEY / ODSAY_API_KEY / ALLOWED_ORIGINS
# 3) docker compose up -d --build
# 4) 헬스체크: curl http://localhost:8000/api/health
#
# 정적 프론트 서빙 + HTTPS는 호스트 nginx + certbot으로 처리한다(deploy/nginx-wtgmate.conf).

services:
# Ollama 서버(로컬 파인튜닝 모델 구동). CPU만으로도 3B 동작.
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama_data:/root/.ollama
expose:
- "11434"

# ollama가 뜨면 모델을 한 번 등록하고 종료하는 1회성 서비스.
model-loader:
image: ollama/ollama:latest
depends_on:
- ollama
environment:
- OLLAMA_HOST=ollama:11434
volumes:
- ./model:/model:ro
entrypoint:
- /bin/sh
- -c
- >
until ollama list >/dev/null 2>&1; do echo 'waiting for ollama...'; sleep 2; done;
ollama create wtgmate-parser -f /model/Modelfile &&
echo 'wtgmate-parser 등록 완료'
restart: "no"

# FastAPI 백엔드.
backend:
build: ./backend
depends_on:
- ollama
environment:
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_MODEL=wtgmate-parser
- KAKAO_REST_API_KEY=${KAKAO_REST_API_KEY:-}
- TMAP_APP_KEY=${TMAP_APP_KEY:-}
- ODSAY_API_KEY=${ODSAY_API_KEY:-}
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
ports:
- "8000:8000"
restart: unless-stopped

volumes:
ollama_data:
Loading