Lesson  in  Kubernetes 101

Events, la primera línea de diagnóstico

Antes de instalar nada, Kubernetes ya te está contando lo que pasa. Provoca tres fallos distintos, léelos en los Events, apréndete los Reason que vas a ver el resto de tu vida, y descubre por qué desaparecen a la hora.

Antes de instalar Prometheus, antes de montar Grafana, antes de discutir sobre trazas: Kubernetes ya te está contando lo que pasa, y lo hace en un sitio que casi nadie mira con atención.

El capítulo de observabilidad del libro recorre métricas, logs y trazas, las tres señales que hay que instrumentar. Este módulo empieza antes: por lo que el clúster ya te está diciendo gratis.

Un Event es un objeto de la API, como un Pod o un ConfigMap. Lo emite un componente del clúster (el scheduler, el kubelet, un controlador) cuando le ocurre algo digno de mención a otro objeto. kubectl describe los enseña al final, y ese "al final" es simbólico: es donde la mayoría de la gente deja de leer, justo antes de la respuesta.

En esta lección hay tres Pods rotos esperándote. Cada uno falla por un motivo distinto, y cada uno tiene un componente distinto quejándose de él.

kubectl get pods

Paso 1: imagen-fantasma, el fallo del kubelet

kubectl describe pod imagen-fantasma

Baja hasta Events. Verás una secuencia: el nodo asignado, un intento de descargar la imagen, y un Warning con el fallo.

Ese Warning te dice, con todas las letras, que el manifest de esa etiqueta no existe en el registro. No hay que adivinar nada: el error viene del registro de contenedores, atraviesa el runtime, y el kubelet lo publica en la API para que tú lo leas.

Fíjate también en el Age del evento: algo como 2m (x5 over 3m). Eso significa que el mismo evento se ha repetido cinco veces y Kubernetes lo ha agrupado en una sola línea con un contador, en lugar de inundarte con cinco entradas idénticas.

Paso 2: nodo-imposible, el fallo del scheduler

Este Pod ni siquiera ha llegado a un nodo. Pide 500 CPUs.

kubectl events --for pod/nodo-imposible
Note

💡 kubectl events (disponible desde la versión 1.25 y estable desde la 1.28) es bastante mejor que el viejo kubectl get events: entiende --for, ordena por tiempo de forma sensata y sabe filtrar por tipo. Si llevas años escribiendo kubectl get events --sort-by=.lastTimestamp, ya puedes dejar de hacerlo.

El Reason te dice que no se ha podido programar, y el mensaje detalla por qué falló cada nodo: cuántos por CPU insuficiente, cuántos por taints, cuántos por afinidad. Es, literalmente, el reports de las fases de filtrado del scheduler que estudiaste en el módulo de Scheduling.

Y hay un detalle que merece la pena: mira quién emite este evento. No es el kubelet de ningún nodo, porque ningún kubelet ha visto jamás este Pod. Es el default-scheduler.

Esa es la utilidad real de los Events: te dicen qué componente del clúster tiene el problema. Un FailedScheduling es un problema de capacidad o de reglas de ubicación. Un Failed al descargar una imagen es un problema del nodo, del registro o de las credenciales. No se parecen en nada, y se diagnostican en sitios distintos.

Paso 3: api-rota, el fallo que se repite

kubectl events --for pod/api-rota
kubectl get pod api-rota

Este Pod arranca bien. La imagen se descarga, el contenedor se ejecuta... y a los pocos segundos el kubelet lo mata. Y otra vez. Y otra vez. La columna RESTARTS sube sola.

Los Events cuentan la historia completa: la sonda de liveness apunta a una ruta que devuelve 404, el kubelet lo marca como Unhealthy, lo mata, y el restartPolicy: Always lo levanta de nuevo. Con el tiempo verás también el BackOff: Kubernetes esperando cada vez más entre reintentos.

Aquí está la lección que separa a quien sabe diagnosticar de quien no:

  • kubectl logs api-rota te enseña la aplicación (y nginx, sin saber nada de todo esto, dirá que va perfectamente).
  • kubectl events --for pod/api-rota te enseña a Kubernetes hablando de la aplicación.

