
El primer paso para desplegar en Kubernetes es colocar tu aplicación en un contenedor. En esta serie, exploraremos cómo puedes crear una imagen de un contenedor pequeño y seguro.
Gracias a Docker, crear imágenes de contenedores nunca ha sido tan fácil. Especifica una imagen base, añade tus cambios y crea el contenedor.

A pesar de que este enfoque es excelente para empezar, utilizar imágenes base predeterminadas puede llevar a un funcionamiento inseguro con imágenes grandes que están llenas de vulnerabilidades.
Además, la mayoría de las imágenes en Docker utilizan Debian o Ubuntu como imagen base, y aunque esto proporciona una excelente compatibilidad y fácil adaptación (el archivo Docker ocupa solo dos líneas de código), las imágenes base pueden añadir cientos de megabytes de carga adicional a tu contenedor. Por ejemplo, un simple archivo de aplicación node.js 'hello-world' ocupa alrededor de 700 megabytes, mientras que el tamaño real de tu aplicación es solo de unos pocos megabytes.

Así, toda esta carga adicional es un desperdicio de espacio digital y un excelente escondite para vulnerabilidades y fallos en el sistema de seguridad. Por lo tanto, veamos dos maneras de reducir el tamaño de la imagen del contenedor.
La primera es utilizar imágenes base de tamaño pequeño, la segunda es implementar el patrón de diseño Builder Pattern. Usar imágenes base más pequeñas es probablemente la forma más sencilla de reducir el tamaño de tu contenedor. Es probable que tu lenguaje o stack que estás utilizando proporcione una imagen original de la aplicación mucho más pequeña que la imagen predeterminada. Echemos un vistazo a nuestro contenedor node.js.

Por defecto, el tamaño de la imagen base node:8 en Docker es de 670 MB, mientras que el tamaño de node:8-alpine es de solo 65 MB, es decir, 10 veces menos. Al usar una imagen base más pequeña como Alpine, reducirás significativamente el tamaño de tu contenedor. Alpine es una distribución de Linux pequeña y ligera, muy popular entre los usuarios de Docker, ya que es compatible con muchas aplicaciones manteniendo un tamaño reducido de los contenedores. A diferencia de la imagen estándar de Docker 'node', 'node:alpine' elimina muchos archivos y programas innecesarios, dejando solo lo esencial para ejecutar tu aplicación.
Para cambiar a una imagen base más pequeña, simplemente actualiza tu archivo Docker para comenzar a trabajar con la nueva imagen base:

Ahora, a diferencia de la antigua imagen onbuild, necesitas copiar tu código al contenedor e instalar las dependencias. En el nuevo archivo Docker, el contenedor comienza con la imagen node:alpine, luego crea un directorio para el código, instala las dependencias usando el gestor de paquetes NPM y, finalmente, ejecuta server.js.

Con esta actualización se obtiene un contenedor 10 veces más pequeño. Si tu lenguaje de programación o stack no dispone de una función para reducir la imagen base, utiliza Alpine Linux. Esto también te dará la posibilidad de gestionar completamente el contenido del contenedor. Usar imágenes base de pequeño tamaño es una excelente forma de crear contenedores pequeños rápidamente. Pero se puede lograr una reducción aún mayor utilizando el Patrón de Builder.

En los lenguajes interpretados, el código fuente se pasa primero al intérprete y luego se ejecuta directamente. En los lenguajes compilados, el código fuente se convierte previamente en código compilado. Este proceso de compilación a menudo utiliza herramientas que en realidad no son necesarias para ejecutar el código. Esto significa que puedes eliminar completamente estas herramientas del contenedor final. Para esto, se puede usar el Patrón de Builder.

El código se crea en el primer contenedor y se compila. Luego, el código compilado se empaqueta en el contenedor final sin los compiladores y herramientas necesarias para compilar ese código. Pasemos por este proceso con una aplicación de Go. Primero, pasaremos de la imagen onbuild a Alpine Linux.

