환경: k3s v1.31+, kube-prometheus-stack 72.6.2, ArgoCD v3.3+, Ubuntu Server 24.04 LTS
들어가며
6편까지 클러스터에 GitOps 파이프라인, Redis, 분산 스토리지를 갖추었습니다. 워크로드를 배포하고 운영할 기반은 마련되었지만, 클러스터 내부에서 무슨 일이 벌어지고 있는지 들여다볼 수단이 부족합니다. Lens 같은 데스크톱 도구가 보여주는 것은 현재 순간의 CPU와 메모리 사용량뿐이며, "어제 밤에 메모리가 갑자기 치솟은 원인"이나 "디스크가 언제쯤 가득 찰 것인지"는 과거 데이터를 저장하는 모니터링 시스템 없이는 파악할 수 없습니다.
Kubernetes가 기본 제공하는 metrics-server는 현재 시점의 CPU와 메모리 수치만 제공합니다. 디스크 사용량, 네트워크 트래픽, 시계열 추이 같은 정보는 별도의 모니터링 스택이 필요합니다. 이 편에서는 Prometheus와 Grafana를 ArgoCD로 배포하여 클러스터 전반의 상태를 시각적으로 모니터링할 수 있는 환경을 구축합니다.
모니터링 아키텍처
모니터링 스택은 세 계층으로 구성됩니다. 각 노드에서 메트릭을 노출하는 Exporter 계층, 이를 주기적으로 수집하여 시계열로 저장하는 Prometheus, 그리고 저장된 데이터를 쿼리하여 대시보드로 시각화하는 Grafana입니다.
Prometheus는 Pull 모델로 동작합니다. Exporter가 메트릭을 Prometheus로 Push하는 것이 아니라, Prometheus가 각 Exporter의 HTTP 엔드포인트를 설정된 주기마다 스크래핑하여 데이터를 가져옵니다. 스크래핑 대상과 주기를 Prometheus가 중앙에서 제어하므로, 새로운 Exporter가 추가되면 스크래핑 설정(ServiceMonitor CRD)만 추가하면 됩니다.
| 컴포넌트 | 타입 | 역할 |
|---|---|---|
| Prometheus | StatefulSet | 메트릭 스크래핑 및 시계열 저장 |
| Grafana | Deployment | 대시보드 시각화 |
| node-exporter | DaemonSet | 노드 하드웨어 메트릭 (CPU, 메모리, 디스크, 네트워크) |
| kube-state-metrics | Deployment | Kubernetes 오브젝트 상태 (Pod, Deployment, PVC 등) |
| prometheus-operator | Deployment | Prometheus 설정을 CRD로 선언적 관리 |
node-exporter는 DaemonSet이므로 클러스터의 모든 노드에서 실행됩니다. 각 노드의 CPU, 메모리, 디스크 I/O, 네트워크 트래픽 등 하드웨어 수준의 메트릭을 수집하며, 이 데이터가 Grafana의 노드별 리소스 대시보드의 근간이 됩니다. kube-state-metrics는 Kubernetes API를 통해 Pod, Deployment, PVC 등 오브젝트의 상태를 메트릭으로 변환하여, "현재 Running인 Pod 수"나 "PVC 사용률" 같은 클러스터 수준의 정보를 제공합니다.
배포
이 컴포넌트들을 개별로 설치할 필요는 없습니다. kube-prometheus-stack Helm 차트가 Prometheus, Grafana, node-exporter, kube-state-metrics, prometheus-operator를 하나의 패키지로 묶어 제공합니다. 기본 대시보드 20개 이상이 포함되어 있어, values.yaml만 조정하면 바로 사용할 수 있는 완성된 모니터링 환경이 구성됩니다. Longhorn과 달리 노드에 별도 패키지를 사전 설치할 필요도 없습니다.
3편의 App-of-Apps 패턴으로 배포합니다.
Grafana 관리자 Secret 생성
Grafana의 관리자 비밀번호를 Kubernetes Secret으로 분리합니다. values.yaml에 평문으로 넣거나 차트 기본 비밀번호(prom-operator)를 그대로 사용하는 것은 보안상 바람직하지 않으므로, Secret을 별도로 생성하고 Grafana가 이를 참조하도록 설정합니다.
# secrets/grafana-secret.yaml (git 커밋 금지)
apiVersion: v1
kind: Secret
metadata:
name: grafana-admin
namespace: monitoring
type: Opaque
stringData:
admin-user: "admin"
admin-password: "원하는_비밀번호"# 네임스페이스 생성 후 Secret 적용
kubectl create namespace monitoring
kubectl apply -f secrets/grafana-secret.yaml이 파일은 git에 커밋하지 않습니다
.gitignore에 secrets/ 디렉토리를 추가하여 실수로 push되지 않도록 합니다. SealedSecret이나 External Secrets Operator 같은 암호화 도구를 도입하기 전까지는 수동 관리가 가장 안전한 방법입니다.
values.yaml 작성
# monitoring/values.yaml
# --- Prometheus ---
prometheus:
prometheusSpec:
retention: 3d
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
# --- Grafana ---
grafana:
admin:
existingSecret: grafana-admin
passwordKey: admin-password
grafana.ini:
dashboards:
default_home_dashboard_path: /tmp/dashboards/nodes.json
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
persistence:
enabled: false
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- grafana.mirunamu.info
tls:
- hosts:
- grafana.mirunamu.info
secretName: grafana-mirunamu-info-tls
# --- Alertmanager ---
alertmanager:
enabled: false
# --- node-exporter ---
prometheus-node-exporter:
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
# --- kube-state-metrics ---
kube-state-metrics:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi설정 항목이 많지만, 의도를 파악하면 몇 가지 핵심 결정으로 정리됩니다.
| 설정 | 값 | 이유 |
|---|---|---|
retention | 3d | 소규모 클러스터에서 디스크를 절약하면서도 최근 추이를 확인할 수 있는 기간 |
storageClassName | local-path | 메트릭 데이터는 유실되어도 다시 수집되므로 Longhorn이 불필요 |
persistence.enabled | false | Grafana 대시보드를 PVC가 아닌 Helm values(GitOps)로 관리 |
alertmanager.enabled | false | 슬랙, 이메일 등 알림 대상이 없으면 불필요 |
existingSecret | grafana-admin | 관리자 비밀번호를 Secret으로 분리하여 보안 강화 |
default_home_dashboard_path | nodes.json | Grafana Home 화면에 노드별 리소스 대시보드를 기본 표시 |
Prometheus에 local-path를 사용하는 이유
6편에서 정리한 StorageClass 선택 기준이 여기에도 적용됩니다. Prometheus의 시계열 데이터는 노드 장애로 유실되더라도 Exporter가 살아 있는 한 다시 수집됩니다. 과거 데이터 일부가 빠지는 것 외에 서비스에 영향이 없으므로, Longhorn의 복제 오버헤드를 감수할 이유가 없습니다.
ArgoCD Application 작성
# argocd/monitoring-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: monitoring
namespace: argocd
spec:
project: default
sources:
- repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 72.6.2
helm:
valueFiles:
- $values/monitoring/values.yaml
- repoURL: https://github.com/mirunamu00/helm-chart.git
targetRevision: master
ref: values
destination:
server: https://kubernetes.default.svc
namespace: monitoring
ignoreDifferences:
- group: apiextensions.k8s.io
kind: CustomResourceDefinition
jsonPointers:
- /metadata/annotations
- /spec/conversion
- /spec/preserveUnknownFields
- /status
- group: admissionregistration.k8s.io
kind: ValidatingWebhookConfiguration
jqPathExpressions:
- .webhooks[]?.clientConfig.caBundle
- group: admissionregistration.k8s.io
kind: MutatingWebhookConfiguration
jqPathExpressions:
- .webhooks[]?.clientConfig.caBundle
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- RespectIgnoreDifferences=trueignoreDifferences 설정에 주목해야 합니다. CRD 관련 항목은 6편의 Longhorn과 동일한 이유로 필요합니다. prometheus-operator가 CRD를 런타임에 수정하기 때문입니다.
여기에 추가로 Webhook caBundle 항목이 있습니다. kube-prometheus-stack은 설치 시 kube-webhook-certgen Job을 Helm hook으로 실행하여 자체 서명 CA를 생성하고, 이를 ValidatingWebhookConfiguration과 MutatingWebhookConfiguration의 caBundle 필드에 주입합니다. Helm이 렌더링한 원본에는 caBundle이 비어 있지만 설치 후에는 값이 채워지므로, ArgoCD가 이를 차이로 감지하여 OutOfSync를 발생시킵니다. jqPathExpressions로 해당 필드를 무시해야 sync 루프가 발생하지 않습니다.
git push → 자동 설치
설정 저장소에 커밋하고 push하면 App-of-Apps가 변경을 감지하여 자동으로 설치를 시작합니다.
git add monitoring/ argocd/monitoring-app.yaml
git commit -m "feat: add Prometheus + Grafana monitoring stack"
git pushApp-of-Apps가 argocd/ 폴더의 변경을 감지하면 monitoring-app.yaml에서 Application 리소스를 생성하고, ArgoCD가 kube-prometheus-stack Helm 차트를 values.yaml과 함께 monitoring 네임스페이스에 배포합니다.
설치 확인
# ArgoCD 앱 상태
kubectl get app monitoring -n argocd
# Pod 상태 (모두 Running이면 정상)
kubectl get pods -n monitoring정상 설치 시 다음과 같은 Pod들이 Running 상태로 확인됩니다.
monitoring-grafana-xxx 3/3 Running
monitoring-kube-prometheus-operator-xxx 1/1 Running
monitoring-kube-state-metrics-xxx 1/1 Running
monitoring-prometheus-node-exporter-xxx 1/1 Running (노드마다 1개)
prometheus-monitoring-kube-prometheus-prometheus-0 2/2 Running
Grafana 접속
values.yaml에서 Ingress를 설정했으므로 https://grafana.mirunamu.info로 바로 접속할 수 있습니다. cert-manager가 TLS 인증서를 자동 발급하며, DNS A 레코드에 해당 도메인이 클러스터의 외부 IP로 연결되어 있어야 합니다. Ingress나 DNS가 아직 준비되지 않은 경우에는 Lens에서 monitoring 네임스페이스 → Services → monitoring-grafana → Forward로 포트포워딩하여 로컬에서 접속할 수 있습니다.
| 항목 | 값 |
|---|---|
| ID | admin |
| PW | grafana-secret.yaml에서 설정한 비밀번호 |
관리자 계정 정보는 Kubernetes Secret(grafana-admin)에서 읽어옵니다. 비밀번호를 변경하려면 Secret을 업데이트하고 Grafana Pod을 재시작하면 반영됩니다.
대시보드
kube-prometheus-stack은 기본 대시보드 20개 이상을 자동 프로비저닝합니다. 별도 구성 없이 설치 직후부터 클러스터의 주요 메트릭을 확인할 수 있습니다.
| 대시보드 | 내용 |
|---|---|
| Node Exporter / Nodes | 노드별 CPU, 메모리, 디스크, 네트워크 사용량 및 추이. Home 화면에 기본 표시 |
| Kubernetes / Compute Resources / Cluster | 클러스터 전체 CPU/메모리 총합 |
| Kubernetes / Compute Resources / Namespace (Pods) | 네임스페이스별 Pod 리소스 사용량 |
| Kubernetes / Networking | 네트워크 트래픽 현황 |
Lens와의 비교
Grafana가 Lens를 대체하는 것은 아닙니다. 두 도구는 역할이 다르며, 상황에 따라 적합한 도구가 달라집니다.
| Lens | Grafana | |
|---|---|---|
| 시간 범위 | 현재 순간만 | 과거 데이터 조회 가능 (보존 기간 내) |
| 그래프 | 간단한 바 차트 | 시계열 그래프로 추이 확인 |
| 디스크 메트릭 | ephemeral storage만 | 노드 디스크 전체 사용량 |
| 알림 | 없음 | 조건 기반 알림 설정 가능 |
| 커스텀 대시보드 | 없음 | PromQL로 자유롭게 구성 |
| 접근 방식 | 데스크톱 앱 | 웹 브라우저 |
Pod 상태를 빠르게 확인하거나 kubectl 명령을 실행할 때는 Lens가 편리하고, 시계열 추이 분석이나 리소스 용량 계획을 세울 때는 Grafana가 적합합니다.
Grafana 데이터 영속성
values.yaml에서 persistence: false로 설정했으므로, Grafana Pod이 재시작되면 런타임 데이터가 초기화됩니다. 다만 모든 것이 사라지는 것은 아닙니다.
| 항목 | 재시작 후 |
|---|---|
| kube-prometheus-stack 기본 대시보드 | 유지 — Helm values에서 ConfigMap으로 프로비저닝 |
| Home 대시보드 설정 | 유지 — grafana.ini에서 지정 |
| UI에서 직접 만든 커스텀 대시보드 | 삭제 |
| UI에서 변경한 설정 | 삭제 |
기본 대시보드는 Helm 차트가 ConfigMap으로 주입하기 때문에 Pod이 재시작되어도 항상 복원됩니다. 문제는 UI에서 직접 생성한 커스텀 대시보드인데, 이를 영구 보존하는 방법은 두 가지입니다.
권장하는 방식은 GitOps 접근입니다. UI에서 대시보드를 만든 뒤 JSON을 export하여 values.yaml의 grafana.dashboards 섹션에 추가하면, 대시보드 정의가 Git에 남으므로 버전 관리와 복원이 가능합니다. 대안으로 persistence.enabled: true를 설정하여 Grafana 데이터를 PVC에 저장할 수도 있지만, 대시보드 상태가 Git 밖에 존재하게 되어 GitOps 원칙에서 벗어나므로 가급적 첫 번째 방식을 권장합니다.
리소스 사용량
모니터링 스택이 클러스터 자원을 얼마나 소모하는지는 홈 클러스터에서 중요한 관심사입니다. values.yaml에서 설정한 리소스 요청 기준으로 전체 사용량을 정리하면 다음과 같습니다.
| 컴포넌트 | CPU (requests) | 메모리 (requests) |
|---|---|---|
| Prometheus | 200m | 256Mi |
| Grafana | 100m | 128Mi |
| prometheus-operator | 차트 기본값 | 차트 기본값 |
| node-exporter × 2 | 50m × 2 = 100m | 32Mi × 2 = 64Mi |
| kube-state-metrics | 50m | 64Mi |
| 합계 | ~450m | ~512Mi |
전체 클러스터(8코어, 16GB) 대비 약 CPU 5.6%, 메모리 3.1%를 사용합니다. 모니터링 스택 자체가 클러스터에 부담을 주지 않는 수준이며, 이 정도 비용으로 시계열 기반의 운영 판단이 가능해지는 것은 충분한 가치가 있습니다.
트러블슈팅
정리
7편 요약
- 모니터링 필요성: metrics-server만으로는 시계열 추이, 디스크 사용량 등을 파악할 수 없습니다
- 아키텍처: Exporter(수집) → Prometheus(저장) → Grafana(시각화)의 3계층 Pull 모델
- kube-prometheus-stack: 모든 컴포넌트와 기본 대시보드를 하나의 Helm 차트로 제공합니다
- StorageClass: Prometheus 메트릭은 유실 가능한 데이터이므로
local-path가 적합합니다 - Grafana 영속성:
persistence: false+ GitOps로 대시보드를 관리하는 것이 권장 방식입니다