Skip to content

Guía de optimización de observabilidad de EKS

Versiones compatibles: Amazon EKS 1.29+, OpenTelemetry 0.90+ Última actualización: February 2025


Tabla de contenido

  1. Descripción general de los tres pilares de la observabilidad
  2. Comparación de soluciones de logging
  3. Recopilación y almacenamiento de métricas
  4. Tracing distribuido
  5. Monitorización sin código basada en eBPF
  6. Monitorización de costes
  7. Dashboard unificado de observabilidad
  8. Desafíos operativos y soluciones
  9. Mejores prácticas y próximos pasos

1. Descripción general de los tres pilares de la observabilidad

En los entornos modernos nativos de la nube, la observabilidad es la capacidad de comprender el estado interno de un sistema a través de sus salidas externas. Para implementar una observabilidad eficaz en entornos EKS, debe comprender tres pilares clave.

1.1 Relación entre logging, métricas y tracing

1.2 Función de cada pilar y criterios de selección

PilarFunción principalTipo de preguntaVolumen de datosCaracterísticas de coste
LoggingRegistro de eventos, auditoría, depuración"¿Qué ocurrió?"AltoCostes de almacenamiento elevados
MetricsMonitorización del estado del sistema, alertas"¿El sistema está en buen estado?"MedioSensible a la cardinalidad
TracingSeguimiento del flujo de solicitudes, análisis de cuellos de botella"¿Por qué es lento?"Alto (requiere sampling)Proporcional a la tasa de sampling

1.3 Arquitectura general de observabilidad de EKS


2. Comparación de soluciones de logging

2.1 Comparación de almacenamiento de logs

CriterioCloudWatch LogsOpenSearchLokiClickHouse
CosteIngesta: $0.50/GB
Almacenamiento: $0.03/GB/mes
Coste de instancia + EBS
r6g.large: ~$150/mes
Coste de almacenamiento de objetos
S3: $0.023/GB/mes
Instancia + almacenamiento
Reducido mediante alta compresión
RendimientoExcelente para pequeña escala
Latencia a gran escala
Optimizado para búsqueda de texto completo
Potente para consultas complejas
Filtrado rápido basado en etiquetas
Búsqueda de texto completo limitada
Optimizado para consultas analíticas
Excelente agregación en tiempo real
Complejidad operativaTotalmente gestionado
Carga operativa mínima
Requiere gestión del clúster
Ajuste complejo
Arquitectura simple
Fácil de operar
Requiere gestión de esquemas
Complejidad media
Capacidades de consultaLogs Insights
Análisis básico
Consulta Lucene
Potente búsqueda de texto completo
LogQL
Filtrado basado en etiquetas
Basado en SQL
Consultas analíticas complejas
EscalabilidadAuto-scaling
Ilimitada
Sharding manual
Requiere añadir nodos
Escalado horizontal sencillo
Aprovecha el almacenamiento de objetos
Compatible con sharding
Escala de petabytes
Casos de uso adecuadosEntornos nativos de AWS
Logging simple
Requisitos de búsqueda complejos
Seguridad/conformidad
Centrado en la eficiencia de costes
Integración con Grafana
Análisis/agregación de logs
Retención a largo plazo

2.2 Comparación de agentes de logs

CriterioFluent BitFluentdVector
Uso de memoria~15MB~60MB~30MB
Uso de CPUBajoMedioBajo
ThroughputHasta ~200K msg/sHasta ~50K msg/sHasta ~300K msg/s
LenguajeCRuby/CRust
Ecosistema de pluginsLimitado, pero con soporte principalMuy completoEn crecimiento
Complejidad de configuraciónBajaMediaMedia
Integración con EKSSoporte nativoCompatibleCompatible

2.3 Ejemplo de configuración de Fluent Bit + Loki para EKS

