Contenedorización de una aplicación Java

Completado

En esta unidad, containerizas una aplicación Java.

Como se mencionó anteriormente, los contenedores se ejecutan directamente sobre el sistema operativo host, el kernel y el hardware como procesos normales del sistema. Los contenedores requieren menos recursos del sistema, lo que da lugar a una superficie más pequeña, menos sobrecarga y tiempos de inicio de aplicaciones más rápidos. Estas ventajas son casos de uso excelentes para el escalado a petición.

Hay contenedores de Windows y contenedores de Linux. En este módulo, usará el entorno de ejecución de Docker ampliamente usado para compilar una imagen de contenedor de Linux. A continuación, implemente la imagen de contenedor de Linux en el sistema operativo host de la máquina local. Por último, implementas la imagen de contenedor de Linux en Azure Kubernetes Service.

Introducción a Docker

El entorno de ejecución de Docker se usa para compilar, extraer, ejecutar e insertar imágenes de contenedor, como se muestra en el diagrama siguiente:

Diagrama que muestra los comandos de Docker.

En la tabla siguiente se describe cada comando de Docker:

Comando de Docker Descripción
docker build Compila una imagen de contenedor que consta de las instrucciones o capas necesarias para que Docker cree un contenedor en ejecución a partir de una imagen. El resultado de este comando es una imagen.
docker pull Los contenedores se inicializan a partir de imágenes, que se extraen de registros como Azure Container Registry. Este es el registro desde el cual Azure Kubernetes Service extrae datos. El resultado de este comando es la descarga de una imagen desde la red, que se realiza en Azure. Opcionalmente, puede extraer imágenes localmente. Esta opción es habitual al compilar imágenes que requieren dependencias o capas que la aplicación podría necesitar, como un servidor de aplicaciones.
docker run Una instancia en ejecución de una imagen es un contenedor y este comando ejecuta todas las capas necesarias para ejecutar e interactuar con la aplicación contenedora en ejecución. El resultado de este comando es un proceso de aplicación en ejecución en el sistema operativo host.
docker push Azure Container Registry almacena las imágenes para que estén fácilmente disponibles y cercanas a la red para las implementaciones y escalabilidad de Azure.

Clonación de la aplicación Java

En primer lugar, clone el repositorio de Flight Booking System for Airline Reservations y navegue a la carpeta del proyecto de aplicación web para compañías aéreas.

Nota:

Si la creación de Azure Kubernetes Service finaliza en la pestaña CLI, use esa pestaña. Si aún se está ejecutando, abra una nueva pestaña y vaya a la ubicación en la que prefiere clonar el sistema de reservas de vuelos para reservas aéreas.

Ejecute los comandos siguientes:

git clone https://github.com/Azure-Samples/containerize-and-deploy-Java-app-to-Azure.git
cd containerize-and-deploy-Java-app-to-Azure/Project/Airlines

Opcionalmente, si tiene Instalado Java y Maven, puede ejecutar el siguiente comando en la consola de terminal para obtener una idea de la experiencia de compilación de la aplicación sin Docker. Si no tiene Instalado Java y Maven, puede continuar con seguridad a la sección siguiente , Construir un archivo docker. En esa sección, usará Docker para extraer Java y Maven para ejecutar las compilaciones en su nombre.

mvn clean package

Nota:

Hemos usado el mvn clean package comando para ilustrar los desafíos operativos de no usar compilaciones de varias fases de Docker, que tratamos a continuación. De nuevo, este paso es opcional. En cualquier caso, puede continuar de forma segura sin ejecutar el comando maven.

Si el proceso se realizó correctamente, Maven creó correctamente el artefacto Flight Booking System for Airline Reservations Web Application Archive AirlinesReservationSample-0.0.1-SNAPSHOT.war, como se muestra en la siguiente salida:

[INFO] Building war: $PROJECT_PATH/containerize-and-deploy-Java-app-to-Azure/Project/Airlines/target/AirlinesReservationSample-0.0.1-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.776 s
[INFO] Finished at: 2024-11-15T09:33:26+09:00
[INFO] ------------------------------------------------------------------------

Imagine que es desarrollador de Java y acaba de compilar AirlinesReservationSample-0.0.1-SNAPSHOT.war. Probablemente, el siguiente paso sea trabajar con los ingenieros de operaciones para implementar este artefacto en un servidor local o en una máquina virtual. Para que la aplicación se inicie y ejecute correctamente, los servidores y las máquinas virtuales deben estar disponibles y configurados con las dependencias necesarias. Este proceso es complicado y requiere mucho tiempo, especialmente a petición, cuando el aumento de la carga está afectando a la aplicación. Con los contenedores, estos desafíos se mitigan.

