Crear imágenes de contenedor Java con Jib
17 de agosto de 2021
0 minutos de lecturaSi llevas más de un minuto trabajando con imágenes de contenedor, probablemente conoces esos documentos omnipresentes que describen, capa por capa, los pasos necesarios para crear una imagen: los Dockerfiles. ¿Sabías que cada vez hay más herramientas para crear imágenes compatibles con OCI sin Dockerfiles? En este artículo veremos Jib, una herramienta 100 % basada en Java que puede crear imágenes altamente optimizadas sin que tengas que preocuparte por redactar correctamente un Dockerfile.
Asumiré que ya entiendes cómo se crean las imágenes y que tienes al menos conocimientos básicos sobre los Dockerfiles. Si no los conoces, quizá quieras leer mi publicación sobre flujos de trabajo impulsados por desarrolladores, donde explico en detalle cómo crear imágenes de contenedor.
El problema
Como desarrollador de Java, además de tu código de aplicación y las dependencias de bibliotecas, probablemente también estás acostumbrado a hacer un seguimiento del JDK y la JVM que usas. Esto puede incluir la versión del servidor de aplicaciones web sobre el que desarrollas y quizá algunas configuraciones de tiempo de ejecución, como el ajuste del recolector de basura y las conexiones a bases de datos. Lo que quizá no hayas tenido que gestionar son aspectos del sistema operativo, como la instalación de paquetes, los permisos del sistema de archivos, el UID/GID que se usa en tiempo de ejecución y otros detalles de configuración que históricamente han estado a cargo de los equipos de operaciones y seguridad que te proporcionan las plataformas.
Con la adopción de los entornos de ejecución de contenedores y los sistemas de orquestación que se ejecutan sobre ellos, muchas de estas preocupaciones de bajo nivel ahora forman parte del ámbito de los equipos de desarrollo, en forma de archivos de configuración de infraestructura como código (IaC) que integran los repositorios de código de las aplicaciones y los servicios. Estos archivos pueden tener distintos formatos, como YAML de Kubernetes, HCL de Terraform, JSON de CloudFormation y, por supuesto, Dockerfiles, que definen la estructura de la imagen de contenedor donde se ejecutará tu aplicación.
Empaquetar tu aplicación en una imagen y ejecutarla en un contenedor no suele ser una tarea muy difícil. Sin embargo, asegurarte de que la imagen esté bien construida, optimizada, sea segura y cumpla con los estándares de tu organización sí puede ser un desafío. Ahora se les pide a los desarrolladores que aprendan sobre herramientas de análisis a nivel de sistema operativo, mantengan estándares de etiquetado de imágenes, conozcan las prácticas recomendadas más recientes para Dockerfiles y se hagan cargo de todo lo demás que se les asigne. Hay herramientas de linting y análisis, como Hadolint, Dockle y nuestra solución Snyk Container, que pueden ayudarte con estas tareas. Pero ¿y si pudiéramos automatizar la mayoría —o incluso todos— estos nuevos requisitos y el contenido repetitivo?
Conoce Jib: una herramienta 100 % basada en Java para crear imágenes de contenedor
Jib es una herramienta de código abierto, 100 % basada en Java, que crea imágenes de contenedor compatibles con OCI (Docker v2) sin necesidad de un Dockerfile ni de tener instalado un entorno de ejecución de contenedores. Jib se puede usar como herramienta CLI independiente, pero lo más común es usarlo como un complemento de Maven o Gradle en el proceso de compilación. Con el complemento, solo tienes que ejecutar el mismo comando de compilación mvn o gradle que ya usas para crear tus artefactos .jar, .war, etc., y también crear —y, si quieres, implementar— una imagen OCI lista para ejecutar tu aplicación en un contenedor.
Si partes de una aplicación Spring Boot, agregar Jib es tan fácil como insertar este fragmento de XML en el archivo pom.xml de Maven (consulta la documentación completa) y volver a ejecutar el comando mvn package. Hay un par de opciones que podemos configurar para indicar dónde queremos colocar el contenedor. Para simplificar, en este ejemplo se implementa en un registro de imágenes que usaremos más adelante para ejecutarlo, pero también puedes guardar la imagen en un archivo **.**tar local o, si tienes un daemon de Docker en ejecución y accesible, en la caché local de imágenes de Docker (como harías con una compilación de Docker).
Nota: En el resultado anterior hay algunas líneas de «advertencia» interesantes que analizaremos más adelante.
Ahora, en una máquina que tenga un entorno de ejecución de contenedores, solo tienes que ejecutar el contenedor: docker run --rm -it -p 8080:8080 myimage:tag
Si usas Gradle, consulta la documentación oficial para obtener toda la información sobre cómo hacer lo mismo en tu archivo build.gradle.
Eso es todo: no necesitas un Dockerfile ni herramientas adicionales para crear imágenes. Basta con usar las mismas herramientas de compilación que ya usas para crear tus artefactos de Java. Esto no solo les facilita la vida a los desarrolladores, sino que también hace que sea mucho más fácil dar soporte a los agentes de compilación de CI, ya que no es necesario exponer el socket de Docker ni administrar otras herramientas de compilación.
Analicemos la imagen en detalle
De acuerdo, el proceso de creación de imágenes puede ser más fácil, pero si eres como yo cuando lo vi por primera vez, probablemente tengas preguntas como estas...
¿Cómo crea imágenes Jib?
Al igual que cualquier herramienta de creación de imágenes, Jib crea un conjunto de capas del sistema de archivos que contienen el entorno de ejecución de Java, tu aplicación y todas sus dependencias, además de los metadatos que el motor de contenedores usa para saber cómo iniciar la JVM.
Esta es la información de las capas de este ejemplo, tal como la muestra el comando docker image history:
Fíjate en que las fechas de CREATED de las primeras 4 capas dicen «hace 51 años». Esto es un efecto secundario de la estrategia de compilaciones repetibles de Jib, que busca crear exactamente los mismos hashes de capa cuando se compila el mismo código. Para obtener más detalles, consulta sus preguntas frecuentes.
Como muestran los comentarios de las capas, tenemos varias capas de la imagen base predeterminada de AdoptOpenJDK —más información a continuación— seguidas, en este orden, por:
Dependencias de bibliotecas definidas en tus archivos de Maven/Gradle
Archivos de recursos
Archivos de clase del código compilado de tu aplicación
Archivos de argumentos de Java JVM.
Veamos con más detalle qué archivos hay en cada una de estas 4 capas mediante la herramienta de código abierto dive:
Dependencias
Esta capa agrega todos los archivos .jar a la carpeta /app/libs.
Recursos
Aquí vemos /app/resources y todos los archivos asociados de nuestras carpetas de recursos.
Clases
Esta capa contiene los archivos .class generados durante la fase de compilación, en /app/classes.
Archivos de argumentos de JVM
Por último, tenemos un par de archivos que contienen los argumentos que se usan al iniciar la JVM en el contenedor, dentro de la carpeta de nivel superior /app. En este ejemplo, si inspeccionaras el contenido de estos dos archivos, encontrarías la ruta de clases de tiempo de ejecución y la información de la clase principal.
Nota: Según la estructura de tu compilación de Maven/Gradle, las imágenes que genera Jib pueden tener más capas. Consulta las preguntas frecuentes de Jib para obtener más información sobre otras capas posibles.
¿Qué imagen base usa Jib y qué pasa si necesito usar mi propia imagen?
De forma predeterminada, Jib usa la imagen base oficial de DockerHub adoptopenjdk:jre-8 para las compilaciones de archivos JAR o la imagen base oficial de DockerHub jetty para las compilaciones de archivos WAR. Puedes configurar esto mediante la configuración del complemento. Consulta la documentación oficial para obtener más detalles, incluso sobre cómo establecer ENTRYPOINT, USERu otras declaraciones personalizadas si las necesitas. En esa misma documentación también se explica por qué conviene establecer tu propia imagen base y usar un hash específico para garantizar que las compilaciones sean repetibles. Muchas organizaciones han aprobado internamente imágenes base que los equipos de desarrollo deben usar y que los equipos de seguridad y operaciones han auditado y reforzado. Aquí tienes un ejemplo de cómo modificar el pom.xml de Maven para usar una de estas imágenes.
También se admiten configuraciones adicionales, como el etiquetado estandarizado de imágenes. Por ejemplo, supongamos que tu organización requiere que todas las imágenes incluyan las siguientes etiquetas:
URL de Git del código fuente
ID/hash de la confirmación de Git
Versión de compilación del proyecto Maven
Si el archivo pom.xml ya tiene acceso a estos datos, agregar las etiquetas en la configuración del complemento Jib es muy sencillo:
Al inspeccionar la imagen creada, vemos que las etiquetas se aplicaron igual que si se hubieran agregado mediante líneas LABELS en un Dockerfile (aquí uso la excelente herramienta de línea de comandos jq para extraer únicamente ese arreglo de la respuesta).
Ahora imagina que toda la complejidad de estas configuraciones estandarizadas se define en un POM principal mediante <pluginManagement> y otras configuraciones habituales de Maven. Así, los desarrolladores ya no tienen que preocuparse por todo este código XML repetitivo ni revisarlo, y los arquitectos pueden aplicar y actualizar los estándares en toda la organización sin tener que molestar a sus equipos.
¿Cómo puedo asegurarme de que todo sea seguro?
Uno de los desafíos que presentan las herramientas más generales para crear imágenes, como Dockerfile, es que permiten hacer prácticamente cualquier cosa durante la compilación, como instalar paquetes, usar ADD para descargar archivos de servidores web aleatorios y realizar muchos otros pasos que hay que revisar y analizar con cuidado. Con Jib, muchas de estas decisiones se aplican automáticamente y, para anularlas, se requieren cambios explícitos en la configuración de Maven/Gradle que se ven claramente en una revisión de código, igual que cuando se cambia una dependencia o cualquier otra configuración de compilación.
En cuanto al análisis de seguridad, una de las excelentes funciones que ofrece Snyk Container es recomendar distintas imágenes base con menos vulnerabilidades. A partir de hoy, esa función también sirve aunque no haya un Dockerfile que indique cuál es la imagen base. Esto significa que las imágenes creadas con Jib —o con cualquier herramienta que no use Dockerfile— pueden aprovechar las mismas recomendaciones.
Si quieres seguir los pasos y analizar tus propios contenedores de Java, crea una cuenta gratis y obtén instrucciones para instalar la herramienta de análisis de Snyk.
Para este ejemplo, establecí openjdk:8u121-jre como imagen base, ejecuté mvn package y descargué la imagen en mi laptop. Ahora solo ejecutaré snyk container test para analizarla.
Como puedes ver, el análisis detectó automáticamente openjdk:8u181-jre-stretch como imagen base (es una etiqueta de alias para openjdk:8u181-jre) e informó 410 vulnerabilidades. También recomendó cambiar la imagen base, desde actualizar a una versión menor hasta usar la etiqueta más reciente de openjdk:8-jre —que tiene aproximadamente la mitad de problemas— e incluso pasar a JVM más nuevas que no tienen vulnerabilidades.
Como ves, no solo podemos descubrir qué vulnerabilidades hay en la imagen que usamos, sino que también recibimos recomendaciones para corregirlas mediante una imagen base más nueva, todo sin tener que escribir una sola línea de código de Dockerfile.
En resumen
Como puedes ver, Jib puede facilitar mucho a los desarrolladores la creación de contenedores para sus aplicaciones Java, ya que elimina la necesidad de aprender la sintaxis de Dockerfile o instalar herramientas desconocidas. También ayuda a los arquitectos a gestionar la estandarización mediante las conocidas funciones de jerarquía de proyectos de Maven o Gradle. Como Jib crea imágenes compatibles con OCI, también puedes aprovechar herramientas estándar de la industria —como el escáner Snyk Container— para inspeccionar, implementar y ejecutar tu aplicación.
Si también trabajas con un proyecto que no sea de Java, quizá quieras considerar otras herramientas de este ámbito, como Buildah, Bazel, Earthly y varios proyectos relacionados con BuildKit. Además, mi colega Pas Apicella publicó recientemente un artículo sobre Cloud Native Build Packs que sin duda deberías leer.
¡No olvides crear tu cuenta gratis y empezar hoy mismo a analizar tus contenedores, dependencias de código abierto y código de IaC!
Protege la infraestructura desde el origen
Snyk automatiza la seguridad y el cumplimiento de IaC en los flujos de trabajo, y detecta recursos con desviaciones de configuración y recursos faltantes.
