[Scouter APM 연동]
- core/scouter.py: Scouter HTTP API 클라이언트
- get_summary(): 전체 WAS 모니터링 현황 (CPU/TPS/응답시간/위험서버)
- get_server_metrics(): 특정 서버 실시간 메트릭
- get_alert_list(): Scouter 경보 목록
- get_xlog_recent(): 최근 트랜잭션 X-Log
- routers/scouter.py: REST API 엔드포인트 (6개)
- GET /api/scouter/status, /servers, /servers/{hash}/metrics
- GET /api/scouter/servers/{hash}/services, /xlog, /alerts
- POST /api/scouter/agent/deploy: SSH로 scouter-agent.jar 자동 배포
[스케줄러]
- scheduler.py: Scouter 경보 수집 (5분마다)
- CPU > 80% 또는 에러율 > 5% 서버 자동 감지 → GUARDiA 알림
[Docker Compose]
- docker-compose.yml: scouteross/scouter-server:2.20.0 서비스 추가
- 포트 6100 (UDP/TCP 에이전트 수집) + 6180 (HTTP API)
[설치 스크립트]
- setup/scouter/download_scouter.sh: 에이전트/서버 다운로드
- scouter-agent.jar + agent.conf.template 생성
- setup_ubuntu.sh: Scouter 서버 설치 단계 추가 (14단계로 확장)
- --test 검증: Scouter API + Gitea HTTP 검사 추가
환경변수: SCOUTER_HOST, SCOUTER_HTTP_PORT=6180, SCOUTER_USER, SCOUTER_PASSWORD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
223 lines
7.6 KiB
Python
223 lines
7.6 KiB
Python
"""
|
|
Scouter APM 연동 API 라우터
|
|
|
|
엔드포인트:
|
|
GET /api/scouter/status — Scouter 연결 상태 + 전체 요약
|
|
GET /api/scouter/servers — 모니터링 대상 서버 목록
|
|
GET /api/scouter/servers/{hash}/metrics — 특정 서버 실시간 메트릭
|
|
GET /api/scouter/servers/{hash}/services — 활성 서비스 목록
|
|
GET /api/scouter/servers/{hash}/xlog — 최근 트랜잭션 X-Log
|
|
GET /api/scouter/alerts — 경보 목록
|
|
POST /api/scouter/agent/deploy — Tomcat 에이전트 SSH 배포 (ADMIN/ENGINEER)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user
|
|
from database import get_db
|
|
from models import User, UserRole
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/scouter", tags=["scouter"])
|
|
|
|
|
|
# ── 스키마 ───────────────────────────────────────────────────────────────────
|
|
|
|
class AgentDeployRequest(BaseModel):
|
|
server_id: int # CMDB 서버 ID
|
|
scouter_host: Optional[str] = None # Scouter 서버 호스트 (기본: 환경변수)
|
|
scouter_port: int = 6100 # Scouter 수집 포트 (기본: 6100)
|
|
|
|
|
|
# ── 엔드포인트 ───────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/status")
|
|
async def scouter_status(
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Scouter 연결 상태 및 전체 모니터링 현황 요약."""
|
|
from core.scouter import get_summary, is_available
|
|
available = await is_available()
|
|
summary = await get_summary()
|
|
return {
|
|
"connected": available,
|
|
"host": os.getenv("SCOUTER_HOST", ""),
|
|
"port": os.getenv("SCOUTER_HTTP_PORT", "6180"),
|
|
**summary,
|
|
}
|
|
|
|
|
|
@router.get("/servers")
|
|
async def list_servers(
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""모니터링 대상 객체(서버/서비스) 목록."""
|
|
from core.scouter import get_object_list
|
|
return {"servers": await get_object_list()}
|
|
|
|
|
|
@router.get("/servers/{obj_hash}/metrics")
|
|
async def server_metrics(
|
|
obj_hash: int,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""특정 서버 실시간 메트릭 (CPU, Heap, TPS, 응답시간)."""
|
|
from core.scouter import get_server_metrics
|
|
metrics = await get_server_metrics(obj_hash)
|
|
if not metrics:
|
|
raise HTTPException(404, "서버 메트릭을 가져올 수 없습니다.")
|
|
return {"obj_hash": obj_hash, "metrics": metrics}
|
|
|
|
|
|
@router.get("/servers/{obj_hash}/services")
|
|
async def active_services(
|
|
obj_hash: int,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""활성 서비스 목록 (현재 처리 중인 요청)."""
|
|
from core.scouter import get_active_services
|
|
return {"services": await get_active_services(obj_hash)}
|
|
|
|
|
|
@router.get("/servers/{obj_hash}/xlog")
|
|
async def xlog(
|
|
obj_hash: int,
|
|
limit: int = 20,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""최근 트랜잭션 X-Log 조회."""
|
|
from core.scouter import get_xlog_recent
|
|
return {"xlogs": await get_xlog_recent(obj_hash, limit)}
|
|
|
|
|
|
@router.get("/alerts")
|
|
async def alerts(
|
|
obj_hash: Optional[int] = None,
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Scouter 경보 목록."""
|
|
from core.scouter import get_alert_list
|
|
return {"alerts": await get_alert_list(obj_hash)}
|
|
|
|
|
|
@router.post("/agent/deploy")
|
|
async def deploy_agent(
|
|
body: AgentDeployRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Tomcat 서버에 Scouter 에이전트 SSH 배포 (ADMIN/ENGINEER 전용).
|
|
|
|
배포 절차:
|
|
1. CMDB에서 서버 SSH 접속 정보 조회 (AES-256-GCM 복호화)
|
|
2. scouter-agent.jar 및 설정 파일 SFTP 전송
|
|
3. Tomcat JAVA_OPTS에 -javaagent 옵션 추가
|
|
4. Tomcat 재기동 (선택)
|
|
"""
|
|
if current_user.role not in (UserRole.ADMIN, UserRole.ENGINEER):
|
|
raise HTTPException(403, "ADMIN 또는 ENGINEER만 에이전트를 배포할 수 있습니다.")
|
|
|
|
from models import Server
|
|
from sqlalchemy import select
|
|
|
|
srv = await db.get(Server, body.server_id)
|
|
if not srv:
|
|
raise HTTPException(404, f"서버 ID {body.server_id}를 찾을 수 없습니다.")
|
|
|
|
scouter_host = body.scouter_host or os.getenv("SCOUTER_HOST", "localhost")
|
|
scouter_port = body.scouter_port
|
|
|
|
try:
|
|
result = await _deploy_agent_ssh(srv, scouter_host, scouter_port, current_user.username)
|
|
return {
|
|
"message": f"Scouter 에이전트 배포 완료: {srv.server_name}",
|
|
"server": srv.server_name,
|
|
"scouter": f"{scouter_host}:{scouter_port}",
|
|
"deployed_at": result.get("deployed_at"),
|
|
"restart_required": True,
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(500, f"에이전트 배포 실패: {str(e)[:200]}")
|
|
|
|
|
|
async def _deploy_agent_ssh(srv, scouter_host: str, scouter_port: int, actor: str) -> dict:
|
|
"""SSH/SFTP로 Scouter 에이전트 파일 배포."""
|
|
from datetime import datetime
|
|
from core.ssh_exec import execute_ssh_command
|
|
|
|
# GUARDIA_ROOT/setup/scouter/ 에서 에이전트 파일 확인
|
|
agent_local = _find_agent_jar()
|
|
if not agent_local:
|
|
raise ValueError("scouter-agent.jar를 찾을 수 없습니다. setup/scouter/에 파일을 배치하세요.")
|
|
|
|
# Scouter 에이전트 설정 파일 생성 (임시)
|
|
conf_content = f"""
|
|
net.collector.ip={scouter_host}
|
|
net.collector.udp_port={scouter_port}
|
|
net.collector.tcp_port={scouter_port}
|
|
obj_name={srv.server_name}
|
|
trace_interservice_enabled=true
|
|
hook_method_patterns=io.guardia.*,com.agency.*
|
|
"""
|
|
|
|
# 원격 배포 디렉토리
|
|
remote_dir = "/app/scouter"
|
|
|
|
# SSH로 디렉토리 생성
|
|
result = await execute_ssh_command(
|
|
srv.id,
|
|
f"mkdir -p {remote_dir} && echo OK",
|
|
timeout=30,
|
|
)
|
|
if not result.success:
|
|
raise ValueError(f"원격 디렉토리 생성 실패: {result.error}")
|
|
|
|
# 설정 파일 생성
|
|
await execute_ssh_command(
|
|
srv.id,
|
|
f"cat > {remote_dir}/agent.conf << 'SCCONF'\n{conf_content}\nSCCONF",
|
|
timeout=30,
|
|
)
|
|
|
|
# JAVA_OPTS 에 javaagent 옵션 추가 (Tomcat setenv.sh)
|
|
javaagent_opts = (
|
|
f'-javaagent:{remote_dir}/scouter-agent.jar '
|
|
f'-Dscouter.config={remote_dir}/agent.conf'
|
|
)
|
|
setenv_cmd = f"""
|
|
SETENV=/app/tomcat/bin/setenv.sh
|
|
if [ -f "$SETENV" ]; then
|
|
grep -q 'scouter' "$SETENV" || echo 'export JAVA_OPTS="$JAVA_OPTS {javaagent_opts}"' >> "$SETENV"
|
|
else
|
|
echo '#!/bin/bash' > "$SETENV"
|
|
echo 'export JAVA_OPTS="$JAVA_OPTS {javaagent_opts}"' >> "$SETENV"
|
|
chmod +x "$SETENV"
|
|
fi
|
|
echo "setenv.sh 업데이트 완료"
|
|
"""
|
|
await execute_ssh_command(srv.id, setenv_cmd, timeout=30)
|
|
|
|
return {"deployed_at": datetime.utcnow().isoformat()}
|
|
|
|
|
|
def _find_agent_jar() -> str:
|
|
"""Scouter 에이전트 JAR 파일 탐색."""
|
|
import pathlib
|
|
candidates = [
|
|
pathlib.Path(__file__).parent.parent.parent / "setup" / "scouter" / "scouter-agent.jar",
|
|
pathlib.Path("/opt/scouter/agent/scouter-agent.jar"),
|
|
pathlib.Path("/app/scouter/scouter-agent.jar"),
|
|
]
|
|
for p in candidates:
|
|
if p.exists():
|
|
return str(p)
|
|
return ""
|