Recomendaciones para compilar contenedores

Last reviewed 2023-02-28 UTC

En este artículo se describe un conjunto de recomendaciones para compilar contenedores. Estas prácticas abarcan una amplia variedad de objetivos, que van desde reducir el tiempo de compilación hasta crear imágenes más pequeñas y resilientes, para facilitar la compilación de contenedores (por ejemplo, mediante Cloud Build) y su ejecución en Google Kubernetes Engine (GKE).

Algunas recomendaciones son más importantes que otras. Por ejemplo, podrás ejecutar una carga de trabajo de producción de manera correcta aunque no sigas algunas de ellas, pero otras son indispensables. En particular, la importancia de las recomendaciones sobre seguridad es subjetiva. Su puesta en práctica dependerá de tu entorno y tus limitaciones.

Para sacarle el máximo provecho a este artículo, necesitas conocer un poco sobre Docker y Kubernetes. Algunas recomendaciones que se tratan aquí también se aplican a los contenedores de Windows, pero la mayoría supone que trabajas con contenedores de Linux. Puedes encontrar asesoramiento sobre la ejecución y el manejo de contenedores en Recomendaciones para trabajar con contenedores.

Empaqueta una sola aplicación por contenedor

Importancia: ALTA

Cuando comienzas a trabajar con contenedores, un error común es tratarlos como máquinas virtuales (VMs) que pueden ejecutar diferentes elementos de manera simultánea. Un contenedor puede trabajar de esta manera, pero, si lo hace, reduce la mayoría de las ventajas del modelo. Por ejemplo, pensemos en una pila Apache/MySQL/PHP clásica: podrías verte tentado de ejecutar todos los componentes en un solo contenedor. Sin embargo, la práctica recomendada consiste en usar dos o tres contenedores diferentes: uno para Apache, uno para MySQL y quizá uno para PHP si ejecutas PHP-FPM.

Debido a que un contenedor está diseñado para tener el mismo ciclo de vida que la app que aloja, cada uno de los contenedores debería contener solo una app. Cuando se inicia un contenedor, también debería hacerlo la app, y cuando la app se detiene, también debería hacerlo el contenedor. En el siguiente diagrama, se muestra esta práctica recomendada:

Un diagrama en el que se muestra el proceso de inicio sin una imagen personalizada

Figura 1. El contenedor de la izquierda se adhiere a la recomendación. El de la derecha, no.

Si tienes varias aplicaciones en un contenedor, podrían tener ciclos de vida diferentes, o estar en estados distintos. Por ejemplo, podrías tener un contenedor que se está ejecutando, pero que tenga un componente principal con fallas o que no responda. Sin una verificación de estado adicional, el sistema de administración general del contenedor (Docker o Kubernetes) no puede diagnosticar si este está en buen estado. En el caso de Kubernetes, esto significa que, si un componente principal no responde, Kubernetes no reiniciará el contenedor de forma automática.

Es posible que veas las siguientes acciones en imágenes públicas, pero no sigas su ejemplo:

  • Utilizar un sistema de administración de procesos como supervisor para administrar una o más aplicaciones en el contenedor.
  • Utilizar una secuencia de comandos de Bash como punto de entrada en el contenedor, y generar diferentes aplicaciones como trabajos en segundo plano. Para conocer el uso correcto de la secuencia de comandos de Bash en contenedores, consulta Usa de forma correcta el PID 1, el manejo de señales y los procesos inertes

Maneja correctamente el PID 1, el manejo de señales y los procesos zombie

Importancia: ALTA

Las señales de Linux son la forma principal de controlar el ciclo de vida de los procesos dentro del contenedor. En línea con la recomendación anterior, a fin de vincular el ciclo de vida de tu aplicación con el contenedor en el que se encuentra, asegúrate de que maneje correctamente las señales de Linux. La señal más importante de Linux es SIGTERM porque termina un proceso. Es posible que la app también reciba una señal SIGKILL, que se usa para terminar un proceso con errores, o una señal SIGINT, que se envía cuando escribes Ctrl+C y, por lo general, se trata como a SIGTERM.

