Lesson  in  Kubernetes 101

Labels, selectors y annotations

El pegamento de Kubernetes: etiqueta objetos, selecciónalos con selectores de igualdad y de conjunto, y descubre por qué las annotations no son simplemente labels largas.

Labels y selectors

Kubernetes no tiene carpetas. No tiene jerarquías, ni grupos, ni un árbol donde meter los objetos. Todo lo que parece una relación entre objetos (un Service que encuentra sus Pods, un Deployment que sabe cuáles son suyos, una NetworkPolicy que protege a unos y no a otros) está construido sobre un único mecanismo: labels y selectors.

Es un diseño deliberadamente pobre y sorprendentemente potente. Un Pod no pertenece a nadie: simplemente lleva labels, y cualquiera que se fije en ellas puede reclamarlo.

El playground trae cinco Pods ya etiquetados. Míralos desde la pestaña dev-machine:

kubectl get pods --show-labels

Cuatro llevan tres labels (app, entorno, tier). El quinto, huerfano, no lleva ninguna de las tres: solo la etiqueta run=huerfano que kubectl run pone por su cuenta cuando no le das ninguna. Ya volveremos a por él.

Paso 1: Seleccionar por igualdad

La forma básica del selector, la que llevas usando todo el curso:

kubectl get pods -l entorno=tienda
kubectl get pods -l app=api,entorno=tienda
kubectl get pods -l entorno!=tienda

¿Qué hace la coma? Un Y lógico, nunca un O. app=api,entorno=tienda selecciona los Pods que cumplen las dos condiciones. Es la misma sintaxis que usa matchLabels en el selector de un Deployment o de un Service, y por eso allí tampoco existe el O.

Paso 2: Seleccionar por conjunto

La segunda familia de selectors, la que casi nadie conoce y resuelve preguntas que la primera no puede:

kubectl get pods -l 'entorno in (tienda,staging)'
kubectl get pods -l 'app notin (api)'
kubectl get pods -l 'entorno'
kubectl get pods -l '!entorno'

Los cuatro casos, en orden: pertenencia a un conjunto (aquí sí hay algo parecido a un O), exclusión, existencia de la etiqueta sin importar su valor, y ausencia de la etiqueta.

Ese último, !entorno, es oro puro en un clúster real: es como se encuentra lo que alguien desplegó sin seguir las convenciones. Pruébalo.

Note

💡 Los selectors de conjunto funcionan en kubectl y en el matchExpressions de los objetos que lo soportan (Deployment, ReplicaSet, NetworkPolicy). Pero el selector de un Service solo admite igualdad: es un mapa plano de clave y valor. Es una de esas asimetrías que solo se aprenden chocando con ellas.

Paso 3: Etiquetar objetos vivos

Adopta al huérfano. Las labels de un objeto existente se cambian con kubectl label:

kubectl label pod huerfano app=tienda entorno=desarrollo
kubectl get pods --show-labels

Y ahora prueba a cambiar una etiqueta que ya existe:

kubectl label pod huerfano entorno=tienda

Error: already has a value. Kubernetes te obliga a decir en voz alta que estás sobrescribiendo, con --overwrite. Es una protección deliberada: cambiar una etiqueta puede mover un Pod de un Service a otro sin que nadie se entere.

Déjalo como estaba (entorno=desarrollo, con --overwrite si hace falta).

Y ya que estamos, aprende a quitar etiquetas. huerfano no tiene ninguna tier, así que prueba sobre uno de los Pods que sí la lleva:

kubectl label pod tienda-dev tier-
kubectl get pods --show-labels

Ese guion final es la sintaxis para borrar una etiqueta. Si la clave no existe, kubectl te lo dice con un label "tier" not found: pruébalo también sobre huerfano para ver el error.

Las convenciones que te ahorran discusiones

Kubernetes reserva un prefijo, app.kubernetes.io/, para un conjunto de labels recomendadas que todo el ecosistema entiende: app.kubernetes.io/name, /instance, /version, /component, /part-of y /managed-by. Helm las pone solas. No son obligatorias, pero adoptarlas significa que tus objetos hablan el idioma de las herramientas de todo el mundo.

Y un detalle de formato que te morderá el primer día: el valor de una etiqueta tiene un máximo de 63 caracteres y no admite cualquier carácter: debe empezar y terminar por alfanumérico. Sí puede estar vacío, que es un detalle que sorprende a casi todo el mundo. Si necesitas guardar algo más largo o más libre, no es una etiqueta lo que necesitas. Es lo que verás en la siguiente unidad.

