Migración de aplicaciones de Tomcat a contenedores en Azure Kubernetes Service

En esta guía se describe lo que hay que tener en cuenta para migrar una aplicación de Tomcat existente a un contenedor de Azure Kubernetes Service (AKS).

Antes de la migración

Para asegurarse de que la migración se realiza correctamente, antes de empezar, complete los pasos de evaluación e inventario descritos en las secciones siguientes.

Recursos externos de inventario

Los recursos externos, tales como los orígenes de datos, los agentes de mensajes JMS y otros, se insertan a través de la interfaz de directorio y nomenclatura de Java (JNDI). Algunos de estos recursos pueden requerir una migración o reconfiguración.

Dentro de la aplicación

Inspeccione el archivo META-INF/context.xml. Busque elementos <Resource> dentro del elemento <Context>.

En los servidores de aplicaciones

Inspeccione los archivos $CATALINA_BASE/conf/context.xml y $CATALINA_BASE/conf/server.xml, así como los archivos .xml que se encuentran en los directorios $CATALINA_BASE/conf/[nombreDelMotor]/[nombreDelHost].

En los archivos context.xml, los recursos de JNDI se describen con los elementos <Resource> que están dentro del elemento <Context> de nivel superior.

En los archivos server.xml, los recursos de JNDI se describen con los elementos <Resource> que están dentro del elemento <GlobalNamingResources>.

Orígenes de datos

Los orígenes de datos son recursos de JNDI con el atributo type establecido en javax.sql.DataSource. Para cada origen de datos, documente la siguiente información:

  • ¿Cuál es el nombre del origen de datos?
  • ¿Cuál es la configuración del grupo de conexiones?
  • ¿Dónde puedo encontrar el archivo JAR del controlador JDBC?

Para más información, consulte los artículos de procedimientos de los orígenes de datos JNDI en la documentación de Tomcat.

Todos los demás recursos externos

No es factible documentar todas las dependencias externas posibles en esta guía. Es responsabilidad del equipo comprobar que puede cumplir todas las dependencias externas de la aplicación después de la migración.

Secretos de inventario

Contraseñas y cadenas seguras

Compruebe las cadenas secretas y las contraseñas en todas las propiedades y los archivos de configuración de los servidores de producción. Asegúrese de comprobar los archivos server.xml y context.xml en $CATALINA_BASE/conf. También puede encontrar archivos de configuración que contengan contraseñas o credenciales dentro de la aplicación. Estos pueden ser archivos META-INF/context.xml y, para las aplicaciones de Spring Boot, archivos application.properties o application.yml.

Determinación de si se usa el sistema de archivos y cómo

Para usar el sistema de archivos en el servidor de aplicaciones será necesario cambiar la configuración o, en raras ocasiones, la arquitectura. Puede identificar algunos o todos los escenarios siguientes.

Contenido estático de solo lectura

Si su aplicación actualmente sirve contenido estático, necesitará una ubicación alternativa para él. Quizás quiera considerar la posibilidad de mover el contenido estático a Azure Blob Storage y agregar Azure CDN para tener descargas de alta velocidad globalmente. Para más información, consulte Hospedaje de sitios web estáticos en Azure Storage e Inicio rápido: Integración de una cuenta de una instancia de Azure Storage con Azure CDN. También puede implementar directamente el contenido estático en una aplicación en el plan Enterprise de Azure Spring Apps. Para obtener más información, consulte Implementación de archivos estáticos web.

Contenido estático publicado dinámicamente

Si su aplicación permite que haya contenido estático que la aplicación carga o produce, pero que es inmutable una vez creado, puede usar Azure Blob Storage y Azure CDN con una función de Azure para controlar las cargas y la actualización de la red CDN. Hemos proporcionado una implementación de ejemplo para su uso en Cargar y carga previa en CDN de contenido estático con Azure Functions. También puede implementar directamente el contenido estático en una aplicación en el plan Enterprise de Azure Spring Apps. Para obtener más información, consulte Implementación de archivos estáticos web.

Contenido dinámico o interno

En el caso de los archivos que la aplicación lee y escribe con frecuencia (por ejemplo, los archivos de datos temporales) o los archivos estáticos que solo son visibles para la aplicación, puede montar recursos compartidos de Azure Storage como volúmenes persistentes. Para más información, consulte Creación dinámica y uso de un volumen persistente con Azure Files en Azure Kubernetes Service.

Identificación del mecanismo de persistencia de sesión

