클러스터 관찰 가능성: Prometheus, Grafana 및 OpenTelemetry
“측정할 수 없으면 관리할 수도 없습니다.” 프로덕션 Kubernetes 클러스터의 경우 이는 이는 세 가지 차원에 대한 가시성을 갖는 것을 의미합니다. 측정항목 (그는 각각 얼마나 소비합니까? 구성요소), 통나무 (시스템에서 무슨 일이 일어나고 있는지), e 추적하다 (요청이 마이크로서비스를 통해 이동하는 방식) 프로메테우스 + 그라파나 + 스택 Loki + OpenTelemetry는 이러한 요구에 대한 오픈 소스 답변입니다.
이 기사에서는 Kubernetes를 위한 완전한 관찰성 스택을 구축할 것입니다. 쿠베-프로메테우스-스택 인프라 측정항목에 대해 구성하겠습니다. 로키 집계 로그를 위해 통합할 예정입니다. OpenTelemetry 수집기 애플리케이션에서 배포한 트레이스를 수집하여 Tempo(백엔드)로 전달합니다. Grafana 추적). 그 결과 통합된 관찰 플랫폼이 탄생했습니다. Grafana에 표시됩니다.
무엇을 배울 것인가
- kube-prometheus-stack 설치: Prometheus Operator, kube-state-metrics, 노드 내보내기
- 자동 앱 스크래핑을 위한 ServiceMonitor 및 PodMonitor 생성
- 중요한 클러스터 경고(OOMKill, CrashLoopBackOff 등)를 위한 PrometheusRule
- Kubernetes 라벨이 포함된 집계 로그용 Loki + Promtail
- OpenTelemetry Collector - 구성 가능한 원격 측정 파이프라인
- 분산 추적을 위한 Grafana Tempo
- Kubernetes용 사전 구축된 Grafana 대시보드
- Grafana의 메트릭-로그-추적 상관 관계(예제)
관찰 가능성 스택의 아키텍처
설치하기 전에 구성 요소가 서로 어떻게 관련되어 있는지 이해해 보겠습니다.
- 프로메테우스: HTTP 스크래핑으로 측정항목을 수집합니다. 15~30일 동안 데이터 저장
- kube-상태-메트릭: K8s 객체(배포, 포드 등) 상태에 대한 측정항목을 노출합니다.
- 노드 내보내기: 노드 하드웨어 측정항목(CPU, 디스크, 네트워크)을 노출합니다.
- 로키: 포드 로그를 집계합니다. 콘텐츠는 색인화하지 않고 라벨만 색인화합니다.
- 약속: Loki에 컨테이너 로그를 보내는 DaemonSet
- OpenTelemetry 수집기: 앱에서 추적/측정항목/로그를 수신하여 백엔드로 라우팅합니다.
- 그라파나 시간: 분산 추적을 위한 백엔드(trace)
- 그라파나: 메트릭(Prometheus), 로그(Loki), 추적(Tempo)을 볼 수 있는 통합 UI
kube-prometheus-stack 설치
# Installa kube-prometheus-stack (include Prometheus, Alertmanager, Grafana, kube-state-metrics, Node Exporter)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# values.yaml per produzione
cat > kube-prometheus-values.yaml << 'EOF'
# Prometheus
prometheus:
prometheusSpec:
retention: 30d
retentionSize: "50GB"
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: ssd
resources:
requests:
storage: 100Gi
# Scrape tutte le ServiceMonitor/PodMonitor nel cluster
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 2000m
memory: 8Gi
# Alertmanager
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: ssd
resources:
requests:
storage: 10Gi
config:
global:
slack_api_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
route:
receiver: 'slack-critical'
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
routes:
- receiver: 'slack-critical'
matchers:
- alertname =~ ".*Critical.*"
- receiver: 'slack-warning'
matchers:
- severity = warning
receivers:
- name: 'slack-critical'
slack_configs:
- channel: '#alerts-critical'
send_resolved: true
title: '[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
- name: 'slack-warning'
slack_configs:
- channel: '#alerts-warning'
send_resolved: true
# Grafana
grafana:
enabled: true
ingress:
enabled: true
hosts:
- grafana.company.com
persistence:
enabled: true
size: 10Gi
# Datasource Loki pre-configurato
additionalDataSources:
- name: Loki
type: loki
url: http://loki.monitoring.svc:3100
jsonData:
derivedFields:
- datasourceUid: tempo
matcherRegex: '"traceID":"(\w+)"'
name: TraceID
url: '${__value.raw}'
- name: Tempo
type: tempo
url: http://tempo.monitoring.svc:3100
# kube-state-metrics
kube-state-metrics:
metricLabelsAllowlist:
- pods=[team,environment,app]
- deployments=[team,environment,app]
EOF
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--version 65.0.0 \
-f kube-prometheus-values.yaml
# Verifica
kubectl get pods -n monitoring
kubectl get servicemonitors -A
애플리케이션 스크래핑을 위한 ServiceMonitor
Prometheus Operator는 다음을 사용합니다. 서비스모니터 e 포드모니터 애플리케이션 메트릭 스크래핑을 동적으로 구성합니다. 소용없어 Prometheus 구성을 수정합니다.
# servicemonitor-api-service.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: api-service-monitor
namespace: team-alpha-production
labels:
team: team-alpha # label per selezionare questo monitor
spec:
selector:
matchLabels:
app: api-service # seleziona il Service con questo label
endpoints:
- port: metrics # nome della porta nel Service
interval: 30s
path: /metrics
# Basic auth se le metriche sono protette
# basicAuth:
# username: { name: metrics-auth, key: username }
# password: { name: metrics-auth, key: password }
namespaceSelector:
matchNames:
- team-alpha-production
---
# Aggiungi la porta metrics al Service dell'applicazione
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: team-alpha-production
labels:
app: api-service
spec:
selector:
app: api-service
ports:
- name: http
port: 8080
- name: metrics # porta dedicata alle metriche
port: 9090
targetPort: 9090
클러스터 중요 경고
# prometheusrule-kubernetes-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kubernetes-critical-alerts
namespace: monitoring
labels:
prometheus: kube-prometheus
spec:
groups:
- name: kubernetes.critical
rules:
# Pod in CrashLoopBackOff
- alert: PodCrashLoopBackOff
expr: |
rate(kube_pod_container_status_restarts_total[15m]) > 0
AND
kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} in CrashLoopBackOff"
# Pod OOMKilled
- alert: PodOOMKilled
expr: |
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
for: 0m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} terminato per OOM"
description: "Aumenta i memory limits del container {{ $labels.container }}"
# Nodo sotto pressione di memoria
- alert: NodeMemoryPressure
expr: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1
for: 2m
labels:
severity: critical
annotations:
summary: "Nodo {{ $labels.node }} sotto MemoryPressure"
# PVC quasi pieno
- alert: PersistentVolumeFillingUp
expr: |
kubelet_volume_stats_available_bytes /
kubelet_volume_stats_capacity_bytes < 0.15
for: 5m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} al 85% della capacita"
# Deployment con zero repliche disponibili
- alert: DeploymentUnavailable
expr: kube_deployment_status_replicas_available == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} ha 0 repliche disponibili"
집계 로그용 Loki + Promtail
# Installa Loki (modalita monolitica per cluster medio)
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki \
--namespace monitoring \
--set loki.commonConfig.replication_factor=1 \
--set loki.storage.type=filesystem \
--set singleBinary.replicas=1 \
--set monitoring.selfMonitoring.enabled=false
# Installa Promtail (DaemonSet che invia log a Loki)
helm install promtail grafana/promtail \
--namespace monitoring \
--set config.clients[0].url=http://loki.monitoring.svc:3100/loki/api/v1/push \
--set config.snippets.extraScrapeConfigs='
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
- labeldrop:
- filename
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_team]
target_label: team
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace'
# Query Loki in Grafana (LogQL):
# Tutti i log error del team-alpha:
# {namespace="team-alpha-production"} |= "ERROR"
# Error rate per app negli ultimi 5 minuti:
# sum(rate({namespace="team-alpha-production"} |= "ERROR" [5m])) by (app)
# Log strutturati JSON - estrai campo:
# {app="api-service"} | json | level="error" | line_format "{{.message}}"
분산 추적을 위한 OpenTelemetry Collector
# Installa OpenTelemetry Operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
---
# otel-collector.yaml - pipeline di telemetria
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
namespace: monitoring
spec:
mode: DaemonSet # un collector per nodo
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
# Aggiunge metadati Kubernetes ai trace (namespace, pod name, etc.)
k8sattributes:
auth_type: "serviceAccount"
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.namespace.name
- k8s.deployment.name
- k8s.node.name
labels:
- tag_name: team
key: team
from: pod
# Campionamento: mantieni solo 10% dei trace in produzione (volume alto)
probabilistic_sampler:
sampling_percentage: 10
exporters:
otlp/tempo:
endpoint: http://tempo.monitoring.svc:4317
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:8889"
const_labels:
cluster: production
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, k8sattributes, probabilistic_sampler]
exporters: [otlp/tempo]
metrics:
receivers: [otlp]
processors: [batch, k8sattributes]
exporters: [prometheus]
그라파나 템포 설치
# Installa Grafana Tempo
helm install tempo grafana/tempo-distributed \
--namespace monitoring \
--set storage.trace.backend=local
# Alternativa: Tempo monolitico per cluster piccoli/medi
helm install tempo grafana/tempo \
--namespace monitoring \
--set tempo.storage.trace.backend=filesystem \
--set tempo.storage.trace.local.path=/var/tempo
Kubernetes용 Grafana 대시보드
Grafana에는 사전 구축된 대시보드 카탈로그가 있습니다. UI에서 이러한 ID를 가져옵니다. (대시보드 > 가져오기):
| 대시보드 | 아이디 그라파나 | 공익사업 |
|---|---|---|
| Kubernetes 클러스터 개요 | 7249 | 노드별 CPU/메모리/Pod 개요 |
| Kubernetes 배포 | 8588 | 배포 상태, 재시작 속도, 복제본 |
| 노드 내보내기 전체 | 1860년 | 노드 하드웨어 측정항목(CPU, 디스크, 네트워크) |
| 쿠버네티스 PVC | 13646 | PVC 저장장치 사용 |
| 로키 대시보드 | 15141 | 집계 로그, 오류율, 로그 탐색기 |
| NGINX 입력 컨트롤러 | 9614 | 요청 속도, 지연 시간, 입력 상태 코드 |
메트릭-로그-추적 상관 관계(예제)
이 스택의 진정한 힘은 상관관계: 측정항목에서 높은 대기 시간으로 해당 추적으로 직접 이동할 수 있고 추적에서 로그로 이동할 수 있습니다. 당시 포드의 모습입니다. 이것은 호출됩니다 전형적인:
# Abilita gli exemplar in Prometheus (già abilitati in kube-prometheus-stack)
# Nell'applicazione, includi il traceID nell'histogram metric:
# Go/Python con OpenTelemetry:
# Quando crei un histogram, aggiungi l'exemplar con il trace ID corrente
# Il Prometheus scraper lo raccoglie e lo conserva
# In Grafana:
# 1. Vai alla dashboard API latency
# 2. Vedi un picco di latenza
# 3. Clicca il diamante (exemplar) sul grafico
# 4. Grafana ti apre automaticamente il trace in Tempo
# 5. Dal trace, clicca sul servizio con errore
# 6. Grafana mostra i log di quel Pod in Loki per quel timestamp
# Questo flusso metriche → trace → log e il "holy grail" dell'osservabilita
Kubernetes 관찰 가능성에 대한 모범 사례
생산 관찰 가능성 체크리스트
- 자원에 대한 USE 방법: 각 리소스(CPU, 메모리, 디스크, 네트워크)에 대해: 사용률, 포화도, 오류. 각 노드에 대한 3가지 기본 경고는 다음과 같습니다.
- RED 서비스 방법: 각 서비스에 대해: 속도(요구/초), 오류(오류율), 기간(대기 시간). 세 가지 모두에 대해 경고
- SLO 기반 알림: 모든 이상 현상에 대해 경고하지 말고 SLO 오류 예산을 모두 사용한 경우에만 경고하십시오. 더 적은 소음, 더 많은 신호
- 차별화된 보존: 원시 지표는 15일, 월별 집계는 1년입니다. 원시 로그 7일, 감사 로그 1년
- 트랙 샘플링: 생산 과정에서 흔적을 100% 유지하지 마십시오. 비용이 너무 많이 듭니다. 디버깅에는 1~10%면 충분합니다.
- 일관된 라벨: 각 지표, 로그 및 추적에는 필터링을 위한 팀, 환경, 앱, 버전이 있어야 합니다.
결론 및 다음 단계
완전한 관찰 가능성 스택 — 메트릭을 위한 Prometheus, 로그를 위한 Loki, OpenTelemetry + Trace Time — Kubernetes를 블랙박스에서 시스템으로 전환합니다. 이해할 수 있습니다. Grafana의 세 가지 신호 간의 상관 관계는 시간을 대폭 단축합니다. 디버깅 평균은 몇 시간에서 몇 분까지입니다.
이 시리즈의 다음이자 마지막 기사 — Federation e를 사용한 Kubernetes 멀티 클라우드 Submariner — 마치 여러 클라우드 제공업체에 걸쳐 여러 클러스터를 관리하는 과제를 해결합니다. 이 시리즈의 모든 개념을 멀티 클러스터로 확장한 것은 단 하나였습니다.
관련 시리즈
- 관찰 가능성 및 OpenTelemetry — 애플리케이션 계측에 대한 심층 분석
- ArgoCD를 사용한 GitOps — Argo Rollouts는 자동 카나리아 분석을 위해 Prometheus를 사용합니다.