En el nuevo archivo Docker, el contenedor comienza con la imagen golang:alpine. Luego, crea un directorio para el código, copia el código fuente en él, compila este código y ejecuta la aplicación. Este contenedor es mucho más pequeño que el contenedor onbuild, pero aún contiene el compilador y otras herramientas de Go que realmente no necesitamos. Así que simplemente extraigamos el programa compilado y lo coloquemos en nuestro propio contenedor.

Puede notar algo curioso en este archivo Docker: contiene dos líneas FROM. La primera sección de 4 líneas se ve exactamente igual que el archivo Docker anterior, salvo que utiliza la palabra clave AS para darle un nombre a esta etapa. En la siguiente sección hay una nueva línea FROM, que permite iniciar una nueva imagen, utilizando Raw alpine como imagen base en lugar de golang:alpine.
Raw Alpine Linux no tiene certificados SSL instalados, lo que causará fallos en la mayoría de las llamadas API a través del protocolo HTTPS, así que instalemos algunos certificados raíz CA.
Y ahora lo más interesante: para copiar el código compilado del primer contenedor al segundo, se puede usar simplemente el comando COPY, ubicado en la línea 5 de la segunda sección. Este copiará solo un archivo de aplicación y no tocará las herramientas auxiliares de Go. El nuevo archivo Docker de múltiples etapas tendrá una imagen del contenedor de solo 12 megabytes, mientras que la imagen original del contenedor era de 700 megabytes, ¡una gran diferencia!
Por lo tanto, el uso de imágenes base pequeñas y el patrón de Builder son excelentes maneras de crear contenedores mucho más pequeños sin un gran esfuerzo.
Es posible que, dependiendo de la pila de la aplicación, existan formas adicionales de reducir el tamaño de la imagen y el contenedor, pero ¿realmente tienen las pequeñas contendores una ventaja medible? Consideremos dos aspectos en los que los contenedores pequeños son extremadamente efectivos: el rendimiento y la seguridad.
Para evaluar el aumento del rendimiento, consideremos la duración del proceso de creación del contenedor, su inserción en el registro (push) y la posterior recuperación (pull). Puede ver que un contenedor de menor tamaño tiene una ventaja indiscutible sobre un contenedor de mayor tamaño.

Docker cacheará las capas, por lo que las compilaciones posteriores se realizarán muy rápidamente. Sin embargo, en muchos sistemas de CI que se utilizan para compilar y probar contenedores, las capas no se almacenan en caché, por lo que aquí hay un ahorro de tiempo significativo. Como puede ver, el tiempo de construcción de un contenedor grande, dependiendo de la potencia de su máquina, varía de 34 a 54 segundos, mientras que al usar un contenedor reducido con el Builder Pattern, el tiempo es de entre 23 y 28 segundos. Para operaciones de este tipo, el aumento de rendimiento será del 40-50%. Así que solo piense en cuántas veces crea y prueba su código.
Después de que el contenedor se construye, necesita insertar su imagen de contenedor (push container image) en el registro de contenedores para luego usarla en su clúster de Kubernetes. Recomiendo usar el registro de contenedores de Google.

Al usar Google Container Registry (GCR), solo paga por el almacenamiento y la red 'en bruto', y no se cobra una tarifa adicional por la gestión de contenedores. Es confidencial, seguro y muy rápido. GCR utiliza muchos trucos para acelerar la operación de pull. Como puede ver, la inserción de la imagen del contenedor Docker Container Image al usar go:onbuild, dependiendo del rendimiento de la computadora, tomará entre 15 y 48 segundos, mientras que la misma operación con un contenedor de menor tamaño tomará entre 14 y 16 segundos, y para máquinas menos potentes, la ventaja en la velocidad de operación es tres veces mayor. Para máquinas grandes, el tiempo es aproximadamente el mismo, ya que GCR utiliza una caché global para la base común de imágenes, lo que significa que no necesita descargarlas en absoluto. En una computadora de baja potencia, la CPU es el cuello de botella, por lo que la ventaja de usar contenedores pequeños es mucho más evidente.
Si utiliza GCR, le recomiendo encarecidamente que use Google Container Builder (GCB) como parte de su sistema de compilación.

