Bonjour à tous!
Je voudrais parler un peu du projet sur lequel je travaille depuis six mois. Je fais le projet pendant mon temps libre, mais la motivation pour sa création est venue des observations faites au travail principal.
Sur un projet de travail, nous utilisons l'architecture des microservices, et l'un des principaux problèmes qui s'est manifesté au fil du temps et le nombre accru de ces services est en train de tester. Lorsqu'un certain service dépend de cinq à sept autres services, plus une autre base de données (ou même plusieurs) pour démarrer, le tester sous une forme «en direct», pour ainsi dire, est très gênant. Vous devez mettre des mokas de tous les côtés si fermement que vous ne pouvez même pas distinguer la pâte elle-même. Eh bien, ou en quelque sorte organiser un environnement de test où toutes les dépendances pourraient vraiment être lancées.
En fait, pour faciliter la deuxième option, je me suis juste assis pour écrire xenvman . En un mot, c'est quelque chose comme un hybride de docker-compose et test de conteneurs , uniquement sans liaison à Java (ou tout autre langage) et avec la possibilité de créer et de configurer dynamiquement des environnements via l'API HTTP.
xenvman
écrit en Go et implémenté comme un simple serveur HTTP, ce qui vous permet d'utiliser toutes les fonctionnalités disponibles à partir de n'importe quelle langue pouvant parler ce protocole.
La principale chose que xenvman peut faire est:
- Décrire de manière flexible le contenu de l'environnement avec de simples scripts JavaScript
- Créez des images à la volée
- Créez le bon nombre de conteneurs et combinez-les en un seul réseau isolé
- Transférer les ports internes de l'environnement à l'extérieur, afin que les tests puissent atteindre les services nécessaires même à partir d'autres hôtes
- Modifiez dynamiquement la composition de l'environnement (arrêtez, démarrez et ajoutez de nouveaux conteneurs) en déplacement, sans arrêter l'environnement de travail.
L'environnement
Le personnage principal de xenvman est l'environnement. Il s'agit d'une telle bulle isolée, dans laquelle toutes les dépendances nécessaires (emballées dans des conteneurs Docker) de votre service sont lancées.

