Helm Values JSON Schema: Valida tu values.yaml Antes de Romper Producción

Helm Values JSON Schema: Valida tu values.yaml Antes de Romper Producción

Helm es el gestor de paquetes de facto para Kubernetes, y values.yaml es su interfaz principal de configuración. Durante años, sin embargo, esa interfaz ha estado completamente sin validar por defecto — un fichero YAML de forma libre donde cualquier clave puede ser cualquier cosa, donde los errores tipográficos pasan silenciosamente, y donde las implantaciones mal configuradas solo se revelan cuando los pods fallan al arrancar en producción. El fichero values.schema.json cambia esa ecuación por completo. Este artículo explica por qué importa la validación de schemas, cómo implementarla correctamente y cómo integrarla en un pipeline de CI/CD moderno.

El Problema: Fallos Silenciosos en Producción

Imaginad un equipo de plataforma gestionando docenas de releases de Helm en múltiples clústeres. Un desarrollador envía un fichero de valores con replicaCount: "3" en lugar de replicaCount: 3 — una cadena de texto donde se espera un entero. O escribe image.pullPolicy: Allways con una errata. O no incluye una referencia a un secret obligatorio que la aplicación necesita para arrancar. En los tres casos, Helm sin validación de schema renderizará felizmente los templates, producirá manifiestos de Kubernetes y los aplicará al clúster. El fallo aparece después — a veces mucho después — como un CrashLoopBackOff, un ImagePullBackOff o un error sutil en tiempo de ejecución que lleva horas depurar.

No es un escenario hipotético. Es la realidad diaria de los equipos que operan a escala sin validación de valores. La causa raíz es arquitectónica: los templates de Helm usan el motor text/template de Go, que es débilmente tipado y permisivo por diseño. Un template que hace {{ .Values.replicaCount }} renderizará tanto si el valor es un entero como si es una cadena o incluso un booleano. El manifiesto de Kubernetes resultante puede ser inválido, pero ese error solo sale a la superficie cuando el API server de Kubernetes lo rechaza — o peor, lo acepta pero lo interpreta de manera diferente a la prevista.

Las consecuencias se amplifican a escala. Cuando un chart lo utilizan varios equipos, la ausencia de un contrato formal para los valores aceptables obliga a cada consumidor a leer los ficheros de template y los comentarios para entender qué entradas son válidas. No hay especificación legible por máquina. No hay soporte en el IDE. No hay ningún guardarraíl. La única documentación es lo que el autor del chart tuvo a bien escribir en los comentarios de values.yaml — y los comentarios no detienen a un pipeline de CI de desplegar una implantación rota.

Qué es values.schema.json

Desde Helm 3.0.0, publicado en noviembre de 2019, Helm soporta un fichero opcional values.schema.json en la raíz del directorio del chart — al mismo nivel que Chart.yaml y values.yaml. Este fichero es un documento JSON Schema draft-07 que describe formalmente la estructura, los tipos, las restricciones y los campos obligatorios de los valores del chart.

Cuando este fichero está presente, Helm valida automáticamente los valores combinados (los valores por defecto de values.yaml fusionados con cualquier sobreescritura del usuario) contra el schema en varios puntos: durante helm install, helm upgrade, helm template y helm lint. Si la validación falla, Helm se niega a continuar e imprime un mensaje de error legible que identifica exactamente qué valor ha fallado y por qué. Esto transforma una clase de fallos en tiempo de ejecución en fallos en tiempo de construcción — la dirección correcta para cualquier sistema en producción.

Vale la pena destacar la elección específica de JSON Schema draft-07. El draft-07 tiene un soporte muy amplio en herramientas, incluyendo la extensión YAML de Red Hat para VS Code, los IDEs de JetBrains y la mayoría de los validadores de JSON Schema. Introdujo las palabras clave condicionales if/then/else que son especialmente útiles para los Helm charts. Las versiones más recientes (2019-09, 2020-12) ofrecen funcionalidades adicionales, pero tienen menos soporte universal en herramientas, lo que hace que draft-07 sea la elección pragmática para los autores de charts hoy en día.

Estructura del Directorio del Chart

my-app/
├── Chart.yaml
├── values.yaml
├── values.schema.json      ← va aquí
├── charts/
└── templates/
    ├── deployment.yaml
    ├── service.yaml
    ├── ingress.yaml
    └── _helpers.tpl

El fichero de schema se incluye cuando un chart se empaqueta con helm package y se distribuye a través de repositorios de charts. Los consumidores del chart obtienen la validación del schema automáticamente sin ninguna configuración adicional — los guardarraíles se distribuyen con el propio chart.

Cómo Utiliza Helm el Schema

El comportamiento de validación de Helm es sencillo, pero tiene algunos matices que merece la pena entender. Cuando Helm procesa un release, primero combina todas las fuentes de valores en orden de precedencia creciente: los valores por defecto del chart (values.yaml), los valores del chart padre, los ficheros de valores pasados con -f y, por último, los flags --set. El resultado combinado se valida entonces contra el schema en una única operación.

Esto significa que el schema valida los valores efectivos, no cada fuente de forma aislada. Un campo obligatorio que tiene un valor por defecto en values.yaml pasará la validación aunque el usuario no lo especifique, porque el resultado combinado incluye el valor por defecto. Este es el comportamiento correcto: valida lo que se usará realmente durante el renderizado.

La validación ocurre antes del renderizado de los templates. Si la validación del schema falla, Helm termina con un código de estado no cero e imprime todos los errores de validación. La salida de error es estructurada y accionable:

$ helm install my-release ./my-app --set replicaCount=abc

Error: values don't meet the specifications of the schema(s) in the following chart(s):
my-app:
- replicaCount: Invalid type. Expected: integer, given: string

Para helm lint, que se usa típicamente en pipelines de CI sin instalar en un clúster, la validación del schema también se ejecuta. Esto convierte a helm lint en una potente puerta de pre-despliegue cuando hay ficheros de schema presentes.

Ventajas en el IDE: Autocompletado y Validación Inline

Más allá de la propia validación de Helm, values.schema.json desbloquea el soporte en el IDE que mejora significativamente la experiencia del desarrollador al trabajar con ficheros de valores. La extensión YAML de Red Hat para VS Code puede referenciar un fichero JSON Schema para proporcionar autocompletado, comprobación de tipos y resaltado de errores inline en ficheros YAML.

Para habilitarlo, añadid una configuración yaml.schemas a los ajustes del workspace de VS Code o al fichero de ajustes del usuario:

// .vscode/settings.json
{
  "yaml.schemas": {
    "./my-app/values.schema.json": "./my-app/values.yaml"
  }
}

Con esta configuración, al editar values.yaml en VS Code se mostrará autocompletado para las claves definidas, errores inline para los errores de tipo y documentación en hover extraída de los campos description de vuestro schema. Para los equipos de plataforma que mantienen Helm charts internos, esto transforma el chart en una interfaz de configuración autodocumentada y preparada para el IDE — sin ninguna inversión adicional en herramientas.

Los IDEs de JetBrains (IntelliJ IDEA, GoLand, etc.) soportan las asociaciones de JSON Schema a través del panel de ajustes Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings, proporcionando una funcionalidad equivalente para los equipos que usan esas herramientas.

Construyendo el Schema: Guía Práctica

Construyamos un ejemplo completo y realista. Partimos de un values.yaml típico para un chart de aplicación web:

# values.yaml
replicaCount: 2

image:
  repository: myorg/my-app
  tag: "1.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  hostname: ""
  tls: false

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

config:
  logLevel: info
  databaseUrl: ""

nodeSelector: {}
tolerations: []
affinity: {}

Ahora el values.schema.json completo que valida esta estructura:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "my-app Helm Chart Values",
  "description": "Valores de configuración para el Helm chart my-app",
  "type": "object",
  "additionalProperties": false,
  "required": ["image", "service"],
  "$defs": {
    "resourceQuantity": {
      "type": "string",
      "pattern": "^[0-9]+(\\.[0-9]+)?(m|Ki|Mi|Gi|Ti|Pi|Ei|k|M|G|T|P|E)?$",
      "description": "Una cantidad de recurso de Kubernetes (p. ej.: 100m, 128Mi, 1Gi)"
    }
  },
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 0,
      "maximum": 50,
      "default": 2,
      "description": "Número de réplicas del pod. Estableced a 0 para escalar a cero."
    },
    "image": {
      "type": "object",
      "additionalProperties": false,
      "required": ["repository", "tag"],
      "description": "Configuración de la imagen del contenedor",
      "properties": {
        "repository": {
          "type": "string",
          "minLength": 1,
          "description": "Repositorio de la imagen del contenedor"
        },
        "tag": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9._-]+$",
          "minLength": 1,
          "description": "Tag de la imagen. Evitad usar 'latest' en producción."
        },
        "pullPolicy": {
          "type": "string",
          "enum": ["Always", "IfNotPresent", "Never"],
          "default": "IfNotPresent",
          "description": "imagePullPolicy de Kubernetes"
        }
      }
    },
    "service": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "port"],
      "properties": {
        "type": {
          "type": "string",
          "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"],
          "description": "Tipo de Service de Kubernetes"
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535,
          "description": "Puerto del Service"
        }
      }
    },
    "ingress": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": false
        },
        "hostname": {
          "type": "string"
        },
        "tls": {
          "type": "boolean",
          "default": false
        }
      },
      "if": {
        "properties": {
          "enabled": { "const": true }
        },
        "required": ["enabled"]
      },
      "then": {
        "required": ["hostname"],
        "properties": {
          "hostname": {
            "type": "string",
            "minLength": 1,
            "pattern": "^[a-zA-Z0-9][a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
            "description": "Nombre de host para el Ingress. Obligatorio cuando ingress está habilitado."
          }
        }
      }
    },
    "resources": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "requests": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "cpu": { "$ref": "#/$defs/resourceQuantity" },
            "memory": { "$ref": "#/$defs/resourceQuantity" }
          }
        },
        "limits": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "cpu": { "$ref": "#/$defs/resourceQuantity" },
            "memory": { "$ref": "#/$defs/resourceQuantity" }
          }
        }
      }
    },
    "autoscaling": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": { "type": "boolean", "default": false },
        "minReplicas": { "type": "integer", "minimum": 1 },
        "maxReplicas": { "type": "integer", "minimum": 1 },
        "targetCPUUtilizationPercentage": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      }
    },
    "config": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "logLevel": {
          "type": "string",
          "enum": ["debug", "info", "warn", "error"],
          "default": "info"
        },
        "databaseUrl": {
          "type": ["string", "null"],
          "description": "URL de conexión a la base de datos."
        }
      }
    },
    "nodeSelector": {
      "type": "object",
      "description": "Selectores de nodo de Kubernetes"
    },
    "tolerations": {
      "type": "array",
      "description": "Tolerations de Kubernetes"
    },
    "affinity": {
      "type": "object",
      "description": "Reglas de afinidad de Kubernetes"
    }
  }
}

Patrones Clave del Schema Explicados

additionalProperties: false

Este es, posiblemente, el patrón más importante de un Helm schema. Sin él, las claves desconocidas pasan la validación silenciosamente — lo que anula gran parte del propósito. Con "additionalProperties": false, cualquier clave no listada en properties provoca un error de validación. Esto detecta erratas como repicaCount en lugar de replicaCount, que de otro modo usarían silenciosamente el valor por defecto y dejarían al desarrollador preguntándose por qué su sobreescritura no tuvo efecto.

Aplicadlo en cada nivel de objeto anidado, no solo en la raíz. Una errata dentro de image: o resources: es tan peligrosa como una en el nivel superior.

$defs para Definiciones Reutilizables

La palabra clave $defs (llamada definitions en versiones anteriores del draft, aunque draft-07 soporta ambas) proporciona un espacio de nombres para fragmentos de schema reutilizables. En el ejemplo anterior, resourceQuantity se define una vez y se referencia mediante $ref tanto en requests como en limits. Esto evita la duplicación y garantiza una lógica de validación consistente en campos relacionados.

Para charts más grandes, $defs se vuelve imprescindible. Los patrones habituales incluyen schemas reutilizables para configuraciones de imagen, requisitos de recursos, configuraciones de probe y mapas de variables de entorno.

Validación Condicional con if/then/else

La construcción if/then/else en JSON Schema draft-07 es especialmente potente para los Helm charts, donde muchos valores son condicionales según un flag de activación de funcionalidad. El ejemplo del ingress lo demuestra: cuando ingress.enabled es true, el campo hostname se vuelve obligatorio y debe coincidir con un patrón de nombre de host válido. Cuando el ingress está deshabilitado, el hostname puede estar vacío u omitirse por completo.

Este patrón puede extenderse para escenarios más complejos. Por ejemplo, para que cuando autoscaling.enabled sea true, el replicaCount independiente no deba establecerse (ya que el HPA controla el número de réplicas):

{
  "if": {
    "properties": {
      "autoscaling": {
        "properties": {
          "enabled": { "const": true }
        },
        "required": ["enabled"]
      }
    }
  },
  "then": {
    "properties": {
      "replicaCount": {
        "description": "replicaCount se ignora cuando autoscaling está habilitado"
      }
    }
  }
}

Validación de Patrones para Tags de Imagen

El campo del tag de imagen es una fuente habitual de problemas en producción. Los equipos despliegan accidentalmente con latest, que es no determinista y hace que los rollbacks sean poco fiables. Una restricción de patrón puede forzar la versión semántica o, como mínimo, prohibir el tag latest en los charts de producción:

"tag": {
  "type": "string",
  "not": {
    "enum": ["latest", ""]
  },
  "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+",
  "description": "Se requiere un tag de versión semántica. 'latest' no está permitido."
}

Esto fuerza que los tags de imagen comiencen con un número de versión semántica, rechazando inmediatamente latest, cadenas vacías o nombres de rama arbitrarios que producirían implantaciones no reproducibles.

Enum para Vocabularios Controlados

Los campos con un conjunto fijo de valores válidos — tipos de Service de Kubernetes, políticas de pull de imágenes, niveles de log — deben usar enum. Esto es más preciso que un patrón y produce mensajes de error más claros. También permite que el autocompletado del IDE muestre exactamente las opciones válidas como una lista de selección, en lugar de requerir que el desarrollador recuerde o busque los valores aceptables.

Integración en CI/CD

GitHub Actions

El punto de integración más directo es helm lint, que ejecuta la validación del schema como parte de sus comprobaciones. Un workflow mínimo de GitHub Actions que valida un chart en cada pull request es el siguiente:

# .github/workflows/helm-lint.yaml
name: Helm Lint

