getting-started/docs/tutorial/image-building-best-practices/index.es.md
Publio Estupiñán 2fd5b07926 Multi language support (Spanish)
mkdocs-i18n implemented for new languages
Spanish translation
2021-05-30 21:38:41 -05:00

12 KiB

Escaneo de Seguridad

Cuando haya creado una imagen, es una buena práctica escanearla en busca de vulnerabilidades de seguridad utilizando el comando docker scan. Docker se ha asociado con Snyk para proporcionar el servicio de análisis de vulnerabilidades.

Por ejemplo, para escanear la imagen getting-started que creó anteriormente en el tutorial, simplemente escriba

docker scan getting-started

El análisis utiliza una base de datos de vulnerabilidades que se actualiza constantemente, por lo que el resultado que ve variará a medida que se descubran nuevas vulnerabilidades, pero podría verse así:

✗ Low severity vulnerability found in freetype/freetype
  Description: CVE-2020-15999
  Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
  Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
  From: freetype/freetype@2.10.0-r0
  From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
  Fixed in: 2.10.0-r1

✗ Medium severity vulnerability found in libxml2/libxml2
  Description: Out-of-bounds Read
  Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
  Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
  From: libxml2/libxml2@2.9.9-r3
  From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
  From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
  Fixed in: 2.9.9-r4

El resultado enumera el tipo de vulnerabilidad, una URL para obtener más información y, lo que es más importante, qué versión de la biblioteca relevante corrige la vulnerabilidad.

Hay varias otras opciones, sobre las que puede leer en documentación docker scan.

Además de escanear su imagen recién construida en la línea de comando, también puede configurar Docker Hub para escanear todas las imágenes recién enviadas automáticamente, y luego puede ver los resultados tanto en Docker Hub como en Docker Desktop.

Escaneo de vulnerabilidades del hub{: style=width:75% } {: .text-center }

Capas de imagen

¿Sabías que puedes mirar lo que compone una imagen? Usando el comando docker image history, puede ver el comando que se usó para crear cada capa dentro de una imagen.

  1. Utilice el comando docker image history para ver las capas en la imagen de getting-started que creó anteriormente en el tutorial.

    docker image history getting-started
    

    Debería obtener un resultado que se parezca a esto (los identificadores de fechas pueden ser diferentes).

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    Cada una de las líneas representa una capa en la imagen. La pantalla aquí muestra la base en la parte inferior con la capa más nueva en la parte superior. Con esto, también puede ver rápidamente el tamaño de cada capa, lo que ayuda a diagnosticar imágenes grandes.

  2. Notarás que varias de las líneas están truncadas. Si agrega el indicador --no-trunc, obtendrá la salida completa (sí ... es curioso cómo usa una bandera truncada para obtener una salida no truncada, ¿eh?)

    docker image history --no-trunc getting-started
    

Almacenamiento en caché de capas

Ahora que ha visto las capas en acción, hay una lección importante que aprender para ayudar a reducir los build times de las imágenes de su contenedor.

Una vez que cambia una capa, todas las capas posteriores también deben volver a crearse

Veamos el Dockerfile que estábamos usando una vez más ...

FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

Volviendo a la salida del historial de la imagen, vemos que cada comando en el Dockerfile se convierte en una nueva capa en la imagen. Quizás recuerde que cuando hicimos un cambio en la imagen, las dependencias yarn tuvieron que reinstalarse. ¿Hay alguna forma de solucionar este problema? No tiene mucho sentido distribuir las mismas dependencias cada vez que construimos, ¿verdad?

