docs: Gateway 문서 원칙 준수 - 코드 블록 최소화, 100줄 이하
This commit is contained in:
parent
a214f8bdfe
commit
f4eb534da4
@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
**날짜**: 2025-11-17
|
**날짜**: 2025-11-17
|
||||||
**작성자**: admin
|
**작성자**: admin
|
||||||
**관련 이슈**: Admin Dashboard 표준 배포 방식 전환
|
**관련 파일**:
|
||||||
|
- `robeing-gateway/app/routers/admin.py`
|
||||||
|
- `robeing-gateway/app/main.py`
|
||||||
|
- `robeing-gateway/docker-compose.yml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 문제 상황
|
## 문제 상황
|
||||||
|
|
||||||
@ -12,7 +17,7 @@ Admin Dashboard를 표준 배포 방식으로 전환하면서 Gateway를 통한
|
|||||||
2. 로그인 후 JWT 토큰 검증 실패 (401 Unauthorized)
|
2. 로그인 후 JWT 토큰 검증 실패 (401 Unauthorized)
|
||||||
3. Docker 컨테이너 간 통신 문제
|
3. Docker 컨테이너 간 통신 문제
|
||||||
|
|
||||||
## 해결 과정
|
## 해결 방안
|
||||||
|
|
||||||
### 1. Gateway Admin Router 등록
|
### 1. Gateway Admin Router 등록
|
||||||
|
|
||||||
@ -22,75 +27,13 @@ Admin Dashboard를 표준 배포 방식으로 전환하면서 Gateway를 통한
|
|||||||
- `robeing-gateway/app/routers/admin.py`의 router가 제대로 등록되지 않음
|
- `robeing-gateway/app/routers/admin.py`의 router가 제대로 등록되지 않음
|
||||||
- router prefix 설정 문제
|
- router prefix 설정 문제
|
||||||
|
|
||||||
**해결** (`robeing-gateway/app/main.py`):
|
**해결**:
|
||||||
```python
|
- `robeing-gateway/app/main.py:92`: `app.include_router(admin.router, prefix="")` 추가
|
||||||
# Include routers
|
- `robeing-gateway/app/routers/admin.py:17`: `router = APIRouter()` (prefix 없음)
|
||||||
app.include_router(slack.router)
|
- `robeing-gateway/app/routers/admin.py:22`: `/admin/api/{path:path}` 라우트 정의
|
||||||
app.include_router(admin.router, prefix="") # prefix 명시
|
|
||||||
```
|
|
||||||
|
|
||||||
**라우터 정의** (`robeing-gateway/app/routers/admin.py`):
|
|
||||||
```python
|
|
||||||
from fastapi import APIRouter, Request, HTTPException, Header
|
|
||||||
from typing import Optional
|
|
||||||
import httpx
|
|
||||||
import logging
|
|
||||||
from app.core.auth import get_verified_user
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000"
|
|
||||||
|
|
||||||
@router.api_route("/admin/api/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
||||||
async def admin_api_proxy(
|
|
||||||
request: Request,
|
|
||||||
path: str,
|
|
||||||
authorization: Optional[str] = Header(None)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Admin API 프록시 - 로그인은 JWT 검증 생략, 나머지는 필수
|
|
||||||
"""
|
|
||||||
# 로그인 API는 JWT 검증 생략
|
|
||||||
if path == "login":
|
|
||||||
logger.info(f"Admin login request to /admin/api/{path}")
|
|
||||||
else:
|
|
||||||
# JWT 검증 (필수)
|
|
||||||
try:
|
|
||||||
user_uuid = get_verified_user(authorization)
|
|
||||||
logger.info(f"Admin API request from user {user_uuid} to /admin/api/{path}")
|
|
||||||
except HTTPException as e:
|
|
||||||
logger.warning(f"Admin API request without valid JWT to /admin/api/{path}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
# Backend로 프록시
|
|
||||||
target_url = f"{ADMIN_BACKEND_URL}/admin/{path}"
|
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
||||||
try:
|
|
||||||
response = await client.request(
|
|
||||||
method=request.method,
|
|
||||||
url=target_url,
|
|
||||||
headers=dict(request.headers),
|
|
||||||
content=await request.body()
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(
|
|
||||||
content=response.content,
|
|
||||||
status_code=response.status_code,
|
|
||||||
headers=dict(response.headers)
|
|
||||||
)
|
|
||||||
except httpx.RequestError as e:
|
|
||||||
logger.error(f"Failed to proxy admin API request: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=503,
|
|
||||||
detail="Admin backend service unavailable"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**검증**:
|
**검증**:
|
||||||
```bash
|
```bash
|
||||||
# Gateway 라우트 확인
|
|
||||||
sudo docker exec robeing-gateway python -c \
|
sudo docker exec robeing-gateway python -c \
|
||||||
"from app.main import app; print([r.path for r in app.routes if 'admin' in r.path])"
|
"from app.main import app; print([r.path for r in app.routes if 'admin' in r.path])"
|
||||||
# ['/admin/api/{path:path}']
|
# ['/admin/api/{path:path}']
|
||||||
@ -104,25 +47,12 @@ sudo docker exec robeing-gateway python -c \
|
|||||||
- Docker 컨테이너 내부에서 `localhost`는 컨테이너 자신을 의미
|
- Docker 컨테이너 내부에서 `localhost`는 컨테이너 자신을 의미
|
||||||
- 다른 컨테이너에 접근하려면 컨테이너 이름 또는 네트워크 IP 사용 필요
|
- 다른 컨테이너에 접근하려면 컨테이너 이름 또는 네트워크 IP 사용 필요
|
||||||
|
|
||||||
**해결** (`robeing-gateway/app/routers/admin.py`):
|
**해결**:
|
||||||
```python
|
- `robeing-gateway/app/routers/admin.py:20`: `ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000"` (localhost → 컨테이너 이름)
|
||||||
# Before: ADMIN_BACKEND_URL = "http://localhost:8000"
|
|
||||||
# After:
|
|
||||||
ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Docker 네트워크 확인**:
|
|
||||||
```bash
|
|
||||||
# 같은 네트워크에 있는 컨테이너 확인
|
|
||||||
sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend"
|
|
||||||
# "Name": "admin-dashboard-backend",
|
|
||||||
# "IPv4Address": "172.21.0.4/16"
|
|
||||||
```
|
|
||||||
|
|
||||||
**교훈**:
|
**교훈**:
|
||||||
- Docker 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용
|
- Docker 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용
|
||||||
- 같은 Docker 네트워크(`appnet`)에 있는 컨테이너는 이름으로 자동 DNS 해석됨
|
- 같은 Docker 네트워크(`appnet`)에 있는 컨테이너는 이름으로 자동 DNS 해석됨
|
||||||
- 컨테이너 이름은 `docker-compose.yml`의 `container_name` 또는 서비스 이름 사용
|
|
||||||
|
|
||||||
### 3. JWT Secret Key 불일치 문제
|
### 3. JWT Secret Key 불일치 문제
|
||||||
|
|
||||||
@ -131,7 +61,7 @@ sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend"
|
|||||||
**원인**:
|
**원인**:
|
||||||
- Gateway의 JWT_SECRET_KEY와 admin-dashboard backend의 SECRET_KEY가 불일치
|
- Gateway의 JWT_SECRET_KEY와 admin-dashboard backend의 SECRET_KEY가 불일치
|
||||||
- Gateway: `.env` 파일의 `JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664`
|
- Gateway: `.env` 파일의 `JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664`
|
||||||
- Backend: `admin_routes.py`의 `SECRET_KEY = "admin_secret_key_robeing_2025"`
|
- Backend: `admin-dashboard/backend/admin_routes.py:32`의 `SECRET_KEY = "admin_secret_key_robeing_2025"`
|
||||||
|
|
||||||
**에러 로그**:
|
**에러 로그**:
|
||||||
```
|
```
|
||||||
@ -139,38 +69,17 @@ sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend"
|
|||||||
{"detail":"Token validation failed: Signature verification failed."}
|
{"detail":"Token validation failed: Signature verification failed."}
|
||||||
```
|
```
|
||||||
|
|
||||||
**해결** (`robeing-gateway/docker-compose.yml`):
|
**해결**:
|
||||||
```yaml
|
- `robeing-gateway/docker-compose.yml:8`: `JWT_SECRET_KEY=admin_secret_key_robeing_2025` 환경변수 추가
|
||||||
services:
|
- Gateway 재빌드: `docker compose down && docker compose up -d --build`
|
||||||
robeing-gateway:
|
|
||||||
container_name: robeing-gateway
|
|
||||||
build: .
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
- JWT_SECRET_KEY=admin_secret_key_robeing_2025 # Backend와 일치
|
|
||||||
ports:
|
|
||||||
- "${CNTR_PORT:-8100}:8000"
|
|
||||||
networks:
|
|
||||||
- appnet
|
|
||||||
```
|
|
||||||
|
|
||||||
**재빌드**:
|
|
||||||
```bash
|
|
||||||
cd /home/admin/robeing-gateway
|
|
||||||
sudo docker compose down
|
|
||||||
sudo docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
**검증**:
|
**검증**:
|
||||||
```bash
|
```bash
|
||||||
# JWT 토큰 발급
|
|
||||||
TOKEN=$(curl -s -X POST http://localhost:8000/admin/login \
|
TOKEN=$(curl -s -X POST http://localhost:8000/admin/login \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"password":"19800508"}' | python3 -c \
|
-d '{"password":"19800508"}' | python3 -c \
|
||||||
"import sys, json; print(json.load(sys.stdin)['access_token'])")
|
"import sys, json; print(json.load(sys.stdin)['access_token'])")
|
||||||
|
|
||||||
# Gateway를 통한 API 호출 테스트
|
|
||||||
curl -X GET http://localhost:8100/admin/api/system/overview \
|
curl -X GET http://localhost:8100/admin/api/system/overview \
|
||||||
-H "Authorization: Bearer $TOKEN"
|
-H "Authorization: Bearer $TOKEN"
|
||||||
# {"timestamp":"2025-11-17T10:37:21.154596","cpu":{"percent":4.6,...},...}
|
# {"timestamp":"2025-11-17T10:37:21.154596","cpu":{"percent":4.6,...},...}
|
||||||
@ -179,35 +88,23 @@ curl -X GET http://localhost:8100/admin/api/system/overview \
|
|||||||
**교훈**:
|
**교훈**:
|
||||||
- JWT 검증을 위해서는 발급 서버와 검증 서버의 secret key가 반드시 일치해야 함
|
- JWT 검증을 위해서는 발급 서버와 검증 서버의 secret key가 반드시 일치해야 함
|
||||||
- Docker Compose의 `environment` 섹션으로 `.env` 파일의 환경변수 오버라이드 가능
|
- Docker Compose의 `environment` 섹션으로 `.env` 파일의 환경변수 오버라이드 가능
|
||||||
- 운영 환경에서는 환경변수로 관리하여 보안 강화 필요
|
|
||||||
|
|
||||||
## 최종 구조
|
## 최종 구조
|
||||||
|
|
||||||
```
|
```
|
||||||
[Browser]
|
[Browser] → [Nginx] /admin/api/* → [Gateway:8100] /admin/api/{path} → [Admin Backend:8000] /admin/{path}
|
||||||
→ [Nginx] /admin/api/*
|
|
||||||
→ [Gateway:8100] /admin/api/{path}
|
|
||||||
→ [Admin Backend:8000] /admin/{path}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**요청 흐름**:
|
## 교훈
|
||||||
1. 브라우저: `POST /admin/api/login` (비밀번호 전송)
|
|
||||||
2. Nginx: Gateway로 프록시 (`http://localhost:8100/admin/api/login`)
|
|
||||||
3. Gateway: 로그인은 JWT 검증 생략, Backend로 프록시 (`http://admin-dashboard-backend:8000/admin/login`)
|
|
||||||
4. Backend: JWT 토큰 발급 후 반환
|
|
||||||
5. 브라우저: 토큰 저장 후 `GET /admin/api/system/overview` (Authorization 헤더 포함)
|
|
||||||
6. Gateway: JWT 검증 후 Backend로 프록시
|
|
||||||
7. Backend: 데이터 반환
|
|
||||||
|
|
||||||
## 관련 파일
|
### Docker 네트워크 통신
|
||||||
|
- 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용
|
||||||
|
- 같은 네트워크의 컨테이너는 이름으로 자동 DNS 해석
|
||||||
|
|
||||||
- `robeing-gateway/app/routers/admin.py`: Admin API 프록시 라우터
|
### JWT 검증
|
||||||
- `robeing-gateway/app/main.py`: Router 등록
|
- 발급 서버와 검증 서버의 secret key 반드시 일치 필요
|
||||||
- `robeing-gateway/docker-compose.yml`: JWT_SECRET_KEY 환경변수 설정
|
- 환경변수 오버라이드를 통한 설정 통일 가능
|
||||||
- `admin-dashboard/backend/admin_routes.py`: JWT 토큰 발급 (SECRET_KEY)
|
|
||||||
|
|
||||||
## 참고
|
|
||||||
|
|
||||||
- [Admin Dashboard 표준 배포 전환 문서](./251117_admin_dashboard_standard_deployment_refactoring.md)
|
|
||||||
- Gateway는 nginx를 통해 프록시되므로 nginx 설정(`/admin/api/` → Gateway) 확인 필요
|
|
||||||
|
|
||||||
|
### Gateway 라우팅
|
||||||
|
- Router 등록 시 prefix 명시 필요
|
||||||
|
- API 경로는 `/admin/api/*` 형식으로 통일
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user