786 lines
30 KiB
Markdown
786 lines
30 KiB
Markdown
# 인프라 주간 모니터링 보고서 시스템 - 기술 리서치
|
||
|
||
## 1. 프로젝트 개요
|
||
|
||
**목표**: Prometheus API와 Kubernetes API에서 인프라 메트릭 데이터를 수집하여 주간 모니터링 보고서를 자동 생성하는 시스템
|
||
|
||
**배포 환경**: Kubernetes 클러스터 내 Pod으로 배포 (namespace: `soo`)
|
||
|
||
**핵심 모니터링 메트릭**:
|
||
- CPU 사용률
|
||
- Memory 사용률
|
||
- Disk 사용률
|
||
- NAS (NFS/CIFS) 스토리지
|
||
- Network Traffic (송/수신)
|
||
|
||
**핵심 분석 항목**:
|
||
- 평균(avg) / 최대(max) / 최소(min) 값
|
||
- Peak 구간 감지 (이상 탐지)
|
||
- 주의가 필요한 데이터 구간 하이라이트
|
||
|
||
---
|
||
|
||
## 2. 데이터 소스 분석
|
||
|
||
### 2.1 Prometheus HTTP API
|
||
|
||
Prometheus는 REST API를 통해 메트릭 데이터를 조회할 수 있다.
|
||
|
||
#### 주요 엔드포인트
|
||
|
||
| 엔드포인트 | 용도 | 설명 |
|
||
|---|---|---|
|
||
| `GET /api/v1/query` | 즉시 쿼리 | 현재 시점의 메트릭 조회 |
|
||
| `GET /api/v1/query_range` | 범위 쿼리 | **주간 보고서의 핵심** - 시작/종료 시간 + step 간격으로 시계열 데이터 조회 |
|
||
| `GET /api/v1/labels` | 레이블 목록 | 사용 가능한 메트릭 레이블 조회 |
|
||
| `GET /api/v1/label/<name>/values` | 레이블 값 | 특정 레이블의 값 조회 |
|
||
| `GET /api/v1/metadata` | 메트릭 메타데이터 | 메트릭 설명 및 타입 정보 |
|
||
|
||
#### query_range API 호출 예시
|
||
|
||
```
|
||
GET /api/v1/query_range?query=<PromQL>&start=<rfc3339>&end=<rfc3339>&step=<duration>
|
||
```
|
||
|
||
- **최대 조회 범위**: 32일 (주간 보고서에 충분)
|
||
- **step**: 데이터 해상도 (예: `5m`, `15m`, `1h`)
|
||
- 주간 보고서용 권장 step: `5m` ~ `15m` (세밀한 peak 감지를 위해)
|
||
|
||
#### 핵심 PromQL 쿼리
|
||
|
||
**CPU 사용률 (%):**
|
||
```promql
|
||
# 노드별 CPU 사용률
|
||
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
|
||
|
||
# 또는
|
||
sum by (instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m])) * 100
|
||
```
|
||
|
||
**Memory 사용률 (%):**
|
||
```promql
|
||
# 노드별 메모리 사용률
|
||
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
|
||
```
|
||
|
||
**Disk 사용률 (%):**
|
||
```promql
|
||
# 파일시스템별 디스크 사용률
|
||
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100
|
||
```
|
||
|
||
**NAS/NFS 모니터링:**
|
||
```promql
|
||
# NFS 마운트 디스크 사용률
|
||
(1 - node_filesystem_avail_bytes{fstype="nfs"} / node_filesystem_size_bytes{fstype="nfs"}) * 100
|
||
|
||
# NFS 마운트 디스크 사용률 (nfs4 포함)
|
||
(1 - node_filesystem_avail_bytes{fstype=~"nfs|nfs4|cifs"} / node_filesystem_size_bytes{fstype=~"nfs|nfs4|cifs"}) * 100
|
||
```
|
||
|
||
> **참고**: NAS 모니터링은 node_exporter의 filesystem collector가 NFS/CIFS 마운트도 수집함. Synology 등 전용 NAS는 SNMP exporter(`snmp_exporter`)를 통해 더 상세한 메트릭 수집 가능.
|
||
|
||
**Network Traffic (bytes/sec):**
|
||
```promql
|
||
# 수신 트래픽 (bytes/sec)
|
||
sum by (instance) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[5m]))
|
||
|
||
# 송신 트래픽 (bytes/sec)
|
||
sum by (instance) (rate(node_network_transmit_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[5m]))
|
||
|
||
# bps 단위로 변환
|
||
sum by (instance) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[5m])) * 8
|
||
```
|
||
|
||
#### 통계 분석용 PromQL (avg / max / peak 감지)
|
||
|
||
**평균 (avg_over_time):**
|
||
```promql
|
||
# 주간 평균 CPU 사용률
|
||
avg_over_time(
|
||
(100 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)[7d:5m]
|
||
)
|
||
```
|
||
|
||
**최대 (max_over_time):**
|
||
```promql
|
||
# 주간 최대 CPU 사용률
|
||
max_over_time(
|
||
(100 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)[7d:5m]
|
||
)
|
||
```
|
||
|
||
**Peak/이상 탐지 (Z-Score 기반):**
|
||
```promql
|
||
# Z-Score = (현재값 - 평균) / 표준편차
|
||
# |Z| > 2 이면 이상 구간으로 판단
|
||
(
|
||
rate(node_cpu_seconds_total{mode!="idle"}[5m])
|
||
- avg_over_time(rate(node_cpu_seconds_total{mode!="idle"}[5m])[1d])
|
||
) / stddev_over_time(rate(node_cpu_seconds_total{mode!="idle"}[5m])[1d])
|
||
```
|
||
|
||
**표준편차 기반 이상 탐지:**
|
||
```promql
|
||
# 평균 + 2*표준편차를 초과하는 구간 감지
|
||
<metric> > (avg_over_time(<metric>[7d:5m]) + 2 * stddev_over_time(<metric>[7d:5m]))
|
||
```
|
||
|
||
---
|
||
|
||
### 2.2 Kubernetes API
|
||
|
||
#### 노드 정보 조회
|
||
|
||
| 엔드포인트 | 용도 |
|
||
|---|---|
|
||
| `GET /api/v1/nodes` | 전체 노드 목록 + 스펙(CPU, Memory, OS 등) |
|
||
| `GET /api/v1/nodes/<name>` | 특정 노드 상세 정보 |
|
||
| `GET /apis/metrics.k8s.io/v1beta1/nodes` | 노드별 실시간 리소스 사용량 (metrics-server 필요) |
|
||
| `GET /apis/metrics.k8s.io/v1beta1/nodes/<name>` | 특정 노드 실시간 사용량 |
|
||
|
||
#### 노드 정보에서 얻을 수 있는 데이터
|
||
|
||
```json
|
||
{
|
||
"metadata": { "name": "node-1", "labels": { "role": "worker" } },
|
||
"status": {
|
||
"capacity": { "cpu": "16", "memory": "65536Mi" },
|
||
"allocatable": { "cpu": "15800m", "memory": "63000Mi" },
|
||
"nodeInfo": {
|
||
"osImage": "Ubuntu 22.04",
|
||
"kubeletVersion": "v1.28.0",
|
||
"containerRuntimeVersion": "containerd://1.7.0"
|
||
},
|
||
"conditions": [
|
||
{ "type": "Ready", "status": "True" },
|
||
{ "type": "MemoryPressure", "status": "False" },
|
||
{ "type": "DiskPressure", "status": "False" }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 인증 방식 (Pod 내부 배포 기준)
|
||
|
||
Pod으로 배포되므로 **In-Cluster Config**를 사용한다. K8s가 자동으로 ServiceAccount 토큰을 Pod에 마운트해준다.
|
||
|
||
```
|
||
/var/run/secrets/kubernetes.io/serviceaccount/token ← Bearer Token
|
||
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt ← CA 인증서
|
||
KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT ← API 서버 주소 (자동 주입)
|
||
```
|
||
|
||
`@kubernetes/client-node` 라이브러리 사용 시:
|
||
```typescript
|
||
import * as k8s from '@kubernetes/client-node';
|
||
|
||
const kc = new k8s.KubeConfig();
|
||
kc.loadFromCluster(); // ← in-cluster config 자동 로드
|
||
|
||
const coreApi = kc.makeApiClient(k8s.CoreV1Api);
|
||
const nodes = await coreApi.listNode();
|
||
```
|
||
|
||
> **참고**: 외부 개발 시에는 `kc.loadFromDefault()`로 로컬 kubeconfig 사용 가능
|
||
|
||
---
|
||
|
||
## 3. 아키텍처 (K8s Pod 배포)
|
||
|
||
### 3.1 배포 아키텍처
|
||
|
||
K8s Pod으로 배포되므로 클러스터 내부 통신을 활용한 **Next.js 풀스택 (BFF 패턴)** 단일 구성이 최적이다.
|
||
|
||
```
|
||
┌─── K8s Cluster ──────────────────────────────────────────────┐
|
||
│ │
|
||
│ ┌─── Pod: infra-report (ns: soo) ────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────────────────────────┐ │ │
|
||
│ │ │ Next.js (App Router) │ │ │
|
||
│ │ │ ┌───────────┐ ┌────────────┐ │ │ │
|
||
│ │ │ │ Frontend │ │ API Routes │──┼──→ Prometheus │ │
|
||
│ │ │ │ (React) │ │ (BFF) │──┼──→ K8s API Server │ │
|
||
│ │ │ └───────────┘ └────────────┘ │ │ │
|
||
│ │ └─────────────────────────────────┘ │ │
|
||
│ │ ServiceAccount: infra-report-sa │ │
|
||
│ └────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌── Service ──┐ │
|
||
│ │ ClusterIP │ ← Ingress / NodePort로 외부 접근 │
|
||
│ │ :3000 │ │
|
||
│ └─────────────┘ │
|
||
│ │
|
||
│ Prometheus: http://prometheus-server.monitoring │
|
||
│ K8s API: https://kubernetes.default.svc (in-cluster) │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 K8s Pod 배포의 이점
|
||
|
||
| 이점 | 설명 |
|
||
|---|---|
|
||
| **CORS 문제 없음** | API Routes가 서버 사이드에서 Prometheus 호출 (Pod 간 통신) |
|
||
| **인증 자동 처리** | ServiceAccount 토큰이 Pod에 자동 마운트 → K8s API 인증 |
|
||
| **Prometheus 내부 접근** | `prometheus-server.monitoring`으로 클러스터 내부 DNS 호출 |
|
||
| **설정 관리** | ConfigMap/Secret으로 환경변수 주입 |
|
||
| **헬스체크** | K8s liveness/readiness probe 활용 |
|
||
| **자동 복구** | Pod 장애 시 자동 재시작 |
|
||
| **수평 확장** | 필요시 replica 증가 가능 (보고서 용도라 보통 1개면 충분) |
|
||
|
||
### 3.3 프론트엔드 Only 가능 여부
|
||
|
||
K8s Pod으로 배포하는 이상 **BFF 패턴이 자연스러운 선택**이다.
|
||
Next.js 자체가 서버 런타임을 포함하므로 "프론트만 구현"해도 API Routes로 백엔드 로직을 가진 풀스택 앱이 된다.
|
||
즉, **프론트엔드 코드만 작성해도 사실상 백엔드가 포함된 구조**이므로 프론트 개발자 혼자서 충분히 구현 가능하다.
|
||
|
||
---
|
||
|
||
## 4. 기술 스택 상세
|
||
|
||
### 4.1 프론트엔드
|
||
|
||
| 카테고리 | 추천 기술 | 대안 | 선택 이유 |
|
||
|---|---|---|---|
|
||
| **프레임워크** | **Next.js (App Router)** | Vite + React | API Routes로 BFF 패턴, standalone 빌드로 K8s 최적화 |
|
||
| **언어** | **TypeScript** | JavaScript | 타입 안정성, API 응답 타입 정의 |
|
||
| **차트 라이브러리** | **Apache ECharts** (`echarts-for-react`) | Recharts, Chart.js | 대량 데이터 처리 성능 우수, 다양한 차트 타입, 인터랙티브 기능 |
|
||
| **UI 프레임워크** | **Tailwind CSS + shadcn/ui** | Ant Design, MUI | 경량, 커스터마이징 용이, 보고서 레이아웃에 적합 |
|
||
| **테이블** | **TanStack Table** | AG Grid | 정렬/필터링, 경량 |
|
||
| **상태관리** | **TanStack Query (React Query)** | SWR, Zustand | API 데이터 캐싱/재조회 최적화 |
|
||
| **날짜 처리** | **date-fns** 또는 **dayjs** | moment.js (deprecated) | 경량, 트리쉐이킹 지원 |
|
||
| **PDF 생성** | **html2canvas + jsPDF** (클라이언트) 또는 **Puppeteer** (서버) | react-pdf | 보고서 레이아웃 그대로 PDF 변환 |
|
||
|
||
#### 차트 라이브러리 비교 (보고서 용도 관점)
|
||
|
||
| 기준 | ECharts | Recharts | Chart.js |
|
||
|---|---|---|---|
|
||
| **대량 데이터 (1만+ 포인트)** | WebGL 렌더링으로 최고 | SVG 기반, 느려짐 | Canvas 기반, 양호 |
|
||
| **시계열 차트** | 내장 dataZoom, 브러시 선택 | 기본적 | 줌 플러그인 필요 |
|
||
| **보고서 스타일** | 풍부한 옵션 | 심플 | 심플 |
|
||
| **툴팁/인터랙션** | 매우 풍부 | 기본적 | 기본적 |
|
||
| **번들 사이즈** | ~1MB (트리쉐이킹 가능) | ~200KB | ~60KB |
|
||
| **React 통합** | echarts-for-react 래퍼 | 네이티브 React | react-chartjs-2 래퍼 |
|
||
|
||
> **권장**: 인프라 모니터링 보고서는 데이터 포인트가 많고 시계열 분석이 핵심이므로 **Apache ECharts** 추천. 데이터가 적고 단순한 보고서라면 **Recharts**도 충분.
|
||
|
||
### 4.2 백엔드 (경량)
|
||
|
||
| 카테고리 | 추천 기술 | 대안 | 선택 이유 |
|
||
|---|---|---|---|
|
||
| **런타임** | **Node.js** | Python (FastAPI) | 프론트와 언어 통일, Next.js API Routes 활용 가능 |
|
||
| **HTTP 클라이언트** | **axios** 또는 **fetch** (Node 18+) | got, node-fetch | Prometheus/K8s API 호출 |
|
||
| **K8s 클라이언트** | **@kubernetes/client-node** | kubectl exec | 공식 JS 클라이언트, in-cluster config 지원 |
|
||
| **스케줄링** | **node-cron** 또는 **K8s CronJob** | Bull Queue | Pod 내부 cron 또는 K8s 네이티브 CronJob |
|
||
| **PDF (서버)** | **Puppeteer** | Playwright | 헤드리스 브라우저로 pixel-perfect PDF |
|
||
|
||
### 4.3 Python 백엔드 대안 (데이터 분석 강점)
|
||
|
||
| 카테고리 | 기술 | 장점 |
|
||
|---|---|---|
|
||
| **프레임워크** | FastAPI | 비동기, 자동 API 문서, 타입 힌트 |
|
||
| **Prometheus 클라이언트** | `prometheus-api-client` | PromQL 결과를 Pandas DataFrame으로 변환 |
|
||
| **K8s 클라이언트** | `kubernetes` (공식) | 완성도 높은 공식 클라이언트 |
|
||
| **데이터 분석** | Pandas, NumPy | 통계 분석(평균/최대/표준편차/이상탐지) 강력 |
|
||
| **PDF 생성** | WeasyPrint, ReportLab | HTML → PDF 변환 |
|
||
|
||
> **참고**: 데이터 분석/통계에 중점을 둔다면 Python 백엔드가 더 강력하지만, 프론트엔드와의 통합성과 단일 프로젝트 관리를 고려하면 Next.js + API Routes가 더 실용적.
|
||
|
||
---
|
||
|
||
## 5. 보고서 데이터 가공 로직
|
||
|
||
### 5.1 데이터 수집 흐름
|
||
|
||
```
|
||
1. 보고 기간 설정 (예: 지난 7일)
|
||
├─ start: 2026-03-05T00:00:00Z
|
||
└─ end: 2026-03-12T00:00:00Z
|
||
|
||
2. Prometheus query_range API 호출
|
||
├─ CPU 사용률 쿼리 (step=5m → 2,016 데이터포인트/노드)
|
||
├─ Memory 사용률 쿼리
|
||
├─ Disk 사용률 쿼리
|
||
├─ NAS 사용률 쿼리
|
||
└─ Network 트래픽 쿼리
|
||
|
||
3. K8s API 호출
|
||
├─ 노드 목록 + 스펙 (CPU cores, RAM 총량)
|
||
└─ 노드 상태 (conditions)
|
||
|
||
4. 데이터 가공
|
||
├─ 노드별 통계 계산 (avg, max, min, p95, p99)
|
||
├─ Peak 구간 감지 (Z-Score 기반)
|
||
├─ 임계값 초과 구간 마킹
|
||
└─ 트렌드 분석 (전주 대비 증감)
|
||
|
||
5. 보고서 렌더링
|
||
├─ 요약 대시보드 (핵심 지표 카드)
|
||
├─ 시계열 차트 (peak 구간 하이라이트)
|
||
├─ 노드별 상세 테이블
|
||
└─ PDF 내보내기
|
||
```
|
||
|
||
### 5.2 Peak 감지 알고리즘
|
||
|
||
#### 방법 1: PromQL 서버 사이드 (Prometheus에서 계산)
|
||
|
||
```promql
|
||
# avg_over_time + stddev_over_time 조합
|
||
# 평균 + 2σ 초과 시 peak으로 판정
|
||
|
||
# CPU peak 감지
|
||
(100 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
|
||
>
|
||
(
|
||
avg_over_time((100 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)[7d:5m])
|
||
+ 2 * stddev_over_time((100 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)[7d:5m])
|
||
)
|
||
```
|
||
|
||
#### 방법 2: 클라이언트/백엔드 사이드 (JavaScript/Python)
|
||
|
||
```javascript
|
||
// Z-Score 기반 peak 감지
|
||
function detectPeaks(dataPoints, threshold = 2) {
|
||
const values = dataPoints.map(d => d.value);
|
||
const mean = values.reduce((a, b) => a + b) / values.length;
|
||
const stddev = Math.sqrt(
|
||
values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length
|
||
);
|
||
|
||
return dataPoints.map(d => ({
|
||
...d,
|
||
zScore: (d.value - mean) / stddev,
|
||
isPeak: Math.abs((d.value - mean) / stddev) > threshold,
|
||
}));
|
||
}
|
||
```
|
||
|
||
### 5.3 보고서 구성 요소
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ 인프라 주간 모니터링 보고서 │
|
||
│ 2026.03.05 ~ 2026.03.12 │
|
||
├─────────────────────────────────────────────────┤
|
||
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||
│ │ CPU │ │ MEM │ │ DISK │ │ NET │ ← 요약 │
|
||
│ │ 평균 │ │ 평균 │ │ 평균 │ │ 평균 │ 카드 │
|
||
│ │ 최대 │ │ 최대 │ │ 최대 │ │ 최대 │ │
|
||
│ │ 최소 │ │ 최소 │ │ 최소 │ │ │ │
|
||
│ └──────┘ └──────┘ └──────┘ └──────┘ │
|
||
├─────────────────────────────────────────────────┤
|
||
│ [CPU 사용률 시계열 차트] │
|
||
│ ████████░░░░████████████████░░░░████████ │
|
||
│ ↑ peak 구간 하이라이트 │
|
||
├─────────────────────────────────────────────────┤
|
||
│ [Memory 사용률 시계열 차트] │
|
||
├─────────────────────────────────────────────────┤
|
||
│ [Disk/NAS 사용률 차트] │
|
||
├─────────────────────────────────────────────────┤
|
||
│ [Network Traffic 차트 (In/Out)] │
|
||
├─────────────────────────────────────────────────┤
|
||
│ 노드별 상세 │
|
||
│ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
|
||
│ │ 노드 │ CPU │ CPU │ CPU │ MEM │ MEM │ MEM │ DISK │ │
|
||
│ │ │ avg │ max │ min │ avg │ max │ min │ max │ │
|
||
│ ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │
|
||
│ │node-1│ 45% │ 92% │ 12% │ 67% │ 85% │ 41% │ 72% │ │
|
||
│ │node-2│ 38% │ 78% │ 8% │ 55% │ 70% │ 35% │ 45% │ │
|
||
│ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
|
||
├─────────────────────────────────────────────────┤
|
||
│ ⚠ 주의 구간 │
|
||
│ - node-1: 03/07 14:00~16:30 CPU 92% 도달 │
|
||
│ - node-2: 03/09 02:00~03:00 Memory 85% 초과 │
|
||
│ - NAS-01: 디스크 사용률 85% 초과 (증설 검토 필요) │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 기술 스택 최종 추천
|
||
|
||
### 6.1 추천 구성: Next.js 풀스택 + K8s 배포
|
||
|
||
```
|
||
infra-report/
|
||
├── src/
|
||
│ ├── app/ # Next.js App Router
|
||
│ │ ├── page.tsx # 메인 보고서 페이지
|
||
│ │ ├── api/ # API Routes (BFF)
|
||
│ │ │ ├── metrics/ # Prometheus 데이터 프록시 + 가공
|
||
│ │ │ │ ├── cpu/route.ts
|
||
│ │ │ │ ├── memory/route.ts
|
||
│ │ │ │ ├── disk/route.ts
|
||
│ │ │ │ ├── nas/route.ts
|
||
│ │ │ │ └── network/route.ts
|
||
│ │ │ ├── nodes/route.ts # K8s 노드 정보
|
||
│ │ │ └── report/route.ts # 보고서 데이터 통합
|
||
│ │ └── report/
|
||
│ │ └── [id]/page.tsx # 개별 보고서 상세
|
||
│ ├── components/
|
||
│ │ ├── charts/ # 차트 컴포넌트
|
||
│ │ │ ├── CpuChart.tsx
|
||
│ │ │ ├── MemoryChart.tsx
|
||
│ │ │ ├── DiskChart.tsx
|
||
│ │ │ ├── NetworkChart.tsx
|
||
│ │ │ └── PeakHighlight.tsx
|
||
│ │ ├── report/ # 보고서 레이아웃 컴포넌트
|
||
│ │ │ ├── SummaryCards.tsx
|
||
│ │ │ ├── NodeTable.tsx
|
||
│ │ │ └── AlertSection.tsx
|
||
│ │ └── ui/ # 공통 UI 컴포넌트
|
||
│ ├── lib/
|
||
│ │ ├── prometheus.ts # Prometheus API 클라이언트
|
||
│ │ ├── kubernetes.ts # K8s API 클라이언트 (in-cluster config)
|
||
│ │ ├── analytics.ts # 통계 계산 (avg, max, peak 감지)
|
||
│ │ └── formatters.ts # 데이터 포맷팅 (bytes→GB 등)
|
||
│ └── types/
|
||
│ ├── metrics.ts # 메트릭 데이터 타입
|
||
│ └── report.ts # 보고서 데이터 타입
|
||
├── k8s/ # K8s 매니페스트
|
||
│ ├── deployment.yaml
|
||
│ ├── service.yaml
|
||
│ ├── ingress.yaml
|
||
│ ├── serviceaccount.yaml
|
||
│ ├── rbac.yaml # ClusterRole + ClusterRoleBinding
|
||
│ └── configmap.yaml
|
||
├── Dockerfile
|
||
├── .dockerignore
|
||
├── package.json
|
||
├── tailwind.config.ts
|
||
├── tsconfig.json
|
||
└── .env.local # 로컬 개발용 (Pod에서는 ConfigMap/Secret 사용)
|
||
```
|
||
|
||
### 6.2 핵심 패키지 목록
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"next": "^14.x",
|
||
"react": "^18.x",
|
||
"typescript": "^5.x",
|
||
|
||
"echarts": "^5.x",
|
||
"echarts-for-react": "^3.x",
|
||
|
||
"@tanstack/react-query": "^5.x",
|
||
"@tanstack/react-table": "^8.x",
|
||
|
||
"tailwindcss": "^3.x",
|
||
"@radix-ui/react-*": "latest",
|
||
|
||
"axios": "^1.x",
|
||
"@kubernetes/client-node": "^0.20.x",
|
||
|
||
"date-fns": "^3.x",
|
||
"jspdf": "^2.x",
|
||
"html2canvas": "^1.x",
|
||
|
||
"zod": "^3.x"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. K8s 배포 상세
|
||
|
||
### 7.1 Dockerfile
|
||
|
||
```dockerfile
|
||
# ── Build Stage ──
|
||
FROM node:20-alpine AS builder
|
||
WORKDIR /app
|
||
COPY package*.json ./
|
||
RUN npm ci
|
||
COPY . .
|
||
RUN npm run build
|
||
|
||
# ── Production Stage ──
|
||
FROM node:20-alpine AS runner
|
||
WORKDIR /app
|
||
ENV NODE_ENV=production
|
||
|
||
RUN addgroup --system --gid 1001 nodejs
|
||
RUN adduser --system --uid 1001 nextjs
|
||
|
||
COPY --from=builder /app/public ./public
|
||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||
|
||
USER nextjs
|
||
EXPOSE 3000
|
||
ENV PORT=3000
|
||
ENV HOSTNAME="0.0.0.0"
|
||
|
||
CMD ["node", "server.js"]
|
||
```
|
||
|
||
> `next.config.js`에 `output: 'standalone'` 설정 필요
|
||
|
||
### 7.2 K8s 매니페스트
|
||
|
||
#### ServiceAccount + RBAC (노드 조회 권한)
|
||
|
||
```yaml
|
||
# k8s/serviceaccount.yaml
|
||
apiVersion: v1
|
||
kind: ServiceAccount
|
||
metadata:
|
||
name: infra-report-sa
|
||
namespace: soo
|
||
---
|
||
# k8s/rbac.yaml
|
||
apiVersion: rbac.authorization.k8s.io/v1
|
||
kind: ClusterRole
|
||
metadata:
|
||
name: infra-report-reader
|
||
rules:
|
||
- apiGroups: [""]
|
||
resources: ["nodes"]
|
||
verbs: ["get", "list"]
|
||
- apiGroups: ["metrics.k8s.io"]
|
||
resources: ["nodes"]
|
||
verbs: ["get", "list"]
|
||
---
|
||
apiVersion: rbac.authorization.k8s.io/v1
|
||
kind: ClusterRoleBinding
|
||
metadata:
|
||
name: infra-report-reader-binding
|
||
subjects:
|
||
- kind: ServiceAccount
|
||
name: infra-report-sa
|
||
namespace: soo
|
||
roleRef:
|
||
kind: ClusterRole
|
||
name: infra-report-reader
|
||
apiGroup: rbac.authorization.k8s.io
|
||
```
|
||
|
||
#### ConfigMap (환경변수)
|
||
|
||
```yaml
|
||
# k8s/configmap.yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: infra-report-config
|
||
namespace: soo
|
||
data:
|
||
PROMETHEUS_URL: "http://prometheus-server.monitoring"
|
||
REPORT_STEP: "5m"
|
||
REPORT_RANGE_DAYS: "7"
|
||
TZ: "Asia/Seoul"
|
||
```
|
||
|
||
#### Deployment
|
||
|
||
```yaml
|
||
# k8s/deployment.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: infra-report
|
||
namespace: soo
|
||
labels:
|
||
app: infra-report
|
||
spec:
|
||
replicas: 1
|
||
selector:
|
||
matchLabels:
|
||
app: infra-report
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: infra-report
|
||
spec:
|
||
serviceAccountName: infra-report-sa
|
||
containers:
|
||
- name: infra-report
|
||
image: harbor.inje-private.com/soo/infra-report:latest
|
||
ports:
|
||
- containerPort: 3000
|
||
envFrom:
|
||
- configMapRef:
|
||
name: infra-report-config
|
||
resources:
|
||
requests:
|
||
cpu: "100m"
|
||
memory: "256Mi"
|
||
limits:
|
||
cpu: "500m"
|
||
memory: "512Mi"
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /api/health
|
||
port: 3000
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 30
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /api/health
|
||
port: 3000
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
```
|
||
|
||
#### Service + Ingress
|
||
|
||
```yaml
|
||
# k8s/service.yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: infra-report
|
||
namespace: soo
|
||
spec:
|
||
type: ClusterIP
|
||
selector:
|
||
app: infra-report
|
||
ports:
|
||
- port: 80
|
||
targetPort: 3000
|
||
protocol: TCP
|
||
---
|
||
# k8s/ingress.yaml
|
||
apiVersion: networking.k8s.io/v1
|
||
kind: Ingress
|
||
metadata:
|
||
name: infra-report
|
||
namespace: soo
|
||
annotations:
|
||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||
spec:
|
||
ingressClassName: nginx
|
||
rules:
|
||
- host: infra-report.gpulive.cloud # 실제 도메인으로 변경
|
||
http:
|
||
paths:
|
||
- path: /
|
||
pathType: Prefix
|
||
backend:
|
||
service:
|
||
name: infra-report
|
||
port:
|
||
number: 80
|
||
```
|
||
|
||
### 7.3 환경변수 관리
|
||
|
||
| 구분 | 로컬 개발 | K8s Pod |
|
||
|---|---|---|
|
||
| Prometheus URL | `.env.local` | ConfigMap |
|
||
| K8s 인증 | kubeconfig (자동) | ServiceAccount (자동) |
|
||
| 민감 정보 | `.env.local` | Secret |
|
||
| 타임존 | OS 설정 | ConfigMap (`TZ=Asia/Seoul`) |
|
||
|
||
### 7.4 빌드/배포 파이프라인
|
||
|
||
```
|
||
1. 코드 Push → Gitea (gitea.inje-private.com)
|
||
2. CI (Gitea Runner)
|
||
├─ npm ci && npm run build
|
||
├─ docker build → harbor.inje-private.com/soo/infra-report:${TAG}
|
||
└─ docker push
|
||
3. CD (ArgoCD: argocd.gpulive.cloud)
|
||
├─ k8s/ 매니페스트 변경 감지
|
||
└─ 자동 배포 (sync)
|
||
```
|
||
|
||
### 7.5 Next.js 설정 (standalone 빌드)
|
||
|
||
```javascript
|
||
// next.config.js
|
||
/** @type {import('next').NextConfig} */
|
||
const nextConfig = {
|
||
output: 'standalone', // Docker 최적화 필수
|
||
experimental: {
|
||
// 서버 컴포넌트에서 K8s 클라이언트 사용 시
|
||
serverComponentsExternalPackages: ['@kubernetes/client-node'],
|
||
},
|
||
};
|
||
|
||
module.exports = nextConfig;
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 구현 우선순위 로드맵
|
||
|
||
### Phase 1: 프로젝트 세팅 + K8s 배포 기반
|
||
- [ ] Next.js 프로젝트 초기 구성 (TypeScript, Tailwind, standalone 빌드)
|
||
- [ ] Dockerfile 작성 + 로컬 빌드 테스트
|
||
- [ ] K8s 매니페스트 작성 (ServiceAccount, RBAC, Deployment, Service)
|
||
- [ ] Harbor 이미지 Push + K8s 클러스터 배포 확인
|
||
- [ ] Health check API (`/api/health`) 구현
|
||
|
||
### Phase 2: 데이터 수집 MVP
|
||
- [ ] Prometheus API 클라이언트 구현 (lib/prometheus.ts)
|
||
- [ ] K8s 노드 정보 조회 (lib/kubernetes.ts, in-cluster config)
|
||
- [ ] CPU/Memory 메트릭 API Routes 구현
|
||
- [ ] 기본 시계열 차트 (ECharts)
|
||
- [ ] 요약 카드 컴포넌트
|
||
|
||
### Phase 3: 데이터 확장
|
||
- [ ] Disk/NAS/Network 메트릭 추가
|
||
- [ ] 노드별 상세 테이블
|
||
- [ ] 통계 계산 로직 (avg, max, p95)
|
||
|
||
### Phase 4: 분석 기능
|
||
- [ ] Peak 구간 감지 (Z-Score)
|
||
- [ ] 차트에 peak 구간 하이라이트
|
||
- [ ] 주의 구간 알림 섹션
|
||
- [ ] 전주 대비 트렌드
|
||
|
||
### Phase 5: 보고서 기능
|
||
- [ ] PDF 내보내기
|
||
- [ ] 보고서 기간 선택 UI
|
||
- [ ] Ingress 설정 + 외부 접근
|
||
|
||
### Phase 6: 자동화 (추후)
|
||
- [ ] 주간 자동 보고서 생성 (CronJob 또는 node-cron)
|
||
- [ ] Slack/Email 발송
|
||
- [ ] Gitea Runner CI/CD 파이프라인 연동
|
||
- [ ] ArgoCD 자동 배포
|
||
|
||
---
|
||
|
||
## 9. 참고 자료
|
||
|
||
### Prometheus
|
||
- [Prometheus Query Examples](https://prometheus.io/docs/prometheus/latest/querying/examples/)
|
||
- [Prometheus API Guide - Last9](https://last9.io/blog/prometheus-api/)
|
||
- [PromQL Cheat Sheet - SigNoz](https://signoz.io/guides/promql-cheat-sheet/)
|
||
- [PromQL Cheat Sheet - PromLabs](https://promlabs.com/promql-cheat-sheet/)
|
||
- [Anomaly Detection with PromQL](https://grigorkh.medium.com/practical-anomaly-detection-with-prometheus-and-promql-d3027b97da96)
|
||
- [Z-Score in PromQL](https://omarghader.github.io/prometheus-anomaly-detection-z-score-in-promql/)
|
||
- [Prometheus Over Time Analysis](https://oneuptime.com/blog/post/2025-12-17-prometheus-metrics-over-time-analysis/view)
|
||
- [PromQL Subqueries and Aggregations](https://oneuptime.com/blog/post/2026-02-02-promql-subqueries-aggregations/view)
|
||
|
||
### Kubernetes
|
||
- [K8s Resource Metrics Pipeline](https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/)
|
||
- [K8s Node Metrics Data](https://kubernetes.io/docs/reference/instrumentation/node-metrics/)
|
||
- [Metrics Server](https://kubernetes-sigs.github.io/metrics-server/)
|
||
- [kubectl proxy](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/)
|
||
- [@kubernetes/client-node](https://github.com/kubernetes-client/javascript)
|
||
- [K8s RBAC Authorization](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
|
||
|
||
### 프론트엔드
|
||
- [React Chart Libraries 비교 2025 - LogRocket](https://blog.logrocket.com/best-react-chart-libraries-2025/)
|
||
- [Apache ECharts](https://echarts.apache.org/)
|
||
- [Chart Libraries for React 2026 - Weavelinx](https://weavelinx.com/best-chart-libraries-for-react-projects-in-2026/)
|
||
|
||
### PDF 생성
|
||
- [Next.js PDF Generation](https://dev.to/wonder2210/generating-pdf-files-using-next-js-24dm)
|
||
- [html2canvas + jsPDF in React](https://medium.com/@saidularefin8/generating-pdfs-from-html-in-a-react-application-with-html2canvas-and-jspdf-d46c5785eff2)
|
||
- [Puppeteer PDF in Next.js](https://medium.com/front-end-weekly/dynamic-html-to-pdf-generation-in-next-js-a-step-by-step-guide-with-puppeteer-dbcf276375d7)
|
||
|
||
### NAS/Storage
|
||
- [Node Exporter - GitHub](https://github.com/prometheus/node_exporter)
|
||
- [Synology NAS + Prometheus](https://github.com/ddiiwoong/synology-prometheus)
|
||
- [Network Interface Metrics - Robust Perception](https://www.robustperception.io/network-interface-metrics-from-the-node-exporter/)
|