El contenedor no se está muriendo. Lo están matando, y el motivo no aparece en los logs de la aplicación por ninguna parte. Nunca. Si solo miras los logs, este bug te puede costar una tarde.

Paso 4: Los Events son objetos, y se filtran como tal

Un Event es un objeto de la API con campos estructurados. Eso significa que se puede filtrar, ordenar y consultar como cualquier otra cosa:

# Todo lo que ha pasado en el clúster, en orden
kubectl events -A

# Solo los problemas
kubectl events --types=Warning -A

# Todo lo relativo a un objeto concreto
kubectl events --for pod/api-rota

# Y el clásico, por si tu clúster es antiguo
kubectl get events --field-selector type=Warning,reason=FailedScheduling

Guarda el listado de avisos del clúster, que es lo primero que harías en una incidencia real:

kubectl events --types=Warning -A > /home/laborant/warnings.txt
cat /home/laborant/warnings.txt

Ahí tienes, en una sola pantalla, los tres fallos del escenario. Sin instalar nada.

Paso 5: Repara el Pod

Ya sabes exactamente qué le pasa a imagen-fantasma. La etiqueta de la imagen no existe, y la imagen de un Pod no se puede cambiar en caliente:

kubectl delete pod imagen-fantasma
kubectl run imagen-fantasma --image=ghcr.io/iximiuz/labs/nginx:alpine

Paso 6: La letra pequeña

Ahora, la parte que hay que aprenderse antes de confiar en los Events.

Los Events caducan. El API server los borra pasado el --event-ttl, que por defecto es una hora. Se guardan en etcd, y etcd no es un sistema de logs: si guardara todos los eventos de un clúster grande para siempre, se lo comería vivo.

Las consecuencias son muy prácticas y hay que interiorizarlas:

  • Un incidente de anoche no tiene Events esta mañana. Se han evaporado. Si nadie los recogió en su momento, la información ya no existe.
  • Los Events no son una auditoría. Para eso está el audit log del API server, que es otra cosa completamente distinta: registra quién hizo qué petición, y se guarda en disco.
  • Por eso se exportan. Herramientas como kubernetes-event-exporter, el receptor de eventos de OpenTelemetry, o los agentes de cualquier suite de observabilidad hacen exactamente una cosa: hacen watch sobre los Events y los mandan a un sitio donde duren más de una hora.

Es decir: el mejor argumento para montar el stack de la lección siguiente no es "quiero gráficas bonitas". Es que Kubernetes tira su mejor información a la basura cada sesenta minutos.

Los Reason que vas a ver el resto de tu vida

ReasonQuién lo emiteQué significa
FailedSchedulingschedulerNingún nodo cumple los requisitos. Capacidad, taints o afinidad.
FailedkubeletNo pudo descargar la imagen (nombre, etiqueta o credenciales).
BackOffkubeletReintentando con espera creciente. Va con CrashLoopBackOff o ImagePullBackOff.
UnhealthykubeletUna probe ha fallado. Mira cuál: liveness reinicia, readiness solo saca del Service.
OOMKillingkubeletEl contenedor superó su limits.memory y el kernel lo mató.
EvictedkubeletEl nodo se quedó sin recursos y desalojó al Pod. Mira su clase QoS.
FailedMountkubeletNo pudo montar un volumen. Suele ser un Secret o ConfigMap que no existe.
PreemptedschedulerOtro Pod de mayor prioridad se quedó con su sitio.
NodeNotReadynode controllerEl nodo dejó de dar señales de vida.

Resumen

  • Los Events son objetos de la API que emiten los componentes del clúster. Son la primera línea de diagnóstico, y son gratis.
  • El Reason te dice qué ha fallado. El emisor te dice quién tiene el problema, y por tanto dónde seguir mirando.
  • kubectl logskubectl events: los logs son la aplicación hablando; los eventos son Kubernetes hablando de la aplicación. Un contenedor al que matan por una liveness rota no deja rastro en sus propios logs.
  • kubectl events --types=Warning -A es el comando con el que empezar cualquier incidencia.
  • Los Events caducan a la hora y no son una auditoría. Si te importan, hay que exportarlos. Ese es el punto de partida de la lección siguiente.
Previous lesson
El operador