yaml
# fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Log_Level     info
        Daemon        off
        Parsers_File  parsers.conf
        HTTP_Server   On
        HTTP_Listen   0.0.0.0
        HTTP_Port     2020

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     50MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name                kubernetes
        Match               kube.*
        Kube_URL            https://kubernetes.default.svc:443
        Kube_CA_File        /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        Kube_Token_File     /var/run/secrets/kubernetes.io/serviceaccount/token
        Kube_Tag_Prefix     kube.var.log.containers.
        Merge_Log           On
        Keep_Log            Off
        K8S-Logging.Parser  On
        K8S-Logging.Exclude On

    [OUTPUT]
        Name                   loki
        Match                  *
        Host                   loki-gateway.logging.svc.cluster.local
        Port                   80
        Labels                 job=fluent-bit
        Label_Keys             $kubernetes['namespace_name'],$kubernetes['pod_name'],$kubernetes['container_name']
        Remove_Keys            kubernetes,stream
        Auto_Kubernetes_Labels on
        Line_Format            json

  parsers.conf: |
    [PARSER]
        Name        docker
        Format      json
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L
        Time_Keep   On

    [PARSER]
        Name        json
        Format      json
        Time_Key    timestamp
        Time_Format %Y-%m-%dT%H:%M:%S.%L
---
# fluent-bit-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
  labels:
    app: fluent-bit
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      serviceAccountName: fluent-bit
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:2.2
          resources:
            limits:
              memory: 200Mi
              cpu: 200m
            requests:
              memory: 100Mi
              cpu: 100m
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: config
              mountPath: /fluent-bit/etc/
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: config
          configMap:
            name: fluent-bit-config
bash
# Install Loki (Helm)
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# Install Loki in Simple Scalable mode
helm install loki grafana/loki \
  --namespace logging \
  --create-namespace \
  --set loki.auth_enabled=false \
  --set loki.storage.type=s3 \
  --set loki.storage.s3.endpoint=s3.ap-northeast-2.amazonaws.com \
  --set loki.storage.s3.region=ap-northeast-2 \
  --set loki.storage.s3.bucketnames=my-loki-bucket \
  --set loki.storage.s3.insecure=false \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::ACCOUNT:role/LokiS3Role

3. Recopilación y almacenamiento de métricas

3.1 Comparación de almacenamiento de métricas

CriterioPrometheusVictoriaMetricsAMP (Amazon Managed Prometheus)
EscalabilidadNodo único
Solo escalado vertical
Modo clúster
Escalado horizontal
Auto-scaling
Ilimitada
CosteSolo coste de infraestructura
EC2/EBS
Coste de infraestructura
Ahorro frente a Prometheus
Ingesta: $0.90/10M muestras
Almacenamiento: $0.03/GB/mes
HARequiere configuración independiente
Thanos/Cortex
Replicación integrada
Failover automático
HA totalmente gestionada
Multi-AZ
Sobrecarga operativaAlta
Gestión del almacenamiento/escalado
Media
Operaciones sencillas
Baja
Gestionado por AWS
Almacenamiento a largo plazoRequiere solución independienteSoporte integradoRetención ilimitada
Rendimiento de consultaExcelenteMuy excelente
(Motor optimizado)
Excelente
Compatibilidad con PromQLNativaTotalmente compatible + extensionesTotalmente compatible

3.2 Estrategia de gestión de cardinalidad

La cardinalidad se refiere al número de series temporales únicas. Una cardinalidad alta afecta directamente al uso de memoria y al rendimiento de las consultas.

yaml
# prometheus-config.yaml - Metric dropping and label optimization
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 30s
      evaluation_interval: 30s

    scrape_configs:
      - job_name: 'kubernetes-pods'
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          # Collect only specific namespaces
          - source_labels: [__meta_kubernetes_namespace]
            regex: 'kube-system|monitoring|production'
            action: keep

          # Remove unnecessary labels
          - regex: '__meta_kubernetes_pod_label_(.+)'
            action: labeldrop

          # Remove Pod UID (high cardinality cause)
          - regex: 'pod_template_hash|controller_revision_hash'
            action: labeldrop

        metric_relabel_configs:
          # Drop unnecessary metrics
          - source_labels: [__name__]
            regex: 'go_.*|promhttp_.*'
            action: drop

          # Limit histogram buckets (major high cardinality culprit)
          - source_labels: [__name__, le]
            regex: '.*_bucket;(0\.001|0\.005|0\.01|0\.05|0\.1|0\.5|1|5|10|30|60|120|300)'
            action: keep

3.3 Mejora del rendimiento de consultas con Recording Rules

Las Recording Rules precalculan consultas complejas y almacenan los resultados.

yaml
# prometheus-recording-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: recording-rules
  namespace: monitoring
