guardia-itsm/routers/cost_optimizer.py
2026-06-02 18:48:18 +09:00

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(),
}