Como pueden ver, su uso permite lograr resultados mucho mejores en la reducción de la duración de la operación Build+Push, incluso mejor que en una máquina potente: en este caso, el proceso de construcción y envío de contenedores al host se acelera casi al doble. Además, todos los días obtienen 120 minutos de compilación gratuitos, lo que en la mayoría de los casos satisface las necesidades de creación de contenedores.
A continuación se presenta la métrica de rendimiento más importante: la velocidad de extracción o descarga de contenedores Pull. Y si no le importa mucho el tiempo dedicado a la operación push, la duración del proceso pull influye seriamente en el rendimiento general del sistema. Supongamos que tiene un clúster de tres nodos y uno de ellos falla. Si utiliza un sistema de gestión, como Google Kubernetes Engine, este reemplazará automáticamente el nodo defectuoso por uno nuevo. Sin embargo, este nuevo nodo estará completamente vacío y tendrá que transferir todos sus contenedores a él para que comience a funcionar. Si la operación pull es lo suficientemente larga, durante todo ese tiempo su clúster funcionará con menor rendimiento.
Hay muchos casos en los que puede ocurrir esto: la adición de un nuevo nodo al clúster, la actualización de nodos o incluso el cambio a un nuevo contenedor para la implementación. Por lo tanto, la minimización del tiempo de extracción pull se convierte en un factor clave. Es indiscutible que un contenedor pequeño se descarga mucho más rápido que uno grande. Si utiliza varios contenedores en un clúster de Kubernetes, el ahorro de tiempo puede ser muy significativo.

Mire la comparación presentada: la operación pull al trabajar con contenedores pequeños toma de 4 a 9 veces menos tiempo, dependiendo de la potencia de la máquina, que la misma operación utilizando go:onbuild. El uso de imágenes base de contenedores de tamaño pequeño acelera significativamente el tiempo y la velocidad con las que los nuevos nodos de Kubernetes pueden desplegarse y salir a Internet.
Examinemos el tema de la seguridad. Se cree que los contenedores más pequeños son mucho más seguros que los grandes, debido a su menor superficie de ataque. ¿Es esto realmente cierto? Una de las funciones más útiles de Google Container Registry es la capacidad de escanear automáticamente sus contenedores en busca de vulnerabilidades. Hace unos meses, creé tanto contenedores onbuild como multietapa, así que veamos si hay algún punto débil allí.

Los resultados son sorprendentes: se encontraron solo 3 vulnerabilidades medias en un contenedor pequeño, mientras que en uno grande se encontraron 16 críticas y 376 otras vulnerabilidades. Al revisar el contenido del contenedor grande, se puede ver que la mayoría de los problemas de seguridad no están relacionados con nuestra aplicación, sino con programas que ni siquiera usamos. Por lo tanto, cuando las personas hablan de una gran superficie de ataque, se refieren precisamente a esto.

La conclusión es clara: crea contenedores pequeños, ya que ofrecen verdaderas ventajas en rendimiento y seguridad para tu sistema.

Un poco de publicidad 🙂
Gracias por permanecer con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más contenido interesante? Apóyanos haciendo un pedido o recomendando a tus conocidos, , un análogo único de servidores entry-level que hemos diseñado para Ti: (disponibles opciones con RAID1 y RAID10, hasta 24 núcleos y hasta 40GB DDR4).
¿Dell R730xd a mitad de precio en el centro de datos Equinix Tier IV en Ámsterdam? Solo aquí ¡en los Países Bajos! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ¡desde $99! Lee sobre cómo
Fuente: habr.com