Para solucionar esto, necesitamos reestructurar nuestro Dockerfile para ayudar a soportar el almacenamiento en caché de las dependencias. Para aplicaciones Node-based, esas dependencias se definen en el archivo package.json. Entonces, ¿qué pasa si copiamos solo ese archivo primero, instalamos las dependencias y, luego, copiamos todo lo demás? Luego, solo recreamos las dependencias yarn si hubo un cambio en el package.json. ¿Tener sentido?

  1. Actualice el Dockerfile para copiar en el package.json primero, instale las dependencias y luego copie todo lo demás en.

    FROM node:12-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "src/index.js"]
    
  2. Cree un archivo llamado .dockerignore en la misma carpeta que Dockerfile con el siguiente contenido.

    node_modules
    

    Los archivos .dockerignore son una manera fácil de copiar selectivamente solo archivos relevantes de imagen. Puede leer más sobre esto aquí. En este caso, la carpeta node_modules debe omitirse en el segundo paso COPY porque de lo contrario, posiblemente sobrescribirá los archivos que fueron creados por el comando en el paso RUN. Para obtener más detalles sobre por qué se recomienda esto para las aplicaciones Node.js y otras prácticas recomendadas, echa un vistazo a su guía en Dockerizar una aplicación web Node.js.

  3. Cree una nueva imagen usando docker build.

    docker build -t getting-started .
    

    Debería ver un resultado como este ...

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package.json yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    Verá que se reconstruyeron todas las capas. Perfectamente bien desde que cambiamos bastante el Dockerfile.

  4. Ahora, haga un cambio en el archivo src/static/index.html (como cambiar el <title> para decir "The Awesome Todo App").

  5. Cree la imagen de Docker ahora usando docker build -t Getting started . nuevamente. Esta vez, su salida debería verse un poco diferente.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package.json yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    Antes que nada, ¡Deberías notar que la construcción fue MUCHO más rápida! Y verá que todos los pasos 1-4 tienen "Using cache". Entonces, ¡hurra! Estamos usando la caché de compilación. Pushing y pulling esta imagen y las actualizaciones también será mucho más rápido. ¡Hurra!

Construcciones Multi-Stage

Si bien no vamos a profundizar demasiado en este tutorial, las compilaciones multi-stage son una herramienta increíblemente poderosa para ayudar a usar multiple stages para crear una imagen. Tienen varias ventajas:

  • Separe las dependencias en build-time de las dependencias en runtime
  • Reduzca el tamaño general de la imagen enviando solo lo que su aplicación necesita para ejecutarse

Ejemplo Maven/Tomcat

Al crear aplicaciones basadas en Java, se necesita un JDK para compilar el código fuente en código de bytes Java. Sin embargo, ese JDK no es necesario en producción. Además, es posible que esté utilizando herramientas como Maven o Gradle para ayudar a crear la aplicación. Esos tampoco son necesarios en nuestra imagen final. Las compilaciones de varias etapas ayudan.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

En este ejemplo, usamos una etapa (llamada build) para realizar la compilación real de Java usando Maven. En la segunda etapa (comenzando en FROM tomcat), copiamos archivos de la etapa de build. La imagen final es solo la última etapa que se está creando (que se puede anular usando el indicador --target).

Ejemplo React

Al crear aplicaciones React, necesitamos un entorno Node para compilar el código JS (normalmente JSX), SASS stylesheets, y más en HTML, JS, y CSS estático. Si no estamos haciendo renderizado del lado del servidor, ni siquiera necesitamos un entorno Node para nuestra compilación de producción. ¿Por qué no enviar los recursos estáticos en un contenedor nginx estático?

FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

Aquí, estamos usando una imagen node: 12 para realizar la compilación (maximizando el almacenamiento en caché de la capa) y luego copiando la salida en un contenedor nginx. Genial, ¿eh?

Resumen

Al comprender un poco cómo se estructuran las imágenes, podemos crear imágenes más rápido y enviar menos cambios. El escaneo de imágenes nos da la confianza de que los contenedores que estamos ejecutando y distribuyendo son seguros. Las compilaciones Multi-stage también nos ayudan a reducir el tamaño general de la imagen y a aumentar la seguridad del contenedor final al separar las dependencias en build-time de las dependencias en runtime.