Algunos incidentes de producción empiezan con un incendio. La mayoría empiezan con un encogimiento de hombros: “Qué raro… funcionaba en mi portátil.” Envías un contenedor, pasa la CI, funciona bien en staging, y luego en producción parece que despertó en otro universo.
Normalmente así es. No porque Docker sea poco fiable. Porque los humanos lo son. Específicamente: humanos que tratan las etiquetas de imagen como números de versión cuando en realidad son apodos. Los apodos mienten.
La única regla: nunca desplegar etiquetas flotantes
Aquí está la regla que termina la mayor parte del drama de “funciona en mi máquina” con contenedores:
Despliega por digest, no por etiqueta mutable.
No “latest”. No “main”. Ni siquiera “1.2”. En producción (y, idealmente, también en staging) despliegas identificadores inmutables: image@sha256:….
¿Qué cuenta como una etiqueta “flotante”?
Cualquier etiqueta que pueda moverse sin que lo notes. Lo cual es… la mayoría de las etiquetas.
latestes flotante por diseño.main,stable,prod,candidateson flotantes porque los humanos las mueven.1.2flota cuando vuelves a construir la misma etiqueta después de parchear una imagen base o de “arreglar solo el Dockerfile”.- Incluso
1.2.3flota si tu registro permite sobrescribir etiquetas y tu proceso no lo impide.
¿Cuál es el efecto práctico de desplegar por digest?
Hace que “qué está corriendo” tenga una respuesta en una sola línea. Hace que los rollbacks sean deterministas. Elimina la clase de incidentes donde el mismo YAML de Kubernetes despliega cosas distintas en días diferentes.
También hay un aspecto cultural: te obliga a tratar “build” y “deploy” como eventos separados. El build produce un artefacto. El deploy selecciona ese artefacto. Si tu paso de “deploy” dispara una reconstrucción, no estás desplegando artefactos; estás apostando con un compilador.
Broma seca #1: La etiqueta “latest” es como la leche en la nevera de la oficina: técnicamente etiquetada, emocionalmente peligrosa y nunca lo que crees que es.
¿Cuándo está bien usar etiquetas?
Las etiquetas están bien para humanos y flujos de trabajo:
- En CI: “este build produjo la etiqueta
pr-1847.” - En pipelines de promoción: “mover
candidatepara que apunte al digest X.” - En desarrollo: “ejecuta
:latestlocalmente si disfrutas las sorpresas.”
Pero producción debe ejecutar digests. Si absolutamente necesitas mantener etiquetas en manifiestos por ergonomía, entonces aplica inmutabilidad de etiquetas en el registro y sigue registrando el digest que desplegaste. De lo contrario vuelves a confiar en un apodo.
Por qué ocurre la deriva (incluso con procesos “buenos”)
El comportamiento de pull no es un juicio moral
Docker y Kubernetes no intentan engañarte. Intentan ser eficientes. Si una imagen con el mismo nombre existe localmente, el runtime puede no volver a descargarla. Si tienes múltiples nodos, cada nodo tiene su propia caché local. Si tu política es “if-not-present”, la deriva es básicamente una característica.
Las reconstrucciones en CI significan que estás enviando un objetivo en movimiento
Un antipatrón clásico se ve así:
- Los desarrolladores hacen merge en main.
- CI construye
myapp:latesty la empuja. - CD despliega
myapp:latest. - Un hotfix dispara otra compilación que también empuja
myapp:latest. - Algunos nodos tiran la nueva versión, otros no. O la descargan en distintos momentos.
Ahora tienes un despliegue canario no planeado distribuido por el comportamiento de la caché. No será el canario que querías.
Las imágenes base mutan y a menudo quieres que lo hagan
El parcheo de seguridad es real. Muchos equipos reconstruyen imágenes para incorporar capas base parchadas. Eso está bien. Pero si reconstruyes y sobrescribes la misma etiqueta, convertiste una buena acción de higiene de seguridad en un problema de ambigüedad de despliegue.
Los registros, proxies y mirrors “útiles” se involucran
Si tienes una caché de registro, un proxy pull-through o un mirror, ahora tienes otra capa que puede devolver resultados obsoletos si está mal configurada. Tu portátil puede hablar con Docker Hub. Producción puede hablar con un mirror corporativo con su propia lógica de refresco. Misma etiqueta. Bytes diferentes. Disfruta tu tarde de depuración.
Hechos e historia que explican por qué esto sigue pasando
- Docker Hub popularizó “latest” como elección UX por defecto. Los flujos tempranos de Docker priorizaban la comodidad, no la procedencia estricta.
- El almacenamiento direccionado por contenido es anterior a los contenedores. El modelo de Git (hash identifica contenido) es más antiguo y conceptualmente similar a los digests de imagen; las etiquetas son como nombres de ramas.
- OCI estandarizó los formatos de imagen. Lo que tiras hoy está en gran medida alineado con una especificación abierta, por eso múltiples runtimes y registros interoperan.
- Las imágenes multi-arch cambiaron el significado de “la imagen”. Una etiqueta puede apuntar a un índice que selecciona diferentes manifiestos por plataforma según el nodo.
- Kubernetes eligió una ImagePullPolicy por defecto basada en etiquetas. Si usas
:latest, por defecto es Always; de lo contrario a menudo es IfNotPresent—sutil y muy malentendido. - El cache de capas es una optimización con consecuencias. Reduce la red y acelera despliegues, pero también oculta las actualizaciones de etiqueta a menos que fuerces pulls.
- La seguridad de la cadena de suministro impulsó el pin de digest al mainstream. Procedencia, firmas, SBOMs—estas prácticas tratan el digest como ancla.
- La “infraestructura inmutable” debía hacer los despliegues aburridos. Los contenedores facilitaron el empaquetado; los digests facilitan saber exactamente lo que empaquetaste.
Un pensamiento para la mentalidad de confiabilidad que vale la pena tatuar en tu pipeline de despliegue:
Werner Vogels (idea parafraseada): trata todo como desechable y diseña pensando en fallos; pasarás menos tiempo rezando y más tiempo restaurando el servicio.
Tres mini-historias corporativas desde las trincheras
Mini-historia #1: El incidente causado por una suposición equivocada
Una empresa SaaS de tamaño medio tenía una configuración ordenada: Kubernetes en producción, GitOps para manifiestos, un registro de contenedores y un controlador CD. Estaban orgullosos de su disciplina. Entonces sufrieron una caída del inicio de sesión que solo afectó a aproximadamente un tercio de los usuarios. No a todos. No a todas las regiones. Suficiente para arruinar la cena del on-call.
La primera pista fue que la firma del error variaba según el pod. Algunos pods lanzaban una excepción sobre una ruta faltante del CA bundle. Otros estaban bien. El manifiesto de despliegue decía myorg/auth-service:1.8. Todos asumieron que 1.8 significaba una sola cosa. Esa fue la suposición equivocada.
Habían reconstruido :1.8 para incorporar CVE de la imagen base una semana antes. El registro permitía sobrescribir etiquetas. Algunos nodos todavía tenían las capas antiguas en caché; otros descargaron la imagen reconstruida durante la rotación rutinaria. Kubernetes estaba configurado en IfNotPresent. El estado final fue un despliegue con cerebro partido: dos imágenes diferentes bajo una misma etiqueta, corriendo simultáneamente.
La solución fue rápida una vez diagnosticado: pinear por digest en el despliegue, forzar un rollout y luego hacer las etiquetas inmutables para todo lo que pareciera un release. El postmortem fue contundente: “Tratamos una etiqueta como identidad.” A la semana auditaron cada manifiesto de producción buscando etiquetas flotantes y las reemplazaron por digests, manteniendo una etiqueta amigable para humanos en la salida de CI.
Mini-historia #2: La optimización que salió mal
Otra organización ejecutaba una API de alto rendimiento y se cansó de la lentitud en el escalado de nodos. Tirar imágenes durante el autoscaling provocaba cold starts. Así que introdujeron un mirror de registro dentro del VPC y cacheo agresivo en los nodos. Funcionó: los scale-outs fueron mucho más rápidos y la salida de red cayó.
Luego desplegaron una corrección crítica y no “se aplicó” en todas partes. Algunos nodos seguían sirviendo el comportamiento antiguo durante horas. El mirror respetaba cabeceras de cache y tenía un intervalo de refresco; los nodos preferían imágenes cacheadas locales para evitar pulls. El equipo había construido velocidad aumentando la obsolescencia.
El problema no fue que existiera caché. El problema fue que la caché podía decidir la corrección. Su despliegue referenciaba api-gateway:stable, actualizado por CI. La etiqueta se movió, pero la caché no lo notó. El controlador de despliegue pensó que había desplegado. Lo había hecho, pero no las bits que pretendían.
Se quedaron con el mirror, porque los scale-outs rápidos valían la pena. Pero cambiaron el contrato: la promoción movía una etiqueta a un digest, los despliegues usaban el digest, y las cachés quedaron seguras porque el identificador dejó de moverse. También añadieron una regla simple: “si el manifiesto de prod contiene una etiqueta con dos puntos sin @sha256, falla el PR.” Aburrido. Efectivo.
Mini-historia #3: La práctica aburrida pero correcta que salvó el día
Una empresa relacionada con finanzas (de las que se auditan por placer) tenía un proceso de release poco glamuroso. Cada build producía una imagen, la firmaba, registraba el digest y almacenaba ese digest junto al ticket de cambio. Los entornos promovían solo por digest. Sin excepciones. Los desarrolladores se quejaban de que era lento y “a la manera empresarial”.
Un viernes cayó una vulnerabilidad en el runtime con titulares alarmantes. Seguridad pidió parche inmediato. El equipo reconstruyó imágenes base, reconstruyó servicios y empujó nuevas imágenes. Entonces, en medio de la prisa, un cambio distinto se coló accidentalmente en el contexto de build de un servicio: un ajuste de configuración no revisado. En muchas empresas, así es como se consigue un outage de fin de semana.
Lo que los salvó fue esto: la promoción requería una selección explícita de digest. El build accidental produjo un digest que no coincidía con el ticket de cambio aprobado. La pipeline se negó a promoverlo. El equipo parcheó la vulnerabilidad usando los digests correctos y mantuvo la configuración accidental fuera de prod.
Nadie recibió aplausos. No hubo depuración heroica. El sistema simplemente se negó a ser ingenioso. Ese es el objetivo.
Broma seca #2: Si tu proceso de despliegue necesita un héroe, tu proceso es básicamente un reality show con peor iluminación.
Tareas prácticas: comandos, salidas y decisiones
Esta sección es la parte “tengo un terminal y un problema”. Cada tarea incluye un comando, un fragmento de salida realista, lo que significa y la decisión que tomas a partir de ello.
Tarea 1: Ver qué imagen está usando realmente un contenedor en ejecución (Docker)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.ID}}'
NAMES IMAGE ID
auth-service registry.local/auth:1.8 7c1d2e9a0b53
billing-worker registry.local/billing:prod 3a9b6d1c4f21
Qué significa: Ves etiquetas, no digests. Esto es una pista, no una prueba. Una etiqueta puede haberse movido desde que el contenedor arrancó.
Decisión: Inspecciona el contenedor para encontrar el mapeo inmóvil entre image ID y digest.
Tarea 2: Inspeccionar image ID y repo digests del contenedor (Docker)
cr0x@server:~$ docker inspect auth-service --format '{{.Image}} {{json .RepoDigests}}'
sha256:0e3d2b4f1e1b6d0f1b6b8c9a3f2a1d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4 ["registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5"]
Qué significa: El contenedor en ejecución referencia un digest específico. Ese digest es lo que debes registrar y comparar entre entornos.
Decisión: Si el manifiesto despliega por etiqueta, cámbialo para que despliegue por este digest (o por el aprobado).
Tarea 3: Comprobar si una etiqueta apunta actualmente a un digest diferente (verdad remota)
cr0x@server:~$ docker pull registry.local/auth:1.8
1.8: Pulling from auth
Digest: sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Status: Image is up to date for registry.local/auth:1.8
Qué significa: Se muestra el digest actual del registro para :1.8. Si no coincide con el digest de la Tarea 2, la etiqueta se movió.
Decisión: Trátalo como una falla de higiene de release. Deja de desplegar por esta etiqueta; fija el digest e investiga por qué cambió la etiqueta.
Tarea 4: Listar imágenes locales y ver repo digests (capturar “misma etiqueta, distintos digests”)
cr0x@server:~$ docker images --digests --format 'table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}}'
REPOSITORY TAG DIGEST IMAGE ID CREATED SINCE
registry.local/auth 1.8 sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e 0e3d2b4f1e1b 9 days ago
registry.local/auth 1.8 <none> 9a1b2c3d4e5f 15 days ago
Qué significa: Puedes tener múltiples imágenes locales que en su momento estaban relacionadas con la misma etiqueta. La antigua puede estar sin etiqueta ahora.
Decisión: No confíes en la presencia de la etiqueta en la caché; usa digests en los despliegues y limpia imágenes antiguas si el espacio en disco es un problema.
Tarea 5: Forzar un pull limpio para eliminar ambigüedad de caché (Docker)
cr0x@server:~$ docker rmi registry.local/auth:1.8
Untagged: registry.local/auth:1.8
Deleted: sha256:0e3d2b4f1e1b6d0f1b6b8c9a3f2a1d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4
cr0x@server:~$ docker pull registry.local/auth:1.8
1.8: Pulling from auth
Digest: sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Status: Downloaded newer image for registry.local/auth:1.8
Qué significa: Ahora la etiqueta local se resuelve al contenido actual del registro.
Decisión: Si esto cambia el comportamiento, acabas de probar una deriva impulsada por caché. Arregla el proceso, no el nodo.
Tarea 6: En Kubernetes, ver qué imagen afirma el Deployment vs qué imagen corren realmente los pods
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
registry.local/auth:1.8
cr0x@server:~$ kubectl -n prod get pods -l app=auth-service -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'
auth-service-6d9df7c6d9-7h5qv docker-pullable://registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5
auth-service-6d9df7c6d9-km2nw docker-pullable://registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Qué significa: Mismo Deployment con la misma etiqueta, diferentes digests en distintos pods. Eso es deriva.
Decisión: Fija el Deployment a un digest único y haz rollout. Luego arregla la pipeline para evitar sobrescrituras de etiquetas.
Tarea 7: Verificar ImagePullPolicy (Kubernetes) para entender el comportamiento de caché
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].imagePullPolicy}{"\n"}'
IfNotPresent
Qué significa: Los nodos pueden no tirar aunque la etiqueta cambie, dependiendo del estado de la caché.
Decisión: No “arregles” esto poniendo Always en todas partes como control principal. Arregla el identificador (digest). Usa Always selectivamente cuando realmente quieras seguimiento de etiqueta en dev.
Tarea 8: Confirmar la revisión del rollout y correlacionarla con un cambio de imagen
cr0x@server:~$ kubectl -n prod rollout history deploy/auth-service
deployment.apps/auth-service
REVISION CHANGE-CAUSE
12 image updated to registry.local/auth:1.8
13 configmap reload
Qué significa: El historial te dice “imagen actualizada” pero no el digest a menos que lo registres explícitamente.
Decisión: Empieza a anotar los despliegues con el digest (o commit SHA + digest) en el momento del despliegue.
Tarea 9: Inspeccionar el digest de una etiqueta en el registro mediante inspección del manifiesto (sin adivinar)
cr0x@server:~$ docker manifest inspect registry.local/auth:1.8 | head -n 12
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e",
"platform": {
"architecture": "amd64",
Qué significa: La etiqueta apunta a un índice (multi-arch). Puedes ver los digest por plataforma.
Decisión: Fija el digest del índice si quieres una referencia única a través de arquitecturas, o fija los digests por plataforma si necesitas control determinista por arquitectura.
Tarea 10: Encontrar qué nodo está ejecutando qué digest (remediación dirigida)
cr0x@server:~$ kubectl -n prod get pods -l app=auth-service -o wide
NAME READY STATUS RESTARTS AGE IP NODE
auth-service-6d9df7c6d9-7h5qv 1/1 Running 0 2h 10.20.1.14 ip-10-0-4-21
auth-service-6d9df7c6d9-km2nw 1/1 Running 0 2h 10.20.2.33 ip-10-0-6-18
cr0x@server:~$ kubectl -n prod get pod auth-service-6d9df7c6d9-7h5qv -o jsonpath='{.status.containerStatuses[0].imageID}{"\n"}'
docker-pullable://registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5
Qué significa: Puedes localizar la deriva en nodos, no solo en pods.
Decisión: Si necesitas una acción inmediata de contención, cordona/desprograma el nodo problemático después de haber arreglado el manifiesto; de lo contrario volverá a ocurrir.
Tarea 11: Verificar qué piensa containerd que está presente (verdad a nivel runtime)
cr0x@server:~$ sudo ctr -n k8s.io images ls | grep registry.local/auth
registry.local/auth:1.8 application/vnd.oci.image.index.v1+json sha256:1111aaaabbbb2222cccc3333dddd4444eeee5555ffff6666777788889999aaaa 245.3 MiB linux/amd64,linux/arm64
registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e application/vnd.oci.image.manifest.v1+json sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e 84.1 MiB linux/amd64
Qué significa: El runtime puede almacenar referencias por etiqueta y por digest. El listado muestra lo que está cacheado y para qué plataformas.
Decisión: Si los pods no están tirando inesperadamente, confirma si el digest ya está presente y si tu política de pull y comportamiento del runtime coinciden con tus expectativas.
Tarea 12: Confirmar que un rollout realmente reemplazó pods (no solo actualizó un spec)
cr0x@server:~$ kubectl -n prod rollout status deploy/auth-service
deployment "auth-service" successfully rolled out
cr0x@server:~$ kubectl -n prod get rs -l app=auth-service --sort-by=.metadata.creationTimestamp
NAME DESIRED CURRENT READY AGE
auth-service-6d9df7c6d9 6 6 6 2h
auth-service-5c7bbf99b8 0 0 0 7d
Qué significa: Tienes un único ReplicaSet activo. Bien. Si hay múltiples activos inesperadamente, podrías tener rollouts parciales o terminaciones atascadas.
Decisión: Si la deriva persiste, el problema probablemente sea la referencia de imagen en sí (etiqueta que se mueve) o caché de nodos combinado con identificadores mutables.
Tarea 13: Detectar si tu manifiesto contiene una etiqueta flotante (guardarraíl barato)
cr0x@server:~$ grep -RIn 'image: .*:[^ @]\+$' k8s/prod | head
k8s/prod/auth/deploy.yaml:27:image: registry.local/auth:1.8
k8s/prod/api/deploy.yaml:19:image: registry.local/api:latest
Qué significa: Las líneas muestran imágenes que solo usan etiqueta. Estás a una sobrescritura de registro de la ambigüedad.
Decisión: Reemplaza por la forma con digest o aplica inmutabilidad de etiquetas; preferiblemente ambas.
Tarea 14: Fijar una imagen por digest en Kubernetes (la solución real)
cr0x@server:~$ kubectl -n prod set image deploy/auth-service auth-service=registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
deployment.apps/auth-service image updated
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Qué significa: Tu desired state ahora es inmutable.
Decisión: Commitea este cambio de vuelta a Git si usas GitOps; no dejes que kubectl sea la única fuente de verdad.
Tarea 15: Registrar el digest como anotación del despliegue (hacer auditorías y rollbacks sensatos)
cr0x@server:~$ kubectl -n prod annotate deploy/auth-service deployed-image-digest=sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e --overwrite
deployment.apps/auth-service annotated
cr0x@server:~$ kubectl -n prod get deploy/auth-service -o jsonpath='{.metadata.annotations.deployed-image-digest}{"\n"}'
sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Qué significa: Ahora puedes responder “¿qué exactamente desplegamos?” sin explorar logs.
Decisión: Haz que la pipeline haga esto automáticamente en la promoción.
Guion de diagnóstico rápido
Si producción se comporta raro y sospechas deriva de imagen/versión, no empieces por Dockerfiles o grafos de dependencias. Empieza por la identidad.
Primero: confirma si los pods ejecutan el mismo digest
- Revisa el
imageIDde los pods entre réplicas. - Si existen múltiples digests, tienes deriva. Para y arregla primero la referencia de despliegue.
Segundo: verifica qué referencia tiene el deployment (etiqueta o digest)
- Si es una etiqueta: asume que puede moverse. Trátalo como inseguro hasta que se demuestre lo contrario.
- Si es un digest: la deriva no debería ocurrir a menos que trates con confusión multi-arch, múltiples contenedores o múltiples despliegues.
Tercero: compara estado del registro vs comportamiento de caché del nodo
- Confirma qué digest apunta la etiqueta ahora mismo usando inspección del manifiesto o un pull que imprima el digest.
- Revisa ImagePullPolicy y caché del runtime en nodos.
- Si usas un mirror/proxy, confirma que no esté sirviendo mapeos de etiqueta obsoletos.
Cuarto: decide la maniobra de contención
- Mejor: fija el digest y despliega.
- Si necesitas uniformidad de emergencia: drena nodos y fuerza nuevos pulls después de fijar el digest.
- Si es multi-arch: confirma qué digest por plataforma está tirando cada nodo; fija el nivel correcto (index vs manifest).
Quinto: escribe la solución permanente
- Inmutabilidad de etiquetas en el registro para etiquetas de release.
- Pipeline de promoción que mueve entornos por digest.
- Checks de política en CI que bloqueen etiquetas flotantes en manifiestos de prod.
Errores comunes: síntoma → causa raíz → solución
Error 1: “Algunos pods se comportan distinto, pero están en el mismo Deployment”
Síntoma: Mensajes de error distintos, comportamiento distinto, mismo YAML.
Causa raíz: Etiqueta mutable + IfNotPresent + cachés de nodos mixtas (o mirror obsoleto).
Solución: Despliega por digest. Luego haz rollout para reemplazar todos los pods. Fuerza inmutabilidad de etiquetas y deja de sobrescribir etiquetas de release.
Error 2: “Actualizamos la etiqueta, pero nada cambió”
Síntoma: CI dice que empujó :stable, CD dice que desplegó, pero el runtime no cambió.
Causa raíz: El runtime no tiró porque la imagen ya estaba presente; el controlador no vio un cambio de spec que dispare rollout.
Solución: Fija digest (fuerza un cambio de spec). Si debes usar etiquetas, establece una política que fuerce nuevos pulls y dispare rollouts—luego acepta el coste operativo.
Error 3: “Pineamos digests y aun así vemos diferencias entre nodos”
Síntoma: Mismo digest en el manifiesto, pero comportamiento distinto en nodos arm64 vs amd64.
Causa raíz: Pineaste un digest de índice en lugar de un digest de manifiesto por plataforma (o viceversa) y la imagen subyacente por plataforma difiere en detalles sutiles (glibc vs musl, dependencias nativas, etc.).
Solución: Decide si quieres un digest de índice multi-arch único o pinear digests por arquitectura. Prueba ambas arquitecturas. No asumas paridad.
Error 4: “Seguridad reconstruyó imágenes base y ahora prod está inconsistente”
Síntoma: Tras una reconstrucción, algunos pods empiezan a fallar TLS, DNS o lógica de zona horaria.
Causa raíz: Reconstruyeron la misma etiqueta; las actualizaciones de la imagen base cambiaron bundles de certificados, comportamiento de libc o versiones de paquetes.
Solución: Reconstruye con una nueva etiqueta (o etiqueta única), fija el digest para despliegue y ejecuta una promoción con tests. Sigue parcheando imágenes base pero deja de sobrescribir etiquetas usadas por clusters en ejecución.
Error 5: “El rollback no retrocedió”
Síntoma: Reviertes el manifiesto de :prod a :previous, y aún ves el comportamiento nuevo.
Causa raíz: Ambas etiquetas apuntan al mismo digest (la etiqueta se movió), o la etiqueta “previous” fue sobrescrita durante reconstrucciones.
Solución: Haz rollback por digest. Mantén un registro inmutable del último digest conocido bueno por servicio y por entorno.
Error 6: “Pusimos ImagePullPolicy=Always, así que estamos seguros”
Síntoma: Lentitud frecuente en despliegues, limitación de tasa por parte de registros y fallos ocasionales durante problemas del registro.
Causa raíz: Usaste el pull como sustituto de identidad inmutable. Ahora la corrección depende de la disponibilidad del registro.
Solución: Usa digests. Mantén Always para flujos de desarrollo donde el seguimiento de etiqueta sea intencional y asegúrate de la resiliencia del registro/mirror si dependes de él.
Listas de verificación / plan paso a paso
Paso a paso: implementar la Regla de Etiquetas de Imagen en una organización real
- Define la política: “Los manifiestos de prod y staging deben referenciar imágenes por digest.” Escríbelo. Hazlo revisable.
- Decide tu esquema de nombres: Mantén etiquetas para humanos (commit SHA, número de build, número de PR). Digests para máquinas.
- Haz la promoción explícita: Una promoción mueve un digest de un entorno al siguiente. No “reconstruir y redeployar”.
- Aplica inmutabilidad de etiquetas donde sea posible: Al menos para etiquetas de release (p. ej.,
v1.2.3). Si tu registro puede rechazar sobrescrituras, actívalo. - Añade un check en CI: Bloquea manifiestos de prod que contengan
image: repo:tagsin@sha256. - Registra los digests desplegados: Añade anotaciones o metadata de release en Git para que la respuesta a incidentes no dependa de la memoria tribal.
- Decide la estrategia multi-arch: ¿Pineas digests de índice o por arquitectura? Documenta y prueba.
- Entrena al on-call: Enseña “revisar imageID primero” como maniobra por defecto ante comportamientos raros.
- Mantén una lista de last-known-good digests: Uno por servicio por entorno. Esto hace que el rollback sea un cambio de una sola línea.
- Audita periódicamente: Busca manifiestos con etiquetas flotantes; trátalo como certificados TLS expirados—trabajo aburrido y predecible.
Checklist operativo: antes de declarar un incidente “resuelto”
- Todas las réplicas ejecutan el mismo digest (o el conjunto esperado por arquitectura).
- El desired state referencia ese digest, no solo una etiqueta.
- La etiqueta humana para la etiqueta amigable apunta al mismo digest que desplegaste (opcional, pero ayuda a la cordura).
- El rollout completó y los ReplicaSets antiguos están escalados a cero (a menos que los mantengas intencionalmente).
- El plan de rollback es “cambiar el digest de vuelta”, no “esperar que la etiqueta signifique lo que significaba ayer”.
Checklist de release: cómo evitar crear deriva desde el principio
- Cada build crea una etiqueta única (commit SHA, ID de build) y la empuja una vez.
- La promoción selecciona un digest desde la salida del build.
- No hay rebuilds durante el deploy; el deploy consume artefactos.
- Las actualizaciones de imagen base disparan nuevas etiquetas/digests, nunca sobrescriben etiquetas de release usadas por clusters en ejecución.
- La respuesta a incidentes incluye capturar el digest de los pods en ejecución dentro del ticket.
Preguntas frecuentes
1) ¿Por qué es tan malo desplegar con :latest?
Porque no es una versión. Es un puntero que se mueve. No puedes responder con fiabilidad “¿qué está corriendo?” ni hacer rollback con seguridad.
2) ¿No es segura una etiqueta semántica como 1.2.3?
Sólo si tu registro y proceso la hacen inmutable. De lo contrario es solo una etiqueta con mejores modales. Pinchar por digest es la garantía técnica.
3) ¿No complica pinchar digests los flujos para los humanos?
Un poco, hasta que separas “etiquetas humanas” de “identidad máquina”. Mantén etiquetas para navegación y dashboards, pero despliega el digest. Tu yo futuro te lo agradecerá en silencio.
4) ¿Y la ImagePullPolicy de Kubernetes—debería poner Always?
No como tu mecanismo principal de seguridad. Always aumenta el tráfico de pulls y liga la corrección a la disponibilidad del registro. Usa digests para inmutabilidad, y luego escoge la política de pull para rendimiento y frescura según necesidad.
5) Si fijo por digest, ¿puedo dejar de preocuparme por builds reproducibles?
No. El pin de digest asegura que despliegues el mismo artefacto repetidamente. Los builds reproducibles aseguran que el artefacto corresponde al código fuente y a las entradas que pretendías. Resuelven modos de fallo distintos.
6) ¿Cómo hago un rollback limpio?
Mantén un digest last-known-good por servicio. Haz rollback cambiando la imagen del despliegue a ese digest. Evita rollbacks que usen etiquetas como :previous a menos que hagas cumplir inmutabilidad.
7) ¿Por qué dos nodos tiran imágenes distintas para la misma etiqueta?
Porque las cachés son locales y la política de pull varía. Además, las etiquetas multi-arch pueden resolverse distinto según la arquitectura del nodo. Las etiquetas no son identidades estables; son claves de búsqueda.
8) ¿Cuál es la diferencia entre pinear un digest de índice y un digest por plataforma?
Un digest de índice identifica una lista de manifiestos (multi-arch). Un digest por plataforma identifica el manifiesto de imagen específico para amd64, arm64, etc. Pinea al nivel que coincida con tu estrategia de flota.
9) Usamos un mirror de registro. ¿Cambia el consejo?
Lo refuerza. Los mirrors mejoran el rendimiento de pulls pero pueden añadir obsolescencia. Los digests hacen que los mirrors sean seguros porque el contenido cacheado está asociado a identificadores inmutables.
10) ¿Es esto solo un problema de Kubernetes?
No. Cualquier sistema que despliegue contenedores puede sufrir deriva por etiquetas: Docker Compose, Nomad, ECS, Docker en VMs. El problema subyacente es el mismo: referencias mutables.
Conclusión: siguientes pasos que puedes hacer esta semana
Si no haces nada más, haz esto: elige un servicio de producción y cambia su despliegue de repo:tag a repo@sha256:digest. Registra el digest en la metadata del despliegue. Valida que cada réplica ejecute el mismo digest. Acabas de eliminar toda una categoría de ambigüedad.
Luego hazlo sistémico:
- Añade una puerta en CI que rechace etiquetas flotantes en manifiestos de producción.
- Deja de sobrescribir etiquetas de release. Si tu registro soporta inmutabilidad, actívala para los namespaces de release.
- Separa build y deploy: el build produce digests; el deploy selecciona digests. La promoción es un cambio de puntero en Git, no una reconstrucción.
- Enseña al on-call a comprobar
imageIDprimero. Es la verdad más rápida que tienes.
“Funciona en mi máquina” no significa que tus compañeros sean descuidados. Significa que tu sistema permitió ambigüedad. Elimina la ambigüedad. Haz que los bytes sean aburridos.