on:
  pull_request:
    paths:
      - 'charts/**'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Helm
        uses: azure/setup-helm@v4
        with:
          version: '3.14.0'

      - name: Lint chart con valores por defecto
        run: helm lint charts/my-app

      - name: Lint chart con valores de staging
        run: helm lint charts/my-app -f charts/my-app/ci/staging-values.yaml

      - name: Lint chart con valores de producción
        run: helm lint charts/my-app -f charts/my-app/ci/production-values.yaml

      - name: Validar renderizado de templates
        run: |
          helm template my-app charts/my-app \
            -f charts/my-app/ci/production-values.yaml \
            --debug > /dev/null

La convención del directorio ci/ (ficheros de valores específicos para pruebas en CI) es un patrón de la herramienta chart-testing y funciona bien para validar múltiples combinaciones de valores realistas, no solo los valores por defecto.

Para los equipos que usan la herramienta CLI ct (chart-testing) del proyecto Helm, la validación del schema se incluye automáticamente en el comando ct lint, que también gestiona las comprobaciones de versiones del chart y el linting de YAML:

      - name: Chart Testing lint
        uses: helm/chart-testing-action@v2.6.1

      - name: Ejecutar chart-testing lint
        run: ct lint --target-branch ${{ github.event.repository.default_branch }}

Hooks de Pre-commit

Para el desarrollo local, los hooks de pre-commit detectan los problemas antes de que el código se suba al repositorio. El framework pre-commit hace esto sencillo:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gruntwork-io/pre-commit
    rev: v0.1.23
    hooks:
      - id: helmlint

  - repo: local
    hooks:
      - id: helm-schema-validate
        name: Helm Schema Validation
        language: script
        entry: scripts/validate-helm-schemas.sh
        files: ^charts/.*values.*\.yaml$
#!/usr/bin/env bash
# scripts/validate-helm-schemas.sh
set -euo pipefail