La figure ci-dessus montre le serveur xenvman et les environnements actifs dans lesquels différents services et bases de données s'exécutent. Chaque environnement a été créé directement à partir du code de test d'intégration et sera supprimé une fois terminé.
Patterns
Ce qui fait directement partie de l'environnement est déterminé par les modèles, qui sont de petits scripts dans JS. xenvman possède un interpréteur intégré de ce langage et lorsqu'il reçoit une demande de création d'un nouvel environnement, il exécute simplement les modèles spécifiés, chacun ajoutant un ou plusieurs conteneurs à la liste pour exécution.
JavaScript a été choisi pour permettre la modification / l'ajout dynamique de modèles sans avoir à reconstruire le serveur. De plus, les modèles n'utilisent généralement que les fonctionnalités de base et les types de données du langage (le bon vieux ES5, pas de DOM, React et autre magie), donc travailler avec des modèles ne devrait pas poser de difficultés particulières, même pour ceux qui connaissent JS.
Les modèles sont paramétrables, c'est-à-dire que nous pouvons contrôler complètement la logique du modèle en passant certains paramètres dans notre requête HTTP.
Créez des images à la volée
À mon avis, l'une des fonctionnalités les plus pratiques de xenvman est la création d'images Docker en cours de configuration de l'environnement. Pourquoi cela pourrait-il être nécessaire?
Eh bien, par exemple, sur notre projet, pour obtenir une image d'un service, vous devez valider les modifications dans une branche distincte, démarrer et attendre que Gitlab CI collecte et remplisse l'image.
Si un seul service a changé, cela peut prendre de 3 à 5 minutes.
Et si nous voyons activement de nouvelles fonctionnalités dans notre service, ou essayons de comprendre pourquoi cela ne fonctionne pas, en ajoutant le bon vieux fmt.Printf
dans les deux sens, ou en changeant souvent le code d'une manière ou d'une autre, même un délai de 5 minutes sera idéal pour éteindre les performances ( les nôtres en tant que rédacteurs de code). Au lieu de cela, nous pouvons simplement ajouter tout le débogage nécessaire au code, le compiler localement, puis simplement attacher le binaire terminé à la demande HTTP.
Après avoir reçu une telle approbation, le modèle prendra ce binaire et créera une image temporaire sur la route, à partir de laquelle nous pouvons déjà lancer le conteneur comme si rien ne s'était passé.
Sur notre projet, dans le modèle principal pour les services, par exemple, nous vérifions si le binaire est présent dans les paramètres, et si oui, nous collectons l'image en déplacement, sinon nous téléchargeons simplement la latest
version de la branche dev
. Le code supplémentaire pour la création de conteneurs est identique pour les deux options.
Un petit exemple
Pour plus de clarté, regardons le micro-exemple.
Disons que nous écrivons une sorte de serveur miracle (appelons-le wut
), qui a besoin d'une base de données pour tout y stocker. Eh bien, comme base, nous avons choisi MongoDB. Par conséquent, pour des tests complets, nous avons besoin d'un serveur Mongo fonctionnel. Vous pouvez, bien sûr, l'installer et l'exécuter localement, mais pour la simplicité et la visibilité de l'exemple, nous supposons que pour une raison quelconque, cela est difficile à faire (avec d'autres configurations plus complexes dans des systèmes réels, ce sera plus comme la vérité).
Nous allons donc essayer d'utiliser xenvman afin de créer un environnement avec Mongo en cours d'exécution et notre serveur wut
.
Tout d'abord, nous devons créer un répertoire de base dans lequel tous les modèles seront stockés:
$ mkdir xenv-templates && cd xenv-templates
Ensuite, créez deux modèles, l'un pour Mongo, l'autre pour notre serveur:
$ touch mongo.tpl.js wut.tpl.js
mongo.tpl.js
Ouvrez mongo.tpl.js
et écrivez ce qui suit:
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}}' }); }
Le modèle doit avoir une fonction execute () avec deux paramètres.
Le premier est une instance de l'objet tpl à travers lequel l'environnement est configuré. Le deuxième argument (params) est juste un objet JSON avec lequel nous allons paramétrer notre modèle.
En ligne
var img = tpl.FetchImage(fmt("mongo:%s", params.tag));
nous demandons à xenvman de télécharger l'image du docker mongo:<tag>
, où <tag>
est la version de l'image que nous voulons utiliser. En principe, cette ligne est équivalente à la commande docker pull mongo:<tag>
, à la seule différence que toutes les fonctions de l'objet tpl
sont essentiellement déclaratives, c'est-à-dire que l'image ne sera réellement téléchargée qu'après que xenvman aura exécuté tous les modèles spécifiés dans la configuration de l'environnement.
Après avoir l'image, nous pouvons créer un conteneur:
var cont = img.NewContainer("mongo");
Encore une fois, le conteneur ne sera pas créé instantanément à cet endroit, nous déclarons simplement l'intention de le créer, pour ainsi dire.
Ensuite, nous mettons une étiquette sur notre conteneur:
cont.SetLabel("mongo", "true");
Des raccourcis sont utilisés pour que les conteneurs puissent se retrouver dans un environnement, par exemple, pour entrer l'adresse IP ou le nom d'hôte dans le fichier de configuration.
Maintenant, nous devons bloquer le port Mongo interne (27017). Cela se fait facilement comme ceci:
cont.SetPorts(27017);
Avant que xenvman ne signale la création réussie de l'environnement, il serait bon de s'assurer que tous les services ne sont pas seulement en cours d'exécution, mais sont prêts à accepter les demandes. Xenvman a des vérifications de préparation pour cela.
Ajoutez-en un pour notre conteneur mongo:
cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' });
Comme nous pouvons le voir, ici dans la barre d'adresse il y a des stubs dans lesquels les valeurs nécessaires seront dynamiquement substituées juste avant le lancement des conteneurs.
Au lieu de {{.ExternalAddress}}
adresse externe de l'hôte exécutant xenvman sera substituée, et au lieu de {{.Self.ExposedPort 27017}}
port externe qui a été transmis au 27017 interne sera substitué.
En savoir plus sur l'interpolation ici .
À la suite de tout cela, nous pouvons nous connecter au Mongo fonctionnant dans l'environnement, juste à l'extérieur, par exemple, de l'hôte sur lequel nous exécutons notre test.
wut.tpl.js
Donc, c, après avoir traité de la monga, nous allons écrire un autre modèle pour notre serveur wut
.
Puisque nous voulons collecter l'image en déplacement, le modèle sera légèrement différent:
function execute(tpl, params) { var img = tpl.BuildImage("wut-image"); img.CopyDataToWorkspace("Dockerfile");
Puisque nous BuildImage()
image ici, nous utilisons BuildImage()
au lieu de FetchImage()
:
var img = tpl.BuildImage("wut-image");
Pour assembler l'image, nous aurons besoin de plusieurs fichiers:
Dockerfile - en fait des instructions sur la façon d'assembler une image
config.toml - fichier de configuration pour notre serveur wut
Utilisation de la img.CopyDataToWorkspace("Dockerfile");
nous copions le Dockerfile du répertoire de données du modèle dans un répertoire de travail temporaire .
Le répertoire de données est un répertoire dans lequel nous pouvons stocker tous les fichiers nécessaires au fonctionnement de notre modèle.
Dans le répertoire de travail temporaire, nous copions les fichiers (en utilisant img.CopyDataToWorkspace ()) qui pénètrent dans l'image.
Ce qui suit suit:
Nous passons le binaire de notre serveur directement dans les paramètres, sous forme encodée (base64). Et dans le modèle, nous le décodons simplement et enregistrons la chaîne résultante dans le répertoire de travail sous forme de fichier sous le nom wut
.
Créez ensuite un conteneur et montez-y le fichier de configuration:
var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true});
Un appel à MountData()
signifie que le fichier config.toml
répertoire de données sera monté à l'intérieur du conteneur sous le nom /config.toml
. L'indicateur d' interpolate
indique au serveur xenvman que tous les stubs doivent être remplacés avant le montage dans le fichier.
Voici à quoi pourrait ressembler la configuration:
{{with .ContainerWithLabel "mongo" "" -}} mongo = "{{.Hostname}}/wut" {{- end}}
Ici, nous recherchons le conteneur avec le label mongo
et substituons son nom d'hôte, quel qu'il soit dans cet environnement.
Après substitution, le fichier peut ressembler à:
mongo = “mongo.0.mongo.xenv/wut”
Ensuite, nous affichons à nouveau le port et commençons une vérification de préparation, cette fois HTTP:
cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] });
Nos modèles sont prêts pour cela, et nous pouvons les utiliser dans le code de test d'intégration:
import "github.com/syhpoon/xenvman/pkg/client" import "github.com/syhpoon/xenvman/pkg/def"
Il peut sembler que l'écriture de modèles prendra trop de temps.
Cependant, avec la bonne conception, il s'agit d'une tâche unique, puis les mêmes modèles peuvent être réutilisés de plus en plus (et même pour différentes langues!) En les affinant simplement en passant certains paramètres. Comme vous pouvez le voir dans l'exemple ci-dessus, le code de test lui-même est très simple, car nous mettons toutes les enveloppes sur la configuration de l'environnement dans des modèles.
Dans ce petit exemple, loin de toutes les fonctionnalités de xenvman, un guide étape par étape plus détaillé est disponible ici.
Les clients
Il existe actuellement des clients pour deux langues:
Allez
Python
Mais en ajouter de nouveaux n'est pas difficile, car l'API fournie est très, très simple.
Interface Web
Dans la version 2.0.0, une interface Web simple a été ajoutée avec laquelle vous pouvez gérer vos environnements et afficher les modèles disponibles.