spec:
  groups:
    - name: k8s.rules
      interval: 30s
      rules:
        # Pre-compute CPU utilization per node
        - record: node:cpu_utilization:ratio
          expr: |
            1 - avg by (node) (
              rate(node_cpu_seconds_total{mode="idle"}[5m])
            )

        # Memory utilization per node
        - record: node:memory_utilization:ratio
          expr: |
            1 - (
              node_memory_MemAvailable_bytes
              / node_memory_MemTotal_bytes
            )

        # CPU usage per namespace
        - record: namespace:container_cpu_usage_seconds_total:sum_rate
          expr: |
            sum by (namespace) (
              rate(container_cpu_usage_seconds_total{container!=""}[5m])
            )

        # Pod restart count (hourly)
        - record: namespace:pod_restarts:sum_increase1h
          expr: |
            sum by (namespace) (
              increase(kube_pod_container_status_restarts_total[1h])
            )

    - name: slo.rules
      interval: 30s
      rules:
        # Error rate per service
        - record: service:http_requests:error_rate5m
          expr: |
            sum by (service) (
              rate(http_requests_total{status=~"5.."}[5m])
            )
            /
            sum by (service) (
              rate(http_requests_total[5m])
            )

        # P99 latency per service
        - record: service:http_request_duration_seconds:p99
          expr: |
            histogram_quantile(0.99,
              sum by (service, le) (
                rate(http_request_duration_seconds_bucket[5m])
              )
            )

3.4 Estrategia de almacenamiento a largo plazo


4. Tracing distribuido

4.1 Descripción general y arquitectura de OpenTelemetry

OpenTelemetry (OTel) es un estándar independiente de proveedores para recopilar y exportar datos de observabilidad (traces, métricas, logs).

4.2 Comparación de backends de tracing

CriterioGrafana TempoJaegerAWS X-Ray
ArquitecturaBasada en almacenamiento de objetos
Sin índice
Elasticsearch/Cassandra
Basada en índices
Gestionada por AWS
Serverless
CosteSolo coste de almacenamiento de S3
Muy económico
Coste de infraestructura
Almacenamiento de índices
Precio por trace
$5/millón de traces
EscalabilidadIlimitada
Escalado horizontal
Requiere añadir nodos
Gestión de índices
Auto-scaling
Ilimitada
Método de consultaBúsqueda directa de TraceID
Integración de Exemplars
Búsqueda basada en tags
Búsqueda por intervalo temporal
Mapa de servicios
Búsqueda con filtros
Integración con GrafanaNativaCompatibleLimitada
Integración con AWSConfiguración independienteConfiguración independienteNativa
Lambda, ECS, etc.
Casos de uso adecuadosCentrado en la eficiencia de costes
Stack de Grafana
Requisitos de búsqueda complejos
Infraestructura autohospedada
Nativo de AWS
Entornos Serverless

4.3 Estrategias de sampling

yaml
# otel-collector-config.yaml - Sampling strategy configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: observability
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

    processors:
      # Batch processing - performance optimization
      batch:
        timeout: 5s
        send_batch_size: 1000
        send_batch_max_size: 1500

      # Memory limit - OOM prevention
      memory_limiter:
        check_interval: 1s
        limit_mib: 1000
        spike_limit_mib: 200

      # Probabilistic sampling - Head Sampling
      probabilistic_sampler:
        hash_seed: 22
        sampling_percentage: 10  # 10% sampling

      # Tail Sampling - condition-based sampling
      tail_sampling:
        decision_wait: 10s
        num_traces: 100000
        policies:
          # Keep 100% of traces with errors
          - name: errors
            type: status_code
            status_code:
              status_codes: [ERROR]

          # Keep 100% of high-latency traces
          - name: slow-traces
            type: latency
            latency:
              threshold_ms: 1000

          # Keep 100% of traces from specific services
          - name: critical-services
            type: string_attribute
            string_attribute:
              key: service.name
              values: [payment-service, order-service]

          # Sample only 5% of the rest
          - name: default
            type: probabilistic
            probabilistic:
              sampling_percentage: 5

      # Add/remove attributes
      attributes:
        actions:
          - key: environment
            value: production
            action: upsert
          - key: sensitive_data
            action: delete

    exporters:
      otlp:
        endpoint: tempo-distributor.observability:4317
        tls:
          insecure: true

      awsxray:
        region: ap-northeast-2

      debug:
        verbosity: detailed

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch, tail_sampling, attributes]
          exporters: [otlp, awsxray]