Los identificadores de procesos (PID) son identificadores únicos que el kernel de Linux le otorga a cada proceso. Los PID tienen espacio de nombres, lo cual significa que un contenedor tiene su propio conjunto de PID que está asignado en los PID del sistema de alojamiento. El primer proceso que se ejecuta cuando se inicia un kernel de Linux tiene el PID 1. En un sistema operativo normal, este proceso es el sistema init, por ejemplo, systemd o SysV. De manera similar, el primer proceso que se inicia en un contenedor obtiene el PID 1. Docker y Kubernetes usan señales a fin de comunicarse con los procesos dentro de contenedores, en especial para terminarlos. Tanto Docker como Kubernetes solo pueden enviar señales al proceso que tenga PID 1 dentro de un contenedor.

En el contexto de los contenedores, los PID y las señales de Linux crean dos problemas que deben tenerse en cuenta.

Problema 1: Cómo maneja las señales el kernel de Linux

El kernel de Linux maneja las señales de forma diferente para el proceso que tiene PID 1 que para otros procesos. Los controladores de señales no se registran automáticamente para este proceso; esto quiere decir que las señales como SIGTERM o SIGINT no tendrán efectos predeterminados. De forma predeterminada, debes terminar procesos mediante SIGKILL, con lo que evitarás que se produzcan cierres ordenados. Dependiendo de tu aplicación, utilizar SIGKILL puede dar como resultado errores para los usuarios, escrituras interrumpidas (para almacenes de datos) o alertas no deseadas en tu sistema de supervisión.

Problema 2: Cómo manejan los sistemas init clásicos los procesos huérfanos

Los sistemas init clásicos como systemd también se utilizan para quitar (recolectar) procesos zombie huérfanos. Los procesos huérfanos, es decir, aquellos cuyos principales han finalizado, se vuelven a unir al proceso con PID 1, que deberá recolectarlos cuando finalicen. Esta tarea la realiza un sistema init normal. Sin embargo, en un contenedor, esta responsabilidad recae en el proceso con PID 1. Si ese proceso no maneja correctamente la recolección, corres el riesgo de quedarte sin memoria o sin otros recursos.

Estos problemas tienen varias soluciones comunes que se detallan en la siguiente sección.

Solución 1: Ejecutar como PID 1 y registrar los controladores de señales

Esta solución solamente sirve para el primer problema. Es válida si la app genera procesos secundarios de forma controlada (lo que suele suceder). Esto evita el segundo problema.

La forma más sencilla de implementar esta solución es iniciar el proceso con las instrucciones CMD o ENTRYPOINT en el Dockerfile. Por ejemplo, en el siguiente Dockerfile, nginx es el primer y único proceso que se inicia.

FROM debian:11

RUN apt-get update && \
    apt-get install -y nginx

EXPOSE 80

CMD [ "nginx", "-g", "daemon off;" ]

A veces, deberás preparar el entorno en tu contenedor para que el proceso se ejecute correctamente. En este caso, la recomendación es hacer que el contenedor inicie una secuencia de comandos shell durante el inicio. Esta secuencia de comandos de shell tiene la tarea de preparar el entorno y, luego, iniciar el proceso principal. Sin embargo, si adoptas este enfoque, la secuencia de comandos de shell tendrá el PID 1, no tu proceso, por lo que deberás usar el comando integrado exec para iniciar el proceso desde la secuencia de comandos de shell. El comando exec reemplaza la secuencia de comandos por el programa que desees. Entonces, tu proceso hereda el PID 1.

Solución 2: Habilita el uso compartido de espacios de nombres de procesos en Kubernetes

Cuando habilitas el uso compartido de espacios de nombres de procesos para un pod, Kubernetes usa un único espacio de nombres de procesos para todos los contenedores en ese pod. El contenedor de la infraestructura del pod de Kubernetes se convierte en el PID 1 y recolecta de forma automática los procesos huérfanos.

Solución 3: Usa un sistema init especializado

Como lo harías en un entorno Linux más clásico, también puedes utilizar un sistema init para abordar esos problemas. Sin embargo, los sistemas init normales, como systemd o SysV, son demasiado complejos y grandes para este propósito, por lo que recomendamos que uses un sistema init como tini, que se creó especialmente para contenedores.

