95 lines
3.6 KiB
Python
95 lines
3.6 KiB
Python
"""
|
|
클라우드 비용 최적화 AI
|
|
|
|
Ollama 기반 비용 분석·최적화 권고·Reserved Instance 추정.
|
|
|
|
엔드포인트:
|
|
GET /api/costopt/analyze — 비용 패턴 분석
|
|
POST /api/costopt/recommend — AI 최적화 권고 생성
|
|
GET /api/costopt/idle-resources — 유휴 리소스 탐지
|
|
GET /api/costopt/savings — 절감 가능 금액 추정
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user
|
|
from database import get_db
|
|
from models import User, MultiCloudConfig
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/costopt", tags=["비용 최적화"])
|
|
OLLAMA_URL = "http://localhost:11434"
|
|
|
|
|
|
async def _llm_analyze(prompt: str) -> str:
|
|
try:
|
|
async with httpx.AsyncClient(timeout=30) as c:
|
|
r = await c.post(f"{OLLAMA_URL}/api/generate", json={
|
|
"model": "llama3", "prompt": prompt, "stream": False,
|
|
})
|
|
return r.json().get("response", "").strip() if r.status_code == 200 else ""
|
|
except Exception: return ""
|
|
|
|
|
|
@router.get("/analyze")
|
|
async def analyze_costs(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
|
|
rows = await db.execute(select(MultiCloudConfig).where(MultiCloudConfig.tenant_id == user.tenant_id, MultiCloudConfig.is_active == True))
|
|
providers = rows.scalars().all()
|
|
return {
|
|
"provider_count": len(providers),
|
|
"providers": [{"name": p.name, "type": p.provider_type} for p in providers],
|
|
"analysis_note": "실제 비용 데이터는 각 CSP API 연동 후 제공",
|
|
"estimated_monthly": "설정 후 조회 가능",
|
|
}
|
|
|
|
|
|
@router.post("/recommend")
|
|
async def get_recommendations(
|
|
monthly_cost: float = 0,
|
|
user: User = Depends(get_current_user),
|
|
):
|
|
prompt = (
|
|
f"공공기관 클라우드 월 비용: {monthly_cost:,.0f}원\n\n"
|
|
"다음 항목을 분석하여 절감 방안을 JSON 형식으로 제시:\n"
|
|
"1. Reserved Instance 전환 가능 여부 (On-demand → RI 최대 60% 절감)\n"
|
|
"2. 개발/테스트 환경 야간/주말 자동 중지 (최대 65% 절감)\n"
|
|
"3. 스토리지 최적화 (미연결 볼륨, 오래된 스냅샷)\n"
|
|
"4. 인스턴스 다운사이징 (사용률 10% 미만)\n"
|
|
'JSON 형식: {"recommendations": [...], "estimated_savings": 금액, "priority": "HIGH|MEDIUM|LOW"}'
|
|
)
|
|
result = await _llm_analyze(prompt)
|
|
import json, re
|
|
match = re.search(r'\{.*\}', result, re.DOTALL)
|
|
try:
|
|
data = json.loads(match.group()) if match else {}
|
|
except Exception:
|
|
data = {"raw": result}
|
|
return {"monthly_cost": monthly_cost, "recommendations": data}
|
|
|
|
|
|
@router.get("/idle-resources")
|
|
async def detect_idle_resources(user: User = Depends(get_current_user)):
|
|
return {
|
|
"idle_resources": [],
|
|
"note": "CSP API 연동 후 CPU 사용률 < 10% 인스턴스 자동 탐지",
|
|
"potential_savings_pct": 20,
|
|
}
|
|
|
|
|
|
@router.get("/savings")
|
|
async def estimate_savings(user: User = Depends(get_current_user)):
|
|
return {
|
|
"ri_conversion": {"savings_pct": 40, "note": "On-demand → 1년 RI 전환 시"},
|
|
"schedule_optimization": {"savings_pct": 30, "note": "개발환경 야간/주말 중지 시"},
|
|
"rightsizing": {"savings_pct": 15, "note": "유휴 인스턴스 다운사이징 시"},
|
|
"total_estimated_savings_pct": 55,
|
|
"last_updated": datetime.utcnow(),
|
|
}
|