4.4 Configuración de DaemonSet de OTel Collector para EKS

yaml
# otel-collector-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
  namespace: observability
  labels:
    app: otel-collector
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      serviceAccountName: otel-collector
      containers:
        - name: collector
          image: otel/opentelemetry-collector-contrib:0.92.0
          args:
            - --config=/conf/config.yaml
          ports:
            - containerPort: 4317  # OTLP gRPC
              hostPort: 4317
            - containerPort: 4318  # OTLP HTTP
              hostPort: 4318
            - containerPort: 8888  # Metrics
          resources:
            limits:
              memory: 1Gi
              cpu: 500m
            requests:
              memory: 200Mi
              cpu: 100m
          volumeMounts:
            - name: config
              mountPath: /conf
          env:
            - name: K8S_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: K8S_POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: K8S_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
      volumes:
        - name: config
          configMap:
            name: otel-collector-config
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector
  namespace: observability
spec:
  selector:
    app: otel-collector
  ports:
    - name: otlp-grpc
      port: 4317
      targetPort: 4317
    - name: otlp-http
      port: 4318
      targetPort: 4318
    - name: metrics
      port: 8888
      targetPort: 8888

Configuración de auto-instrumentación con OTel SDK para aplicaciones:

yaml
# Adding auto-instrumentation to application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  template:
    metadata:
      annotations:
        # Enable OTel Operator auto-instrumentation
        instrumentation.opentelemetry.io/inject-java: "true"
        # Or for Python, Node.js, etc.
        # instrumentation.opentelemetry.io/inject-python: "true"
        # instrumentation.opentelemetry.io/inject-nodejs: "true"
    spec:
      containers:
        - name: app
          image: my-app:latest
          env:
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "http://otel-collector.observability:4317"
            - name: OTEL_SERVICE_NAME
              value: "my-app"
            - name: OTEL_RESOURCE_ATTRIBUTES
              value: "service.namespace=production,deployment.environment=prod"

5. Monitorización sin código basada en eBPF

5.1 Por qué usar monitorización con eBPF

eBPF (extended Berkeley Packet Filter) es una tecnología que permite la ejecución segura de programas dentro del kernel de Linux. La mayor ventaja de la monitorización basada en eBPF es lograr observabilidad sin modificaciones de código.

CaracterísticaInstrumentación tradicionalInstrumentación con eBPF
Modificación de códigoObligatoriaNo obligatoria
Impacto en el despliegueRequiere redespliegueDespliegue independiente
SobrecargaNivel de aplicaciónNivel de kernel (muy baja)
Dependencia del lenguajeSe necesita soporte de SDK por lenguajeIndependiente del lenguaje
CoberturaSolo partes instrumentadasTodo el sistema
MantenimientoGestionado con el códigoIndependiente

5.2 Coroot: mapas de servicios automáticos y análisis de latencia

Coroot utiliza eBPF para generar automáticamente mapas de servicios y analizar la latencia.

yaml
# coroot-helm-values.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: coroot
---
# Install Coroot via Helm
# helm repo add coroot https://coroot.github.io/helm-charts
# helm install coroot coroot/coroot -n coroot -f coroot-helm-values.yaml

coroot:
  replicas: 1
  resources:
    requests:
      cpu: 200m
      memory: 1Gi
    limits:
      cpu: 1
      memory: 2Gi

  # Prometheus integration
  prometheus:
    url: "http://prometheus-server.monitoring:9090"

  # ClickHouse storage (logs/traces)
  clickhouse:
    enabled: true
    persistence:
      size: 100Gi
      storageClass: gp3

node-agent:
  # eBPF-based agent
  ebpf:
    enabled: true

  resources:
    requests:
      cpu: 100m
      memory: 100Mi
    limits:
      cpu: 500m
      memory: 500Mi

  tolerations:
    - operator: Exists

Características principales de Coroot:

  • Descubrimiento automático de servicios: Detecta conexiones de red mediante eBPF para generar automáticamente mapas de servicios
  • Análisis de latencia: Mide automáticamente la latencia entre cada servicio
  • Seguimiento del uso de recursos: Analiza CPU, memoria y E/S de disco por servicio
  • Recopilación de logs: Recopila logs de aplicaciones sin modificaciones de código

