Hola a todos!
Me gustaría hablar un poco sobre el proyecto en el que he estado trabajando durante los últimos seis meses. Realizo el proyecto en mi tiempo libre, pero la motivación para su creación provino de las observaciones realizadas en el trabajo principal.
En un proyecto en funcionamiento, utilizamos la arquitectura de microservicios, y uno de los principales problemas que se ha manifestado con el tiempo y el aumento en el número de estos servicios son las pruebas. Cuando un determinado servicio depende de otros cinco o siete servicios, más alguna otra base de datos (o incluso varias) para arrancar, probarlo en forma "en vivo", por así decirlo, es muy inconveniente. Tienes que ponerte mokas por todos lados con tanta fuerza que ni siquiera puedas distinguir la masa. Bueno, o de alguna manera organice un entorno de prueba donde todas las dependencias realmente puedan iniciarse.
En realidad, para facilitar la segunda opción, simplemente me senté a escribir xenvman . En pocas palabras, esto es algo así como un híbrido de Docker-compose y de contenedores de prueba , solo sin vincularse a Java (o cualquier otro lenguaje) y con la capacidad de crear y configurar dinámicamente entornos a través de la API HTTP.
xenvman
escrito en Go y se implementa como un servidor HTTP simple, que le permite utilizar todas las funciones disponibles desde cualquier idioma que pueda hablar este protocolo.
Lo principal que puede hacer xenvman es:
- Describa de manera flexible el contenido del entorno con scripts JavaScript simples
- Crea imágenes sobre la marcha
- Cree la cantidad correcta de contenedores y combínelos en una sola red aislada
- Reenvíe los puertos internos del entorno al exterior, para que las pruebas puedan llegar a los servicios necesarios incluso desde otros hosts
- Cambie dinámicamente la composición del entorno (detener, iniciar y agregar nuevos contenedores) sobre la marcha, sin detener el entorno de trabajo.
Medio ambiente
El personaje principal en xenvman es el medio ambiente. Esta es una burbuja tan aislada, en la que se inician todas las dependencias necesarias (empaquetadas en contenedores Docker) de su servicio.