Construcción de un Dockerfile

Ya estás listo para crear un Dockerfile. Un Dockerfile es un documento de texto que contiene todos los comandos que un usuario ejecutaría en la línea de comandos para ensamblar una imagen de contenedor. Cada imagen es una capa que se puede almacenar en caché para mejorar la eficacia. Las capas se superponen unas sobre otras.

Por ejemplo, el sistema de reservas de vuelos para reservas aéreas debe implementarse y ejecutarse dentro de un servidor de aplicaciones. Un servidor de aplicaciones no se empaqueta dentro de AirlinesReservationSample-0.0.1-SNAPSHOT.war. Es una dependencia externa necesaria para que AirlinesReservationSample-0.0.1-SNAPSHOT.war se ejecute, escuche y procese solicitudes HTTP, administre sesiones de usuario y facilite las reservas de vuelos. Si usó una implementación tradicional no en contenedores, los ingenieros de operaciones instalarían y configurarían un servidor de aplicaciones en algún servidor físico o máquina virtual antes de implementar airlinesReservationSample-0.0.1-SNAPSHOT.war en él. Estos ingenieros de operaciones también necesitarían asegurarse de que el JDK que se usa en la máquina ,que es lo que mvn clean package se usa para compilar el archivo WAR, de hecho corresponde al mismo JRE que usa el servidor de aplicaciones. La administración de estas dependencias es difícil y lenta.

Con un Dockerfile, puede escribir las instrucciones o capas necesarias para lograr este objetivo automáticamente, añadiendo los pasos necesarios en capas para asegurarse de que el sistema de reservaciones aéreas tiene todas las dependencias necesarias para desplegar en el entorno de ejecución del contenedor de Docker. Esta solución es convincente cuando se trabaja con escalabilidad a petición en intervalos no planeados. Cada capa usa la memoria caché de Docker, que contiene el estado de la imagen de contenedor en cada hito informativo, optimizando el tiempo de proceso y la reutilización. Si una capa no cambia, se usan capas almacenadas en caché. Los casos de uso comunes para las capas almacenadas en caché son el entorno de ejecución de Java, el servidor de aplicaciones y otras dependencias para la aplicación web Flight Booking System for Airline Reservations. Si y cuando cambia una versión en una capa almacenada previamente en caché, se crea una nueva entrada almacenada en caché.

En el diagrama siguiente se muestran las capas de una imagen de contenedor. Cuando se ejecutan los comandos del Dockerfile, se crean las capas. La capa superior es la capa de aplicación web Flight Booking System for the Airline Reservations de lectura y escritura. Esa capa está construida sobre las capas anteriores de solo lectura.

Diagrama que muestra las capas de Docker.

Docker tiene el concepto de compilaciones de varias fases, una característica que permite crear una imagen de contenedor más pequeña con un mejor almacenamiento en caché y una superficie de seguridad más pequeña, lo que permite una mayor optimización y mantenimiento del Dockerfile a lo largo del tiempo. Por ejemplo, puede separar la etapa de creación del contenedor para compilar y construir la aplicación de la etapa para ejecutar la aplicación. Esta característica permite copiar solo los artefactos generados durante la compilación en la imagen de contenedor de producción, lo que reduce la superficie. Dado que las imágenes de contenedor se almacenan en caché, si no hay cambios, las imágenes almacenadas en caché se pueden reutilizar, lo que reduce el costo y el tiempo de descarga de la red.

Los servicios expuestos en el entorno de producción deben administrarse cuidadosamente para la seguridad. Por lo tanto, el entorno de producción usa y opera una imagen de contenedor segura. En el ejemplo se usa la CBL-Mariner imagen proporcionada por Microsoft.

CBL-Mariner Linux es un sistema operativo ligero, que solo contiene los paquetes necesarios para un entorno de nube. Puede personalizarlo a través de paquetes y herramientas personalizados para ajustarse a los requisitos de la aplicación. CBL-Mariner somete a pruebas de validación de Azure y es compatible con los agentes de Azure. Microsoft compila y prueba CBL-Mariner para impulsar varios casos de uso, desde servicios de Azure hasta la potencia de la infraestructura de IoT. Es la distribución de Linux recomendada internamente para su uso con servicios en la nube de Microsoft y productos relacionados.

Nota:

Microsoft proporciona imágenes de contenedor agrupadas con OpenJDK, incluidas las imágenes Ubuntu, CBL-Mariner y distroless. La distroless imagen tiene el tamaño de imagen más pequeño, pero la ejecución de Tomcat en ella es difícil. Para lograr un diseño ligero, la distroless imagen quita muchos comandos y herramientas, incluido el shell, lo que significa que no se puede llamar catalina.sh para iniciar Tomcat. La distroless imagen es adecuada para ejecutar archivos JAR ejecutables, como los que se usan con Spring Boot o Quarkus.

En el ejemplo siguiente, se usa la misma versión de Microsoft Build de OpenJDK tanto en la fase de compilación como en la fase final. Este enfoque garantiza que compile el código fuente con la misma versión del JDK que usa la implementación del servicio Tomcat, lo que ayuda a evitar un comportamiento inesperado debido a errores de coincidencia de versiones.

En la imagen siguiente se muestra la compilación de varias fases y lo que ocurre en cada fase en función de los comandos especificados en el Dockerfile:

Diagrama que muestra la compilación de varias fases de Docker.

En la fase 0, Tomcat se descarga y extrae en un directorio especificado por una variable de entorno en una imagen de Ubuntu. La TOMCAT_VERSION variable especifica la versión de Tomcat que se va a descargar. Si se publica una nueva versión de Tomcat, debe actualizar el número de versión, ya que una nueva imagen solo se captura cuando cambia el número de versión. De lo contrario, se usa la imagen almacenada en caché. Tomcat descargado se copia en el entorno de fase final para su uso.

En la fase 1, Maven se instala en una imagen de Ubuntu y el código fuente y los archivos de configuración creados se copian antes de compilar el proyecto de Maven. Cada capa se almacena en caché, por lo que la imagen del sistema operativo y las capas de imagen de Maven reutilizan la memoria caché. Si se actualizan los archivos de configuración, los archivos de código fuente o el directorio web, se vuelven a generar las capas a partir de los cambios. Si la compilación se completa correctamente sin errores durante la compilación, se genera un artefacto denominado AirlinesReservationSample-0.0.1-SNAPSHOT.war en el directorio de destino . Este artefacto se copia en el entorno de fase final para su uso.

En la fase final, la imagen segura CBL-Mariner proporcionada por Microsoft se usa para copiar los artefactos de compilación de Tomcat y Java de la fase 0 y la fase 1, respectivamente. Un usuario denominado app posee todos los archivos usados en el proyecto y la aplicación también se ejecuta como el app usuario en lugar de tener root privilegios. Esta configuración garantiza que la imagen de contenedor se pueda operar de forma segura sin conceder permisos innecesarios. Por último, se expone el número de puerto 8080 y se ejecuta el script catalina.sh para iniciar Tomcat. Cuando se ejecuta en el escritorio de Docker local, puede acceder a él a través de la dirección URL http://localhost:8080/AirlinesReservationSample.

En la carpeta raíz del proyecto, containerize-and-deploy-Java-app-to-Azure/Project/Airlines, use el siguiente comando para crear un archivo denominado Dockerfile:

vi Dockerfile

Agregue el siguiente contenido al Dockerfile y, a continuación, guarde y salga. Para guardar y salir, presione ESC, escriba :wq!y presione Entrar.

############################################
# Tomcat Intall stage
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat

ENV CATALINA_HOME=/usr/local/tomcat

# Configure Tomcat Version (Be sure to use the latest version)
ENV TOMCAT_VERSION=10.1.33

# Install Tomcat and required packages
RUN apt-get update ; \
    apt-get install -y curl ; \
    curl -O https://downloads.apache.org/tomcat/tomcat-10/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz ; \
    tar xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz ; \
    mv apache-tomcat-${TOMCAT_VERSION} ${CATALINA_HOME} ; \
    rm apache-tomcat-${TOMCAT_VERSION}.tar.gz && \
    apt-get remove --purge -y curl && \
    apt-get autoremove -y && \
    apt-get clean

############################################
# Build stage (Compiles with Java 17)
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build

WORKDIR /build

# Install Maven
RUN apt-get update && apt-get install -y maven && mvn --version

# Copy source code
COPY pom.xml .
COPY src ./src
COPY web ./web

# Build the project
RUN mvn clean package

############################################
# Package final stage
############################################
FROM mcr.microsoft.com/openjdk/jdk:17-mariner

# Configure the location of the Tomcat installation
ENV CATALINA_HOME=/usr/local/tomcat
# Configure the path to the Tomcat binaries
ENV PATH=$CATALINA_HOME/bin:$PATH