5.3 Pixie (ahora New Relic): observabilidad específica de Kubernetes

Pixie es una plataforma de observabilidad basada en eBPF especializada en entornos Kubernetes.

bash
# Install Pixie CLI
bash -c "$(curl -fsSL https://withpixie.ai/install.sh)"

# Deploy Pixie
px deploy

# Check cluster status
px get viziers

# Real-time HTTP traffic monitoring
px live http_data

# Per-service latency analysis
px live service_stats

Características principales de Pixie:

  • Dashboards listos para usar: Monitorización automática de HTTP, DNS, MySQL, PostgreSQL, etc. inmediatamente después del despliegue
  • Scripts PxL: Análisis personalizado con un lenguaje de consulta similar a Python
  • Almacenamiento local de datos: Los datos confidenciales nunca salen del clúster
  • Análisis automático de cifrado: Descifra tráfico TLS mediante eBPF para su análisis

5.4 Cilium Hubble: observación del flujo de red

Para los clústeres EKS que utilizan Cilium CNI, Hubble proporciona visibilidad de red.

yaml
# cilium-hubble-values.yaml
hubble:
  enabled: true

  relay:
    enabled: true
    resources:
      requests:
        cpu: 100m
        memory: 128Mi

  ui:
    enabled: true
    replicas: 1
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
      hosts:
        - hubble.example.com

  metrics:
    enabled:
      - dns
      - drop
      - tcp
      - flow
      - icmp
      - http
    serviceMonitor:
      enabled: true
bash
# Real-time flow observation with Hubble CLI
hubble observe --namespace production

# Filter traffic to specific service
hubble observe --to-service production/api-server

# Monitor DNS requests
hubble observe --protocol dns

# Analyze dropped packets
hubble observe --verdict DROPPED

5.5 Kepler: monitorización del consumo energético

Kepler (Kubernetes Efficient Power Level Exporter) utiliza eBPF para medir el consumo energético de las cargas de trabajo.

yaml
# kepler-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kepler
  namespace: kepler
spec:
  selector:
    matchLabels:
      app: kepler
  template:
    metadata:
      labels:
        app: kepler
    spec:
      serviceAccountName: kepler
      containers:
        - name: kepler
          image: quay.io/sustainable_computing_io/kepler:release-0.7
          securityContext:
            privileged: true
          ports:
            - containerPort: 9102
              name: metrics
          volumeMounts:
            - name: lib-modules
              mountPath: /lib/modules
            - name: tracing
              mountPath: /sys/kernel/tracing
            - name: kernel-src
              mountPath: /usr/src/kernels
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
      volumes:
        - name: lib-modules
          hostPath:
            path: /lib/modules
        - name: tracing
          hostPath:
            path: /sys/kernel/tracing
        - name: kernel-src
          hostPath:
            path: /usr/src/kernels

Ejemplos de métricas de Kepler:

promql
# Energy consumption by namespace (joules)
sum by (namespace) (kepler_container_joules_total)

# Power consumption by Pod (watts)
rate(kepler_container_joules_total[5m]) * 1000

# Top 10 Pods consuming the most energy
topk(10, sum by (pod_name) (rate(kepler_container_joules_total[5m])))

6. Monitorización de costes

6.1 Instalación y configuración de KubeCost / OpenCost

OpenCost es un proyecto de CNCF y el estándar de código abierto para la monitorización de costes de Kubernetes.

bash
# Install OpenCost
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update

helm install opencost opencost/opencost \
  --namespace opencost \
  --create-namespace \
  --set opencost.prometheus.internal.enabled=false \
  --set opencost.prometheus.external.enabled=true \
  --set opencost.prometheus.external.url="http://prometheus-server.monitoring:9090" \
  --set opencost.ui.enabled=true
yaml
# opencost-values.yaml - Detailed configuration
opencost:
  exporter:
    defaultClusterId: "eks-production"

    # AWS cost integration
    aws:
      spotDataRegion: ap-northeast-2
      spotDataBucket: "my-spot-data-bucket"
      athenaProjectID: "my-aws-project"
      athenaRegion: ap-northeast-2
      athenaDatabase: "athenacurcfn_my_cur"
      athenaTable: "my_cur"
      masterPayerARN: "arn:aws:iam::ACCOUNT:role/OpenCostRole"

  prometheus:
    external:
      enabled: true
      url: "http://prometheus-server.monitoring:9090"

  ui:
    enabled: true
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
      hosts:
        - host: opencost.example.com
          paths:
            - path: /
              pathType: Prefix