La figura anterior muestra el servidor xenvman y los entornos activos en los que se ejecutan diferentes servicios y bases de datos. Cada entorno se creó directamente a partir del código de prueba de integración y se eliminará al finalizar.
Patrones
Lo que directamente forma parte del entorno está determinado por las plantillas, que son pequeños scripts en JS. xenvman tiene un intérprete incorporado de este lenguaje, y cuando recibe una solicitud para crear un nuevo entorno, simplemente ejecuta las plantillas especificadas, cada una de las cuales agrega uno o más contenedores a la lista para su ejecución.
Se eligió JavaScript para permitir cambiar / agregar plantillas dinámicamente sin tener que reconstruir el servidor. Además, las plantillas generalmente usan solo las características básicas y los tipos de datos del lenguaje (el antiguo ES5, sin DOM, React y otra magia), por lo que trabajar con plantillas no debería causar dificultades especiales incluso para aquellos que están completamente familiarizados con JS.
Las plantillas son parametrizables, es decir, podemos controlar completamente la lógica de la plantilla al pasar ciertos parámetros en nuestra solicitud HTTP.
Crea imágenes sobre la marcha
Una de las características más convenientes de xenvman, en mi opinión, es la creación de imágenes Docker en el proceso de configuración del entorno. ¿Por qué podría ser esto necesario?
Bueno, por ejemplo, en nuestro proyecto, para obtener una imagen de un servicio, debe confirmar los cambios en una rama separada, iniciar y esperar hasta que Gitlab CI recopile y complete la imagen.
Si solo un servicio ha cambiado, puede demorar de 3 a 5 minutos.
Y si estamos guardando activamente nuevas funciones en nuestro servicio, o tratando de entender por qué no funciona, agregando el viejo fmt.Printf
ida y vuelta, o cambiando el código a menudo de alguna manera, incluso un retraso de 5 minutos será excelente para extinguir el rendimiento ( los nuestros como escritores de códigos). En cambio, simplemente podemos agregar toda la depuración necesaria al código, compilarlo localmente y luego simplemente adjuntar el binario terminado a la solicitud HTTP.
Una vez recibida dicha aprobación, la plantilla tomará este binario y creará una imagen temporal sobre la marcha, desde la cual ya podemos lanzar el contenedor como si nada hubiera pasado.
En nuestro proyecto, en la plantilla principal de servicios, por ejemplo, verificamos si el binario está presente en los parámetros, y si es así, recopilamos la imagen sobre la marcha, de lo contrario solo descargamos la latest
versión de la rama de desarrollo. El código adicional para crear contenedores es idéntico para ambas opciones.
Un pequeño ejemplo
Para mayor claridad, veamos el microejemplo.
Digamos que escribimos algún tipo de servidor milagroso (llamémoslo wut
), que necesita una base de datos para almacenar todo allí. Bueno, como base, elegimos MongoDB. Por lo tanto, para realizar pruebas completas, necesitamos un servidor Mongo que funcione. Por supuesto, puede instalarlo y ejecutarlo localmente, pero por simplicidad y visibilidad del ejemplo, asumimos que por alguna razón esto es difícil de hacer (con otras configuraciones más complejas en sistemas reales, esto será más como la verdad).
Así que intentaremos usar xenvman para crear un entorno con Mongo en ejecución y nuestro servidor wut
.
En primer lugar, necesitamos crear un directorio base en el que se almacenarán todas las plantillas:
$ mkdir xenv-templates && cd xenv-templates
A continuación, cree dos plantillas, una para Mongo y la otra para nuestro servidor:
$ touch mongo.tpl.js wut.tpl.js
mongo.tpl.js
Abra mongo.tpl.js
y escriba lo siguiente allí:
function execute(tpl, params) { var img = tpl.FetchImage(fmt("mongo:%s", params.tag)); var cont = img.NewContainer("mongo"); cont.SetLabel("mongo", "true"); cont.SetPorts(27017); cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' }); }
La plantilla debe tener una función execute () con dos parámetros.
El primero es una instancia del objeto tpl a través del cual se configura el entorno. El segundo argumento (params) es solo un objeto JSON con el que parametrizaremos nuestra plantilla.
En linea
var img = tpl.FetchImage(fmt("mongo:%s", params.tag));
le pedimos a xenvman que descargue la imagen de docker mongo:<tag>
, donde <tag>
es la versión de la imagen que queremos usar. En principio, esta línea es equivalente al docker pull mongo:<tag>
, con la única diferencia de que todas las funciones del objeto tpl
son esencialmente declarativas, es decir, la imagen se descargará solo después de que xenvman ejecute todas las plantillas especificadas en la configuración del entorno.
Después de tener la imagen, podemos crear un contenedor:
var cont = img.NewContainer("mongo");
Nuevamente, el contenedor no se creará instantáneamente en este lugar, simplemente declaramos la intención de crearlo, por así decirlo.
A continuación, ponemos una etiqueta en nuestro contenedor:
cont.SetLabel("mongo", "true");
Los accesos directos se utilizan para que los contenedores puedan encontrarse entre sí en un entorno, por ejemplo, para ingresar la dirección IP o el nombre de host en el archivo de configuración.
Ahora tenemos que colgar el puerto interno de Mongo (27017). Esto se hace fácilmente así:
cont.SetPorts(27017);
Antes de que xenvman informe sobre la creación exitosa del entorno, sería genial asegurarse de que todos los servicios no solo se estén ejecutando, sino que estén listos para aceptar solicitudes. Xenvman tiene controles de preparación para esto.
Agregue uno para nuestro contenedor mongo:
cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' });
Como podemos ver, aquí en la barra de direcciones hay stubs en los que los valores necesarios serán sustituidos dinámicamente justo antes del lanzamiento de los contenedores.
En lugar de {{.ExternalAddress}}
se sustituirá {{.ExternalAddress}}
dirección externa del host que ejecuta xenvman, y en lugar de {{.Self.ExposedPort 27017}}
puerto externo que se reenvió al 27017 interno.
Lea más sobre la interpolación aquí .
Como resultado de todo esto, podemos conectarnos al Mongo que se ejecuta en el entorno, justo afuera, por ejemplo, desde el host en el que ejecutamos nuestra prueba.
wut.tpl.js
Entonces, c, después de haber tratado con la monga, escribiremos otra plantilla para nuestro servidor wut
.
Como queremos recopilar la imagen sobre la marcha, la plantilla será ligeramente diferente:
function execute(tpl, params) { var img = tpl.BuildImage("wut-image"); img.CopyDataToWorkspace("Dockerfile");
Como estamos BuildImage()
imagen aquí, usamos BuildImage()
lugar de FetchImage()
:
var img = tpl.BuildImage("wut-image");
Para ensamblar la imagen, necesitaremos varios archivos:
Dockerfile: realmente instrucciones sobre cómo ensamblar una imagen
config.toml: archivo de configuración para nuestro servidor wut
Usando el img.CopyDataToWorkspace("Dockerfile");
copiamos el Dockerfile del directorio de datos de la plantilla a un directorio de trabajo temporal .
El directorio de datos es un directorio en el que podemos almacenar todos los archivos necesarios para que nuestra plantilla funcione.
En el directorio de trabajo temporal, copiamos los archivos (usando img.CopyDataToWorkspace ()) que entran en la imagen.
Lo siguiente sigue:
Pasamos el binario de nuestro servidor directamente en los parámetros, en forma codificada (base64). Y en la plantilla, simplemente la decodificamos y guardamos la cadena resultante en el directorio de trabajo como un archivo con el nombre wut
.
Luego cree un contenedor y monte el archivo de configuración en él:
var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true});
Una llamada a MountData()
significa que el archivo config.toml
del directorio de datos se montará dentro del contenedor con el nombre /config.toml
. El indicador de interpolate
le dice al servidor xenvman que todos los apéndices deben reemplazarse antes de montarlos en el archivo.
Así es como se vería la configuración:
{{with .ContainerWithLabel "mongo" "" -}} mongo = "{{.Hostname}}/wut" {{- end}}
Aquí buscamos el contenedor con la etiqueta mongo
, y sustituimos su nombre de host, sea cual sea en este entorno.
Después de la sustitución, el archivo puede verse así:
mongo = “mongo.0.mongo.xenv/wut”
A continuación, nuevamente publicamos el puerto y comenzamos una verificación de disponibilidad, esta vez HTTP:
cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] });
Nuestras plantillas están listas para esto, y podemos usarlas en el código de prueba de integración:
import "github.com/syhpoon/xenvman/pkg/client" import "github.com/syhpoon/xenvman/pkg/def"
Puede parecer que escribir plantillas llevará demasiado tiempo.
Sin embargo, con el diseño correcto, esta es una tarea única, y luego las mismas plantillas se pueden reutilizar cada vez más (¡e incluso para diferentes idiomas!) Simplemente ajustándolas pasando ciertos parámetros. Como puede ver en el ejemplo anterior, el código de prueba en sí es muy simple, debido al hecho de que ponemos todas las cáscaras en la configuración del entorno en plantillas.
En este pequeño ejemplo, lejos de mostrar todas las características de xenvman, aquí encontrará una guía paso a paso más detallada .
Los clientes
Actualmente hay clientes para dos idiomas:
Ir
Pitón
Pero agregar nuevos no es difícil, ya que la API proporcionada es muy, muy simple.
Interfaz web
En la versión 2.0.0, se agregó una interfaz web simple con la que puede administrar sus entornos y ver las plantillas disponibles.



¿En qué se diferencia xenvman de docker-compose?
Por supuesto, hay muchas similitudes, pero xenvman me parece un enfoque ligeramente más flexible y dinámico, en comparación con la configuración estática en el archivo.
Aquí están las principales características distintivas, en mi opinión:
- Absolutamente todo el control se lleva a cabo a través de la API HTTP, por lo tanto, podemos crear entornos a partir del código de cualquier lenguaje que comprenda HTTP
- Dado que xenvman puede ejecutarse en un host diferente, podemos usar todas sus funciones incluso desde un host en el que no esté instalada la ventana acoplable.
- La capacidad de crear dinámicamente imágenes sobre la marcha.
- La capacidad de cambiar la composición del entorno (agregar / detener contenedores) durante su funcionamiento
- Código reducido repetitivo, composición mejorada y la capacidad de reutilizar el código de configuración mediante el uso de plantillas parametrizables
Referencias
Página del proyecto Github
Ejemplo detallado paso a paso, en inglés.
Conclusión
Eso es todo En un futuro cercano planeo agregar la oportunidad
llame a plantillas desde plantillas y, por lo tanto, le permite combinarlas con mayor eficiencia.
Trataré de responder cualquier pregunta, y me alegrará si alguien más encuentra este proyecto útil.