홈 k3s 클러스터 구축기 (7) — Prometheus + Grafana 모니터링
kube-prometheus-stack 기반 클러스터 모니터링 환경의 ArgoCD 배포와 Grafana 대시보드 활용
읽는 데 47분연재 7 / 8편
- #k3s
- #kubernetes
- #prometheus
- #grafana
- #monitoring
- #argocd
- #kube-prometheus-stack
이 문서의 목차
환경: 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 패턴으로 배포합니다.
helm-chart
monitoring
- values.yaml
secrets
- grafana-secret.yaml
argocd
- monitoring-app.yaml
Grafana 관리자 Secret 생성
Grafana의 관리자 비밀번호를 Kubernetes Secret으로 분리합니다. values.yaml에 평문으로 넣거나 차트 기본 비밀번호(
prom-operator)를 그대로 사용하는 것은 보안상 바람직하지 않으므로, Secret을 별도로 생성하고 Grafana가 이를 참조하도록 설정합니다.yaml # secrets/grafana-secret.yaml (git 커밋 금지) apiVersion: v1 kind: Secret metadata: name: grafana-admin namespace: monitoring type: Opaque stringData: admin-user: "admin" admin-password: "원하는_비밀번호"bash # 네임스페이스 생성 후 Secret 적용 kubectl create namespace monitoring kubectl apply -f secrets/grafana-secret.yaml이 파일은 git에 커밋하지 않습니다
.gitignore에secrets/디렉토리를 추가하여 실수로 push되지 않도록 합니다. SealedSecret이나 External Secrets Operator 같은 암호화 도구를 도입하기 전까지는 수동 관리가 가장 안전한 방법입니다.values.yaml 작성
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설정 항목이 많지만, 의도를 파악하면 몇 가지 핵심 결정으로 정리됩니다.
설정 값 이유 retention3d소규모 클러스터에서 디스크를 절약하면서도 최근 추이를 확인할 수 있는 기간 storageClassNamelocal-path메트릭 데이터는 유실되어도 다시 수집되므로 Longhorn이 불필요 persistence.enabledfalseGrafana 대시보드를 PVC가 아닌 Helm values(GitOps)로 관리 alertmanager.enabledfalse슬랙, 이메일 등 알림 대상이 없으면 불필요 existingSecretgrafana-admin관리자 비밀번호를 Secret으로 분리하여 보안 강화 default_home_dashboard_pathnodes.jsonGrafana Home 화면에 노드별 리소스 대시보드를 기본 표시 Prometheus에 local-path를 사용하는 이유
6편에서 정리한 StorageClass 선택 기준이 여기에도 적용됩니다. Prometheus의 시계열 데이터는 노드 장애로 유실되더라도 Exporter가 살아 있는 한 다시 수집됩니다. 과거 데이터 일부가 빠지는 것 외에 서비스에 영향이 없으므로, Longhorn의 복제 오버헤드를 감수할 이유가 없습니다.
ArgoCD Application 작성
yaml # 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-certgenJob을 Helm hook으로 실행하여 자체 서명 CA를 생성하고, 이를 ValidatingWebhookConfiguration과 MutatingWebhookConfiguration의 caBundle 필드에 주입합니다. Helm이 렌더링한 원본에는 caBundle이 비어 있지만 설치 후에는 값이 채워지므로, ArgoCD가 이를 차이로 감지하여 OutOfSync를 발생시킵니다.jqPathExpressions로 해당 필드를 무시해야 sync 루프가 발생하지 않습니다.git push → 자동 설치
설정 저장소에 커밋하고 push하면 App-of-Apps가 변경을 감지하여 자동으로 설치를 시작합니다.
bash 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네임스페이스에 배포합니다.설치 확인
bash # ArgoCD 앱 상태 kubectl get app monitoring -n argocd # Pod 상태 (모두 Running이면 정상) kubectl get pods -n monitoring정상 설치 시 다음과 같은 Pod들이 Running 상태로 확인됩니다.
text 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
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 | 네트워크 트래픽 현황 |
Grafana가 Lens를 대체하는 것은 아닙니다. 두 도구는 역할이 다르며, 상황에 따라 적합한 도구가 달라집니다.
| Lens | Grafana | |
|---|---|---|
| 시간 범위 | 현재 순간만 | 과거 데이터 조회 가능 (보존 기간 내) |
| 그래프 | 간단한 바 차트 | 시계열 그래프로 추이 확인 |
| 디스크 메트릭 | ephemeral storage만 | 노드 디스크 전체 사용량 |
| 알림 | 없음 | 조건 기반 알림 설정 가능 |
| 커스텀 대시보드 | 없음 | PromQL로 자유롭게 구성 |
| 접근 방식 | 데스크톱 앱 | 웹 브라우저 |
Pod 상태를 빠르게 확인하거나 kubectl 명령을 실행할 때는 Lens가 편리하고, 시계열 추이 분석이나 리소스 용량 계획을 세울 때는 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%를 사용합니다. 모니터링 스택 자체가 클러스터에 부담을 주지 않는 수준이며, 이 정도 비용으로 시계열 기반의 운영 판단이 가능해지는 것은 충분한 가치가 있습니다.
Prometheus Pod이 Pending 상태에서 멈춤
PVC가 바인딩되지 않는 경우입니다. local-path StorageClass가 정상인지 확인합니다.
kubectl get pvc -n monitoring
kubectl get storageclasslocal-path가 default StorageClass로 설정되어 있고, PVC의 STATUS가 Bound가 아니라면 local-path-provisioner Pod의 로그를 확인합니다.
kubectl logs -n kube-system -l app=local-path-provisionerGrafana에서 No data 표시
Prometheus가 정상적으로 메트릭을 수집하고 있는지 확인합니다. Prometheus UI에서 Status → Targets를 확인했을 때 모든 타겟이 UP 상태여야 합니다.
kubectl port-forward svc/prometheus-operated 9090:9090 -n monitoringhttp://localhost:9090/targets에 접속하여 DOWN 상태인 Target이 있다면, 해당 Exporter Pod의 상태와 네트워크 정책을 점검합니다.
ArgoCD에서 OutOfSync가 반복됨
Application의 ignoreDifferences에 CRD와 Webhook 관련 항목이 모두 포함되어 있는지 확인합니다. 특히 admissionregistration.k8s.io의 ValidatingWebhookConfiguration과 MutatingWebhookConfiguration에 대한 jqPathExpressions가 누락되면 Helm hook이 주입한 caBundle 때문에 sync 루프가 발생합니다. syncOptions에 ServerSideApply=true와 RespectIgnoreDifferences=true도 필수입니다.
Grafana 로그인 실패
Secret이 올바르게 생성되었는지 확인합니다.
kubectl get secret grafana-admin -n monitoring
kubectl get secret grafana-admin -n monitoring -o jsonpath='{.data.admin-password}' | base64 -dSecret의 네임스페이스가 monitoring이 아니거나, values.yaml의 passwordKey가 Secret의 키 이름과 일치하지 않으면 Grafana가 비밀번호를 읽지 못합니다. Secret을 수정한 후에는 Grafana Pod을 재시작해야 반영됩니다.
7편 요약
- 모니터링 필요성: metrics-server만으로는 시계열 추이, 디스크 사용량 등을 파악할 수 없습니다
- 아키텍처: Exporter(수집) → Prometheus(저장) → Grafana(시각화)의 3계층 Pull 모델
- kube-prometheus-stack: 모든 컴포넌트와 기본 대시보드를 하나의 Helm 차트로 제공합니다
- StorageClass: Prometheus 메트릭은 유실 가능한 데이터이므로
local-path가 적합합니다 - Grafana 영속성:
persistence: false+ GitOps로 대시보드를 관리하는 것이 권장 방식입니다
관련 문서
글쓴이 mirunamu00