6.2 Asignación de costes por Namespace/equipo

yaml
# cost-allocation-labels.yaml
# Label standardization for team cost tracking
apiVersion: v1
kind: Namespace
metadata:
  name: team-alpha
  labels:
    cost-center: "engineering"
    team: "alpha"
    environment: "production"
---
# Apply cost labels to Pods
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: team-alpha
spec:
  template:
    metadata:
      labels:
        cost-center: "engineering"
        team: "alpha"
        component: "api"
    spec:
      containers:
        - name: api
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi

Consulta de costes mediante la API de OpenCost:

bash
# Cost by namespace (last 7 days)
curl -s "http://opencost.opencost:9003/allocation/compute?window=7d&aggregate=namespace" | jq '.'

# Cost by team label
curl -s "http://opencost.opencost:9003/allocation/compute?window=7d&aggregate=label:team" | jq '.'

# Daily cost trend
curl -s "http://opencost.opencost:9003/allocation/compute?window=30d&step=1d&aggregate=namespace" | jq '.'

6.3 Optimización de costes de CloudWatch

yaml
# cloudwatch-log-retention.yaml
# Cost reduction through log retention period optimization
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-cloudwatch-config
  namespace: logging
data:
  fluent-bit.conf: |
    [OUTPUT]
        Name                cloudwatch_logs
        Match               *
        region              ap-northeast-2
        log_group_name      /eks/production/application
        log_stream_prefix   ${HOSTNAME}-
        auto_create_group   true
        # Set log retention period (cost optimization)
        log_retention_days  14

        # Batch settings for API call optimization
        log_format          json
        max_batch_size      1048576
        max_batch_put_limit 100
bash
# Batch set CloudWatch Logs retention period
aws logs describe-log-groups --query 'logGroups[*].logGroupName' --output text | \
while read log_group; do
  aws logs put-retention-policy \
    --log-group-name "$log_group" \
    --retention-in-days 14
done

# Clean up unused log groups
aws logs describe-log-groups --query 'logGroups[?storedBytes==`0`].logGroupName' --output text | \
while read log_group; do
  echo "Deleting empty log group: $log_group"
  aws logs delete-log-group --log-group-name "$log_group"
done

6.4 Estrategias de reducción de costes de almacenamiento de logs/métricas

EstrategiaObjetivoAhorro esperado
Filtrado por nivel de logDescartar logs DEBUG/TRACE40-60%
SamplingEventos de alta frecuencia30-50%
CompresiónTodos los logs/métricas60-80%
Almacenamiento por nivelesDatos antiguos70-90%
Optimización del período de retenciónDatos de baja prioridad50-70%

7. Dashboard unificado de observabilidad

7.1 Configuración de dashboard unificado basado en Grafana

yaml
# grafana-datasources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
  namespace: monitoring
data:
  datasources.yaml: |
    apiVersion: 1
    datasources:
      # Prometheus - Metrics
      - name: Prometheus
        type: prometheus
        access: proxy
        url: http://prometheus-server:9090
        isDefault: true
        jsonData:
          httpMethod: POST
          exemplarTraceIdDestinations:
            - name: traceID
              datasourceUid: tempo

      # Loki - Logs
      - name: Loki
        type: loki
        access: proxy
        url: http://loki-gateway:80
        jsonData:
          derivedFields:
            - name: TraceID
              matcherRegex: '"traceId":"([a-f0-9]+)"'
              url: '$${__value.raw}'
              datasourceUid: tempo

      # Tempo - Traces
      - name: Tempo
        type: tempo
        access: proxy
        url: http://tempo-query-frontend:3100
        uid: tempo
        jsonData:
          httpMethod: GET
          tracesToLogs:
            datasourceUid: loki
            tags: ['service.name', 'pod']
          serviceMap:
            datasourceUid: prometheus
          nodeGraph:
            enabled: true
          lokiSearch:
            datasourceUid: loki

7.2 Correlación de Log -> Metrics -> Trace (Exemplars)

Exemplars es una función que vincula los ID de trace con los puntos de datos de métricas.