En quoi xenvman est-il différent de docker-compose?
Bien sûr, il y a beaucoup de similitudes, mais xenvman me semble une approche légèrement plus flexible et dynamique, par rapport à la configuration statique du fichier.
Voici les principales caractéristiques distinctives, à mon avis:
- Absolument tout le contrôle est effectué via l'API HTTP, donc nous pouvons créer des environnements à partir du code de n'importe quel langage qui comprend HTTP
- Étant donné que xenvman peut être exécuté sur un hôte différent, nous pouvons utiliser toutes ses fonctionnalités même à partir d'un hôte sur lequel Docker n'est pas installé.
- La possibilité de créer dynamiquement des images à la volée
- La possibilité de modifier la composition de l'environnement (ajout / arrêt de conteneurs) pendant son fonctionnement
- Code passe-partout réduit, composition améliorée et possibilité de réutiliser le code de configuration grâce à l'utilisation de modèles paramétrables
Les références
Page du projet Github
Exemple détaillé étape par étape, en anglais.
Conclusion
C'est tout. Dans un avenir proche, je prévois d'ajouter l'opportunité
appeler des modèles à partir de modèles et ainsi vous permettre de les combiner avec une plus grande efficacité.
J'essaierai de répondre à toutes vos questions et je serai heureux si quelqu'un d'autre trouve ce projet utile.