for chart_dir in charts/*/; do
  if [[ -f "${chart_dir}/values.schema.json" ]]; then
    echo "Linting ${chart_dir}..."
    helm lint "${chart_dir}" --strict
  fi
done

Integración con ArgoCD y Flux

Tanto ArgoCD como Flux (Helm Controller) invocan helm template internamente al reconciliar los Helm releases. Dado que helm template ejecuta la validación del schema cuando hay un fichero de schema presente, cualquier valor inválido en un manifiesto HelmRelease de Flux o en una Application de ArgoCD hará que la reconciliación falle con un mensaje de error claro — visible en los logs del controlador y que aparece como un estado degradado del recurso. No se requiere configuración adicional; la validación del schema es automática.

Generando Schemas a Partir de Charts Existentes

Para charts que ya tienen un values.yaml bien estructurado, escribir un schema desde cero es laborioso pero no se parte de cero. Varias herramientas pueden generar un schema borrador que luego se refina:

  • helm-values-schema-json — un plugin de Helm (helm plugin install https://github.com/losisin/helm-values-schema-json) que inspecciona values.yaml y genera un schema borrador con tipos inferidos. Ejecutadlo con helm schema-gen values.yaml.
  • Herramientas online de generación de JSON Schema — pegad vuestros valores como JSON (convertid primero el YAML a JSON) y obtendréis un schema borrador.
  • Manualmente desde cero — para charts nuevos, escribir el schema junto con el fichero de valores desde el principio es el enfoque más preciso y no requiere herramientas adicionales.

Los schemas generados siempre son puntos de partida. Infieren tipos a partir de los valores existentes, pero no pueden conocer las restricciones previstas, los enums, los patrones, los campos obligatorios en casos condicionales ni additionalProperties: false en los niveles anidados. La revisión y refinamiento manual es siempre necesaria.

Errores Comunes y Cómo Evitarlos

ErrorSíntomaSolución
Sin additionalProperties: falseLas erratas en nombres de claves pasan la validación silenciosamenteAñadidlo en cada nivel de objeto, incluyendo los anidados
Schema solo en la raízLas erratas en niveles anidados no se detectanAplicad additionalProperties: false recursivamente
Sin valores por defecto en el schemaEl IDE muestra campos como obligatorios cuando son opcionalesAñadid default a todos los campos opcionales
Patrones demasiado estrictos que bloquean valores válidosImplantaciones legítimas fallan la validación del schemaProbad los patrones contra vuestro espacio de valores real antes de publicar
Usar definitions en lugar de $defsFunciona en la mayoría de herramientas pero es terminología de draft-2019-09+Usad $defs para cumplimiento con draft-07; ambos funcionan en la práctica
Schema no comprometido en el repositorio del chartLos consumidores no obtienen validación al obtener el chart del repositorioIncluir siempre values.schema.json junto al chart
Validar valores de subcharts desde el schema del chart padreErrores de schema para valores de subcharts que el padre no poseeNo intentéis validar valores de subcharts en el schema del padre; cada chart es dueño de su propio schema

El Problema de los Valores Nulos

Un problema sutil pero común: en YAML, una clave no establecida sin valor (clave:) se resuelve como null, no como una cadena vacía o un cero. Si vuestro schema define un campo como "type": "string", un valor nulo fallará la validación. Para gestionar campos opcionales que los usuarios podrían dejar en blanco, utilizad una unión de tipos:

"databaseUrl": {
  "type": ["string", "null"],
  "description": "URL de conexión a la base de datos. Dejad null para usar el valor por defecto."
}

Como alternativa, aseguraos de que los valores por defecto en values.yaml usen cadenas vacías ("") en lugar de claves sin valor, y documentad esa convención para los consumidores del chart.

Desviación del Schema

A medida que los charts evolucionan, se añaden nuevos valores a values.yaml sin las correspondientes actualizaciones en values.schema.json. Con el tiempo, el schema queda obsoleto y proporciona una cobertura parcial. La solución es procedimental: tratad las actualizaciones del schema como parte de la definición de hecho para cualquier PR que modifique los valores. La revisión de código debe incluir la comprobación de que los valores nuevos o modificados tienen entradas correspondientes en el schema.

Un patrón útil es incluir la validación del schema como paso obligatorio en la checklist de revisión del chart:

  • ¿Se ha añadido el nuevo campo a values.yaml con un valor por defecto sensato?
  • ¿Se ha añadido la entrada correspondiente en values.schema.json con el tipo correcto?
  • ¿Se ha ejecutado helm lint localmente con los valores de CI para verificar que pasa la validación?

Preguntas Frecuentes

¿Valida values.schema.json los valores de los subcharts?

No. Cada chart en una relación de dependencia valida únicamente sus propios valores contra su propio schema. Si el chart A depende del chart B, y el chart B tiene un schema, el schema del chart B valida los valores bajo la clave b: en el values.yaml del chart A — pero solo cuando se procesa en el contexto del chart B. El schema del chart A no debe intentar describir la estructura de valores del chart B. Esto es por diseño: mantiene el acoplamiento flexible entre charts y permite que los subcharts evolucionen sus schemas de forma independiente.

¿Puedo usar JSON Schema draft-2020-12 en lugar de draft-07?

Técnicamente, Helm no fuerza estrictamente qué versión del draft usáis — utiliza la librería Go github.com/xeipuuv/gojsonschema, que soporta draft-04 hasta draft-07. El uso de palabras clave de drafts más nuevos que no están soportadas por esta librería puede provocar que se ignoren silenciosamente en lugar de lanzar un error. Para el soporte en el IDE, draft-07 tiene la compatibilidad más amplia. Si necesitáis características de drafts más nuevos (como unevaluatedProperties de 2020-12), probad cuidadosamente para confirmar que las hace cumplir el validador de Helm y no se omiten silenciosamente.

¿Cómo gestiono valores que difieren entre entornos sin conflictos de schema?

El schema debe describir todos los valores válidos en todos los entornos. Usad enum para enumerar todos los valores válidos para un campo, y usad if/then/else para restricciones que solo aplican en ciertas configuraciones. El schema es un contrato de lo que acepta el chart, no una política de lo que debe usar un entorno específico. Las políticas específicas de entorno (como “producción debe usar un mínimo de 3 réplicas”) se aplican mejor en un nivel superior — mediante controladores de admisión como OPA Gatekeeper o Kyverno — en lugar de en el schema del chart.

¿Se ejecuta la validación del schema cuando se usa helm template para dry runs?

Sí. helm template ejecuta la validación del schema antes de renderizar los templates. Esto lo hace útil como paso de validación en pipelines de CI incluso sin un clúster activo: helm template release-name ./chart -f values-override.yaml fallará con errores de schema si los valores son inválidos, y emitirá los manifiestos renderizados si son válidos. Pasar la salida a kubectl apply --dry-run=client -f - añade una capa adicional de validación de la API de Kubernetes para una comprobación offline exhaustiva.

¿Debo añadir values.schema.json a los charts que no mantengo (charts upstream)?

Para los charts upstream que consumís pero no mantenéis (como los charts de Bitnami, ingress-nginx, cert-manager), el enfoque recomendado es mantener un fichero JSON Schema separado en vuestro propio repositorio GitOps que valide vuestros ficheros de sobreescritura de valores específicos. Herramientas como jsonschema (Python) o ajv (Node.js) pueden validar un fichero de valores YAML/JSON contra un schema en CI sin que Helm esté involucrado. Esto os proporciona validación del schema para vuestras sobreescrituras específicas del entorno sin necesidad de modificar las fuentes de charts upstream.

Conclusión

values.schema.json es una de las funcionalidades más infrautilizadas de Helm. Está disponible desde Helm 3.0.0, no requiere dependencias externas, y transforma los errores de configuración de fallos silenciosos en tiempo de ejecución en errores explícitos en tiempo de construcción.

El coste de adoptarlo es bajo: un fichero JSON por chart, mantenido junto al código del chart. El beneficio es sustancial: validación de tipos, campos obligatorios forzados, vocabularios controlados, autocompletado en el IDE, y una puerta de CI que rechaza valores incorrectos antes de que lleguen a un clúster.

Para los equipos de plataforma que mantienen charts internos consumidos por múltiples equipos, el schema actúa como documentación ejecutable — una especificación formal que no puede desincronizarse con el comportamiento real porque es el comportamiento real. Los comentarios en values.yaml se ignoran durante el despliegue; el schema de JSON no.

Si vuestros charts de Helm no tienen todavía un values.schema.json, el siguiente paso es claro: elegid el chart más crítico, escribid un schema que cubra sus campos más usados con additionalProperties: false en cada nivel, añadid helm lint a vuestro pipeline de CI, y ejecutad el lint contra el conjunto de valores de CI que usáis hoy. Los primeros errores que encontréis serán reales — problemas existentes que llevan tiempo latentes y que el schema acaba de hacer visibles.

Prometheus 3.0 y OpenTelemetry: Soporte Nativo OTLP Explicado

Prometheus 3.0 y OpenTelemetry: Soporte Nativo OTLP Explicado

Siete años es mucho tiempo en el mundo de la observabilidad. Desde que Prometheus 2.0 apareció en 2017, el ecosistema ha sido transformado por la adopción cloud-native, el auge del distributed tracing y la consolidación de OpenTelemetry como estándar de facto para la instrumentación. Prometheus 3.0, publicado en noviembre de 2024, es la respuesta del proyecto a esa transformación — y su cambio más significativo es la capacidad nativa de ingerir métricas OpenTelemetry directamente, sin ningún componente intermediario de por medio.

Este artículo entra en detalle en lo que Prometheus 3.0 cambia realmente para ingenieros de plataforma y arquitectos cloud que gestionan — o planean gestionar — cargas de trabajo instrumentadas con OTel junto a stacks de monitorización basados en Prometheus. Cubriremos el endpoint nativo de ingesta OTLP, el soporte de nombres de métricas UTF-8, Remote Write 2.0, las consideraciones de migración y los patrones arquitectónicos que siguen teniendo sentido incluso cuando OTLP nativo está disponible.

Qué Cambió en Prometheus 3.0: Lo Relevante para OTel

Prometheus 3.0 incluye un conjunto sustancial de cambios. No todos son igualmente relevantes para la integración con OpenTelemetry, así que veamos qué mueve realmente la aguja para los usuarios de OTel antes de entrar en detalle en cada área.

Ingesta Nativa OTLP

La funcionalidad estrella: Prometheus 3.0 incorpora un receptor OTLP integrado que expone un endpoint HTTP que acepta métricas en formato OpenTelemetry Protocol. Las aplicaciones instrumentadas con cualquier SDK OTel pueden ahora enviar métricas directamente a Prometheus sin pasar por un OpenTelemetry Collector. No es un sidecar, no es un plugin, no es un adaptador externo — es un endpoint de primera clase en el propio binario de Prometheus.

Nombres de Métricas UTF-8

Prometheus restringía históricamente los nombres de métricas a [a-zA-Z_:][a-zA-Z0-9_:]*. OpenTelemetry utiliza puntos y barras en los nombres de métricas por convención — http.server.request.duration es un nombre canónico de métrica OTel. Prometheus 3.0 elimina esta restricción y admite caracteres UTF-8 arbitrarios en nombres de métricas y etiquetas, que es el cambio de compatibilidad más importante para la interoperabilidad con OTel.

Remote Write 2.0

Remote Write 2.0 sustituye el protocolo original con una codificación más eficiente basada en protobuf, añade soporte nativo de histogramas en el formato de wire y reduce significativamente el consumo de ancho de banda en despliegues a gran escala. Si estáis federando métricas hacia Thanos, Mimir o Cortex, esto tiene impacto en el coste operativo.

Nueva Interfaz Web

La interfaz web de Prometheus ha sido completamente reescrita. La nueva UI usa React, soporta exploración de metadata de métricas y proporciona una experiencia de construcción de queries notablemente mejorada. Es una mejora de calidad de vida más que un cambio arquitectónico, pero reduce la dependencia de herramientas externas como Grafana para la investigación ad-hoc.

Resumen de Cambios Incompatibles

Prometheus 3.0 elimina varias funcionalidades que estaban deprecadas en 2.x. Las más significativas operacionalmente son: eliminación de la ruta de flag deprecated --web.enable-admin-api, eliminación de ciertas opciones de formato de almacenamiento legacy, cambios en los timeouts de scrape por defecto, y validación más estricta de configuraciones que anteriormente se aceptaban en silencio. Más adelante en este artículo incluimos un checklist de migración.

El Receptor OTLP: Cómo Funciona y Qué Acepta

El receptor OTLP en Prometheus 3.0 está implementado como una funcionalidad opcional que debe activarse explícitamente. Una vez habilitado, expone un endpoint HTTP en /api/v1/otlp/v1/metrics que acepta payloads OTLP ExportMetricsServiceRequest codificados en protobuf — el mismo formato de wire que utiliza el exportador OTLP del OpenTelemetry Collector.

Qué Acepta (y Qué No)

Esto es crítico entenderlo antes de diseñar vuestra arquitectura en torno a la ingesta OTLP nativa: el soporte OTLP de Prometheus 3.0 es exclusivamente para métricas. No acepta trazas ni logs. OTLP es un protocolo unificado que cubre las tres señales, pero Prometheus es un almacén de métricas — el receptor gestiona únicamente la parte de métricas de la especificación OTLP.

Tipos de métricas soportados en el receptor OTLP:

  • Gauge — se mapea directamente a un Gauge de Prometheus
  • Sum (monotónico) — se mapea a un Counter de Prometheus
  • Sum (no monotónico) — se mapea a un Gauge de Prometheus
  • Histogram (explicit bucket) — se mapea a un Histogram de Prometheus
  • ExponentialHistogram — se mapea a Prometheus Native Histograms (funcionalidad de 3.0)
  • Summary — se mapea a un Summary de Prometheus

Los resource attributes del payload OTLP — cosas como service.name, k8s.pod.name, cloud.region — se convierten en labels de Prometheus. Esta conversión es configurable, y por defecto Prometheus aplica una estrategia de promoción que convierte los resource attributes más comunes a labels descartando los que crearían cardinalidad extremadamente alta.

Activar el Receptor OTLP

Activar la ingesta OTLP nativa requiere dos cosas: un feature flag y un bloque de configuración en prometheus.yml.

Arrancad el binario de Prometheus con el feature flag:

prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --enable-feature=otlp-write-receiver

Luego añadid la configuración del receptor OTLP a vuestro prometheus.yml:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

otlp:
  # Promover estos resource attributes de OTLP a labels de Prometheus
  promote_resource_attributes:
    - service.name
    - service.namespace
    - service.instance.id
    - k8s.namespace.name
    - k8s.pod.name
    - k8s.node.name
    - cloud.region
    - deployment.environment

Con esta configuración, Prometheus escuchará en el puerto 9090 (por defecto) y aceptará métricas OTLP en http://<prometheus-host>:9090/api/v1/otlp/v1/metrics.

Estrategia de Promoción de Resource Attributes

La lista promote_resource_attributes merece una reflexión cuidadosa. OTLP transporta un contexto de nivel de recurso muy rico — cada payload de métricas incluye un objeto ResourceMetrics con atributos que describen el origen: nombre del servicio, versión, entorno, pod de Kubernetes, nodo, cluster, detalles del proveedor cloud y más. Las labels de Prometheus son pares clave-valor planos en cada serie temporal. Promover demasiados resource attributes dispara la cardinalidad; promover demasiado pocos pierde contexto importante.

Una lista pragmática de partida para despliegues en Kubernetes:

otlp:
  promote_resource_attributes:
    - service.name          # Crítico: identifica el servicio
    - service.namespace     # Agrupación lógica
    - deployment.environment  # prod/staging/dev
    - k8s.namespace.name    # Namespace de Kubernetes
    - k8s.pod.name          # Cardinalidad a nivel de pod — valorad omitirlo a gran escala
    - k8s.node.name         # Útil para correlación con infraestructura

Evitad promover k8s.pod.name indiscriminadamente a escala — en un cluster con miles de pods de vida corta, esto genera una presión de cardinalidad significativa. Prefered service.name y service.namespace para la mayoría de los casos de uso de alertas, reservando las labels a nivel de pod para dashboards de depuración.

Nombres de Métricas UTF-8: El Cambio que Realmente Importa

Para apreciar por qué el soporte de nombres de métricas UTF-8 importa tanto, necesitáis entender la fricción que elimina. Las convenciones semánticas de OpenTelemetry definen nombres de métricas usando puntos como separadores de namespace. La métrica canónica de duración de servidor HTTP es http.server.request.duration. La duración canónica de consulta a base de datos es db.client.operation.duration. Estos nombres están estandarizados entre lenguajes y frameworks — vuestro servicio en Go, vuestro servicio en Java y vuestro servicio en Python emiten el mismo nombre de métrica cuando se instrumentan con OTel.

Prometheus 2.x no podía almacenar estos nombres. Los puntos son caracteres ilegales en la nomenclatura de métricas de Prometheus. Todos los puentes OTel-a-Prometheus — el exportador Prometheus del OpenTelemetry Collector, las capas de compatibilidad de prom-client, el antiguo exportador prometheusremotewrite — tenían que traducir estos nombres, típicamente reemplazando los puntos por guiones bajos: http_server_request_duration.

Esta traducción es imprecisa y crea múltiples problemas:

  • Colisiones de nombres: http.server.request_duration y http.server.request.duration se convierten ambas en http_server_request_duration
  • Rotura de dashboards: Los dashboards de Grafana construidos contra las convenciones semánticas de OTel no funcionan contra métricas Prometheus traducidas sin modificación
  • Correlación entre señales: Los atributos de trazas usan notación de puntos; cuando los nombres de métricas difieren, las herramientas de correlación automatizada pierden el hilo
  • Presión hacia el vendor lock-in: Los equipos acaban con convenciones de nomenclatura separadas para “métricas Prometheus” y “métricas OTel” y mantienen ambas

Prometheus 3.0 con soporte UTF-8 almacena http.server.request.duration de forma nativa. Sin traducción. Sin colisiones. El nombre de métrica con el que instrumentáis es el nombre de métrica que consultáis.

Activar Nombres de Métricas UTF-8

Los nombres de métricas UTF-8 requieren el feature flag utf8-names:

prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --enable-feature=utf8-names \
  --enable-feature=otlp-write-receiver

Una vez habilitado, las queries PromQL deben usar nombres de métrica entrecomillados cuando el nombre contiene caracteres fuera del conjunto de caracteres legacy:

# Nombre de métrica legacy — sin comillas funciona bien
http_server_requests_total

# Nombre de métrica OTel con puntos — requiere entrecomillado en PromQL
{"__name__"="http.server.request.duration"}

# O usando la nueva sintaxis PromQL de Prometheus 3.0
http.server.request.duration{service_name="api-gateway"}

El parser de PromQL en Prometheus 3.0 se ha actualizado para gestionar nombres de métricas entrecomillados como construcción de primera clase. El motor PromQL de Grafana también se ha actualizado para gestionar esta sintaxis — verificad vuestra versión de Grafana (10.3+ tiene soporte completo) antes de desplegar.

Del SDK OTel Directamente a Prometheus 3.0: Sin Collector

Para equipos que solo necesitan introducir métricas de aplicación en Prometheus, la ingesta OTLP nativa permite una arquitectura dramáticamente más simple. Así es como se ve con diferentes SDKs OTel.

Go (OpenTelemetry SDK)

package main

import (
    "context"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func initMetrics(ctx context.Context) (*metric.MeterProvider, error) {
    res, err := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("mi-api"),
            semconv.ServiceNamespace("plataforma"),
            semconv.DeploymentEnvironment("produccion"),
        ),
    )
    if err != nil {
        return nil, err
    }

    // Apuntar directamente al endpoint OTLP de Prometheus 3.0
    exporter, err := otlpmetrichttp.New(ctx,
        otlpmetrichttp.WithEndpoint("prometheus:9090"),
        otlpmetrichttp.WithURLPath("/api/v1/otlp/v1/metrics"),
        otlpmetrichttp.WithInsecure(), // Usar WithTLSClientConfig en producción
    )
    if err != nil {
        return nil, err
    }

    provider := metric.NewMeterProvider(
        metric.WithResource(res),
        metric.WithReader(
            metric.NewPeriodicReader(exporter,
                metric.WithInterval(30*time.Second),
            ),
        ),
    )

    otel.SetMeterProvider(provider)
    return provider, nil
}

Python (OpenTelemetry SDK)

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_NAMESPACE

resource = Resource.create({
    SERVICE_NAME: "mi-api",
    SERVICE_NAMESPACE: "plataforma",
    "deployment.environment": "produccion",
})

exporter = OTLPMetricExporter(
    endpoint="http://prometheus:9090/api/v1/otlp/v1/metrics",
)

reader = PeriodicExportingMetricReader(
    exporter,
    export_interval_millis=30_000,
)

provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)

# Usar el meter
meter = metrics.get_meter("mi-api")
request_counter = meter.create_counter(
    name="http.server.request.count",
    description="Total de peticiones HTTP al servidor",
    unit="1",
)
request_duration = meter.create_histogram(
    name="http.server.request.duration",
    description="Duración de peticiones HTTP al servidor",
    unit="s",
)

Java (OpenTelemetry SDK con Spring Boot)

# application.properties (Spring Boot con auto-instrumentación OTel)
otel.service.name=mi-api
otel.resource.attributes=service.namespace=plataforma,deployment.environment=produccion

# Configurar el exportador OTLP para enviar directamente a Prometheus
otel.metrics.exporter=otlp
otel.exporter.otlp.metrics.endpoint=http://prometheus:9090/api/v1/otlp/v1/metrics
otel.exporter.otlp.metrics.protocol=http/protobuf

# Intervalo de exportación
otel.metric.export.interval=30000

Con Spring Boot y el agente Java de OTel, no se necesitan cambios de código más allá de la configuración — el agente instrumenta vuestro servidor HTTP, clientes de base de datos y sistemas de mensajería automáticamente y envía métricas usando los nombres definidos en las convenciones semánticas de OTel.

OTel Collector hacia Prometheus 3.0: Cuándo Necesitáis el Intermediario

La ingesta OTLP nativa es atractiva, pero el OpenTelemetry Collector sigue siendo relevante para un conjunto significativo de casos de uso. Entender cuándo es apropiado cada patrón es la decisión arquitectónica central que afrontaréis al adoptar Prometheus 3.0 en un entorno OTel.

Patrón 1: OTel Collector como Gateway de Fan-Out

Cuando necesitáis enviar métricas a múltiples backends simultáneamente — Prometheus para alertas, un almacén de larga duración como Thanos para análisis histórico, y una plataforma de observabilidad comercial para correlación full-stack — el OTel Collector gestiona el fan-out eficientemente. Las aplicaciones envían una sola vez al Collector; el Collector distribuye a todos los backends.

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1000
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  # Enviar a Prometheus 3.0 vía OTLP
  otlphttp/prometheus:
    endpoint: http://prometheus:9090/api/v1/otlp
    tls:
      insecure: true

  # Fan-out a Thanos vía remote_write
  prometheusremotewrite/thanos:
    endpoint: http://thanos-receive:10908/api/v1/receive
    resource_to_telemetry_conversion:
      enabled: true

  # Fan-out a backend comercial
  otlp/datadog:
    endpoint: https://otel-intake.datadoghq.com
    headers:
      DD-API-KEY: "${DD_API_KEY}"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp/prometheus, prometheusremotewrite/thanos, otlp/datadog]

Patrón 2: Collector para Transformación de Métricas

Los procesadores transform y metricstransform del OTel Collector permiten remodelar las métricas antes de que lleguen a Prometheus: renombrar labels, añadir atributos estáticos, filtrar series de alta cardinalidad, agregar métricas para reducir el coste de almacenamiento, o aplicar conversiones de unidades. Estas operaciones no están disponibles en el receptor OTLP nativo de Prometheus.

processors:
  transform/metrics:
    metric_statements:
      - context: metric
        statements:
          # Descartar métricas internas de depuración
          - delete_matching_keys(attributes, "internal.*")
          # Normalizar valores del label de entorno
          - set(attributes["deployment.environment"], "prod")
            where attributes["deployment.environment"] == "production"

  filter/drop_debug:
    metrics:
      exclude:
        match_type: regexp
        metric_names:
          - ".*\.debug\..*"
          - "runtime\.go\.internal\..*"

  metricstransform:
    transforms:
      # Renombrar una métrica para ajustarse a vuestra convención de nomenclatura Prometheus
      - include: http.server.request.duration
        action: update
        new_name: http_server_request_duration_seconds

Patrón 3: Collector para Trazas y Logs (Siempre Necesario)

Si vuestra arquitectura incluye trazas y logs junto a métricas — y en 2025 casi con toda seguridad lo hace — necesitáis un OTel Collector independientemente de lo que hagáis con las métricas. Prometheus no acepta trazas ni logs. Jaeger, Tempo y Loki tienen sus propios protocolos de ingesta. El Collector es la capa de enrutamiento universal para los tres pilares de la observabilidad.

En esta arquitectura, suele ser más sencillo enrutar las tres señales a través del Collector y dejar que envíe las métricas a Prometheus vía OTLP o remote_write, en lugar de dividir las métricas para que vayan directamente y todo lo demás a través del Collector.

Cuándo Usar OTLP Nativo vs. OTel Collector: Marco de Decisión

EscenarioOTLP NativoOTel Collector
Un solo backend de métricas (solo Prometheus)PreferidoInnecesariamente complejo
Múltiples backends de métricasInsuficienteNecesario
Trazas + Logs en scopeNo aplicableNecesario
Transformación/filtrado de métricas necesarioNo soportadoNecesario
Despliegue Kubernetes simplePreferidoComplejidad adicional
Entornos air-gapped / restringidosPreferido (menos componentes)Valorar con cuidado
Mezcla de targets OTel + Prometheus legacyFunciona junto al scrapingPuede normalizar nomenclatura
Alto volumen, necesidad de bufferingControl limitadoPreferido

La recomendación pragmática para la mayoría de los equipos de ingeniería de plataforma: si ya estáis ejecutando el OTel Collector (y deberíais si las trazas están en scope), continuad enrutando las métricas a través de él. Utilizad el exportador otlphttp del Collector para enviar a Prometheus 3.0. Reservad el patrón de SDK directo a Prometheus para servicios simples donde el Collector sería la única razón para añadir complejidad.

Remote Write 2.0: Qué Cambia para Vuestros Setups Existentes

Remote Write 2.0 es una actualización significativa del protocolo con implicaciones operativas reales para equipos que usan Prometheus como fuente de métricas para sistemas de almacenamiento a largo plazo como Thanos, Mimir, VictoriaMetrics o Cortex.

Cambios Clave en el Protocolo

  • Codificación protobuf con compresión snappy — sustituye al formato anterior basado en texto. Típicamente supone una reducción del 50-70% en el tamaño del wire para grandes lotes de métricas
  • Soporte nativo de histogramas en el formato de wire — los histogramas exponenciales pueden ahora enviarse sin convertirlos a histogramas clásicos, preservando la resolución completa
  • Reenvío de metadata — la información de tipo y unidad de métricas se transmite ahora junto a las muestras, habilitando un procesamiento downstream mejor
  • Timestamps de creación — el timestamp en que se creó un counter se reenvía, habilitando cálculos de tasa más precisos tras reinicios

Configurar Remote Write 2.0

# prometheus.yml
remote_write:
  - url: "http://thanos-receive:10908/api/v1/receive"
    # Remote Write 2.0 se negocia automáticamente con receptores compatibles
    # Forzar RW2.0 explícitamente si es necesario:
    send_native_histograms: true
    metadata_config:
      send: true
      send_interval: 1m
    queue_config:
      capacity: 10000
      max_shards: 200
      max_samples_per_send: 2000
      batch_send_deadline: 5s

Remote Write 2.0 usa negociación de protocolo por content — Prometheus 3.0 intentará RW2.0 primero y volverá a RW1.0 si el receptor no lo soporta. Esto significa que las actualizaciones son generalmente retrocompatibles. Verificad que vuestro sistema receptor (Thanos Receive 0.35+, Mimir 2.12+, VictoriaMetrics 1.98+) soporta RW2.0 antes de esperar los beneficios.

Migración desde Prometheus 2.x: Checklist de Cambios Incompatibles

La actualización de Prometheus 2.x a 3.0 requiere atención a varios cambios incompatibles. Este checklist cubre los más significativos operacionalmente para equipos que ejecutan despliegues Prometheus en producción.

Cambios de Configuración

  • Eliminado: cambio del valor por defecto de query.lookback-delta — el valor por defecto cambió de 5 minutos a coincidir con el scrape interval. Las queries que dependían del valor por defecto de 5m pueden devolver resultados diferentes. Auditad las reglas de alerta que usen instant queries sobre counters.
  • Eliminadas: opciones remote_write deprecadas — la semántica de remote_write[].queue_config.capacity cambió. Revisad y actualizad las configuraciones de cola.
  • Eliminado: flag storage.tsdb.allow-overlapping-blocks — el manejo de bloques solapados ahora es automático. Eliminad este flag de vuestros scripts de arranque.
  • Cambio en protocolos de scrape por defecto — Prometheus 3.0 usa por defecto el formato OpenMetrics para el scraping cuando los targets lo soportan. Esto habilita histogramas nativos pero puede revelar diferencias de parsing. Probad el comportamiento si dependíais del comportamiento anterior.
  • Cambios en Agent mode — si usáis Prometheus en modo Agent, revisad las opciones de configuración actualizadas para la gestión del WAL.

Cambios en PromQL

  • Parsing más estricto — algunas expresiones PromQL anteriormente aceptadas pero técnicamente inválidas ahora fallan. Pasad vuestras reglas de alerta por promtool check rules contra un binario de Prometheus 3.0 antes del cutover.
  • Funciones de histograma nativo — nuevas funciones como histogram_fraction() y histogram_quantile() tienen comportamiento actualizado con histogramas nativos. Las queries existentes de dashboards que usen histogram_quantile() sobre histogramas clásicos siguen funcionando sin cambios.

Compatibilidad de Almacenamiento

Prometheus 3.0 puede leer datos TSDB existentes de 2.x. La ruta de actualización no requiere migración de datos. Sin embargo, Prometheus 2.x no puede leer bloques de datos escritos por 3.0 (el downgrade no está soportado sin pérdida de datos una vez que se han producido escrituras). Tomad un snapshot antes de actualizar si necesitáis capacidad de rollback:

# Tomar un snapshot de TSDB antes de actualizar
curl -X POST http://prometheus:9090/api/v1/admin/tsdb/snapshot

# Verificar que el snapshot existe
ls /prometheus/snapshots/

Pasos de Validación Previos a la Actualización

# 1. Validar la configuración contra Prometheus 3.0
docker run --rm -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus:v3.0.0 \
  promtool check config /etc/prometheus/prometheus.yml

# 2. Validar las reglas de alerta
docker run --rm -v $(pwd)/rules:/etc/prometheus/rules \
  prom/prometheus:v3.0.0 \
  promtool check rules /etc/prometheus/rules/*.yml

# 3. Ejecutar en paralelo (modo shadow) antes del cutover completo
# Desplegar Prometheus 3.0 junto a 2.x, scrapeando los mismos targets
# Comparar resultados de queries entre versiones usando promtool query range

Despliegue Práctico en Kubernetes

A continuación se muestra un despliegue Kubernetes de Prometheus 3.0 preparado para producción con ingesta OTLP habilitada, adecuado como punto de partida para equipos de ingeniería de plataforma.

ConfigMap de Prometheus 3.0

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      evaluation_interval: 15s
      external_labels:
        cluster: produccion
        region: eu-west-1

    otlp:
      promote_resource_attributes:
        - service.name
        - service.namespace
        - deployment.environment
        - k8s.namespace.name
        - k8s.pod.name

    rule_files:
      - /etc/prometheus/rules/*.yml

    scrape_configs:
      - job_name: kubernetes-pods
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
            action: keep
            regex: "true"
          - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
            action: replace
            target_label: __metrics_path__
            regex: (.+)

    remote_write:
      - url: http://thanos-receive.monitoring.svc.cluster.local:10908/api/v1/receive
        send_native_histograms: true
        metadata_config:
          send: true

Fijaos en el uso de region: eu-west-1 en external_labels. Para equipos en Europa, esta etiqueta es útil no solo para correlación operativa sino también para trazabilidad de datos: saber en qué región reside vuestra telemetría facilita la demostración de cumplimiento del Reglamento General de Protección de Datos (RGPD). En organizaciones bajo supervisión de la Agencia Española de Protección de Datos (AEPD) o cualquier otra autoridad de control europea, poder demostrar que los datos de monitorización no salen de la UE es un requisito real. Etiquetas de región en todas vuestras métricas os dan esa trazabilidad sin esfuerzo adicional.

Deployment de Prometheus 3.0

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      containers:
        - name: prometheus
          image: prom/prometheus:v3.0.0
          args:
            - --config.file=/etc/prometheus/prometheus.yml
            - --storage.tsdb.path=/prometheus/data
            - --storage.tsdb.retention.time=15d
            - --web.enable-lifecycle
            - --web.enable-admin-api
            - --enable-feature=otlp-write-receiver
            - --enable-feature=utf8-names
            - --enable-feature=native-histograms
          ports:
            - name: http
              containerPort: 9090
              protocol: TCP
          volumeMounts:
            - name: config
              mountPath: /etc/prometheus
            - name: data
              mountPath: /prometheus/data
          resources:
            requests:
              cpu: 500m
              memory: 2Gi
            limits:
              cpu: 2000m
              memory: 8Gi
          livenessProbe:
            httpGet:
              path: /-/healthy
              port: http
            initialDelaySeconds: 30
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /-/ready
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: config
          configMap:
            name: prometheus-config
        - name: data
          persistentVolumeClaim:
            claimName: prometheus-data
---
apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: monitoring
spec:
  selector:
    app: prometheus
  ports:
    - name: http
      port: 9090
      targetPort: http
  type: ClusterIP

Configurar las Aplicaciones para Enviar OTLP

Con este despliegue, cualquier aplicación en el cluster puede enviar métricas OTLP configurando las siguientes variables de entorno (funciona con cualquier SDK OTel que soporte OTLP HTTP):

env:
  - name: OTEL_SERVICE_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.labels['app']
  - name: OTEL_SERVICE_NAMESPACE
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace
  - name: OTEL_METRICS_EXPORTER
    value: "otlp"
  - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
    value: "http://prometheus.monitoring.svc.cluster.local:9090/api/v1/otlp/v1/metrics"
  - name: OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
    value: "http/protobuf"
  - name: OTEL_METRIC_EXPORT_INTERVAL
    value: "30000"
  - name: OTEL_RESOURCE_ATTRIBUTES
    value: "deployment.environment=produccion,k8s.namespace.name=$(NAMESPACE)"

Este enfoque funciona especialmente bien en entornos que usan el OTel Operator para Kubernetes, donde el CRD Instrumentation puede inyectar estas variables de entorno automáticamente en los pods basándose en selectores de namespace o de etiquetas de pod — instrumentación zero-touch con almacenamiento nativo en Prometheus.

Prometheus 3.0 y la Soberanía de Datos en Europa

Un aspecto que suele pasarse por alto en los análisis técnicos de Prometheus 3.0, pero que resulta especialmente relevante para empresas europeas, es cómo la arquitectura de observabilidad interactúa con los requisitos del RGPD.

Cuando las métricas contienen datos que pueden considerarse personales — identificadores de sesión en labels, IPs de usuarios en atributos de recursos, nombres de usuario en trazas correlacionadas — la elección del backend de almacenamiento y su ubicación geográfica tiene implicaciones legales directas. En España y en la UE en general, el RGPD exige que los datos personales no se transfieran a países terceros sin las salvaguardas adecuadas.

Prometheus, al ser autoalojado, ofrece control total sobre dónde residen los datos. Para equipos que trabajan con proveedores cloud europeos — Hetzner, OVHcloud, IONOS, Exoscale — o que gestionan infraestructura propia como un homelab o un cluster on-premises, Prometheus 3.0 con almacenamiento local garantiza que los datos de observabilidad no salen de la jurisdicción europea.

Algunas recomendaciones prácticas para entornos con requisitos de cumplimiento:

  • Usad external_labels con region: eu-west-X en todas las instancias de Prometheus para tener trazabilidad geográfica completa
  • Evitad promover resource attributes que contengan datos personales (IPs de usuario, identificadores de sesión) mediante la lista promote_resource_attributes — filtradlos en el OTel Collector antes de que lleguen a Prometheus
  • Si usáis Thanos o Mimir para almacenamiento a largo plazo, verificad que los buckets de object storage (S3-compatible) están en regiones europeas
  • Documentad vuestros flujos de datos de telemetría en el registro de actividades de tratamiento que exige el RGPD

Preguntas Frecuentes

¿Puedo usar la ingesta OTLP de Prometheus 3.0 para trazas y logs?

No. El receptor OTLP de Prometheus 3.0 gestiona únicamente métricas. Prometheus es un almacén de métricas — no tiene modelo de datos para trazas ni logs. Para trazas necesitáis un backend como Jaeger o Grafana Tempo. Para logs necesitáis Loki, Elasticsearch o un sistema similar. El OTel Collector es la capa de enrutamiento apropiada cuando necesitáis enviar las tres señales a sus respectivos backends desde un único endpoint de push en el lado de la aplicación.

¿El chart Helm de kube-prometheus-stack soporta Prometheus 3.0?

Sí, con matices. El chart kube-prometheus-stack actualizó su imagen de Prometheus a 3.0 a partir de la versión 66.0.0 del chart. Sin embargo, algunas recording rules y alerting rules incluidas pueden necesitar ajuste por los cambios en PromQL y las diferencias de comportamiento por defecto. El propio Prometheus Operator (versión 0.78+) se ha actualizado para soportar las nuevas opciones de configuración incluyendo el bloque de configuración otlp. Si gestionáis Prometheus vía Operator, configuraréis los ajustes OTLP a través de spec.additionalArgs del CRD Prometheus y un recurso PrometheusConfiguration personalizado.

¿Qué ocurre con los nombres de métricas de Prometheus 2.x existentes cuando activo el soporte UTF-8?

Las métricas existentes con nombres basados en guiones bajos siguen funcionando exactamente igual que antes. Activar el soporte UTF-8 es puramente aditivo — permite el almacenamiento y consulta de nombres de métricas que contienen puntos y otros caracteres UTF-8, pero no renombra ni modifica las métricas existentes. Vuestros dashboards, reglas de alerta y recording rules existentes siguen funcionando sin modificación. Solo las métricas ingeridas vía OTLP (o expuestas por exportadores que usen convenciones de nomenclatura OTel) usarán nombres separados por puntos.

¿Cómo afecta la ingesta OTLP nativa al modelo pull de Prometheus?

Coexiste con él. Prometheus 3.0 sigue scrapeando targets vía el modelo pull en el mismo puerto 9090. El endpoint OTLP es una ruta de ingesta adicional, no un reemplazo del scraping. Podéis tener una instancia de Prometheus scrapeando simultáneamente pods de Kubernetes vía service discovery y recibiendo métricas OTLP push de aplicaciones — ambas se almacenan en el mismo TSDB y son consultables vía la misma interfaz PromQL. Este enfoque híbrido es habitual durante las migraciones, donde los componentes legacy se scrapean y los nuevos servicios instrumentados con OTel envían vía OTLP.

¿Es el receptor OTLP de Prometheus 3.0 adecuado para cargas de trabajo de producción de alto volumen?

Para volúmenes moderados, sí. El receptor OTLP es síncrono — la petición HTTP se completa solo después de que las muestras se escriben en el WAL. Bajo tasas de ingesta muy altas (cientos de miles de muestras por segundo), esto puede crear back-pressure que afecta a la latencia de las aplicaciones. El OTel Collector gestiona esto mejor mediante buffering interno, colas de reintentos y procesamiento por lotes. Para escenarios de alto volumen, el patrón recomendado es: las aplicaciones envían al OTel Collector (que confirma inmediatamente y hace buffer), el Collector envía a Prometheus vía OTLP o remote_write en lotes optimizados. Para la mayoría de las cargas de trabajo en Kubernetes — decenas o centenares de servicios con cardinalidad típica de métricas — el receptor OTLP nativo rinde bien sin intermediario.

¿Funciona esto con el Prometheus que viene incluido en kube-prometheus-stack?

Sí. Si ya tenéis kube-prometheus-stack desplegado y queréis añadir ingesta OTLP, la forma más limpia es actualizar el chart a la versión 66.0.0 o posterior y añadir los additionalArgs correspondientes al CRD Prometheus:

# values.yaml para kube-prometheus-stack
prometheus:
  prometheusSpec:
    additionalArgs:
      - name: enable-feature
        value: otlp-write-receiver
      - name: enable-feature
        value: utf8-names
    additionalScrapeConfigs: []

Combinad esto con la configuración otlp.promote_resource_attributes en un PrometheusConfiguration adicional o directamente en el additionalConfig del spec.

¿Cómo migrar de la configuración basada en OpenTelemetry Collector Prometheus exporter a OTLP nativo?

La migración es incremental. Podéis ejecutar ambas configuraciones en paralelo durante la transición:

  1. Habilitad el receptor OTLP en Prometheus 3.0
  2. Configurad vuestras aplicaciones para enviar a ambos endpoints simultáneamente (Collector y Prometheus directo)
  3. Verificad que las métricas aparecen correctamente en Prometheus con los nombres UTF-8 originales
  4. Comparad dashboards y alertas entre ambas fuentes
  5. Una vez validado, eliminad el exportador Prometheus del Collector y simplificad la configuración

La ventaja de esta migración gradual es que podéis verificar que las queries PromQL existentes funcionan con los nuevos nombres (o adaptarlas si fuera necesario) antes de eliminar el path anterior.

Construyendo un Framework de Migración para Kubernetes: Lecciones de Ingress-NGINX

Construyendo un Framework de Migración para Kubernetes: Lecciones de Ingress-NGINX

Si gestionas infraestructura Kubernetes en producción, el anuncio de la depreciación de Ingress-NGINX no fue una sorpresa, sino un recordatorio crudo de la realidad del ecosistema: la obsolescencia es una constante. Lo que hoy es un componente crítico, mañana puede ser un legacy que amenaza la estabilidad y seguridad de tu plataforma.

La depreciación de Ingress-NGINX, un controlador que ha sido la columna vertebral del tráfico entrante para miles de clusters, no es un evento aislado. Es un patrón que se repetirá con otros componentes a medida que Kubernetes evoluciona. La pregunta crítica para los equipos de plataforma no es «¿cómo migramos este componente?», sino «¿cómo construimos un marco de trabajo reutilizable para gestionar estas migraciones de forma sistemática, segura y con mínima interrupción?».

En este artículo, no solo analizaremos las lecciones específicas de Ingress-NGINX, sino que las abstraeremos en un framework de migración para Kubernetes que tu equipo puede aplicar a cualquier cambio mayor: desde la transición a Gateway API, hasta la migración de CNIs, cambios de versión de control-plane, o la adopción de nuevos CRDs. Este framework está diseñado para ingenieros senior, arquitectos y SREs que necesitan un enfoque práctico y metódico para el cambio en entornos empresariales.

Por qué necesitas un framework, no solo un plan ad-hoc

Migrar un componente como el controlador de ingress en un cluster de producción es una operación de alto riesgo. Impacta el tráfico de red, la seguridad (TLS), la observabilidad y, en última instancia, la disponibilidad de tus aplicaciones. Un enfoque ad-hoc, basado en scripts sueltos y manuales, introduce puntos de fallo, hace imposible la validación consistente y dificulta el rollback en caso de problemas.

Un framework proporciona:

  • Consistencia: El mismo proceso para diferentes migraciones.
  • Visibilidad: Estado claro en cada fase.
  • Automatización: Reducción de errores humanos.
  • Seguridad: Puntos de validación y compuertas (gates) definidos.
  • Reversibilidad: Un camino claro para deshacer los cambios.

La documentación oficial de Kubernetes sobre gestión de despliegues enfatiza estrategias como el despliegue gradual y el rollback, principios que son la base de nuestro framework.

Fase 1: Evaluación y Análisis de Impacto

Antes de escribir una línea de código o un manifiesto YAML, debes entender completamente el alcance. Esta fase responde a la pregunta: «¿Qué estamos tocando y qué puede romperse?».

1.1. Inventario de Recursos y Dependencias

Para una migración de ingress, no solo son los objetos Ingress. Debes catalogar:

  • Recursos Directos: Todos los objetos Ingress en todos los namespaces, con sus anotaciones específicas (nginx.ingress.kubernetes.io/*).
  • Configuración Global: ConfigMaps que contienen la configuración del controlador (e.g., nginx-configuration).
  • Dependencias de Infraestructura: Services de tipo LoadBalancer, certificados TLS (manejados por cert-manager o similares), políticas de red (NetworkPolicies).
  • Integraciones de Observabilidad: Dashboards de Grafana, alertas de Prometheus, logs estructurados que dependen de etiquetas específicas de NGINX.

Un comando inicial como kubectl get ingress -A es el punto de partida, pero se necesita un análisis más profundo para capturar anotaciones personalizadas y configuraciones complejas.

1.2. Análisis de Brechas (Gap Analysis)

Compara las capacidades del componente antiguo con las del nuevo. En el caso de migrar de Ingress-NGINX a Gateway API (usando, por ejemplo, un controlador como Gateway API con Envoy o NGINX Kubernetes Gateway), identifica:

  • Funcionalidades equivalentes: ¿El nuevo componente soporta rewrite rules, auth annotations, canary deployments de la misma forma?
  • Paradigmas diferentes: Gateway API separa los roles (Infraestructura vs. Aplicación) mediante GatewayClass, Gateway, y HTTPRoute. Esto es un cambio arquitectónico, no solo una sustitución.
  • Características no soportadas: Algunas anotaciones muy específicas de NGINX pueden no tener un equivalente directo y requerir una solución alternativa.

Este análisis te permitirá categorizar los recursos: aquellos con una migración trivial, aquellos que requieren reconfiguración, y aquellos que pueden ser un bloqueador (show-stopper).

Fase 2: Diseño y Estrategia de Despliegue

Con el alcance definido, diseñamos el cómo. La clave aquí es la coexistencia pacífica y el despliegue incremental.

2.1. Patrón de Despliegue Dual (Dual-Homing)

El patrón más seguro para migrar tráfico de red es ejecutar ambos controladores (el antiguo y el nuevo) en paralelo. Esto permite:

  • Migración por servicio/dominio: Enrutar el tráfico de un host específico (app1.example.com) al nuevo controlador, mientras el resto sigue por el antiguo.
  • Pruebas en producción con tráfico real controlado: Usar un subconjunto de usuarios (por ejemplo, mediante un header HTTP) para enviar tráfico al nuevo ingress.
  • Rollback instantáneo: Basta con reconfigurar el DNS o el balanceador de capa 4 para volver al controlador antiguo.

Como señala el framework de Google para esta migración, esta fase requiere una cuidadosa planificación de la IP del balanceador de carga y la gestión de DNS.

2.2. Automatización de la Conversión de Manifiestos

Convertir manualmente cientos de objetos Ingress es inviable y propenso a errores. Debes crear o utilizar herramientas de conversión. El objetivo no es una conversión 1:1 perfecta (el análisis de brechas ya habrá identificado diferencias), sino generar un artefacto base que luego será revisado y ajustado.

Un script simple en Python o Go puede leer los objetos Ingress existentes y generar los correspondientes HTTPRoute y referencias a Gateway. Este es un ejemplo conceptual de la lógica:

# Pseudocódigo para ilustrar el flujo de conversión
ingress_resources = kubectl_get_all_ingresses()
for ingress in ingress_resources:
    http_route = HTTPRoute()
    http_route.metadata.name = ingress.metadata.name
    http_route.metadata.namespace = ingress.metadata.namespace
    
    for rule in ingress.spec.rules:
        host_rule = HTTPRouteRule()
        host_rule.hostnames = [rule.host]
        # Mapear paths y backends...
        # **Aquí se aplicarían las reglas del Gap Analysis**
        http_route.spec.rules.append(host_rule)
    
    # Gestionar TLS: mapeo a Gateway, no a Secret en HTTPRoute
    if ingress.spec.tls:
        # Nota: En Gateway API, TLS se configura en el recurso Gateway.
        # Esto requiere una estrategia de agrupación por host/Secret.
        pass

La salida de esta automatización debe ser revisada por un ingeniero para los casos complejos antes de aplicarse al cluster.

Fase 3: Implementación y Validación Gradual

Esta es la fase de ejecución, donde el framework impone compuertas (gates) de validación entre cada paso.

3.1. Fase 1: Despliegue del Nuevo Controlador en Modo Pasivo

Instala el nuevo controlador (ej: un operator de Gateway API) pero no lo expongas al tráfico de producción. Configúralo para que escuche en un Service interno o en una IP de LoadBalancer diferente. Valida:

  • Los pods del controlador están Ready.
  • Los CRDs necesarios están instalados.
  • Puedes crear recursos de prueba (HTTPRoute) y el controlador los sincroniza (revisa los logs o el estado del recurso).

3.2. Fase 2: Migración de un Servicio No Crítico (Piloto)

Selecciona un servicio de bajo riesgo, preferiblemente interno o con poco tráfico. Aplica los manifiestos convertidos y configura el DNS o el balanceador de capa 4 para enviar el tráfico de ese host específico al nuevo controlador.

Validaciones post-migración (Checklist):

  • Conectividad básica: ¿Responde el servicio con un código 2xx/3xx?
  • Funcionalidad específica: ¿Funcionan las reglas de rewrite, redirecciones, autenticación?
  • TLS: ¿Los certificados se presentan correctamente? ¿La cadena de confianza es válida?
  • Observabilidad: ¿Aparecen logs en el nuevo formato? ¿Las métricas (peticiones/segundo, latencia) se exponen a Prometheus?
  • Prueba de rollback: Vuelve a enrutar el tráfico al controlador antiguo y confirma que todo sigue funcionando.

3.3. Fase 3: Expansión y Automatización del Pipeline

Tras el éxito del piloto, migra grupos de servicios siguiendo un criterio (por namespace, por equipo dueño, por criticidad). Idealmente, integra este proceso en tu pipeline de GitOps:

  1. El ingeniero modifica la ruta (Kustomize overlay, Helm value) para apuntar al nuevo Gateway.
  2. El cambio se fusiona (merge) y es aplicado por el operador GitOps (ArgoCD, Flux).
  3. Un job de validación post-despliegue ejecuta smoke tests contra el servicio.
  4. Una alerta monitoriza el error rate del servicio migrado; si supera un umbral, puede disparar un rollback automático.

Fase 4: Rollback y Lecciones Aprendidas

Un framework de migración robusto trata el rollback no como un fracaso, sino como una característica esencial del proceso.

4.1. Estrategias de Rollback Granular

Debes poder revertir a diferentes niveles:

  • Rollback por servicio: Reconfigurar el enrutamiento de un host específico al controlador antiguo.
  • Rollback masivo: Tener un script o playbook listo para reenrutar todo el tráfico al controlador antiguo en minutos. Esto implica mantener el controlador antiguo operativo y con capacidad suficiente hasta el final de la ventana de migración.
  • Rollback de configuración: Si el problema está en un manifiesto HTTPRoute erróneo, debes poder revertir el commit en Git y dejar que GitOps sincronice el estado anterior.

4.2. Documentación y Conclusión

Una vez completada la migración (o incluso después de cada fase), documenta:

  • Problemas encontrados y su solución: Este es el conocimiento tribal más valioso.
  • Métricas de éxito: Tiempo total, tasa de error durante la migración, recursos consumidos.
  • Mejoras para el framework: ¿Faltó un punto de validación? ¿Podría automatizarse más un paso?

Esta documentación no es un reporte para la gerencia; es la entrada para refinar el framework y prepararlo para la próxima migración inevitable.

Conclusión: Más allá de Ingress-NGINX

La depreciación de Ingress-NGINX es un caso de estudio perfecto, pero el framework descrito aquí es aplicable a cualquier cambio disruptivo en tu plataforma Kubernetes: la migración a un nuevo CNI, el cambio de una versión mayor del control-plane con APIs obsoletas, la adopción de un nuevo modelo de service mesh, o la consolidación de múltiples clusters.

La constante en Kubernetes es el cambio. La ventaja competitiva de un equipo de plataforma no reside en conocer el componente de moda, sino en poseer un proceso sistemático, automatizado y seguro para adoptar, operar y eventualmente retirar cualquier componente. Invertir en construir y refinar este framework de migración es invertir en la resiliencia y velocidad a largo plazo de tu plataforma. La próxima depreciación importante no será una crisis, sino solo otra ejecución en el playbook de tu equipo.

Problemas con los Ingress en Kubernetes: limitaciones reales y por qué Gateway API es el futuro

Problemas con los Ingress en Kubernetes: limitaciones reales y por qué Gateway API es el futuro

Introducción

Los Ingress han sido, desde las primeras versiones de Kubernetes, la forma más habitual de exponer aplicaciones al exterior. Aunque su diseño inicial era sencillo y elegante, el éxito de Kubernetes y la creciente complejidad de los casos de uso han convertido al Ingress en una pieza problemática: limitado, inconsistente entre vendors y difícil de gobernar en entornos empresariales.

En este artículo analizamos por qué los Ingress se han convertido en una fuente constante de fricción, cómo han influido los distintos Ingress Controllers en esta situación y por qué cada vez más organizaciones están considerando alternativas como Gateway API.

Qué son los Ingress y por qué fueron diseñados así

El ecosistema de Ingress gira en torno a dos recursos principales:

🏷️ IngressClass

Define qué controlador gestionará los Ingress asociados. Su alcance es global al clúster, por lo que suele ser administrado por el equipo de plataforma.

🌐 Ingress

Es el recurso que los desarrolladores usan para exponer un servicio. Permite definir rutas, dominios, certificados TLS y poco más.

Su especificación es mínima por diseño, lo que permitió una adopción rápida, pero también sembró las bases de los problemas actuales.

El problema: un estándar demasiado simple para necesidades complejas

A medida que Kubernetes se convirtió en estándar empresarial, los usuarios querían replicar configuraciones avanzadas de proxies tradicionales: re-escrituras, timeouts, cabeceras personalizadas, CORS, etc.
Pero Ingress no daba soporte nativo a todo esto.

Los vendors reaccionaron… y ahí nació el caos.

Anotaciones vs CRDs: dos caminos incompatibles

Los distintos Ingress Controllers han tomado caminos muy diferentes para añadir capacidades avanzadas:

📝 Anotaciones (NGINX, HAProxy…)

Ventajas:

  • Flexibles y fáciles de usar
  • Directamente en el recurso Ingress

Desventajas:

  • Cientos de anotaciones propietarias
  • Documentación fragmentada
  • Configuraciones no portables entre vendors

📦 CRDs personalizados (Traefik, Kong…)

Ventajas:

  • Más estructurado y potente
  • Mejor validación y control

Desventajas:

  • Añade nuevos objetos no estándar
  • Requiere instalarlos y gestionarlos
  • Menor interoperabilidad

¿Resultado?
Infraestructuras profundamente acopladas a un vendor, lo que complica migraciones, auditorías y automatización.

La complejidad para los equipos de desarrollo

El diseño de Ingress implica dos responsabilidades muy diferenciadas:

  • Plataforma: define IngressClass
  • Aplicación: define Ingress

Pero la realidad es que el desarrollador acaba tomando decisiones que deberían ser responsabilidad del área de plataforma:

  • Certificados
  • Políticas de seguridad
  • Reglas de reescritura
  • CORS
  • Timeouts
  • Prácticas corporativas de naming

Esto provoca:

  • Configuraciones inconsistentes
  • Cuellos de botella en revisiones
  • Dependencia constante entre equipos
  • Falta de estandarización efectiva

En empresas grandes, donde la seguridad y la gobernanza son críticas, esto es especialmente problemático.

NGINX Ingress: la descomisión que reavivó el debate

La reciente descontinuación del NGINX Ingress Controller ha puesto en evidencia lo frágil del ecosistema:

  • Miles de clústeres dependen de él
  • Múltiples proyectos usan sus anotaciones
  • Migrar implica reescribir configuraciones enteras

Esto ha reactivado la conversación sobre la necesidad de un estándar real… y ahí aparece Gateway API.

Gateway API: una alternativa prometedora (pero no perfecta)

Gateway API nace para resolver muchas de las limitaciones de Ingress:

  • Separación clara entre responsabilidades (infraestructura vs aplicación)
  • Extensibilidad estandarizada
  • Más tipos de rutas (HTTPRoute, TCPRoute…)
  • Mayor expresividad sin depender de anotaciones propietarias

Pero también trae desafíos:

  • Requiere adopción gradual
  • No todos los vendors implementan lo mismo
  • La migración no es trivial

Aun así, se perfila como el futuro de la gestión de tráfico en Kubernetes.

Conclusión

Los Ingress han sido fundamentales para el éxito de Kubernetes, pero su propia simplicidad los ha llevado a convertirse en un cuello de botella. La falta de interoperabilidad, las diferencias entre vendors y la compleja gobernanza en entornos empresariales hacen evidente que es momento de adoptar modelos más maduros.

Gateway API no es perfecto, pero avanza en la dirección correcta.
Las organizaciones que quieran estabilidad a futuro deberían empezar a planificar su transición.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Ingress vs Routes en OpenShift: Diferencias, Traducción y Cuándo Usar Cada Uno

Ingress vs Routes en OpenShift: Diferencias, Traducción y Cuándo Usar Cada Uno

Introducción: OpenShift, la plataforma de Kubernetes empresarial de Red Hat, tiene su propia forma de exponer servicios a clientes externos mediante Routes. Comprender la relación entre Ingress de Kubernetes y Routes de OpenShift es crucial para arquitectos y SREs que gestionan cargas de trabajo en esta plataforma. En Kubernetes estándar, normalmente se usa un recurso Ingress junto con un controlador de Ingress para enrutar el tráfico externo a los servicios. Sin embargo, OpenShift introdujo el concepto de Route y un router integrado (basado en HAProxy) mucho antes de que existiera Ingress en Kubernetes. Hoy OpenShift admite tanto Routes como objetos Ingress estándar, lo que a veces puede generar confusión sobre cuándo usar cada uno y cómo se relacionan. Este artículo explora cómo OpenShift maneja los recursos Ingress, cómo se traducen en Routes, las limitaciones de este enfoque y ofrece orientación sobre cuándo utilizar Ingress frente a Routes.

Routes de OpenShift: Capacidades Avanzadas del Router HAProxy Las Routes son recursos específicos de OpenShift diseñados para exponer servicios externamente. Son atendidas por el router de OpenShift, un proxy basado en HAProxy que se ejecuta dentro del clúster. Las Routes admiten funciones avanzadas como:

  • Balanceo de carga con backends ponderados para dividir el tráfico.
  • Sesiones persistentes (afinidad de sesión).
  • Varios modos de terminación TLS (edge, passthrough, re‑encrypt).
  • Subdominios comodín.
  • Certificados personalizados y SNI.
  • Enrutamiento basado en rutas. Debido a que las Routes son nativas de OpenShift, el router comprende estas funciones de forma nativa y puede configurarse en consecuencia. Esta integración estrecha permite capacidades de enrutamiento potentes y flexibles adaptadas a entornos de OpenShift.

OpenShift admite los recursos Ingress estándar de Kubernetes. Cuando se crea un Ingress, la plataforma lo traduce internamente en una Route equivalente. Cuando creas un Ingress, OpenShift lo traduce automáticamente en una Route equivalente tras bastidores. Esto significa que puedes usar manifiestos Ingress estándar y OpenShift se encargará de exponer tus servicios externamente creando Routes en consecuencia. Esta traducción automática simplifica la migración y admite casos de uso básicos sin necesidad de manifestos específicos de Route. El comportamiento se puede ajustar con anotaciones compatibles con el router de OpenShift (por ejemplo, route.openshift.io/termination, haproxy.router.openshift.io/balance, etc.).

Sin embargo, usar Ingress para generar Routes tiene limitaciones clave: muchas funciones avanzadas del router de OpenShift, como los backends ponderados y las sesiones persistentes (sticky sessions), requieren anotaciones específicas de Route (route.openshift.io) y no son compatibles a través de la especificación estándar de Ingress. Otros modos de TLS, como passthrough y re‑encrypt, también requieren anotaciones específicas de Route. Los Ingress sin host no crean una Route; las Routes requieren un nombre de host. Los hosts comodín solo son compatibles mediante Routes. Además, algunas anotaciones de Route no tienen equivalentes en Ingress. El soporte de protocolos también es más limitado en Ingress, que solo admite HTTP/HTTPS, mientras que las Routes pueden manejar protocolos no HTTP con TLS de paso.

La elección entre Ingress y Routes depende de tus requisitos: utiliza Ingress si buscas portabilidad entre plataformas Kubernetes, ya tienes manifiestos de Ingress existentes o tu aplicación usa solo enrutamiento HTTP/HTTPS básico. Utiliza Routes si necesitas funciones avanzadas como backends ponderados, sesiones persistentes o múltiples modos de terminación TLS; si tu despliegue es específico de OpenShift y puede aprovechar funciones nativas de la plataforma; si necesitas exponer protocolos no HTTP o usar hosts comodín o anotaciones personalizadas no compatibles con Ingress. Muchas veces, los equipos usan una combinación: Ingress para portabilidad y Routes para funciones avanzadas o específicas de OpenShift.

Conclusión: En OpenShift, los recursos Ingress de Kubernetes se convierten automáticamente en Routes, lo que permite exponer servicios externos con poco esfuerzo. Esto permite a los usuarios aprovechar manifiestos de Kubernetes existentes y mantener la portabilidad. Sin embargo, para escenarios de enrutamiento avanzados y para aprovechar plenamente las potentes funciones del router de OpenShift, se recomienda utilizar Routes directamente. Ambas opciones coexisten sin problemas en OpenShift, lo que te permite elegir la herramienta adecuada según los requisitos de tu aplicación.

Frequently Asked Questions

¿OpenShift crea automáticamente una Route por cada Ingress de Kubernetes?

Sí, OpenShift traduce automáticamente un recurso Ingress de Kubernetes estándar en una Route equivalente. Sin embargo, esto solo ocurre si el Ingress especifica un host. Los Ingress sin host no generarán una Route en la plataforma.

¿Cuándo debo usar Routes nativas en lugar de Ingress en OpenShift?

Debes usar Routes directamente cuando necesites: terminación TLS en modo passthrough o re-encrypt, balanceo de carga con backends ponderados, sesiones persistentes (sticky sessions), hosts comodín, o exponer protocolos no HTTP (como TCP). Estas son funciones avanzadas del router HAProxy de OpenShift.

¿Puedo usar anotaciones de Route en un objeto Ingress?

Algunas anotaciones del router de OpenShift (prefijadas con haproxy.router.openshift.io/ o route.openshift.io/) pueden funcionar si se añaden a un recurso Ingress, ya que este se convertirá en una Route. Sin embargo, no todas las funcionalidades están garantizadas. Para un control total y predecible, se recomienda usar el recurso Route directamente.

¿Es mejor usar Ingress para mantener la portabilidad entre Kubernetes y OpenShift?

Usar Ingress es una buena estrategia para mantener la portabilidad de manifiestos entre distribuciones de Kubernetes estándar y OpenShift, siempre que tus necesidades se limiten a enrutamiento HTTP/HTTPS básico. Si necesitas funciones específicas de OpenShift, deberás usar Routes, lo que reduce la portabilidad pero maximiza las capacidades de la plataforma.

Uso de Ingress de Kubernetes en OpenShift: Cómo se Generan las Rutas y Cuándo Usar Cada Una

Uso de Ingress de Kubernetes en OpenShift: Cómo se Generan las Rutas y Cuándo Usar Cada Una

Introducción
OpenShift, la plataforma de Kubernetes de Red Hat, tiene su propia forma de exponer servicios a clientes externos. En Kubernetes estándar, normalmente usarías un recurso de Ingreso junto con un controlador de ingreso para enrutar el tráfico externo a los servicios. Sin embargo, OpenShift introdujo el concepto de un Ruta y un Enrutador integrado (construido sobre HAProxy) desde el principio, antes de que el Ingreso de Kubernetes siquiera existiera. Hoy en día, OpenShift admite tanto Rutas como objetos de Ingreso estándar, lo que a veces puede llevar a confusión sobre cuándo usar cada uno y cómo se relacionan.

Este artículo explora cómo OpenShift maneja los recursos de Ingreso de Kubernetes, cómo se traducen en Rutas, las limitaciones de este enfoque y orientación sobre cuándo usar Ingreso versus Rutas.

Rutas de OpenShift y el Enrutador: Una Visión Rápida


Las Rutas de OpenShift son recursos específicos de OpenShift diseñados para exponer servicios externamente. Son servidas por el Enrutador de OpenShift, que es un proxy basado en HAProxy que se ejecuta dentro del clúster. Las Rutas admiten características avanzadas como:

  • Backends ponderados para división de tráfico
  • Sesiones persistentes (afinidad de sesión)
  • Múltiples modos de terminación TLS (borde, paso a través, re-encriptar)
  • Subdominios comodín
  • Certificados personalizados y SNI
  • Enrutamiento basado en rutas

Debido a que las Rutas son nativas de OpenShift, el Enrutador entiende estas características de manera nativa y puede configurarse en consecuencia. Esta integración estrecha permite capacidades de enrutamiento poderosas y flexibles adaptadas a los entornos de OpenShift.

Uso de Ingreso de Kubernetes en OpenShift (Comportamiento Predeterminado)


A partir de OpenShift Container Platform (OCP) 3.10, se admiten los recursos de Ingreso de Kubernetes. Cuando creas un Ingreso, OpenShift lo traduce automáticamente en una Ruta equivalente detrás de escena. Esto significa que puedes usar manifiestos estándar de Ingreso de Kubernetes, y OpenShift se encargará de exponer tus servicios externamente creando Rutas en consecuencia.

Ejemplo: Ingreso de Kubernetes y Ruta Resultante

Aquí hay un manifiesto de Ingreso simple:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test-service
            port:
              number: 80

OpenShift creará una Ruta similar a:

apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: example-route
spec:
  host: www.example.com
  path: /testpath
  to:
    kind: Service
    name: test-service
    weight: 100
  port:
    targetPort: 80
  tls:
    termination: edge

Esta traducción automática simplifica la migración y admite casos de uso básicos sin requerir manifiestos específicos de Ruta.

Ajuste del Comportamiento con Anotaciones (Ingreso ➝ Ruta)

Cuando usas Ingreso en OpenShift, solo se respetan las anotaciones conscientes de OpenShift durante la traducción de Ingreso ➝ Ruta. Las anotaciones específicas del controlador para otros controladores de ingreso (por ejemplo, nginx.ingress.kubernetes.io/*) son ignoradas por el Enrutador de OpenShift. Las siguientes anotaciones son comúnmente usadas y admitidas por el enrutador de OpenShift para ajustar la Ruta generada:

Propósito Anotación Valores Típicos Efecto en la Ruta Generada
Terminación TLS route.openshift.io/termination edge · reencrypt · passthrough Establece spec.tls.termination de la Ruta al modo elegido.
Redirección HTTP→HTTPS (borde) route.openshift.io/insecureEdgeTerminationPolicy Redirect · Allow · None Controla spec.tls.insecureEdgeTerminationPolicy (comúnmente Redirect).
Balanceo de carga del backend haproxy.router.openshift.io/balance roundrobin · leastconn · source Establece el algoritmo de balanceo de HAProxy para la Ruta.
Timeout por ruta haproxy.router.openshift.io/timeout duración como 60s, 5m Configura el timeout de HAProxy para solicitudes en esa Ruta.
Cabecera HSTS haproxy.router.openshift.io/hsts_header por ejemplo, max-age=31536000;includeSubDomains;preload Inyecta la cabecera HSTS en las respuestas (borde/re-encriptar).

Nota: Las características avanzadas como backends ponderados/canario o hosts comodín no son expresables a través de Ingreso estándar. Usa una Ruta directamente para esos casos.

Ejemplo: Ingreso con anotaciones del enrutador de OpenShift

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress-https
  annotations:
    route.openshift.io/termination: edge
    route.openshift.io/insecureEdgeTerminationPolicy: Redirect
    haproxy.router.openshift.io/balance: leastconn
    haproxy.router.openshift.io/timeout: 60s
    haproxy.router.openshift.io/hsts_header: max-age=31536000;includeSubDomains;preload
spec:
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: test-service
            port:
              number: 80

Este Ingreso se realizará como una Ruta con TLS de borde y una redirección automática de HTTP→HTTPS, usando balanceo de menos conexiones y un timeout de ruta de 60s. La cabecera HSTS será añadida por el enrutador en las respuestas HTTPS.

Limitaciones de Usar Ingreso para Generar Rutas
Aunque conveniente, usar Ingreso para generar Rutas tiene limitaciones:

  • Falta de características avanzadas: Los backends ponderados y las sesiones persistentes requieren anotaciones específicas de Ruta y no son compatibles a través de Ingreso.
  • Modos de paso a través y re-encriptar TLS: Estos requieren anotaciones específicas de OpenShift en las Rutas y no son compatibles a través de Ingreso estándar.
  • Ingreso sin host: Un Ingreso sin un nombre de host no creará una Ruta; las Rutas requieren un host.
  • Hosts comodín: Los hosts comodín (por ejemplo, *.example.com) solo son compatibles a través de Rutas, no Ingreso.
  • Compatibilidad de anotaciones: Algunas anotaciones de Ruta de OpenShift no tienen equivalentes en Ingreso, lo que lleva a brechas de configuración.
  • Soporte de protocolo: Ingreso solo admite protocolos HTTP/HTTPS, mientras que las Rutas pueden manejar protocolos no HTTP con TLS de paso a través.
  • Riesgo de deriva de configuración: Debido a que las Rutas creadas a partir de Ingreso son gestionadas por OpenShift, las ediciones manuales a la Ruta generada pueden ser sobrescritas o causar inconsistencias.

Estas limitaciones significan que para configuraciones de enrutamiento avanzadas o características específicas de OpenShift, es preferible usar Rutas directamente.

Cuándo Usar Ingreso vs. Cuándo Usar Rutas
Elegir entre Ingreso y Rutas depende de tus requisitos:

  • Usa Ingreso si:
  • Quieres portabilidad entre plataformas de Kubernetes.
  • Tienes manifiestos de Ingreso existentes y quieres minimizar los cambios.
  • Tu aplicación usa solo enrutamiento básico HTTP o HTTPS.
  • Prefieres manifiestos neutrales a la plataforma para pipelines de CI/CD.
  • Usa Rutas si:
  • Necesitas características avanzadas de enrutamiento como backends ponderados, sesiones persistentes o múltiples modos de terminación TLS.
  • Tu implementación es específica de OpenShift y puede aprovechar las características nativas de OpenShift.
  • Requieres estabilidad y soporte completo para las capacidades de enrutamiento de OpenShift.
  • Necesitas exponer protocolos no HTTP o usar modos de paso a través/re-encriptar TLS.
  • Quieres usar hosts comodín o anotaciones personalizadas no compatibles con Ingreso.

En muchos casos, los equipos usan una combinación: Ingreso para portabilidad y Rutas para necesidades avanzadas o específicas de OpenShift.

Conclusión


En OpenShift, los recursos de Ingreso de Kubernetes se convierten automáticamente en Rutas, permitiendo la exposición básica de servicios externos con un esfuerzo mínimo. Esto permite a los usuarios aprovechar los manifiestos existentes de Kubernetes y mantener la portabilidad. Sin embargo, para escenarios de enrutamiento avanzados y para utilizar completamente las potentes características del Enrutador de OpenShift, se recomienda usar Rutas directamente.

Ambos, Ingreso y Rutas, coexisten sin problemas en OpenShift, permitiéndote elegir la herramienta adecuada para los requisitos de tu aplicación.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Talos Linux: Sistema Operativo Inmutable y Seguro para Kubernetes

Talos Linux: Sistema Operativo Inmutable y Seguro para Kubernetes

Introducción

Gestionar un clúster de Kubernetes puede volverse rápidamente abrumador, especialmente cuando tu sistema operativo agrega complejidad innecesaria. Entra Talos Linux: un sistema operativo innovador, optimizado para contenedores e inmutable, diseñado explícitamente para entornos de Kubernetes. Este SO especializado para Kubernetes representa un cambio de paradigma en la gestión de nodos. Es impulsado por API, completamente seguro y elimina los métodos de gestión tradicionales, incluidos SSH y los gestores de paquetes.

Talos Linux revoluciona la gestión de nodos simplificando drásticamente las operaciones y mejorando la seguridad. En este análisis profundo, exploraremos por qué Talos está captando atención, su arquitectura central y las implicaciones prácticas para los equipos de Kubernetes.

¿Qué es Talos Linux? Características del SO inmutable para Kubernetes

Talos Linux es una distribución de Linux de código abierto especializada, meticulosamente diseñada para ejecutar Kubernetes de manera segura y eficiente. A diferencia de los sistemas operativos de propósito general, Talos descarta todas las características irrelevantes y se enfoca exclusivamente en Kubernetes, asegurando:

  • Diseño Inmutable: Los cambios se manejan a través de actualizaciones atómicas en lugar de intervenciones manuales.
  • Gestión Impulsada por API: Los administradores usan talosctl, una CLI que interactúa de manera segura con los nodos a través de una API gRPC.
  • Seguridad por Defecto: Sin acceso SSH, endurecimiento integral del kernel, integración TPM, cifrado de disco y características de arranque seguro.
  • Mínimo y Predecible: Talos minimiza el uso de recursos y reduce la sobrecarga operativa al eliminar servicios y procesos innecesarios.

Mantenedores y Respaldo

Talos es mantenido por Sidero Labs, reconocidos por su experiencia en herramientas de Kubernetes y aprovisionamiento de hardware. La comunidad activa y de código abierto de ingenieros nativos de la nube y SREs contribuye continuamente a su crecimiento y evolución.

Análisis Profundo de la Arquitectura de Talos

Talos Linux emplea un diseño radical que prioriza la seguridad, la simplicidad y el rendimiento:

  • Interacción Solo por API: No hay acceso tradicional a la shell, eliminando muchas vulnerabilidades comunes asociadas con SSH.
  • Actualizaciones Atómicas: Las actualizaciones del sistema son atómicas: las nuevas versiones arrancan directamente en un estado estable y validado.
  • Eficiencia de Recursos: El diseño simplificado de Talos reduce significativamente su huella, asegurando una utilización óptima de recursos y tiempos de inicio más rápidos.
  • Medidas de Seguridad Mejoradas: Incorpora protecciones a nivel de kernel, arranque seguro, cifrado de disco y seguridad basada en TPM, alineándose con requisitos de cumplimiento estrictos.

Distribución de Kubernetes basada en Talos Linux

Sidero Labs también ofrece una distribución completa de Kubernetes construida directamente sobre Talos Linux, conocida como “Talos Kubernetes”. Esta distribución simplificada combina los beneficios de Talos Linux con componentes de Kubernetes preconfigurados, facilitando y acelerando el despliegue de clústeres de Kubernetes altamente seguros y listos para producción. Esto simplifica aún más la gestión de clústeres al reducir la sobrecarga y la complejidad típicamente asociadas con la instalación y el mantenimiento de Kubernetes por separado.

Casos de Uso en el Mundo Real

Talos brilla particularmente bien en escenarios que demandan mayor seguridad, predictibilidad y operaciones simplificadas:

  • Clústeres Conscientes de la Seguridad: Las arquitecturas de confianza cero se benefician enormemente del modelo inmutable y de acceso restringido de Talos.
  • Computación en el Borde e IoT: Su consumo mínimo de recursos y su gestión robusta a través de API lo hacen ideal para implementaciones en el borde, donde la gestión remota es esencial.
  • Pipelines de CI/CD y GitOps: La configuración declarativa, compatible con metodologías YAML y GitOps, permite entornos de Kubernetes automatizados y reproducibles.

Cómo Descargar y Probar Talos Linux

Talos Linux es fácil de probar y evaluar. Puedes descargarlo directamente desde los lanzamientos oficiales de Talos en GitHub. Sidero Labs proporciona documentación completa y guías de inicio rápido para desplegar Talos Linux en varias plataformas, incluidos servidores bare-metal, máquinas virtuales y entornos en la nube como AWS, Azure y GCP. Para una prueba rápida, ejecutarlo dentro de una máquina virtual local o contenedor es una opción conveniente.

Talos Comparado con Opciones de SO Tradicionales

Talos presenta ventajas distintas en comparación con opciones más familiares como Ubuntu, CoreOS o Flatcar:

Característica Talos Ubuntu Flatcar
Acceso SSH
Gestor de Paquetes ✅ (apt) ✅ (rpm)
Nativo de Kubernetes ✅ Incorporado ✅ (a través de herramientas)
Seguridad por Defecto 🔒 Alta Moderada Alta
SO Inmutable
Eficiencia de Recursos ✅ Alta Moderada Alta
Gestión Impulsada por API Limitada

Lo que No Puedes Hacer con Talos Linux

El diseño especializado de Talos Linux restringe intencionalmente ciertas funcionalidades tradicionales del sistema operativo. Notablemente:

  • Sin Acceso SSH: El acceso directo a la shell de los nodos está deshabilitado. Todas las interacciones deben ocurrir a través de talosctl.
  • Sin Gestores de Paquetes: Las herramientas tradicionales como apt, yum o similares están ausentes; los cambios se realizan a través de actualizaciones inmutables.
  • Sin Aplicaciones Adicionales: No admite la ejecución de servicios o cargas de trabajo adicionales, no relacionados con Kubernetes, directamente en los nodos de Talos.

Estas restricciones, aunque pueden parecer limitantes, refuerzan las mejores prácticas de seguridad en Kubernetes, mejoran significativamente la postura de seguridad y aseguran un entorno operativo predecible y consistente ideal para implementaciones de producción.

Conclusión

Talos Linux representa un cambio sustancial en la gestión de nodos de Kubernetes: seguro, ligero y completamente enfocado en Kubernetes. Para las organizaciones que priorizan la seguridad, el cumplimiento, la simplicidad operativa y la eficiencia, Talos proporciona una base robusta y preparada para el futuro.

Si tu estrategia de Kubernetes valora el minimalismo, la seguridad y la simplicidad, Talos Linux ofrece razones convincentes para considerar su adopción.

Referencias
Documentación de Talos
Sidero Labs
Repositorio de Talos en GitHub

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Frequently Asked Questions

¿Puedo usar Talos Linux con cualquier distribución de Kubernetes?

Sí, Talos Linux es compatible con cualquier distribución estándar de Kubernetes, incluyendo Kubernetes vanilla, así como distribuciones como k3s y k0s. Sidero Labs también ofrece Talos Kubernetes, una distribución preconfigurada que combina Talos Linux con componentes de Kubernetes optimizados. La API de Talos (talosctl) gestiona el ciclo de vida del nodo independientemente de la distribución de Kubernetes que ejecutes encima.

¿Cómo soluciono problemas en Talos Linux sin acceso SSH?

Talos Linux proporciona herramientas de diagnóstico a través de su API gRPC y la CLI talosctl. Puedes usar comandos como talosctl logs para ver registros del sistema, talosctl dashboard para métricas en tiempo real, y talosctl support para generar bundles de diagnóstico. Para depuración avanzada, Talos soporta contenedores efímeros de Kubernetes que pueden ejecutarse temporalmente en el nodo para tareas de troubleshooting.

¿Es Talos Linux adecuado para entornos de desarrollo local?

Talos Linux es óptimo para producción pero puede usarse en desarrollo. Para entornos locales, considera usar Talos en máquinas virtuales o mediante Docker/Podman para pruebas. Sin embargo, para desarrollo rápido, distribuciones como Minikube o Kind con SO tradicionales pueden ser más convenientes. Talos brilla en entornos que requieren consistencia entre desarrollo, staging y producción.

¿Qué ventajas tiene Talos Linux sobre Ubuntu para Kubernetes?

Talos Linux ofrece: 1) Seguridad por defecto (sin SSH, arranque seguro, TPM), 2) Inmutabilidad (actualizaciones atómicas y rollback), 3) Minimalismo (menor superficie de ataque y uso de recursos), y 4) Gestión declarativa (API-driven vs. manual). Ubuntu es más flexible pero requiere hardening manual y tiene mayor complejidad operativa para cargas de trabajo Kubernetes puras.

¿Cómo maneja Talos Linux el almacenamiento persistente?

Talos Linux soporta volúmenes persistentes de Kubernetes estándar. Para almacenamiento local, configura Local Persistent Volumes o integra con soluciones como Ceph, Longhorn, o proveedores cloud. El sistema de archivos raíz es inmutable y de solo lectura; el almacenamiento de aplicaciones se gestiona exclusivamente a través de Kubernetes, no directamente en el SO.

Kyverno en Kubernetes: aplicar políticas estándar y personalizadas paso a paso (vs PSA)

Kyverno en Kubernetes: aplicar políticas estándar y personalizadas paso a paso (vs PSA)

En el ecosistema de Kubernetes, la seguridad y la gobernanza son aspectos clave que requieren atención continua. Aunque Kubernetes ofrece algunas características de seguridad listas para usar (OOTB) como la Admisión de Seguridad de Pods (PSA), estas pueden no ser suficientes para entornos complejos con requisitos de cumplimiento variables. Aquí es donde Kyverno entra en juego, proporcionando una solución poderosa pero flexible para gestionar y hacer cumplir políticas en todo tu clúster.

En esta publicación, exploraremos las diferencias clave entre Kyverno y PSA, explicaremos cómo se puede usar Kyverno en diferentes casos de uso y te mostraremos cómo instalar y desplegar políticas con él. Aunque la creación de políticas personalizadas se cubrirá en una publicación separada, haremos referencia a algunas políticas preconstruidas que puedes usar de inmediato.

¿Qué es la Admisión de Seguridad de Pods (PSA)?

Kubernetes introdujo Admisión de Seguridad de Pods (PSA) como un reemplazo para la ahora obsoleta Política de Seguridad de Pods (PSP). PSA se centra en hacer cumplir tres niveles de seguridad predefinidos: Privilegiado, Básico y Restringido. Estos niveles controlan qué pods pueden ejecutarse en un espacio de nombres según sus configuraciones de contexto de seguridad.

  • Privilegiado: Restricciones mínimas, permitiendo contenedores privilegiados y acceso al host.
  • Básico: Aplica restricciones estándar, no permitiendo contenedores privilegiados y limitando el acceso al host.
  • Restringido: El nivel más estricto, asegurando configuraciones seguras por defecto y aplicando las mejores prácticas para ejecutar contenedores.

Si bien PSA es efectivo para requisitos de seguridad básicos, carece de flexibilidad al aplicar políticas personalizadas o de grano fino. Tenemos un artículo completo que cubre este tema que puedes leer aquí.

Kyverno vs. PSA: Diferencias Clave

Kyverno va más allá de las capacidades de PSA al ofrecer un control más granular y flexibilidad. Así es como se compara:

  1. Tipos de Políticas: Mientras que PSA se centra únicamente en la seguridad, Kyverno permite la creación de políticas para validación, mutación y generación de recursos. Esto significa que puedes modificar o generar nuevos recursos, no solo aplicar reglas de seguridad.
  2. Personalización: Kyverno admite políticas personalizadas que pueden hacer cumplir los requisitos de cumplimiento de tu organización. Puedes escribir políticas que gobiernen tipos de recursos específicos, como asegurar que todos los despliegues tengan ciertas etiquetas o que las imágenes de los contenedores provengan de un registro de confianza.
  3. Política como Código: Las políticas de Kyverno están escritas en YAML, lo que permite una fácil integración con pipelines de CI/CD y flujos de trabajo de GitOps. Esto hace que la gestión de políticas sea declarativa y controlada por versiones, lo cual no es el caso con PSA.
  4. Auditoría e Informes: Con Kyverno, puedes generar registros de auditoría detallados e informes sobre violaciones de políticas, dando a los administradores una visión clara de cómo se aplican las políticas y dónde ocurren las violaciones. PSA carece de esta capacidad de informes integrada.
  5. Aplicación y Mutación: Mientras que PSA principalmente aplica restricciones en los pods, Kyverno permite no solo la validación de configuraciones sino también la modificación de recursos (mutación) cuando sea necesario. Esto añade una capa adicional de flexibilidad, como agregar automáticamente anotaciones o etiquetas.

Cuándo Usar Kyverno en Lugar de PSA

Si bien PSA podría ser suficiente para entornos más simples, Kyverno se convierte en una herramienta valiosa en escenarios que requieren:

  • Reglas de Cumplimiento Personalizadas: Por ejemplo, hacer cumplir que todos los contenedores usen una imagen base específica o restringir capacidades específicas de contenedores en diferentes entornos.
  • Integraciones CI/CD: Kyverno puede integrarse en tus pipelines de CI/CD, asegurando que los recursos cumplan con las políticas organizacionales antes de ser desplegados.
  • Gobernanza Compleja: Al gestionar grandes clústeres con múltiples equipos, la jerarquía de políticas y el alcance de Kyverno permiten un control más fino sobre quién puede desplegar qué y cómo se configuran los recursos.

Si tu organización necesita una solución de seguridad más robusta y flexible, Kyverno es una mejor opción en comparación con el enfoque más genérico de PSA.

Instalando Kyverno

Para comenzar a usar Kyverno, necesitarás instalarlo en tu clúster de Kubernetes. Este es un proceso sencillo usando Helm, lo que facilita su gestión y actualización.

Instalación Paso a Paso

Agrega el repositorio Helm de Kyverno:

helm repo add kyverno https://kyverno.github.io/kyverno/

Actualiza los repositorios de Helm:

helm repo update

Instala Kyverno en tu clúster de Kubernetes:

helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace

Verifica la instalación:

kubectl get pods -n kyverno

Después de la instalación, Kyverno comenzará a aplicar políticas en todo tu clúster, pero necesitarás desplegar algunas políticas para comenzar.

Desplegando Políticas con Kyverno

Las políticas de Kyverno están escritas en YAML, al igual que los recursos de Kubernetes, lo que las hace fáciles de leer y gestionar. Puedes encontrar varias políticas listas para usar en la Biblioteca de Políticas de Kyverno, o crear las tuyas propias para que se ajusten a tus requisitos.

A continuación, un ejemplo de una política de validación simple que asegura que todos los pods usen imágenes de contenedores de confianza de un registro específico:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-trusted-registry
spec:
  validationFailureAction: enforce
  rules:
  - name: check-registry
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "Solo se permiten imágenes de 'myregistry.com'."
      pattern:
        spec:
          containers:
          - image: "myregistry.com/*"

Esta política bloqueará automáticamente el despliegue de cualquier pod que use una imagen de un registro diferente a myregistry.com.

Aplicando la Política

Para aplicar la política anterior, guárdala en un archivo YAML (por ejemplo, trusted-registry-policy.yaml) y ejecuta el siguiente comando:

kubectl apply -f trusted-registry-policy.yaml

Una vez aplicada, Kyverno hará cumplir esta política en todo tu clúster.

Visualizando Informes de Políticas de Kyverno

Kyverno genera informes detallados sobre violaciones de políticas, que son útiles para auditorías y seguimiento del cumplimiento de políticas. Para verificar los informes, puedes usar los siguientes comandos:

Lista todos los informes de políticas de Kyverno:

kubectl get clusterpolicyreport

Describe un informe de política específico para obtener más detalles:

kubectl describe clusterpolicyreport <report-name>

Estos informes pueden integrarse en tus herramientas de monitoreo para activar alertas cuando ocurran violaciones críticas.

Conclusión

Kyverno ofrece una forma flexible y poderosa de hacer cumplir políticas en Kubernetes, convirtiéndose en una herramienta esencial para organizaciones que necesitan más que las capacidades básicas proporcionadas por PSA. Ya sea que necesites asegurar el cumplimiento con estándares de seguridad internos, automatizar modificaciones de recursos o integrar políticas en pipelines de CI/CD, el extenso conjunto de características de Kyverno lo convierte en una opción preferida para la gobernanza de Kubernetes.

Por ahora, comienza con las políticas listas para usar disponibles en la biblioteca de Kyverno. En publicaciones futuras, profundizaremos en la creación de políticas personalizadas adaptadas a tus necesidades específicas.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Admisión de Seguridad de Pods (PSA) en Kubernetes: qué es, cómo funciona y sus limitaciones

Admisión de Seguridad de Pods (PSA) en Kubernetes: qué es, cómo funciona y sus limitaciones

En Kubernetes, la seguridad es una preocupación clave, especialmente a medida que los contenedores y los microservicios crecen en complejidad. Una de las características esenciales de Kubernetes para la aplicación de políticas es Pod Security Admission (PSA), que reemplaza las obsoletas Pod Security Policies (PSP). PSA proporciona un enfoque más sencillo y flexible para aplicar políticas de seguridad, ayudando a los administradores a proteger los clústeres asegurándose de que solo se ejecuten pods compatibles.

Este artículo te guiará a través de PSA, los Estándares de Seguridad de Pods disponibles, cómo configurarlos y cómo aplicar políticas de seguridad a espacios de nombres específicos usando etiquetas.

¿Qué es Pod Security Admission (PSA)?

PSA es un controlador de admisión incorporado introducido en Kubernetes 1.23 para reemplazar las Políticas de Seguridad de Pods (PSP). Las PSP tenían una curva de aprendizaje pronunciada y podían volverse engorrosas al escalar políticas de seguridad en varios entornos. PSA simplifica este proceso aplicando los Estándares de Seguridad de Pods de Kubernetes basados en niveles de seguridad predefinidos sin necesidad de lógica personalizada para cada política.

Con PSA, los administradores de clústeres pueden restringir los permisos de los pods utilizando etiquetas que corresponden a Estándares de Seguridad de Pods específicos. PSA opera a nivel de espacio de nombres, permitiendo una mejor granularidad en el control de políticas de seguridad para diferentes cargas de trabajo.

Estándares de Seguridad de Pods

Kubernetes proporciona tres Estándares de Seguridad de Pods clave en el marco de PSA:

  • Privilegiado: Sin restricciones; permite todas las características y es el modo menos restrictivo. No se recomienda para cargas de trabajo de producción, pero puede usarse en entornos controlados o para cargas de trabajo que requieren permisos elevados.
  • Básico: Proporciona un buen equilibrio entre usabilidad y seguridad, restringiendo los aspectos más peligrosos de los privilegios de los pods mientras permite configuraciones comunes. Es adecuado para la mayoría de las aplicaciones que no necesitan permisos especiales.
  • Restringido: El nivel más estricto de seguridad. Este nivel está destinado a cargas de trabajo que requieren el más alto nivel de aislamiento y control, como clústeres multi-tenant o cargas de trabajo expuestas a internet.

Cada estándar incluye reglas específicas para limitar los privilegios de los pods, como prohibir contenedores privilegiados, restringir el acceso a la red del host y prevenir cambios en ciertos contextos de seguridad.

Configuración de Pod Security Admission (PSA)

Para habilitar PSA, necesitas etiquetar tus espacios de nombres según el nivel de seguridad que deseas aplicar. El formato de la etiqueta es el siguiente:

kubectl label --overwrite ns  pod-security.kubernetes.io/enforce=<value>

Por ejemplo, para aplicar una política de seguridad restringida en el espacio de nombres producción, ejecutarías:

kubectl label --overwrite ns production pod-security.kubernetes.io/enforce=restricted

En este ejemplo, Kubernetes aplicará automáticamente las reglas asociadas con la política restringida a todos los pods desplegados en el espacio de nombres producción.

Modos Adicionales de PSA

PSA también proporciona modos adicionales para un mayor control:

  • Auditar: Registra una violación de política pero permite que el pod se cree.
  • Advertir: Emite una advertencia pero permite la creación del pod.
  • Aplicar: Bloquea la creación del pod si viola la política.

Para configurar estos modos, utiliza las siguientes etiquetas:

kubectl label --overwrite ns      pod-security.kubernetes.io/enforce=baseline     pod-security.kubernetes.io/audit=restricted     pod-security.kubernetes.io/warn=baseline

Esta configuración aplica el estándar básico mientras emite advertencias y registra violaciones para las reglas de nivel restringido.

Ejemplo: Configuración de Seguridad de Pods en un Espacio de Nombres

Veamos un ejemplo de cómo configurar la seguridad básica para el espacio de nombres dev. Primero, necesitas aplicar las etiquetas de PSA:

kubectl create namespace dev
kubectl label --overwrite ns dev pod-security.kubernetes.io/enforce=baseline

Ahora, cualquier pod desplegado en el espacio de nombres dev será verificado contra el estándar de seguridad básico. Si un pod viola la política básica (por ejemplo, al intentar ejecutar un contenedor privilegiado), se bloqueará su inicio.

También puedes combinar los modos advertir y auditar para rastrear violaciones sin bloquear pods:

kubectl label --overwrite ns dev     pod-security.kubernetes.io/enforce=baseline     pod-security.kubernetes.io/warn=restricted     pod-security.kubernetes.io/audit=privileged

En este caso, PSA permitirá que los pods se ejecuten si cumplen con la política básica, pero emitirá advertencias por violaciones de nivel restringido y registrará cualquier violación de nivel privilegiado.

Aplicación de Políticas por Defecto

Una de las fortalezas de PSA es su simplicidad para aplicar políticas a nivel de espacio de nombres, pero los administradores podrían preguntarse si hay una forma de aplicar una política por defecto en nuevos espacios de nombres automáticamente. Hasta ahora, Kubernetes no proporciona de forma nativa una opción para aplicar políticas de PSA globalmente por defecto. Sin embargo, puedes usar webhooks de admisión o herramientas de automatización como OPA Gatekeeper o Kyverno para aplicar políticas por defecto para nuevos espacios de nombres.

Conclusión

Pod Security Admission (PSA) simplifica la aplicación de políticas en clústeres de Kubernetes, facilitando el cumplimiento de los estándares de seguridad en diferentes entornos. Al configurar los Estándares de Seguridad de Pods a nivel de espacio de nombres y usar etiquetas, los administradores pueden controlar el nivel de seguridad de las cargas de trabajo con facilidad. La flexibilidad de PSA permite una gestión eficiente de la seguridad sin la complejidad asociada con las antiguas Políticas de Seguridad de Pods (PSP).

Para más detalles sobre la configuración de PSA y los Estándares de Seguridad de Pods, consulta la documentación oficial de PSA de Kubernetes y la documentación de Estándares de Seguridad de Pods.

📚 Want to dive deeper into Kubernetes? This article is part of our comprehensive Kubernetes Architecture Patterns guide, where you’ll find all fundamental and advanced concepts explained step by step.

Exponiendo Puertos TCP Usando Istio Ingress Gateway

Exponiendo Puertos TCP Usando Istio Ingress Gateway

Istio se ha convertido en una herramienta esencial para gestionar el tráfico HTTP dentro de los clústeres de Kubernetes, ofreciendo características avanzadas como Implementaciones Canary, mTLS y visibilidad de extremo a extremo. Sin embargo, algunas tareas, como exponer un puerto TCP usando el Istio IngressGateway, pueden ser desafiantes si nunca lo has hecho antes. Este artículo te guiará a través del proceso de exponer puertos TCP con Istio Ingress Gateway, completo con ejemplos del mundo real y casos de uso prácticos.

Entendiendo el Contexto

Istio se utiliza a menudo para gestionar el tráfico HTTP en Kubernetes, proporcionando capacidades poderosas como gestión de tráfico, seguridad y observabilidad. El Istio IngressGateway sirve como punto de entrada para el tráfico externo en el clúster de Kubernetes, manejando típicamente tráfico HTTP y HTTPS. Sin embargo, Istio también soporta tráfico TCP, lo cual es necesario para casos de uso como exponer bases de datos u otros servicios no HTTP que se ejecutan en el clúster a consumidores externos.

Exponer un puerto TCP a través de Istio implica configurar el IngressGateway para manejar el tráfico TCP y enrutarlo al servicio apropiado. Esta configuración es particularmente útil en escenarios donde necesitas exponer servicios como TIBCO EMS o bases de datos basadas en Kubernetes a otras aplicaciones internas o externas.

Pasos para Exponer un Puerto TCP con Istio IngressGateway

1.- Modificar el Servicio Istio IngressGateway:

Antes de configurar el Gateway, debes asegurarte de que el servicio Istio IngressGateway esté configurado para escuchar en el nuevo puerto TCP. Este paso es crucial si estás utilizando un servicio NodePort, ya que este puerto necesita ser abierto en el Balanceador de Carga.

   apiVersion: v1
   kind: Service
   metadata:
 name: istio-ingressgateway
 namespace: istio-system
   spec:
 ports:
 - name: http2
   port: 80
   targetPort: 80
 - name: https
   port: 443
   targetPort: 443
 - name: tcp
   port: 31400
   targetPort: 31400
   protocol: TCP

2.- Actualiza el servicio Istio IngressGateway para incluir el nuevo puerto 31400 para tráfico TCP.

Configura el Istio IngressGateway: Después de modificar el servicio, configura el Istio IngressGateway para escuchar en el puerto TCP deseado.

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: tcp-ingress-gateway
  namespace: istio-system
spec:
  selector:
istio: ingressgateway
  servers:
  - port:
	  number: 31400
	  name: tcp
	  protocol: TCP
	hosts:
	- "*"

En este ejemplo, el IngressGateway está configurado para escuchar en el puerto 31400 para tráfico TCP.

3.- Crea un Servicio y VirtualService:

Después de configurar el gateway, necesitas crear un Servicio que represente la aplicación backend y un VirtualService para enrutar el tráfico TCP.

apiVersion: v1
kind: Service
metadata:
  name: tcp-service
  namespace: default
spec:
  ports:
  - port: 31400
	targetPort: 8080
	protocol: TCP
  selector:
app: tcp-app

El Servicio anterior mapea el puerto 31400 en el IngressGateway al puerto 8080 en la aplicación backend.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: tcp-virtual-service
  namespace: default
spec:
  hosts:
  - "*"
  gateways:
  - tcp-ingress-gateway
  tcp:
  - match:
	- port: 31400
	route:
	- destination:
		host: tcp-service
		port:
		  number: 8080

El VirtualService enruta el tráfico TCP que llega al puerto 31400 en el gateway al tcp-service en el puerto 8080.

4.- Aplica la Configuración

Aplica las configuraciones anteriores usando kubectl para crear los recursos necesarios de Kubernetes.

kubectl apply -f istio-ingressgateway-service.yaml
kubectl apply -f tcp-ingress-gateway.yaml
kubectl apply -f tcp-service.yaml
kubectl apply -f tcp-virtual-service.yaml

Después de aplicar estas configuraciones, el Istio IngressGateway expondrá el puerto TCP al tráfico externo.

Casos de Uso Prácticos

  • Exponiendo el Servidor TIBCO EMS: Un escenario común es exponer un servidor TIBCO EMS (Enterprise Message Service) que se ejecuta dentro de un clúster de Kubernetes a otras aplicaciones internas o consumidores externos. Al configurar el Istio IngressGateway para manejar el tráfico TCP, puedes exponer de manera segura el puerto TCP de EMS, permitiendo que se comunique con servicios fuera del entorno de Kubernetes.
  • Exponiendo Bases de Datos: Otro caso de uso es exponer una base de datos que se ejecuta dentro de Kubernetes a servicios externos o diferentes clústeres. Al exponer el puerto TCP de la base de datos a través del Istio IngressGateway, permites que otras aplicaciones interactúen con ella, independientemente de su ubicación.
  • Exponiendo un Servicio Personalizado Basado en TCP: Supongamos que tienes una aplicación personalizada que se ejecuta dentro de Kubernetes y se comunica a través de TCP, como un servidor de juegos o un servicio API personalizado basado en TCP. Puedes usar el Istio IngressGateway para exponer este servicio a usuarios externos, haciéndolo accesible desde fuera del clúster.

Conclusión

Exponer puertos TCP usando el Istio IngressGateway puede ser una técnica poderosa para gestionar tráfico no HTTP en tu clúster de Kubernetes. Con los pasos descritos en este artículo, puedes exponer con confianza servicios como TIBCO EMS, bases de datos o aplicaciones personalizadas basadas en TCP a consumidores externos, mejorando la flexibilidad y conectividad de tus aplicaciones.