yaml
# prometheus-exemplars-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      # Enable Exemplars
      enable_features:
        - exemplar-storage

    scrape_configs:
      - job_name: 'application'
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
            regex: 'true'
            action: keep

Exportación de Exemplars desde aplicaciones (ejemplo de Go):

go
// Adding Exemplars to Prometheus histograms
import (
    "github.com/prometheus/client_golang/prometheus"
    "go.opentelemetry.io/otel/trace"
)

var httpDuration = prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds",
        Help:    "HTTP request duration",
        Buckets: prometheus.DefBuckets,
    },
    []string{"method", "path", "status"},
)

func recordMetric(ctx context.Context, method, path, status string, duration float64) {
    span := trace.SpanFromContext(ctx)
    traceID := span.SpanContext().TraceID().String()

    httpDuration.WithLabelValues(method, path, status).(prometheus.ExemplarObserver).
        ObserveWithExemplar(duration, prometheus.Labels{"traceID": traceID})
}

7.3 Estrategia de alertas: prevención de la fatiga de alertas

yaml
# alertmanager-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: alertmanager-config
  namespace: monitoring
data:
  alertmanager.yml: |
    global:
      resolve_timeout: 5m

    # Routing rules
    route:
      receiver: 'default'
      group_by: ['alertname', 'namespace', 'service']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 4h

      routes:
        # Routing by severity
        - match:
            severity: critical
          receiver: 'critical-alerts'
          group_wait: 10s
          repeat_interval: 1h

        - match:
            severity: warning
          receiver: 'warning-alerts'
          group_wait: 1m
          repeat_interval: 4h

        # Suppress alerts outside business hours
        - match:
            severity: info
          receiver: 'info-alerts'
          mute_time_intervals:
            - off-hours

    # Alert inhibition rules
    inhibit_rules:
      # Suppress individual service alerts when cluster is down
      - source_match:
          alertname: ClusterDown
        target_match_re:
          alertname: '.+'
        equal: ['cluster']

      # Suppress Pod alerts when node is down
      - source_match:
          alertname: NodeDown
        target_match_re:
          alertname: 'Pod.*'
        equal: ['node']

    # Define off-hours
    time_intervals:
      - name: off-hours
        time_intervals:
          - weekdays: ['saturday', 'sunday']
          - times:
              - start_time: '00:00'
                end_time: '09:00'
              - start_time: '18:00'
                end_time: '24:00'

    receivers:
      - name: 'default'
        slack_configs:
          - channel: '#alerts-default'

      - name: 'critical-alerts'
        slack_configs:
          - channel: '#alerts-critical'
        pagerduty_configs:
          - service_key: '<pagerduty-key>'

      - name: 'warning-alerts'
        slack_configs:
          - channel: '#alerts-warning'

      - name: 'info-alerts'
        slack_configs:
          - channel: '#alerts-info'

7.4 Monitorización basada en SLO/SLI

yaml
# slo-recording-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: slo-rules
  namespace: monitoring