# This is the user that runs the Tomcat process
USER app

# Copy the Tomcat installation from the Tomcat stage
COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME}
# Copy the Tomcat configuration files
COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf
# Copy the compiled WAR file from the build stage
COPY --chown=app:app --from=build /build/target/*.war ${CATALINA_HOME}/webapps/AirlinesReservationSample.war

# Expose the default Tomcat port
EXPOSE 8080
# Start Tomcat
CMD ["catalina.sh", "run"]

Nota:

Opcionalmente, puede usar el archivo Dockerfile_Solution en la raíz del proyecto, que contiene el contenido que necesita.

El Dockerfile se divide en tres fases, que se describen en las tablas siguientes:

  • La fase de instalación de Tomcat:

    Comando de Docker Descripción
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS tomcat establece la imagen base en Microsoft Build of OpenJDK 17 on Ubuntu y asigna el nombre a esta fase tomcat. Aquí es donde está instalado Tomcat.
    ENV ENV CATALINA_HOME=/usr/local/tomcat establece una variable de entorno para el directorio de instalación de Tomcat.
    ENV ENV TOMCAT_VERSION=10.1.33 establece la versión de Tomcat que se va a instalar. Esto debe actualizarse a la versión más reciente según sea necesario.
    RUN El RUN comando actualiza la lista de paquetes, instala curl, descarga la versión especificada de Tomcat, la extrae, la mueve al directorio especificado y limpia los archivos y paquetes innecesarios. Esto garantiza que la imagen permanezca ligera.
  • La fase de compilación, que se compila con Java 17:

    Comando de Docker Descripción
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build establece la imagen base en Microsoft Build of OpenJDK 17 on Ubuntu y asigna el nombre a esta fase build. Esta fase se usa para compilar la aplicación Java.
    WORKDIR WORKDIR /build establece el directorio de trabajo dentro del contenedor /builden , donde se copia y compila el código fuente.
    RUN RUN apt-get update && apt-get install -y maven && mvn --version instala Maven, una herramienta de automatización de compilación que se usa para proyectos de Java y comprueba su instalación.
    COPY COPY pom.xml . copia el archivo de configuración de Maven en el directorio de trabajo. Este archivo es esencial para compilar el proyecto.
    COPY COPY src ./src copia el directorio de código fuente en el contenedor. Aquí es donde reside el código de la aplicación Java.
    COPY COPY web ./web copia el directorio de recursos web en el contenedor. Esto incluye los recursos de aplicación web necesarios para la compilación.
    RUN RUN mvn clean package ejecuta el proceso de compilación de Maven, que compila la aplicación Java y la empaqueta en un archivo WAR.
  • La fase final del paquete:

    Comando de Docker Descripción
    FROM FROM mcr.microsoft.com/openjdk/jdk:17-mariner establece la imagen base en Microsoft Build of OpenJDK 17 on CBL-Mariner, que se usa para la implementación final de la aplicación.
    ENV ENV CATALINA_HOME=/usr/local/tomcat establece la variable de entorno para el directorio de instalación de Tomcat, similar a la fase de instalación.
    ENV ENV PATH=$CATALINA_HOME/bin:$PATH agrega el directorio bin de Tomcat al sistema PATH, lo que permite que los comandos de Tomcat se ejecuten fácilmente.
    USER USER app especifica el usuario en el que se ejecuta el proceso de Tomcat, lo que mejora la seguridad al no ejecutarse como usuario raíz.
    COPY COPY --chown=app:app --from=tomcat ${CATALINA_HOME} ${CATALINA_HOME} copia la instalación de Tomcat desde la fase tomcat, estableciendo la propiedad en el usuario app.
    COPY COPY --chown=app:app tomcat-users.xml ${CATALINA_HOME}/conf copia el archivo de configuración de usuario de Tomcat en el contenedor, asignando la propiedad al usuario app.
    COPY COPY --chown=app:app --from=build /build/target/*.war ${CATALINA_HOME}/webapps/AirlinesReservationSample.war copia el archivo WAR compilado de la fase build en el directorio webapps de Tomcat, estableciendo la propiedad en el usuario app.
    EXPOSE EXPOSE 8080 expone el puerto 8080, el puerto predeterminado para Tomcat, lo que permite el acceso externo a la aplicación.
    CMD CMD ["catalina.sh", "run"] especifica el comando para iniciar Tomcat cuando se ejecuta el contenedor.

Para obtener más información sobre la construcción de Dockerfile, consulte la referencia de Dockerfile.