Tester votre infrastructure en tant que code avec Pulumi. Partie 1

Bonjour mes amis. En prévision du début d'un nouveau flux sur le cours "DevOps Practices and Tools", nous partageons avec vous une nouvelle traduction. Allons-y.



L'utilisation de Pulumi et des langages de programmation à usage général pour Infrastructure en tant que code offre de nombreux avantages: avoir des compétences et des connaissances, éliminer le passe-partout dans le code grâce à l'abstraction, des outils familiers à votre équipe, tels que les IDE et les linters. Tous ces outils d'ingénierie logicielle nous rendent non seulement plus productifs, mais améliorent également la qualité du code. Par conséquent, il est naturel que l'utilisation de langages de programmation à usage général vous permette d'introduire une autre pratique importante dans le développement de logiciels - les tests .

Dans cet article, nous verrons comment Pulumi aide à tester notre «infrastructure en tant que code».



Pourquoi tester l'infrastructure?


Avant d'entrer dans les détails, il convient de se poser la question: «Pourquoi devons-nous tester l'infrastructure?» Il y a plusieurs raisons à cela, et en voici quelques-unes:

  • Unité testant des fonctions individuelles ou des éléments de logique dans votre programme
  • Vérifiez l'état souhaité de l'infrastructure pour vous conformer à certaines restrictions.
  • Détection des erreurs courantes, telles que le manque de chiffrement du bucket de stockage ou l'insécurité, l'accès ouvert d'Internet aux machines virtuelles.
  • Vérification de l'approvisionnement des infrastructures.
  • Exécution de tests d'exécution de la logique de l'application s'exécutant à l'intérieur de votre infrastructure «programmée» pour vérifier l'intégrité après le provisionnement.
  • Comme nous pouvons le voir, il existe un large éventail d'options de test d'infrastructure. Polumi a des mécanismes pour tester à chaque point de ce spectre. Commençons et voyons comment cela fonctionne.

Tests unitaires


Les programmes Pulumi sont créés dans des langages de programmation à usage général tels que JavaScript, Python, TypeScript ou Go. Par conséquent, la pleine puissance de ces langages est disponible pour eux, y compris leurs outils et bibliothèques, y compris les cadres de test. Pulumi est multi-cloud, ce qui signifie la possibilité d'utiliser n'importe quel fournisseur de cloud pour les tests.

