Construye la imagen de la tienda
Todo el libro despliega tienda/api:2.1.0 dándola por hecha. Aquí la construyes tú.
En /home/laborant/projects/api tienes el código de la api —un servidor HTTP en Go, solo librería estándar, que escucha en el 8080— y el Dockerfile con el que se viene construyendo. Sus capas están bien ordenadas y funciona, y ese es el problema: nadie vuelve a mirar un Dockerfile que funciona.
Empieza por construirlo tal cual está, desde la pestaña dev-machine, y mira lo que sale:
cd ~/projects/api
docker build -t registry.iximiuz.com/tienda/api:2.1.0 .
docker images registry.iximiuz.com/tienda/api
Más de 350 MB para servir una línea de texto. Dentro van el compilador de Go, su biblioteca estándar en código fuente, el gestor de paquetes de Alpine y tu propio código. Y el proceso corre como root.
Tu trabajo es dejar esa imagen en menos de 30 MB, sin shell y sin root, publicarla en el registro del clúster y desplegarla.
La imagen, publicada
El registro del playground vive en registry.iximiuz.com y es el único camino por el que tu imagen puede llegar al clúster: el daemon de Docker de esta máquina no es el runtime que ejecuta los Pods.
Pista 1
El nombre completo de una imagen incluye el registro. Si construyes con -t registry.iximiuz.com/tienda/api:2.1.0, el docker push de ese mismo nombre ya sabe a dónde ir. Y si el registro pidiera credenciales, son iximiuzlabs / rules!.
La imagen, mínima
Ahora la parte que importa: que lo que se despliega no sea lo mismo en lo que compilaste.
Criterio: menos de 30 MB, un usuario no-root declarado en la imagen, y sin shell dentro.
Pista 2
Un Dockerfile puede tener varios FROM. Cada uno abre un stage, y solo el último acaba siendo la imagen. Un stage compila, con todo lo que eso necesita; el otro parte de una base mínima y se trae únicamente el binario con COPY --from=.
Pista 3
Para el stage final necesitas una base sin sistema operativo alrededor: gcr.io/distroless/static o directamente scratch. Ahí no hay shell, ni gestor de paquetes, ni ls, y por eso pesan lo que pesan.
Un binario de Go solo funciona en esas bases si está enlazado estáticamente: compila con CGO_ENABLED=0.
Pista 4
El usuario no se cambia en el Pod, se declara en la imagen con la instrucción USER y un UID numérico, por ejemplo USER 10001. Un UID numérico y no un nombre: sin /etc/passwd dentro de la imagen, un nombre no se puede resolver.
Corriendo en el clúster
En /home/laborant/manifests/api.yaml está el Pod que la usa.
Pista 5
Si el Pod se queda en ImagePullBackOff cuando la imagen sí está publicada, comprueba el nombre exacto. Y si arranca y se cae sin decir nada, casi siempre es el binario: enlazado dinámicamente contra unas librerías que en el stage final no existen.
Por qué importa
Las dos últimas líneas de ese Dockerfile son las que más consecuencias tienen en el resto del libro, y no lo parece.
La base mínima es lo que hace que el capítulo de seguridad pueda dar por hecho que no hay una shell que robar dentro del contenedor, y a la vez es la razón de que depurar en producción duela: cuando quieras un curl ahí dentro no lo vas a tener, y tendrás que recurrir a un contenedor efímero con kubectl debug.
El USER no-root es literalmente el requisito que Pod Security Admission comprueba con runAsNonRoot en el nivel restricted. Una imagen que corre como root no pasa ese nivel, y no hay campo del Pod que lo arregle sin tocar la imagen.