Salta al contenuto principale

11 - Osservabilita Cluster: Prometheus, Grafana e OpenTelemetry

Osservabilita Cluster: Prometheus, Grafana e OpenTelemetry "Se non puoi misurarlo, non puoi gestirlo." Per un cluster Kubernetes in produzione, questo...

Cosa ne pensi di questo articolo?

11 - Osservabilita Cluster: Prometheus, Grafana e OpenTelemetry
Kubernetes AvanzatoFoto:Jakub ZerdzickiPexels

In Breve

Osservabilita Cluster: Prometheus, Grafana e OpenTelemetry "Se non puoi misurarlo, non puoi gestirlo." Per un cluster Kubernetes in produzione, questo...

Osservabilita Cluster: Prometheus, Grafana e OpenTelemetry

"Se non puoi misurarlo, non puoi gestirlo." Per un cluster Kubernetes in produzione, questo significa avere visibilita su tre dimensioni: metriche (quanto consuma ogni componente), log (cosa sta succedendo nel sistema), e trace (come si muovono le richieste attraverso i microservizi). La stack Prometheus + Grafana + Loki + OpenTelemetry e la risposta open-source a questa esigenza.

In questo articolo costruiremo una stack di osservabilita completa per Kubernetes: installeremo kube-prometheus-stack per le metriche infrastrutturali, configureremo Loki per i log aggregati, e integreremo OpenTelemetry Collector per raccogliere traces distribuiti dalle applicazioni e inoltrarli a Tempo (il backend di tracing di Grafana). Il risultato e una piattaforma di osservabilita unificata visualizzata in Grafana.

Cosa Imparerai

  • Installare kube-prometheus-stack: Prometheus Operator, kube-state-metrics, Node Exporter
  • Creare ServiceMonitor e PodMonitor per scraping automatico delle app
  • PrometheusRule per alert critici del cluster (OOMKill, CrashLoopBackOff, etc.)
  • Loki + Promtail per log aggregati con label Kubernetes
  • OpenTelemetry Collector: pipeline di telemetria configurabile
  • Grafana Tempo per distributed tracing
  • Dashboard Grafana predefinite per Kubernetes
  • Correlazione metriche-log-trace in Grafana (Exemplars)

Architettura della Stack di Osservabilita

Prima di installare, capiamo come i componenti si relazionano:

  • Prometheus: Raccoglie metriche con scraping HTTP. Conserva dati per 15-30 giorni
  • kube-state-metrics: Espone metriche sullo stato degli oggetti K8s (Deployment, Pod, etc.)
  • Node Exporter: Espone metriche hardware dei nodi (CPU, disco, rete)
  • Loki: Aggrega i log dei Pod. Non indicizza il contenuto, solo i label
  • Promtail: DaemonSet che invia i log dei container a Loki
  • OpenTelemetry Collector: Riceve traces/metriche/log dalle app e li instrada ai backend
  • Grafana Tempo: Backend per distributed tracing (traces)
  • Grafana: UI unificata per visualizzare metriche (Prometheus), log (Loki), trace (Tempo)

Installazione 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 per Scraping delle Applicazioni

Il Prometheus Operator usa ServiceMonitor e PodMonitor per configurare dinamicamente lo scraping delle metriche delle applicazioni. Non serve modificare la configurazione di 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

Alert Critici del Cluster

# 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 per Log Aggregati

# 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 per Traces Distribuiti

# 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]

Installazione di Grafana Tempo

# 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

Dashboard Grafana per Kubernetes

Grafana ha un catalogo di dashboard pre-costruite. Importa questi ID dalla UI (Dashboards > Import):

Dashboard ID Grafana Utilità
Kubernetes Cluster Overview 7249 Overview CPU/Memoria/Pod per nodo
Kubernetes Deployments 8588 Stato deployment, restart rate, replicas
Node Exporter Full 1860 Metriche hardware nodi (CPU, disco, rete)
Kubernetes PVC 13646 Utilizzo storage PVC
Loki Dashboard 15141 Log aggregati, error rate, log explorer
NGINX Ingress Controller 9614 Request rate, latenza, status code ingress

Correlazione Metriche-Log-Trace (Exemplars)

La vera potenza di questa stack e la correlazione: da una metrica di latenza elevata puoi passare direttamente al trace corrispondente, e dal trace ai log del Pod in quel momento. Questo si chiama Exemplar:

# 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

Best Practices per l'Osservabilita Kubernetes

Checklist Osservabilita di Produzione

  • USE Method per le risorse: Per ogni risorsa (CPU, memoria, disco, rete): Utilization, Saturation, Errors. Questi sono i 3 alert fondamentali per ogni nodo
  • RED Method per i servizi: Per ogni servizio: Rate (req/s), Errors (error rate), Duration (latenza). Alert su tutti e tre
  • SLO-based alerting: Non alertare su ogni anomalia, ma solo quando stai consumando il tuo error budget SLO. Meno rumore, piu segnale
  • Retention differenziata: Metriche raw 15 giorni, aggregati mensili 1 anno. Log raw 7 giorni, log di audit 1 anno
  • Sampling per i traces: Non conservare il 100% dei traces in produzione - costa troppo. 1-10% e sufficiente per il debugging
  • Label consistenti: Ogni metrica, log e trace deve avere team, environment, app, version per il filtering

Conclusioni e Prossimi Passi

Una stack di osservabilita completa - Prometheus per le metriche, Loki per i log, OpenTelemetry + Tempo per i trace - trasforma Kubernetes da una black box in un sistema comprensibile. La correlazione tra i tre segnali in Grafana riduce drasticamente il tempo medio di debug da ore a minuti.

Il prossimo e ultimo articolo di questa serie - Kubernetes Multi-Cloud con Federation e Submariner - affronta la sfida di gestire piu cluster su cloud provider diversi come se fossero uno solo, estendendo tutti i concetti di questa serie al multi-cluster.

Serie Correlate

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti