Prácticas recomendadas para compilar contenedores

En este artículo, se describe un conjunto de prácticas recomendadas 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, con Cloud Build) y su ejecución en Google Kubernetes Engine (GKE).

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

Para aprovechar al máximo este artículo, necesitas conocer Docker y Kubernetes. Algunas prácticas recomendadas que se tratan aquí también se aplican a los contenedores de Windows, pero, en la mayoría, se da por sentado que trabajas con contenedores de Linux. Puedes encontrar asesoramiento sobre la ejecución y el manejo de contenedores en Prácticas recomendadas para trabajar con contenedores.

Empaqueta una sola app por contenedor

Importancia: ALTA

Cuando comienzas a trabajar con contenedores, un error común es tratarlos como máquinas virtuales que pueden ejecutar diferentes elementos de manera simultánea. Un contenedor puede trabajar de esta forma, pero, si lo hace, reduce la mayoría de las ventajas del modelo. Por ejemplo, considera una pila de Apache/MySQL/PHP clásica, es posible que quieras 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 posiblemente 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 aplica la práctica recomendada. El de la derecha, no.

Si tienes varias apps en un contenedor, podrían tener ciclos de vida diferentes o estar en estados distintos. Por ejemplo, podrías tener un contenedor en ejecución, pero con 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, significa que el contenedor no se reiniciará de forma predeterminada si es necesario.

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

Usa de forma correcta el PID 1, el manejo de señales y los procesos inertes

Importancia: ALTA

Las señales de Linux son el método principal para controlar el ciclo de vida de los procesos dentro de un contenedor. De acuerdo con la práctica recomendada anterior, para vincular el ciclo de vida de la app con el contenedor en el que se encuentra, asegúrate de que la app maneje de forma correcta 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 espacios de nombres, lo que significa que un contenedor tiene su propio conjunto de PID que están asignados a los PID del sistema host. 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. Docker y Kubernetes solo pueden enviar señales al proceso que tenga el 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 el 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 efecto de forma predeterminada. De forma predeterminada, debes terminar procesos mediante SIGKILL, para evitar que se produzcan cierres ordenados. Según tu app, usar SIGKILL puede generar errores para los usuarios, escrituras interrumpidas (para almacenes de datos) o alertas no deseadas en el 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 usan para quitar (cosechar) procesos inertes huérfanos. Los procesos huérfanos, es decir, aquellos cuyos procesos superiores terminaron, se vuelven a unir al proceso que tiene el PID 1, que deberá cosecharlos cuando terminen. Esta tarea la realiza un sistema init normal. Sin embargo, en un contenedor, esta responsabilidad recae en el proceso que tenga el PID 1. Si ese proceso no controla de forma correcta la cosecha, corres el riesgo de quedarte sin memoria o sin otros recursos.

Estos problemas tienen varias soluciones comunes que se detallan en las siguientes secciones.

Solución 1: Ejecuta como PID 1 y registra los controladores de señales

Esta solución solo 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:9

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

    EXPOSE 80

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

A veces, deberás preparar el entorno en el contenedor para que el proceso se ejecute de forma correcta. En este caso, la práctica recomendada es hacer que el contenedor inicie una secuencia de comandos de 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 en todos los contenedores en ese pod. El contenedor de infraestructura del pod de Kubernetes se convierte en el PID 1 y cosecha de forma automática los procesos huérfanos.

Solución 3: Usa un sistema init especializado

Al igual que en un entorno de Linux más clásico, también puedes usar 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.
  • Cosecha 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 de contenedor y usarla 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 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 que puede ser costoso. Docker solo puede usar su caché de compilación si todos los pasos de compilación anteriores la usaron. Si bien este comportamiento suele ser positivo, ya que acelera las compilaciones, debes tener en cuenta algunos casos.

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á usar 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 STEP 1, Docker puede reutilizar solo las capas del paso FROM debian:9. Sin embargo, si cambias STEP 3, Docker puede reutilizar las capas de 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, el 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 te deja con una caché de apt desactualizada.

FROM debian:9

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

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

FROM debian:9

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

Quita herramientas innecesarias

Importancia: MEDIA

A fin de proteger las apps de los atacantes, intenta reducir la superficie de ataque; para ello, quita todas las herramientas innecesarias. Por ejemplo, quita utilidades como netcat, que puedes usar para crear una shell inversa dentro del sistema. Si netcat no está presente dentro del contenedor, el atacante deberá encontrar otra manera.

Esta práctica recomendada se aplica para cualquier carga de trabajo, incluso las que no están dentro de un contenedor. La diferencia consiste en que es mucho más simple implementar con contenedores que con las máquinas virtuales clásicas o los servidores en equipos físicos.

Algunas de estas herramientas pueden ser útiles para depurar. Por ejemplo, si sigues esta práctica recomendada al pie de la letra, los registros exhaustivos, el seguimiento, la creación de perfiles y los sistemas de administración del rendimiento de las aplicaciones se vuelven casi obligatorios. De hecho, ya no puedes usar las herramientas de depuración locales porque suelen tener muchos privilegios.

Contenido del sistema de archivos

En la primera parte de esta práctica recomendada, se analiza el contenido de la imagen del contenedor. Mantén la menor cantidad de elementos posibles en la 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 disminuyes la cantidad de herramientas empaquetadas en la imagen, se reduce lo que un posible atacante podría hacer en el contenedor. Para obtener más información, consulta Compila la imagen más pequeña posible.

Seguridad del sistema de archivos

Quitar la herramientas de la 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. Puedes aplicar esto en Kubernetes mediante una PodSecurityPolicy.

Compila la imagen más pequeña posible

Importancia: MEDIA

Compilar una imagen pequeña ofrece ventajas como tiempos de carga y descarga más cortos, lo que es muy importante para el tiempo de inicio en frío de un pod en Kubernetes, ya que cuanto más pequeña sea la imagen, más rápido podrá descargarla el nodo. Sin embargo, compilar una imagen pequeña puede ser difícil debido a que podrías incluir de forma involuntaria dependencias de compilación o capas no optimizadas en la 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 el 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.7 es 71 MB más pequeña que la imagen de centos:7.

Incluso puedes usar la imagen base inicial, que es una imagen vacía en la que puedes compilar tu propio entorno de ejecución. Si la app es un objeto binario vinculado de forma estática, es muy fácil usar la imagen base inicial:

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

El proyecto distroless te proporciona imágenes base mínimas para varios lenguajes diferentes. Las imágenes contienen solo dependencias del entorno de ejecución para el lenguaje, pero no incluyen muchas herramientas como en una distribución de Linux, como shells o administradores de paquetes.

Reduce la cantidad de desorden en la imagen

Para reducir el tamaño de tu imagen, instala solo lo que se necesite dentro de ella. Puede que desees instalar paquetes adicionales y, luego, quitarlos en un paso posterior. Sin embargo, este enfoque no es suficiente. Debido a que cada instrucción del 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í, solo están ocultos en una capa más profunda). Considera el siguiente ejemplo:

Dockerfile incorrecto Dockerfile correcto

    FROM debian:9
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:9
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 en la imagen resultante no se puede acceder a los datos que contiene.

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 están 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 forma útil de reducir la cantidad de desorden en la imagen es usar 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.10 as builder

    WORKDIR /tmp/go
    COPY hello.go ./
    RUN CGO_ENABLED=0 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 en ese momento. El resultado es que la cantidad de datos descargados es mucho menor para la segunda imagen.

En un nivel de organización, puedes aprovechar esta reducción y brindarle a los desarrolladores un conjunto de imágenes base estándar y comunes. El sistema debe descargar cada imagen base solo una vez. Luego de la descarga inicial, solo 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.

Usa el análisis de vulnerabilidades en Container Registry

Importancia: MEDIA

Las vulnerabilidades del software son un problema muy conocido en el mundo de los servidores en equipos físicos 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. Suscríbete a las notificaciones sobre vulnerabilidad de los sistemas operativos ascendentes para que se te informe cuando una vulnerabilidad afecta tus servidores y puedas aplicar parches como corresponda.

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 parches directamente 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 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, Container Registry cuenta con una función de análisis de vulnerabilidades. Cuando está habilitada, esta función identifica vulnerabilidades en los paquetes para las imágenes de contenedor. Las imágenes se analizan cuando se suben a Container Registry y cuando hay una actualización en la base de datos de vulnerabilidades. Puedes usar la información que genera esta función de varias maneras:

  • Crea un trabajo de tipo cron que enumere 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.

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 la canalización de integración continua, también se recomienda implementar de forma automática la imagen corregida cuando esté lista. Sin embargo, la mayoría de las personas prefieren un paso de verificación manual antes de la implementación. Eso se logra mediante el siguiente proceso:

  1. Almacena las imágenes en Container Registry y habilita el análisis de vulnerabilidades.
  2. Configura un trabajo que recupere con regularidad vulnerabilidades nuevas de Container Registry y active una segunda compilación de las imágenes en caso de que sea necesario.
  3. Cuando las imágenes nuevas estén compiladas, haz que el sistema de implementación continua las implemente en un entorno de etapa de pruebas.
  4. Revisa de forma manual 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 las imágenes de forma correcta

Importancia: MEDIA

Las imágenes de Docker se suelen identificar mediante dos componentes: su nombre y su etiqueta. Por ejemplo, para la imagen google/cloud-sdk:193.0.0, google/cloud-sdk es el nombre y 193.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 si lo necesitas.

Cuando compilas una imagen, eres responsable de etiquetarla de forma correcta. Usa una política de etiquetado coherente. Registra la política de etiquetado para que los usuarios de la imagen puedan comprenderla con facilidad.

Las imágenes de contenedor son una forma de empaquetar y crear una actualización de un software particular. Etiquetar la imagen permite que los usuarios identifiquen una versión específica del software para descargarlo. Por este motivo, vincula de forma estrecha el sistema de etiquetado de las imágenes de 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 con 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 actualización del parche de la última actualizació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 del software quieren usar. Pueden elegir una versión X.Y.Z específica y tener la seguridad de que la imagen nunca cambiará o pueden obtener actualizaciones de forma automática 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 el software con regularidad, es probable que no uses números de versiones como se describe en la Especificación de control de versiones semántico. En este caso, una forma común de controlar los números de versiones consiste en usar el hash SHA-1 de confirmación de Git (o una versión corta de este) como el número de versión. Gracias a su diseño, el hash de confirmación de Git es inmutable y hace referencia a una versión específica del software.

Puedes usar este hash de confirmación como un número de versión para el software, pero también como etiqueta de la imagen de Docker compilada desde esta versión específica del software. Si lo haces, las imágenes de Docker serán rastreables. Debido a que en este caso la etiqueta de la imagen es inmutable, sabes de inmediato qué versión específica del software se está ejecutando dentro de un contenedor determinado. En la canalización de entrega continua, automatiza la actualización del número de versión usado para las implementaciones.

Analiza detenidamente 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 diseñes una estrategia de contenedores para tu organización, es probable que tengas restricciones 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:

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

La respuesta a todas esas restricciones es la misma y, por desgracia, es costosa: debes compilar tus propias imágenes. Compilar tus propias imágenes es adecuado para un número limitado, pero este número tiende a aumentar muy rápido. Para tener alguna posibilidad de administrar un sistema de este tipo a gran escala, considera usar las siguientes opciones:

  • Una forma automatizada de compilar imágenes de forma confiable, incluso para imágenes que casi nunca 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 usar.
  • 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 Usa el análisis de vulnerabilidades en Container Registry.
  • Una forma de aplicar tus estándares internos a las imágenes creadas por diferentes equipos en tu organización.

Hay varias herramientas disponibles para aplicar políticas en las imágenes que compilas y que implementas:

  • container-diff puede analizar el contenido de imágenes y comparar dos entre ellas.
  • container-structure-test puede comprobar 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 que puedes almacenar metadatos sobre las imágenes para controlar si cumplen con tus políticas.
  • Kubernetes cuenta con controladores de admisión que puedes usar para verificar varios requisitos previos antes de implementar una carga de trabajo en Kubernetes.
  • Kubernetes también cuenta con políticas de seguridad del pod que puedes usar a fin de exigir el uso de opciones de seguridad en el clúster.

También puede que quieras adoptar un sistema híbrido, es decir, usar una imagen pública, como Debian o Alpine, como la imagen base y compilar el resto sobre esa imagen. También puedes usar imágenes públicas para las imágenes que no sean esenciales y compilar tus propias imágenes en otros 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.

Próximos pasos

Prueba otras funciones de Google Cloud. Consulta nuestros instructivos.