Para identificar el administrador de persistencia de sesión que se está usando, inspeccione los archivos context.xml de la aplicación y la configuración de Tomcat. Busque el elemento <Manager> y, a continuación, anote el valor del atributo className.

Las implementaciones integradas de PersistentManager de Tomcat, como StandardManager o FileStore no están diseñadas para usarse con una plataforma distribuida y escalada como Kubernetes. AKS puede equilibrar la carga entre varios pods y reiniciar de forma transparente cualquier pod en cualquier momento, por lo que no se recomienda conservar el estado mutable en un sistema de archivos.

Si se necesita la persistencia de la sesión, deberá usar una implementación de PersistentManager alternativa que escribirá en un almacén de datos externo, como VMware Tanzu Session Manager con Redis Cache. Para más información, consulte Uso de Redis como caché de sesión con Tomcat.

Casos especiales

Algunos escenarios de producción pueden requerir otros cambios o imponer limitaciones adicionales. Aunque estos escenarios no son frecuentes, es importante asegurarse de que no son aplicables a la aplicación o que se han resuelto correctamente.

Determinación de si la aplicación se basa en trabajos programados

Los trabajos programados, como las tareas del programador de Quartz o los trabajos de cron, no se pueden usar con implementaciones de Tomcat en contenedor. Si la aplicación se escala horizontalmente, un trabajo programado se podría ejecutar más de una vez por cada período programado. Esta situación puede tener consecuencias no deseadas.

Haga un inventario de todos los trabajos programados, dentro o fuera del servidor de aplicaciones.

Determinación de si la aplicación contiene código específico del sistema operativo

Si la aplicación contiene código adaptado al sistema operativo en el que se ejecuta la aplicación, es necesario refactorizar la aplicación para que no dependa del sistema operativo subyacente. Por ejemplo, los usos de / o \ en las rutas de acceso del sistema de archivos tendrán que reemplazarse por File.Separator o Path.get.

Determinación de si se usa MemoryRealm

MemoryRealm requiere un archivo XML persistente. En Kubernetes, este archivo deberá agregarse a la imagen de contenedor o cargarse en un almacenamiento compartido que esté disponible para los contenedores. El parámetro pathName tendrá que modificarse según corresponda.

Para determinar si MemoryRealm se usa actualmente, inspeccione los archivos server.xml y context.xml y busque elementos <Realm> en los que el atributo className esté establecido en org.apache.catalina.realm.MemoryRealm.

Determinación de si se utiliza el seguimiento de sesión SSL

En las implementaciones en contenedor, lo habitual es que las sesiones SSL se descarguen fuera del contenedor de la aplicación, y normalmente lo hace el controlador de entrada. Si su aplicación requiere seguimiento de sesión SSL, asegúrese de que el tráfico SSL pase directamente al contenedor de la aplicación.

Determinación de si se usa AccessLogValve

Si se utiliza AccessLogValve, el parámetro directory debe establecerse en un recurso compartido de Azure Files montado o en uno de sus subdirectorios.

Pruebas en contexto

Antes de crear imágenes de contenedor, migre la aplicación al JDK y Tomcat que piensa usar en AKS. Pruebe la aplicación exhaustivamente para garantizar su compatibilidad y rendimiento.

Parametrización de la configuración

En la migración previa, es probable que haya identificado secretos y dependencias externas, como orígenes de bits, en los archivos server.xml y context.xml. En cada elemento identificado, reemplace los nombres de usuario, contraseñas, cadenas de conexión o direcciones URL por una variable de entorno.

Por ejemplo, supongamos que el archivo context.xml contiene el siguiente elemento:

<Resource
    name="jdbc/dbconnection"
    type="javax.sql.DataSource"
    url="jdbc:postgresql://postgresdb.contoso.com/wickedsecret?ssl=true"
    driverClassName="org.postgresql.Driver"
    username="postgres"
    password="t00secure2gue$$"
/>

En este caso, podría cambiarlo como se muestra en el ejemplo siguiente:

<Resource
    name="jdbc/dbconnection"
    type="javax.sql.DataSource"
    url="${postgresdb.connectionString}"
    driverClassName="org.postgresql.Driver"
    username="${postgresdb.username}"
    password="${postgresdb.password}"
/>

Migración

A excepción del primer paso ("Aprovisionamiento del registro de contenedor y AKS"), le recomendamos que siga los pasos que se indican a continuación de forma individual para cada aplicación (archivo WAR) que quiera migrar.

Nota:

Algunas implementaciones de Tomcat pueden tener varias aplicaciones que se ejecutan en un solo servidor de Tomcat. Si este es el caso de su implementación, es muy recomendable ejecutar cada aplicación en un pod independiente. Esto le permite optimizar el uso de recursos para cada aplicación a la vez que minimiza la complejidad y el acoplamiento.

Aprovisionamiento del registro de contenedor y AKS

Cree un registro de contenedor y un clúster de Azure Kubernetes cuya entidad de servicio tenga el rol de lector en el registro. Asegúrese de elegir el modelo de red adecuado para los requisitos de red del clúster.

az group create \
    --resource-group $resourceGroup \
    --location eastus
az acr create \
    --resource-group $resourceGroup \
    --name $acrName \
    --sku Standard
az aks create \
    --resource-group $resourceGroup \
    --name $aksName \
    --attach-acr $acrName \
    --network-plugin azure

Preparación de los artefactos de implementación

Clone el repositorio de GitHub Tomcat on Containers Quickstart. Contiene un archivo Dockerfile y archivos de configuración de Tomcat con varias optimizaciones recomendadas. En los pasos siguientes, se describen las modificaciones que probablemente deba realizar en estos archivos antes de compilar la imagen de contenedor e implementarla en AKS.

Apertura de los puertos para la agrupación en clústeres, si es necesario

Si tiene previsto usar la agrupación en clústeres de Tomcat en AKS, asegúrese de que los intervalos de puertos necesarios estén expuestos en el archivo Dockerfile. Para especificar la dirección IP del servidor en server.xml, asegúrese de usar un valor de una variable que se inicializa durante el inicio del contenedor en la dirección IP del pod.

Como alternativa, el estado de sesión se puede conservar en una ubicación alternativa para que esté disponible para todas las réplicas.

Para determinar si la aplicación usa la agrupación en clústeres, busque el elemento <Cluster> dentro de los elementos <Host> o <Engine> del archivo server.xml.

Adición de recursos de JNDI

Edite server.xml para agregar los recursos que preparó en los pasos previos a la migración, como los orígenes de datos.

Por ejemplo:

<!-- Global JNDI resources
      Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml"
               />

    <!-- Migrated datasources here: -->
    <Resource
        name="jdbc/dbconnection"
        type="javax.sql.DataSource"
        url="${postgresdb.connectionString}"
        driverClassName="org.postgresql.Driver"
        username="${postgresdb.username}"
        password="${postgresdb.password}"
    />
    <!-- End of migrated datasources -->
</GlobalNamingResources>

Para más instrucciones sobre orígenes de datos, consulte las siguientes secciones de los artículos de procedimientos de los orígenes de datos JNDI en la documentación de Tomcat:

Compilación e inserción de la imagen

El comando az acr build es la manera más sencilla de compilar y cargar la imagen en Azure Container Registry (ACR) para que AKS pueda usarla. Este comando no requiere que Docker esté instalado en el equipo. Por ejemplo, si tiene el archivo Dockerfile anterior y el paquete de aplicación petclinic.war en el directorio actual, puede compilar la imagen de contenedor en ACR con un solo paso:

az acr build \
    --image "${acrName}.azurecr.io/petclinic:{{.Run.ID}}" \
    --registry $acrName \
    --build-arg APP_FILE=petclinic.war \
    --build-arg=prod.server.xml .

Puede omitir el parámetro --build-arg APP_FILE... si el archivo WAR se llama ROOT.war. Puede omitir el parámetro --build-arg SERVER_XML... si el archivo XML del servidor se llama server.xml. Ambos archivos deben estar en el mismo directorio que el archivo Dockerfile.

Como alternativa, puede usar la CLI de Docker para compilar la imagen localmente. Este enfoque puede simplificar las pruebas y perfeccionar la imagen antes de la implementación inicial en ACR. Sin embargo, requiere instalar la CLI de Docker y el demonio de Docker para ejecutarse.

# Build the image locally
sudo docker build . --build-arg APP_FILE=petclinic.war -t "${acrName}.azurecr.io/petclinic:1"

# Run the image locally
sudo docker run -d -p 8080:8080 "${acrName}.azurecr.io/petclinic:1"

# Your application can now be accessed with a browser at http://localhost:8080.

# Log into ACR
sudo az acr login --name $acrName

# Push the image to ACR
sudo docker push "${acrName}.azurecr.io/petclinic:1"

Para más información, consulte el módulo Learn para compilar y almacenar imágenes de contenedor en Azure.

Aprovisionamiento de una dirección IP pública