Resumen

  • Las labels son el único mecanismo de relación entre objetos en Kubernetes. No hay carpetas.
  • selectors de igualdad (a=b,c=d, siempre Y lógico) y de conjunto (in, notin, existencia, !ausencia).
  • El selector de un Service solo entiende igualdad; los Deployments y las NetworkPolicies admiten matchExpressions.
  • kubectl label etiqueta, --overwrite sobrescribe y el sufijo - borra.

Annotations

Una annotation se parece mucho a una label: es un par clave y valor en la metadata de cualquier objeto. Y la diferencia es una sola, pero lo cambia todo:

Nadie puede seleccionar por annotations.

Esa restricción es el motivo de su existencia. Las labels son un índice: cortas, restringidas, pensadas para que la API filtre millones de objetos rápido. Las annotations son un cajón: pueden guardar hasta 256 KB, admiten cualquier carácter, un JSON entero, un certificado, un mensaje de commit. Nadie las va a indexar, así que pueden permitírselo.

Paso 1: Anotar un objeto

Apunta quién se hace responsable de api en producción:

kubectl annotate pod api-prod responsable="equipo-plataforma@empresa.com"
kubectl annotate pod api-prod descripcion="API de pedidos. Contacto de guardia en el canal #api-oncall"
kubectl get pod api-prod -o jsonpath='{.metadata.annotations}' | jq .

La sintaxis es gemela de kubectl label: --overwrite para reemplazar y el sufijo - para borrar (kubectl annotate pod api-prod descripcion-).

Y ahora la comprobación que fija el concepto. Intenta seleccionar por ella:

kubectl get pods -l responsable

No resources found. Ni siquiera preguntando solo por la clave, sin valor. No es que la anotación no exista: es que -l mira las labels, y esto es una annotation. Ese silencio es la lección entera.

Y si intentas afinar más, seleccionando por el valor completo, ni siquiera llegas al silencio:

kubectl get pods -l responsable=equipo-plataforma@empresa.com
# Invalid value: "equipo-plataforma@empresa.com": a valid label must be an empty
# string or consist of alphanumeric characters, '-', '_' or '.'

La API rechaza la pregunta antes de buscar nada. Esa arroba que la annotation aceptó sin pestañear es ilegal en una label: los valores de label están restringidos precisamente porque son un índice. Dos comandos, la misma lección desde los dos lados.

Paso 2: Las annotations que sí hacen cosas

Aquí viene lo interesante, y es lo que confunde a todo el mundo al principio: muchas annotations no son notas, son configuración. Kubernetes y su ecosistema las usan como un canal de extensión, una forma de pasarle parámetros a un componente sin tener que cambiar el esquema de la API.

Ya te has cruzado con varias sin saberlo:

  • deployment.kubernetes.io/revision: la anotación donde el Deployment guarda el número de revisión que consultaste en el rollback.
  • kubectl.kubernetes.io/last-applied-configuration: la copia del último YAML aplicado que kubectl apply guarda para poder calcular los cambios.
  • kubernetes.io/change-cause: si aplicas un cambio con --record o la escribes tú, aparece en la columna CHANGE-CAUSE de kubectl rollout history. Es lo que convierte un historial de revisiones anónimo en un historial legible.
  • nginx.ingress.kubernetes.io/rewrite-target, cert-manager.io/cluster-issuer y compañía: toda la configuración avanzada de los Ingress controllers vive en annotations. Fue precisamente ese abuso, la configuración propietaria escondida en cadenas de texto sin validar, el problema que la Gateway API vino a resolver llevando esos campos al esquema.

Míralo en un objeto que ya conoces:

kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations}{"\n"}{end}'

¿Y esto no es peligroso? Un poco. Una annotation es texto sin validar: si te equivocas en el nombre de la clave, no pasa absolutamente nada, ningún error, y la configuración simplemente se ignora en silencio. Cuando una anotación de un Ingress controller "no hace nada", el primer sospechoso es siempre una errata en el nombre.

Cuándo cada cosa

La regla es corta y no falla:

  • ¿Necesito encontrar este objeto por este dato, o que otro objeto lo seleccione? → label.
  • ¿Es información para las personas o para una herramienta, y nadie va a filtrar por ella? → annotation.

El error clásico es meter en labels lo que debería ir en annotations (un timestamp de despliegue, un identificador de commit, una descripción larga) y acabar con un índice de la API lleno de valores únicos que no sirven para agrupar nada.

Resumen

  • Labels: para seleccionar. Cortas (63 caracteres), restringidas, indexadas.
  • Annotations: para todo lo demás. Grandes (hasta 256 KB), libres, invisibles para los selectores.
  • Media Kubernetes se configura por annotations: revisiones de Deployment, last-applied-configuration, change-cause, y toda la configuración de los Ingress controllers.
  • Una annotation mal escrita no da error: se ignora. Revisa siempre la clave antes de culpar al controller.
Previous lesson
Challenge: Pod roto