Si usas un sistema init especializado, el proceso init tiene el PID 1 y hace lo siguiente:

  • Registra los controladores de señal correctos.
  • Garantiza que las señales funcionen para tu aplicación.
  • Recolecta cualquier proceso inerte futuro.

Puedes usar esta solución en Docker mediante la opción --init del comando docker run. Para usar esta solución en Kubernetes, debes instalar el sistema init en la imagen del contenedor y usarlo como punto de entrada del contenedor.

Optimiza para la caché de compilación de Docker

Importancia: ALTA

La caché de compilación de Docker puede acelerar ampliamente la compilación de imágenes de contenedor. Las imágenes se compilan capa por capa, y en un Dockerfile cada instrucción crea una capa en la imagen resultante. Durante la compilación, cuando sea posible, Docker reutiliza una capa de una compilación previa y omite un paso potencialmente costoso. Docker puede utilizar su caché de compilación únicamente si todos los pasos de compilación anteriores la utilizaron. Si bien este comportamiento generalmente es positivo, ya que agiliza las compilaciones, debes tener en cuenta algunas cuestiones.

Por ejemplo, para aprovechar al máximo la caché de compilación de Docker, debes colocar los pasos de compilación que cambian a menudo en la parte inferior del Dockerfile. Si los colocas en la parte superior, Docker no podrá utilizar su caché de compilación para los otros pasos de compilación que cambian con menos frecuencia. Debido a que, por lo general, se compila una imagen de Docker nueva para cada versión nueva del código fuente, agrega el código fuente a la imagen lo más tarde posible en el Dockerfile. En el siguiente diagrama, puedes ver que, si cambias el STEP 1, Docker puede vuelve a usar solo las capas del paso FROM debian:11. Sin embargo, si cambias el STEP 3, Docker puede volver a usar las capas del STEP 1 y STEP 2.

Ejemplos de cómo usar la caché de compilación de Docker

Figura 2. Ejemplos de cómo utilizar la caché de compilación de Docker. Las capas que se pueden volver a utilizar figuran en verde. En rojo, las que se deben volver a crear.

La reutilización de capas tiene otra consecuencia: si un paso de compilación se basa en cualquier tipo de caché almacenada en el sistema de archivos local, esta debe generarse en el mismo paso de compilación. Si esta caché no se genera, tu paso de compilación podría ejecutarse con una caché desactualizada proveniente de una compilación anterior. Este comportamiento se suele ver con administradores de paquetes como apt o yum: debes actualizar los repositorios en el mismo comando RUN que la instalación del paquete.

Si cambias el segundo paso RUN en el siguiente Dockerfile, el comando apt-get update no se vuelve a ejecutar, lo que deja una caché de apt desactualizada.

FROM debian:11

RUN apt-get update
RUN apt-get install -y nginx

En cambio, combina dos comandos en un solo paso RUN:

FROM debian:11

RUN apt-get update && \
    apt-get install -y nginx

Quita herramientas innecesarias

Importancia: MEDIA

A fin de proteger tus aplicaciones de los atacantes, intenta reducir la superficie de ataque de tu aplicación; para ello, quita todas las herramientas innecesarias. Por ejemplo, quita herramientas como netcat, que puedes utilizar para crear un shell inverso dentro de tu sistema. Si netcat no está presente dentro del contenedor, el atacante deberá encontrar otra manera.

Esta recomendación se aplica para cualquier carga de trabajo, incluso si no está dentro de un contenedor. La diferencia es que esta práctica recomendada está optimizada para contenedores, en lugar de VMs clásicas o servidores físicos.

Cuando quitas herramientas innecesarias, esto también puede ayudarte a mejorar tus procesos de depuración. Por ejemplo, si implementas esta práctica recomendada lo suficiente, los registros exhaustivos, los sistemas de seguimiento y generación de perfiles pueden volverse casi obligatorios. De hecho, ya no puedes confiar en las herramientas de depuración locales, ya que generalmente tienen muchos privilegios.

Contenido del sistema de archivos

