Como crear imágenes Distroless para Node.js y Go



¿Qué es una imagen Distroless?

Las imágenes distroless están basadas en imágenes Debian, pero son muy diferentes a las de Ubuntu. En primer lugar, Google gestiona estos contenedores, y nos da la confianza de que van a estar preparadas para no tener ningún problema.

La segunda diferencia es que hay contenedores específicos para lenguajes concretos. ¿Por qué tener contenedores específicos para imágenes específicas? ¿Por qué no instalarlos todos en un solo contenedor? Además del problema del tamaño, Google ha eliminado el 90% del contenedor y ha mantenido únicamente lo que se requiere para ejecutar el lenguaje específico. Esto elimina en gran medida la cantidad de vulnerabilidades que se pueden encontrar en el contenedor. Por ejemplo, digamos que alguien es capaz y quiere ejecutar un comando en el contenedor dentro de tu cluster. ¿Sabes que pasaría? Nada, ya que estos contenedores no contienen ninguna shell. Pongamos que se ha encontrado una vulnerabilidad en un paquete de Debian. Lo más probable es que el paquete no exista en la imagen y no tengamos dicha vulnerabilidad.



¿Qué lenguajes se pueden utilizar?

A día hoy solo se pueden utilizar estos lenguajes y versiones:

  • Base
  • Static
  • Dotnet
  • CC
  • Java
  • Node.js
  • Python 2.7
  • Python 3

También puedes consultarlas en el repositorio de imágenes de Google en este enlace.



Diferencias entre la imagen static y base:

La imagen static contiene un sistema Linux basado en glibc que contiene certificados CA, tzdata, un directorio en /etc/passwd para un usuario root y un directorio temporal en /tmp.

La imagen base contiene los paquetes glibc, libssl, openssl y todo lo nombrado anteriormente en la imagen static. La mayoría de aplicaciones deberían usar base.



Ventajas de Distroless

  • En la imagen no hay nada más que tu aplicación, con lo cual si alguien tiene acceso a tu cluster no podrá ejecutar ningún tipo de programa en el contenedor.
  • Las imágenes tienden a ser más livianas.
  • A menos paquetes, menos posibilidades de tener vulnerabilidades.



Desventajas de Distroless

  • No es recomendable para desarrollar, ya que no puedes entrar al contenedor para debuggear el código.
  • Algunas veces tu aplicación necesitará alguna dependencia del sistema, esta al no tener shell no podremos lanzar ningún comando para instalar dependencias.
  • Solo puedes emplear un lenguaje en cada imagen.



Demo

A continuación explicaré como construir la imagen de un servidor web tanto en Node.js como en Go. También optimizaremos las imágenes haciendo uso de multi-stage.

Para ello clonaremos mi repositorio y nos situaremos en el directorio distroless:

git clone https://github.com/victorargento/victorargento.git && cd victorargento/distroless



Node.js

La imagen de Node.js tiene 3 fases: construcción de la aplicación para poder ser compilada, instalación solamente de las dependencias de producción y por último transferir la aplicación a la imagen distroless.

# Construimos la aplicación con las dependencias de desarrollo.
FROM node:16-alpine3.14 AS pre-build-env
WORKDIR /app
COPY package*.json ./

RUN npm install --only=development

COPY . .
RUN npm run build

# Instalamos solamente las dependencias necesarias para la ejecución de la aplicación.
FROM node:16-alpine3.14 AS build-env
WORKDIR /app
COPY package*.json ./

RUN npm install --only=production

# Copiamos la carpeta dist construida en la imagen PRE-BUILD-ENV.
COPY --from=pre-build-env /app/dist ./dist
# Este paso podemos omitirlo, ya que solamente necesitas la carpeta dist para ejecutar la aplicación,
# pero tal vez tu aplicación necesite algunos archivos que tienes en la raíz del proyecto, si ese es el caso, copia solamente archivos necesarios.
COPY . . 

# Volvemos a copiar la aplicación, pero esta vez en la imagen Distroless.
FROM gcr.io/distroless/nodejs:16
USER nonroot:nonroot
WORKDIR /app
COPY --from=build-env --chown=nonroot:nonroot /app /app
CMD ["dist/index.js"]

Para construir la imagen ejecutaremos el siguiente comando:

docker build -t hello-app:node ./hello-app-node



Go

La imagen de Go solamente tiene 2 fases: construcción del binario y transferir la aplicación a la imagen distroless.

# Construimos el binario en la imagen que contiene Go.
FROM golang:1.17 as build-env

WORKDIR /go/src/app
COPY *.go ./

RUN go mod init
RUN go get -d -v ./...
RUN go vet -v
RUN go test -v

RUN CGO_ENABLED=0 go build -o /go/bin/app

# Copiamos el binario desde BUILD-ENV a la imagen Distroless.
# En este caso utilizaremos la imagen static, que contiene las dependencias mínimas,
# si nuestra aplicación depende de paquetes como glibc, libssl o openssl utilizaremos la imagen base
# FROM gcr.io/distroless/base
FROM gcr.io/distroless/static
USER nonroot:nonroot

COPY --from=build-env --chown=nonroot:nonroot /go/bin/app /
CMD ["/app"]

Para construir la imagen ejecutaremos el siguiente comando:

docker build -t hello-app:go ./hello-app-go

Source link