(Dans cet article, bien qu'il soit multilingue et multi-cloud, nous utilisons JavaScript et Mocha et nous nous concentrons sur AWS. Vous pouvez utiliser Python unittest , le framework de test Go ou tout autre framework de test que vous aimez. Et, bien sûr, Pulumi fonctionne très bien avec Azure, Google Cloud, Kubernetes.)

Comme nous l'avons vu, il existe plusieurs raisons pour lesquelles vous devrez peut-être tester votre code d'infrastructure. L'un d'eux est le test unitaire habituel. Étant donné que votre code peut avoir des fonctions - par exemple, pour calculer le CIDR, calculer dynamiquement les noms, les balises, etc. - vous voulez probablement les tester. Cela revient à écrire des tests unitaires réguliers pour les applications dans votre langage de programmation préféré.
Si vous compliquez un peu les choses, vous pouvez vérifier comment votre programme alloue les ressources. Pour illustrer cela, imaginons que nous devons créer un simple serveur EC2 et nous voulons être sûrs de ce qui suit:

  • Les instances ont une étiquette de Name .
  • Les instances ne doivent pas utiliser le script en ligne userData - nous devons utiliser l'AMI (image).
  • Il ne devrait pas y avoir de SSH ouvert sur Internet.

Cet exemple est écrit sur la base de mon exemple aws-js-webserver :

index.js:


 "use strict"; let aws = require("@pulumi/aws"); let group = new aws.ec2.SecurityGroup("web-secgrp", { ingress: [ { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }, { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, ], }); let userData = `#!/bin/bash echo "Hello, World!" > index.html nohup python -m SimpleHTTPServer 80 &`; let server = new aws.ec2.Instance("web-server-www", { instanceType: "t2.micro", securityGroups: [ group.name ], // reference the group object above ami: "ami-c55673a0" // AMI for us-east-2 (Ohio), userData: userData // start a simple web server }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns; 

Il s'agit du programme Pulumi de base: il alloue simplement le groupe et l'instance de sécurité EC2. Cependant, il convient de noter que nous violons ici les trois règles énoncées ci-dessus. Écrivons des tests!

Tests d'écriture


La structure générale de nos tests ressemblera à des tests Mocha réguliers:

ec2tests.js


 test.js: let assert = require("assert"); let mocha = require("mocha"); let pulumi = require("@pulumi/pulumi"); let infra = require("./index"); describe("Infrastructure", function() { let server = infra.server; describe("#server", function() { // TODO(check 1):    Name. // TODO(check 2):    inline- userData. }); let group = infra.group; describe("#group", function() { // TODO(check 3):    SSH,   . }); }); 

Écrivons maintenant notre premier test: assurez-vous que les instances ont une balise Name . Pour vérifier cela, nous obtenons simplement l'objet instance EC2 et vérifions la propriété des tags correspondante:

  // check 1:    Name. it("must have a name tag", function(done) { pulumi.all([server.urn, server.tags]).apply(([urn, tags]) => { if (!tags || !tags["Name"]) { done(new Error(`Missing a name tag on server ${urn}`)); } else { done(); } }); }); 

Cela ressemble à un test régulier, mais avec quelques fonctionnalités dignes d'attention:

  • Comme nous demandons l'état de la ressource avant le déploiement, nos tests sont toujours exécutés en mode «plan» (ou «prévisualisation»). Ainsi, il existe de nombreuses propriétés dont les valeurs ne seront tout simplement pas reçues ou ne seront pas déterminées. Cela inclut toutes les propriétés de sortie calculées votre fournisseur de cloud. Pour nos tests, c'est normal - nous ne vérifions que les données d'entrée. Nous reviendrons sur ce problème plus tard en ce qui concerne les tests d'intégration.
  • Étant donné que toutes les propriétés des ressources Pulumi sont des "sorties" et que beaucoup d'entre elles sont calculées de manière asynchrone, nous devons utiliser la méthode apply pour accéder aux valeurs. C'est très similaire aux promesses et then .
  • Puisque nous utilisons plusieurs propriétés afin d'afficher la ressource URN dans le message d'erreur, nous devons utiliser la fonction pulumi.all pour les combiner.
  • Enfin, puisque ces valeurs sont calculées de manière asynchrone, nous devons utiliser la fonction asynchrone intégrée de Mocha avec un rappel done ou un retour de promesse.

Après avoir tout configuré, nous aurons accès aux données d'entrée sous forme de simples valeurs JavaScript. La propriété tags est une carte (tableau associatif), donc nous allons juste nous assurer qu'elle n'est (1) pas fausse, et (2) il y a une clé pour Name . C'est très simple et maintenant nous pouvons tout vérifier!

Écrivons maintenant notre deuxième chèque. C'est encore plus simple:

  // check 2:    inline- userData. it("must not use userData (use an AMI instead)", function(done) { pulumi.all([server.urn, server.userData]).apply(([urn, userData]) => { if (userData) { done(new Error(`Illegal use of userData on server ${urn}`)); } else { done(); } }); }); 


Et enfin, nous écrirons le troisième test. Ce sera un peu plus compliqué car nous recherchons des règles de connexion associées à un groupe de sécurité, qui peut être nombreux, et des plages CIDR dans ces règles, qui peuvent également être nombreuses. Mais nous avons réussi:

  // check 3:    SSH,   . it("must not open port 22 (SSH) to the Internet", function(done) { pulumi.all([ group.urn, group.ingress ]).apply(([ urn, ingress ]) => { if (ingress.find(rule => rule.fromPort == 22 && rule.cidrBlocks.find(block => block === "0.0.0.0/0"))) { done(new Error(`Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ${urn}`)); } else { done(); } }); }); 

C’est tout. Maintenant, exécutons les tests!

Exécution de tests


Dans la plupart des cas, vous pouvez exécuter des tests de la manière habituelle en utilisant le cadre de test de votre choix. Mais il y a une caractéristique de Pulumi à laquelle vous devez faire attention.
Habituellement, Pulimi CLI (interface de ligne de commande, interface de ligne de commande) est utilisée pour démarrer les programmes Pulumi, qui configure le temps d'exécution du langage, contrôle le démarrage du moteur Pulumi, de sorte qu'il est possible d'enregistrer des opérations avec des ressources et de les inclure dans le plan, etc. Cependant, il y a un problème. Une fois lancé sous le contrôle de votre framework de test, il n'y aura pas de connexion entre la CLI et le moteur Pulumi.

Pour contourner ce problème, il suffit de spécifier les éléments suivants:

  • Le nom du projet, qui est contenu dans la variable d'environnement PULUMI_NODEJS_PROJECT (ou, plus généralement, PULUMI__PROJECT ).
    Nom de la pile spécifié dans la variable d'environnement PULUMI_NODEJS_STACK (ou, plus généralement, PULUMI__ STACK).
    Vos variables de configuration de pile. Ils peuvent être obtenus à l'aide de la PULUMI_CONFIG environnement PULUMI_CONFIG et leur format est une carte JSON avec des paires clé / valeur.

    Le programme émettra des avertissements indiquant qu'au moment de l'exécution, une connexion à l'interface CLI / moteur n'est pas disponible. C'est important, car en réalité, votre programme ne déploiera rien et cela peut vous surprendre si ce n'est pas ce que vous vouliez faire! Pour indiquer à Pulumi que c'est exactement ce dont vous avez besoin, vous pouvez définir PULUMI_TEST_MODE sur true .

    Imaginez que nous devons spécifier le nom du projet dans my-ws , le nom de la pile de développement et la région AWS us-west-2 . La ligne de commande pour exécuter les tests Mocha ressemblera à ceci:

     $ PULUMI_TEST_MODE=true \ PULUMI_NODEJS_STACK="my-ws" \ PULUMI_NODEJS_PROJECT="dev" \ PULUMI_CONFIG='{ "aws:region": "us-west-2" }' \ mocha tests.js 

    Faire cela, comme prévu, nous montrera que nous avons trois tests tombés!

     Infrastructure #server 1) must have a name tag 2) must not use userData (use an AMI instead) #group 3) must not open port 22 (SSH) to the Internet 0 passing (17ms) 3 failing 1) Infrastructure #server must have a name tag: Error: Missing a name tag on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #server must not use userData (use an AMI instead): Error: Illegal use of userData on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #group must not open port 22 (SSH) to the Internet: Error: Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group 

    Corrigeons notre programme:

     "use strict"; let aws = require("@pulumi/aws"); let group = new aws.ec2.SecurityGroup("web-secgrp", { ingress: [ { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, ], }); let server = new aws.ec2.Instance("web-server-www", { tags: { "Name": "web-server-www" }, instanceType: "t2.micro", securityGroups: [ group.name ], // reference the group object above ami: "ami-c55673a0" // AMI for us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns; 

    Et puis relancez les tests:

     Infrastructure #server ✓ must have a name tag ✓ must not use userData (use an AMI instead) #group ✓ must not open port 22 (SSH) to the Internet 3 passing (16ms) 

    Tout s'est bien passé ... Hourra! ✓ ✓ ✓

    C'est tout pour aujourd'hui, mais nous parlerons du test de déploiement dans la deuxième partie de la traduction ;-)

Source: https://habr.com/ru/post/fr463001/


All Articles