La primera parte de esta recomendación trata sobre el contenido de la imagen del contenedor. Mantén la menor cantidad de elementos posibles en tu imagen. Si puedes compilar la app en un solo objeto binario vinculado de forma estática, agregar este objeto a la imagen inicial te permite obtener una imagen final que contenga solo la app y nada más. Si reduces la cantidad de herramientas empaquetadas en tu imagen, reduces lo que un atacante potencial podría hacer en tu contenedor. Para obtener más información, consulta Compila la imagen más pequeña posible.

Seguridad del sistema de archivos

No tener herramientas en tu imagen no es suficiente: debes evitar que los posibles atacantes instalen sus propias herramientas. Puedes combinar los siguientes dos métodos:

  • Evita la ejecución como raíz dentro del contenedor: este método ofrece una primera capa de seguridad y podría evitar, por ejemplo, que los atacantes modifiquen los archivos de la raíz mediante un administrador de paquetes incorporado en la imagen (como apt-get o apk). Para que este método sea útil, debes inhabilitar o desinstalar el comando sudo. Este tema se trata en mayor profundidad en Evita la ejecución como raíz.

  • Inicia el contenedor en modo de solo lectura mediante la marca --read-only del comando docker run o mediante la opción readOnlyRootFilesystem en Kubernetes.

Compila la imagen más pequeña posible

Importancia: MEDIA

Compilar una imagen pequeña ofrece ventajas como cargas y tiempos de descarga más veloces, lo cual es especialmente importante para el inicio en frío de un pod en Kubernetes: cuanto más pequeña sea la imagen, más rápido podrá descargarla el nodo. No obstante, compilar una imagen pequeña puede ser difícil debido a que es fácil incluir involuntariamente dependencias de compilación o capas no autorizadas en tu imagen final.

Usa la imagen base más pequeña posible

La imagen base es a la que se hace referencia en la instrucción FROM en tu Dockerfile. Todas las demás instrucciones en Dockerfile se compilan sobre esta imagen. Cuanto más pequeña sea la imagen base, más pequeña será la imagen resultante y más rápido se la podrá descargar. Por ejemplo, la imagen de alpine:3.17 es 23 MB más pequeña que la imagen de ubuntu:22.04.

Incluso puedes usar la imagen base de scratch, que es una imagen vacía en la cual puedes compilar tu propio entorno de ejecución. Si tu aplicación es un objeto binario vinculado estáticamente, puedes usar la imagen base inicial de la siguiente manera:

FROM scratch
COPY mybinary /mybinary
CMD [ "/mybinary" ]

En el siguiente video de prácticas recomendadas de Kubernetes, se abordan estrategias adicionales para la compilación de contenedores pequeños, a la vez que se reduce la exposición a vulnerabilidades de seguridad.

Reduce la cantidad de desorden en la imagen

A fin de reducir el tamaño de tu imagen, instala solamente lo que de verdad se necesite dentro de ella. Puede ser tentador instalar paquetes adicionales y, luego, quitarlos en un paso posterior. Sin embargo, este enfoque no es suficiente. Debido a que cada instrucción de Dockerfile crea una capa, quitar datos de la imagen en un paso posterior al paso en el que se creó no reduce el tamaño de la imagen total (los datos siguen allí, simplemente están ocultos en una capa más profunda). Considera el siguiente ejemplo:

Dockerfile incorrecto Dockerfile correcto

FROM debian:11
RUN apt-get update && \ apt-get install -y \ [buildpackage] RUN [build my app] RUN apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

FROM debian:11
RUN apt-get update && \ apt-get install -y \ [buildpackage] && \ [build my app] && \ apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