Si la aplicación va a ser accesible desde fuera de las redes internas o virtuales, necesitará una dirección IP estática pública. Esta dirección IP debe aprovisionarse dentro del grupo de recursos del nodo del clúster.

export nodeResourceGroup=$(az aks show \
    --resource-group $resourceGroup \
    --name $aksName \
    --query 'nodeResourceGroup' \
    --output tsv)
export publicIp=$(az network public-ip create \
    --resource-group $nodeResourceGroup \
    --name applicationIp \
    --sku Standard \
    --allocation-method Static \
    --query 'publicIp.ipAddress' \
    --output tsv)
echo "Your public IP address is ${publicIp}."

Implementación en AKS

Cree y aplique los archivos YAML de Kubernetes. Si va a crear un equilibrador de carga externo (ya sea para la aplicación o para un controlador de entrada), asegúrese de proporcionar la dirección IP aprovisionada en la sección anterior como LoadBalancerIP.

Incluya los parámetros externos como variables de entorno. No incluya secretos (por ejemplo, contraseñas, claves de API y cadenas de conexión de JDBC). Los secretos se describen en la sección Configuración de un volumen flexible de KeyVault.

Configuración de almacenamiento persistente

Si su aplicación requiere almacenamiento no volátil, configure uno o más volúmenes persistentes.

Quizás quiera crear un volumen persistente con Azure Files, montado en el directorio de registros de Tomcat (/tomcat_logs) para conservar los registros de forma centralizada. Para más información, consulte Creación dinámica y uso de un volumen persistente con Azure Files en Azure Kubernetes Service (AKS).

Configuración de un volumen flexible de KeyVault

Cree un almacén de claves de Azure y rellene todos los secretos necesarios. Después, configure un volumen flexible de KeyVault para que esos secretos sean accesibles para los pods.

Tendrá que modificar el script de inicio (startup.sh en el repositorio Tomcat on Containers de GitHub) para importar los certificados en el almacén de claves local del contenedor.

Migración de los trabajos programados

Para ejecutar trabajos programados en el clúster de AKS, defina los trabajos de cron que sean necesarios.

Después de la migración

Ahora que ha migrado la aplicación a AKS, debe comprobar que funciona como se espera. Una vez hecho esto, tenemos algunas recomendaciones que pueden hacer que su aplicación sea más nativa de la nube.

  • Considere la posibilidad de agregar un nombre DNS a la dirección IP asignada al controlador de entrada o al equilibrador de carga de la aplicación. Para más información, consulte Creación de un controlador de entrada con una dirección IP pública estática en AKS.

  • Considere la posibilidad de agregar gráficos de HELM para la aplicación. Un gráfico de Helm permite parametrizar la implementación de la aplicación para que un conjunto de clientes más diverso pueda usarla y personalizarla.

  • Diseñe e implemente una estrategia de DevOps. Para mantener la confiabilidad y, al mismo tiempo, aumentar la velocidad de desarrollo, considere la posibilidad de automatizar las implementaciones y pruebas con Azure Pipelines.

  • Habilite la supervisión de Azure en el clúster para poder recopilar los registros de contenedor o realizar un seguimiento del uso, entre otras tareas.

  • Considere la posibilidad de exponer métricas específicas de la aplicación mediante Prometheus. Prometheus es un marco de métricas de código abierto que se ha adoptado ampliamente en la comunidad de Kubernetes. Puede configurar el barrido de métricas de Prometheus en Azure Monitor en lugar de hospedar su propio servidor de Prometheus para habilitar la agregación de métricas de sus aplicaciones y automatizar la respuesta o el escalado en caso de condiciones anómalas.

  • Diseñe e implemente una estrategia de continuidad empresarial y recuperación ante desastres. En el caso de las aplicaciones críticas, considere la posibilidad de usar una arquitectura de implementación de varias regiones.

  • Consulte la directiva de compatibilidad de versiones de Kubernetes. Es su responsabilidad mantener actualizado el clúster de AKS para asegurarse de que siempre ejecuta una versión compatible.

  • Haga que todos los miembros del equipo responsables del desarrollo de aplicaciones y la administración del clúster revisen los procedimientos recomendados de AKS.

  • Evalúe los elementos del archivo logging.properties. Considere la posibilidad de eliminar o reducir parte de la salida del registro para mejorar el rendimiento.

  • Considere la posibilidad de supervisar el tamaño de la caché de código y de agregar los parámetros -XX:InitialCodeCacheSize y -XX:ReservedCodeCacheSize a la variable JAVA_OPTS en Dockerfile para optimizar aún más el rendimiento.