spec:
  groups:
    - name: slo.rules
      rules:
        # Availability SLI: Successful request ratio
        - record: sli:availability:ratio
          expr: |
            sum(rate(http_requests_total{status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total[5m]))

        # Latency SLI: P99 < 500ms ratio
        - record: sli:latency:ratio
          expr: |
            sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
            /
            sum(rate(http_request_duration_seconds_count[5m]))

        # Error budget burn rate (30-day basis)
        - record: slo:error_budget:remaining
          expr: |
            1 - (
              (1 - sli:availability:ratio)
              /
              (1 - 0.999)  # 99.9% SLO target
            )

    - name: slo.alerts
      rules:
        # Warning when 50% of error budget consumed
        - alert: ErrorBudgetBurnRateHigh
          expr: slo:error_budget:remaining < 0.5
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "More than 50% of error budget consumed"
            description: "Remaining error budget: {{ $value | humanizePercentage }}"

        # Critical when 80% of error budget consumed
        - alert: ErrorBudgetBurnRateCritical
          expr: slo:error_budget:remaining < 0.2
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "More than 80% of error budget consumed"
            description: "Remaining error budget: {{ $value | humanizePercentage }}"

8. Desafíos operativos y soluciones

8.1 Respuesta al aumento descontrolado de costes de almacenamiento de logs/métricas

ProblemaCausaSolución
Pico de coste de logsLogs DEBUG excesivosFiltrado por nivel de log, sampling
Explosión de cardinalidad de métricasUID de Pod, etiquetas de marca de tiempoLimpieza de etiquetas, descarte de métricas
Coste de almacenamiento de tracesSampling al 100 %Aplicar Tail Sampling
Coste de retención a largo plazoMisma retención para todos los datosAlmacenamiento por niveles
yaml
# cost-optimization-config.yaml
# Fluent Bit log filtering
[FILTER]
    Name     grep
    Match    *
    Exclude  log ^.*DEBUG.*$
    Exclude  log ^.*TRACE.*$

# High-frequency log sampling (10%)
[FILTER]
    Name          throttle
    Match         kube.var.log.containers.nginx*
    Rate          10
    Window        60
    Print_Status  true

8.2 Monitorización de nodos de EKS Auto Mode

En EKS Auto Mode, los nodos se gestionan automáticamente, por lo que se requieren estrategias especiales de monitorización.

yaml
# auto-mode-monitoring.yaml
# Managed Node Pool monitoring
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: auto-mode-nodes
  namespace: monitoring
spec:
  selector:
    matchLabels:
      eks.amazonaws.com/managed: "true"
  namespaceSelector:
    any: true
  podMetricsEndpoints:
    - port: metrics
      interval: 30s
---
# Enable CloudWatch Container Insights
# Recommended for use with EKS Auto Mode
apiVersion: v1
kind: ConfigMap
metadata:
  name: cwagent-config
  namespace: amazon-cloudwatch
data:
  cwagentconfig.json: |
    {
      "logs": {
        "metrics_collected": {
          "kubernetes": {
            "cluster_name": "eks-auto-cluster",
            "metrics_collection_interval": 60
          }
        }
      }
    }

8.3 Análisis de correlación de datos entre herramientas

8.4 Mantenimiento del rendimiento del sistema de monitorización a gran escala

yaml
# high-scale-prometheus.yaml
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 2
  retention: 7d
  retentionSize: 100GB

  # Sharding for load distribution
  shards: 3

  resources:
    requests:
      cpu: 2
      memory: 8Gi
    limits:
      cpu: 4
      memory: 16Gi

  # Offload to external storage
  remoteWrite:
    - url: "http://victoriametrics:8428/api/v1/write"
      queueConfig:
        capacity: 10000
        maxShards: 30
        maxSamplesPerSend: 5000

  # Query performance optimization
  queryLogFile: /prometheus/query.log

  additionalArgs:
    # Query concurrency limit
    - name: query.max-concurrency
      value: "20"
    # Query timeout
    - name: query.timeout
      value: "2m"

8.5 Configuración del stack de observabilidad de alta disponibilidad


9. Mejores prácticas y próximos pasos

9.1 Estrategia de adopción por fases

FaseComponentesDuraciónCosteComplejidad operativa
Fase 1 (básica)Basada en CloudWatch1-2 díasBajoBaja
Fase 2 (intermedia)Stack de Grafana1-2 semanasMedioMedia
Fase 3 (avanzada)OpenTelemetry + eBPF2-4 semanasAltoAlta

9.2 Análisis coste-beneficio

Combinación de herramientasCoste mensual estimado (100 nodos)Cobertura funcionalROI
CloudWatch completo$500-1,000BásicaBajo
Prometheus + Loki + Grafana$200-400 (infraestructura)IntermediaMedio
AMP + Tempo + eBPF$300-600AvanzadaAlto
Soluciones comerciales (Datadog, etc.)$2,000-5,000CompletaVaría

9.3 Lista de verificación

Lista de verificación de implementación de observabilidad:

  • [ ] Implementar los tres pilares: logging, métricas, tracing
  • [ ] Configurar la correlación de datos entre los pilares
  • [ ] Establecer políticas de gestión de cardinalidad
  • [ ] Definir y aplicar estrategias de sampling
  • [ ] Desplegar herramientas de monitorización de costes
  • [ ] Optimizar reglas de alertas (prevenir la fatiga de alertas)
  • [ ] Definir SLO/SLI y configurar dashboards
  • [ ] Establecer una estrategia de almacenamiento a largo plazo
  • [ ] Completar la configuración de alta disponibilidad
  • [ ] Documentación y capacitación del equipo

9.4 Documentos y cuestionarios relacionados

Documentos relacionados:

Cuestionario relacionado:


Referencias