En la versión incorrecta del Dockerfile, el [buildpackage] y los archivos en /var/lib/apt/lists/* aún existen en la capa correspondiente a la primera RUN. Esta capa forma parte de la imagen y debe subirse y descargarse con el resto, incluso si no se puede acceder a los datos que contiene en la imagen resultante.

En la versión correcta del Dockerfile, todo se hace en una sola capa que contiene solo la app compilada. El [buildpackage] y los archivos en /var/lib/apt/lists/* no existen en ninguna parte de la imagen resultante, ni siquiera existen ocultos en una capa más profunda.

Si deseas obtener más información sobre las capas de imágenes, consulta Optimiza para la caché de compilación de Docker.

Otra buena forma de reducir la cantidad de desorden en tu imagen es utilizar compilaciones de varias etapas (agregadas en Docker 17.05). Las compilaciones de varias etapas te permiten compilar la app en el primer contenedor de “compilación” y usar el resultado en otro contenedor, mientras usas el mismo Dockerfile.

Proceso de compilación de varias etapas de Docker

Figura 3. El proceso de compilación de varias etapas de Docker.

En el siguiente Dockerfile, el objeto binario hello se compila en un primer contenedor y se inserta en un segundo contenedor. Debido a que el segundo contenedor se basa en la imagen inicial, la imagen resultante contiene solo el objeto binario hello y no el archivo de origen ni los archivos de objetos necesarios durante la compilación. El objeto binario debe estar vinculado de forma estática para poder funcionar sin una biblioteca externa en la imagen inicial.

FROM golang:1.20 as builder

WORKDIR /tmp/go
COPY hello.go ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s' -o hello

FROM scratch
CMD [ "/hello" ]
COPY --from=builder /tmp/go/hello /hello

Intenta crear imágenes con capas comunes

Si debes descargar una imagen de Docker, este verifica primero si ya tienes algunas de las capas que están en la imagen. Si las tienes, no las descarga. Esta situación puede suceder si ya descargaste antes otra imagen con la misma base que la imagen que quieres descargar actualmente. El resultado es que la cantidad de datos descargados es mucho menor para la segunda imagen.

En un nivel organizativo, puedes aprovechar esta reducción y brindarle a tus desarrolladores un conjunto de imágenes base comunes y estándares. Tu sistema debe descargar cada imagen base solo una vez. Tras la descarga inicial, solamente se necesitan las capas que hacen que cada imagen sea única. De hecho, cuanto más tengan en común las imágenes, más rápida será la descarga.

Intenta crear imágenes con capas comunes

Figura 4. Crea imágenes con capas comunes.

Analiza las imágenes en busca de vulnerabilidades

Importancia: MEDIA

Las vulnerabilidades del software son un problema muy comprendido en el mundo de los servidores sin sistema operativo y las máquinas virtuales. Una forma común de tratar estas vulnerabilidades es usar un sistema de inventario centralizado que enumere los paquetes instalados en cada servidor. A fin de estar informado del momento en el que una vulnerabilidad afecta tus servidores, y para corregirlo como corresponda, suscríbete a las noticias de vulnerabilidad de los sistemas operativos ascendentes.

Sin embargo, debido a que se supone que los contenedores son inmutables (consulta contenedores inmutables y sin estado para obtener más detalles), no les apliques un parche en forma directa en caso de una vulnerabilidad. Se recomienda volver a compilar la imagen, con los parches incluidos, y volver a implementarla. Los contenedores tienen un ciclo de vida mucho más corto y una identidad mucho menos definida que los servidores. Por lo tanto, usar un sistema de inventario centralizado similar no es un método eficiente para detectar vulnerabilidades en contenedores.

Para ayudarte a tratar este problema, Artifact Analysis puede analizar tus imágenes en busca de vulnerabilidades de seguridad en paquetes supervisados de forma pública. Las siguientes opciones están disponibles:

Análisis de vulnerabilidades automático

Cuando está habilitada, esta función identifica vulnerabilidades en los paquetes para las imágenes de tu contenedor. Las imágenes se analizan cuando se suben a Artifact Registry o Container Registry, y los datos se supervisan continuamente para encontrar vulnerabilidades nuevas durante un máximo de 30 días después de enviar la imagen. Puedes usar la información que genera esta función de varias maneras:

  • Crea un trabajo de tipo cron que enumere las vulnerabilidades y active el proceso para corregirlas, cuando exista una solución.
  • En cuanto se detecte una vulnerabilidad, usa la integración de Pub/Sub para activar el proceso de aplicación de parches que usa tu organización.
API de On-Demand Scanning

Cuando está habilitado, puedes analizar de forma manual las imágenes locales o las imágenes almacenadas en Artifact Registry o Container Registry. Esta función te ayuda a detectar y abordar las vulnerabilidades de forma anticipada en la canalización de compilación. Por ejemplo, puedes usar Cloud Build para analizar una imagen después de compilarla y, luego, bloquear la carga en Artifact Registry si el análisis detecta vulnerabilidades en un nivel de gravedad especificado. Si también habilitaste el análisis automático de vulnerabilidades, Artifact Registry también analiza las imágenes que subes al registro.

Te recomendamos automatizar el proceso de aplicación de parches y emplear la canalización de integración continua existente usada al comienzo para compilar la imagen. Si confías en tu canalización de integración continua, podrás implementar automáticamente la imagen fija cuando esté lista. Sin embargo, la mayoría de las personas prefiere un paso de verificación manual antes de la implementación. El proceso a continuación lo logra:

  1. Almacena tus imágenes en Container Registry y habilita el análisis de vulnerabilidades.
  2. Configura un trabajo que obtenga regularmente nuevas vulnerabilidades de Artifact Registry y activa una recompilación de las imágenes en caso de que sea necesario.
  3. Cuando las nuevas imágenes se compilen, haz que tu sistema de implementación continua las implemente en un entorno en etapa de pruebas.
  4. Verifica manualmente el entorno en etapa de pruebas en busca de problemas.
  5. Si no encuentras ningún problema, activa manualmente la implementación en producción.

Etiqueta correctamente tus imágenes

Importancia: MEDIA

Las imágenes de Docker se suelen identificar mediante dos componentes: su nombre y su etiqueta. Por ejemplo, en el caso de la imagen google/cloud-sdk:419.0.0, google/cloud-sdk es el nombre y 419.0.0 es la etiqueta. La etiqueta latest se usa de forma predeterminada si no proporcionas una en los comandos de Docker. El par de nombre y etiqueta es único en todo momento. Sin embargo, puedes reasignar una etiqueta a una imagen diferente según lo necesites.

Cuando construyes una imagen, depende de ti que la etiquetes correctamente. Utiliza una política de etiquetado coherente. Registra tu política de etiquetado para que los usuarios de la imagen puedan comprenderla fácilmente.

Las imágenes del contenedor son una forma de empaquetar y crear una actualización de un software particular. Etiquetar la imagen permite a los usuarios identificar una versión específica de tu software a fin de descargarlo. Por este motivo, vincula estrechamente el sistema de etiquetado de las imágenes del contenedor con la política de actualización de tu software.

Etiqueta mediante el control de versiones semántico

Una forma común de actualizar software es “etiquetar” (como en el comando git tag) una versión particular del código fuente con un número de versión. La Especificación de control de versiones semántico proporciona una forma clara de manejar los números de versiones. En este sistema, el software tiene un número de versión de tres partes, X.Y.Z, en el que cada valor representa lo siguiente:

  • X es la versión principal, que se incrementa solo para los cambios incompatibles de la API.
  • Y es la versión secundaria, que se incrementa para las funciones nuevas.
  • Z es la versión del parche, que se incrementa para las correcciones de errores.

Cada incremento en el número de versión secundaria o del parche debe realizarse por un cambio retrocompatible.

Si usas este sistema, o uno similar, etiqueta las imágenes según la siguiente política:

  • La etiqueta latest siempre se refiere a la imagen más reciente (posiblemente estable). Esta etiqueta se mueve en cuanto se crea una imagen nueva.
  • La etiqueta X.Y.Z hace referencia a una versión específica del software. No la muevas a otra imagen.
  • La etiqueta X.Y hace referencia a la última versión del parche de la rama secundaria X.Y del software. Se la mueve cuando se lanza una nueva versión del parche.
  • La etiqueta X hace referencia a la última versión de parche de la versión secundaria de la rama principal X. Se la mueve cuando se lanza una versión del parche o una versión secundaria nueva.

Esta política ofrece a los usuarios la flexibilidad de elegir qué versión de tu software quieren utilizar. Pueden elegir una versión X.Y.Z específica y tener la seguridad de que la imagen nunca cambiará, o pueden obtener actualizaciones automáticamente si eligen una etiqueta menos específica.

Etiqueta con el hash de confirmación de Git

Si tienes un sistema de entrega continua avanzado y actualizas tu software regularmente, posiblemente no utilices números de versiones como se describe en la Especificación de control semántico de versiones. En este caso, una forma común de manejar números de versiones consiste en utilizar el hash SHA-1 de confirmación de Git (o una versión corta de este) como el número de versión. Por diseño, el hash de confirmación de Git es inmutable y hace referencia a una versión específica de tu software.

Puedes utilizar este hash de confirmación como un número de versión para tu software, pero también como etiqueta de la imagen de Docker compilada desde esta versión específica de tu software. Si lo haces así, las imágenes de Docker serán rastreables: como en este caso la etiqueta de la imagen es inmutable, instantáneamente sabes qué versión específica de tu software se está ejecutando dentro de un contenedor determinado. En tu canalización de entrega continua, automatiza la actualización del número de versión utilizado para tus implementaciones.

Considera cuidadosamente si usarás una imagen pública

Importancia: N/A

Una de las grandes ventajas de Docker es la cantidad de imágenes públicas disponibles, para todo tipo de software. Estas imágenes te permiten comenzar con rapidez. Sin embargo, cuando estás diseñando una estrategia de contenedores para tu organización, posiblemente tengas limitaciones que las imágenes públicas no podrán cumplir. A continuación, te presentamos algunos ejemplos de limitaciones que podrían hacer que el uso de imágenes públicas no sea posible:

  • Deseas controlar exactamente lo que está dentro de tus imágenes.
  • No quieres depender de un repositorio externo.
  • Quieres controlar estrictamente las vulnerabilidades en tu entorno de producción.
  • Quieres tener el mismo sistema operativo de base en cada imagen.

La respuesta a todas esas limitaciones es la misma y, lamentablemente, es costosa: debes compilar tus propias imágenes. Compilar tus propias imágenes es factible para un número limitado de ellas, pero esta cantidad tiende a aumentar rápidamente. Para tener alguna posibilidad de administrar un sistema de este tipo a gran escala, considera utilizar las siguientes opciones:

  • Una forma automatizada de compilar imágenes, de forma confiable, incluso para imágenes que apenas se compilan. Los activadores de compilación en Cloud Build son una buena manera de lograrlo.
  • Una imagen base estandarizada. Google proporciona algunas imágenes base que puedes utilizar.
  • Una forma automatizada de propagar actualizaciones en la imagen base a imágenes "secundarias".
  • Una forma de abordar las vulnerabilidades en las imágenes. Para obtener más información, consulta Analiza las imágenes en busca de vulnerabilidades.
  • Una forma de obligar a aplicar tus estándares internos a las imágenes creadas por diferentes equipos en tu organización.

Tienes varias herramientas a tu disposición para ayudarte a aplicar políticas en las imágenes que compilas e implementas:

  • container-diff puede analizar el contenido de imágenes y comparar dos entre ellas.
  • container-structure-test puede poner a prueba si el contenido de una imagen cumple con un conjunto de reglas que tú definas.
  • Grafeas es una API de metadatos de artefactos, en la cual puedes almacenar metadatos sobre tus imágenes para controlar luego si cumplen con tus políticas.
  • Kubernetes cuenta con controladores de admisión que puedes utilizar para verificar una cantidad de prerrequisitos antes de implementar una carga de trabajo en Kubernetes.

También tienes la opción de adoptar un sistema híbrido, es decir, utilizar una imagen pública como Debian o Alpine como la imagen base y compilar el resto sobre esa imagen. O también puedes utilizar imágenes públicas para ciertas imágenes poco importantes y compilar tus imágenes en esos casos. No existe una respuesta correcta o incorrecta para esas preguntas, pero debes abordarlas.

Aclaración sobre licencias

Antes de incluir bibliotecas y paquetes de terceros en la imagen de Docker, asegúrate de que las licencias correspondientes lo permitan. Las licencias de terceros también pueden imponer restricciones a la redistribución, que se aplican cuando publicas una imagen de Docker en un registro público.

¿Qué sigue?

Explora arquitecturas de referencia, diagramas y prácticas recomendadas sobre Google Cloud. Consulta nuestro Cloud Architecture Center.