La contenedorización ha transformado la forma en que se desarrollan e implementan nuevas aplicaciones. Sin embargo, muchas organizaciones conservan un catálogo atrasado de sistemas más antiguos que necesitan un enfoque diferente. Esta desconexión entre lo nuevo y lo viejo no tiene por qué perpetuarse: también puedes empaquetar sistemas más antiguos como contenedores, lo que facilita su evolución con métodos de desarrollo más modernos.
En este artículo, veremos un proceso que puede utilizar para comenzar a crear contenedores de software “heredado”. Si bien no habrá dos productos iguales y el término “heredado” es subjetivo, nos centraremos en pasos ampliamente aplicables para empaquetar sistemas estrechamente acoplados actualmente vinculados a entornos individuales.
1. Identificar sistemas candidatos
Primero vale la pena preparar un inventario de sistemas que le permita identificar buenos candidatos para la contenedorización. En algunos casos, podría concluir que una aplicación en particular simplemente no se puede contener. Esto suele ocurrir cuando tiene requisitos de hardware profundamente arraigados o depende de características del kernel y lenguajes de programación obsoletos.
Los mejores candidatos son los sistemas de uso frecuente que se beneficiarán inmediatamente de un desarrollo futuro acelerado. Busque aplicaciones que ya sean bastante autónomas si es completamente nuevo en el uso de contenedores. Seleccionar un sistema que sea bien utilizado pero que no sea de misión crítica le dará margen de maniobra si las cosas salen mal y le permitirá reconocer los beneficios de una migración exitosa.
2. Componentizar el sistema
Puede contener su sistema candidato escribiendo un Dockerfile, incluidas todas las dependencias de la aplicación, y finalizándolo. Si bien esta es una forma válida de colocar rápidamente un sistema en un contenedor, no debería ser el objetivo final de sus esfuerzos. Un contenedor monolítico dará como resultado compilaciones largas, tamaños de imagen enormes y escalabilidad deficiente.
En su lugar, debería buscar oportunidades para dividir cada uno de sus sistemas en componentes individuales. Esos componentes deben terminar en sus propios contenedores, evitando que cualquier pieza se vuelva demasiado grande. Podrá escalar los componentes individualmente creando réplicas adicionales de contenedores con recursos limitados.
Este paso también es importante para establecer la modularidad general y fomentar una mayor adopción de contenedores. A medida que separa más sistemas en sus componentes, comenzará a encontrar superposiciones que le permitirán reutilizar imágenes de contenedores que ya ha creado. Notarás que gradualmente se vuelve más fácil continuar colocando contenedores.
Decidir dónde dividir los componentes no debería resultar demasiado agotador. Comience por identificar dónde depende el sistema de servicios que ya son externos a su código fuente. Las conexiones de bases de datos, colas de mensajes, servidores de correo electrónico, servidores proxy y puertas de enlace deben ser independientes del componente que aumentan. Los separará en sus propios contenedores que se ubicarán junto a la instancia que ejecuta su código.
También vale la pena buscar oportunidades para refactorizar lo que queda. ¿Su servicio tiene demasiadas responsabilidades que podrían dividirse en unidades funcionales separadas? Es posible que tenga una API de perfil de usuario que acepte la carga de fotografías; el servicio que cambia el tamaño de esas fotos podría ser un buen candidato para ejecutarse de forma autónoma en su propio contenedor.
3. Prepare sus componentes
Después de separar los componentes, debe prepararlos para operar en un entorno en contenedores. Los contenedores tienen varias diferencias clave en comparación con las máquinas virtuales tradicionales. El almacenamiento persistente, la configuración y los enlaces entre componentes son los tres más importantes a considerar desde el principio.
Almacenamiento persistente
Los contenedores son entornos efímeros. Las modificaciones del sistema de archivos se pierden cuando los contenedores se detienen. Usted es responsable de administrar los datos persistentes de su aplicación utilizando los mecanismos que proporciona el tiempo de ejecución de su contenedor.
En el caso de Docker, los volúmenes se utilizan para conservar datos fuera de las instancias de su contenedor. Los volúmenes se montan en rutas específicas dentro de contenedores. Para evitar tener que montar docenas de volúmenes, es mejor concentrar los datos de su aplicación en unos pocos directorios de nivel superior. Montar volúmenes en esas ubicaciones garantizará la persistencia de los archivos que almacena su aplicación.
Es importante auditar las interacciones del sistema de archivos de su aplicación para comprender qué volúmenes necesita y cualquier problema que encontrará. No prestar atención a este paso podría resultar costoso si los datos que supone que persisten se pierden cada vez que se reinicia un contenedor.
Gestión de la configuración
Muchas aplicaciones heredadas se configuran mediante archivos de configuración estáticos. Estos pueden estar en un formato dedicado, como XML, JSON o INI, o codificados utilizando el lenguaje de programación del sistema.
Los contenedores normalmente se configuran mediante variables de entorno externas. Las variables se definen cuando se crean los contenedores, utilizando mecanismos como Docker.
-e
bandera con
docker run
. Se inyectan en el entorno del contenedor en ejecución.
El uso de este sistema garantiza que pueda confiar en la cadena de herramientas de su contenedor para establecer y cambiar los parámetros de configuración. Es posible que primero deba refactorizar su aplicación para admitir la lectura de configuraciones de variables de entorno. Una forma común de facilitar la transición es colocar un pequeño script dentro del punto de entrada del contenedor. Esto puede enumerar variables de entorno al crear el contenedor y escribirlas en un archivo de configuración para su aplicación.
Enlaces entre servicios
La contenedorización también te hace pensar en la creación de redes entre servicios. Los servicios generalmente no están expuestos entre sí excepto mediante una configuración explícita. Puede configurar enlaces automáticos en Docker uniéndose múltiples contenedores para la misma red Docker. Esto ofrece una función de descubrimiento de servicios que permite que los contenedores se comuniquen entre sí por su nombre.
Otras tecnologías de contenedorización utilizan diferentes enfoques para la creación de redes y el descubrimiento de servicios. Después de haber separado sus sistemas en componentes individuales, debe volver a unirlos utilizando las funciones que ofrece su tiempo de ejecución. La naturaleza de las implementaciones en contenedores significa que a menudo hay más complejidad que la creación de redes entre máquinas virtuales o hosts físicos. Es necesario enrutar el tráfico y equilibrar la carga entre todas las réplicas de contenedores y sus dependencias, por lo que debes reconocer estos requisitos desde el principio.
4. Escribe tus Dockerfiles
Una vez que haya planificado su arquitectura, puede comenzar el trabajo físico asociado con la contenedorización. El primer paso es escribir Dockerfiles para los componentes de su aplicación. Estos definen la secuencia de comandos y acciones que crean un sistema de archivos que contiene todo lo que el componente necesita para ejecutarse.
Los Dockerfiles comienzan con una imagen base apropiada a la que hace referencia un
FROM
declaración. Este es comúnmente un sistema operativo (
ubuntu:20.04
,
alpine:3
) o un entorno de lenguaje de programación prediseñado (
php:8
,
node:16
). Puede elegir la imagen que mejor se adapte al entorno existente de su aplicación. Es posible comenzar desde un sistema de archivos vacío, pero generalmente no es necesario a menos que necesite un control extremadamente granular.
El contenido adicional se superpone a la imagen base mediante instrucciones como
COPY
y
RUN
. Estos le permiten copiar archivos desde su host y ejecutar comandos en el sistema de archivos temporal de la compilación. Una vez que haya escrito su Dockerfile, puede compilarlo con el
docker build -t my-image:latest .
dominio.
5. Configurar la orquestación
Suponiendo que haya componenteizado su sistema, terminará con una imagen de contenedor para cada pieza. Ahora necesita una forma de abrir todos los contenedores simultáneamente para poder iniciar cómodamente una instancia de aplicación que funcione.
Las instalaciones de producción más grandes suelen utilizar Kubernetes para este fin. Es un sistema de orquestación dedicado que agrega sus propios conceptos de nivel superior para crear implementaciones en contenedores replicadas. Los sistemas y entornos de desarrollo más pequeños suelen contar con un buen servicio con Docker Compose, una herramienta que se basa en archivos YAML más simples para iniciar una “pila” de varios contenedores:
version: "3"app:
image: my-web-app:latest
ports:
- 80:80
database:
image: mysql:8.0
ports:
- 3306:3306
A
docker-compose.yml
El archivo le permite iniciar todos sus servicios utilizando el
docker-compose
binario:
docker-compose up -d
Configurar algún tipo de orquestación hace que su flota de contenedores sea más manejable y facilita el escalado mediante replicación. Tanto Kubernetes como Docker Compose pueden iniciar múltiples instancias de sus servicios, una capacidad que no se puede lograr con aplicaciones heredadas formadas por componentes estrechamente acoplados.
6. Después de la mudanza: seguimiento y ampliación de su flota de contenedores
La creación de contenedores no termina con el inicio de una instancia de su aplicación. Para aprovechar al máximo la tecnología, debe monitorear adecuadamente sus contenedores para mantenerse informado sobre los errores y la utilización de recursos.
Los sistemas más grandes funcionan mejor con una plataforma de observabilidad dedicada que pueda agregar registros y métricas de toda su flota. Es posible que ya esté utilizando una solución similar con las implementaciones de sus aplicaciones heredadas, pero es aún más importante para los contenedores. Una buena observabilidad le permitirá rastrear los problemas hasta la instancia de contenedor en la que se originaron, lo que permitirá obtener información importante cuando tenga cientos o miles de réplicas.
Para seguir ampliando su flota, duplique la documentación y la estandarización. Ya hemos visto cómo dividir los sistemas en componentes ayuda a su reutilización futura. Sin embargo, esto sólo funciona eficazmente si has documentado lo que tienes y cómo encaja cada pieza. Tomarse el tiempo para escribir sobre su sistema y el proceso por el que ha pasado agilizará el trabajo futuro. También ayudará a los nuevos miembros del equipo a comprender las decisiones que ha tomado.
¿Vale la pena?
La creación de contenedores vale la pena cuando se siente que el desarrollo de un sistema se ve frenado por sus procesos actuales. Poder implementarlo como un conjunto de contenedores simplifica la experiencia de desarrollo y le brinda más versatilidad en la implementación. Ahora puede iniciar el servicio en cualquier lugar donde haya un tiempo de ejecución de contenedor disponible, ya sea una instancia en su computadora portátil o 1000 en un proveedor de nube pública.
Optar por los contenedores hace que sea más fácil aprovechar el poder de la nube, consolidar sus implementaciones y reducir los costos de infraestructura local. Sin embargo, estas aparentes victorias pueden verse contrarrestadas por la necesidad de volver a capacitar a los ingenieros, contratar nuevos talentos especializados y mantener los contenedores a lo largo del tiempo.
La decisión de contener un sistema heredado debe considerar el valor de ese sistema para su negocio, el tiempo actual dedicado a su mantenimiento y la posible reducción como resultado del uso de contenedores. Podría ser que sea mejor dejar en paz los servicios de baja prioridad si los procesos asociados con ellos no están causando problemas inmediatos.
Debe reconocerse que no todas las aplicaciones heredadas necesitarán o serán capaces de utilizar todos los beneficios promocionados de la contenedorización. La adopción es un espectro, desde ejecutar el sistema en un único contenedor monolítico hasta la componenteización, orquestación e integración completa con suites de observabilidad. Este último modelo es el objetivo ideal para aplicaciones críticas para el negocio que los ingenieros desarrollan todos los días; por el contrario, el primero puede ser adecuado para servicios que rara vez se utilizan, donde el principal obstáculo es el tiempo dedicado a aprovisionar nuevos entornos de desarrollo basados en VM.
Conclusión
Migrar aplicaciones heredadas a flujos de trabajo en contenedores puede parecer un desafío en la superficie. Dividir el proceso en distintos pasos suele ayudar a definir dónde se encuentra y dónde quiere estar. En este artículo, analizamos seis etapas granulares que puede utilizar para abordar la contenedorización de sistemas existentes. También analizamos algunas de las consideraciones que debe tener en cuenta al decidir si desea continuar.
Desde un punto de vista conceptual, contener una aplicación heredada no es muy diferente a trabajar con una nueva. Está aplicando los mismos principios de componenteización, servicios vinculados y configuración que se inyectan desde el entorno externo. La mayoría de los sistemas son relativamente sencillos de contener en contenedores cuando se ven desde esta perspectiva. Centrarse en estos aspectos le ayudará a desacoplar sus aplicaciones, crear componentes escalables e idear una metodología de contenedorización eficaz.