Testen van infrastructuur als code met Pulumi. Deel 1

Goedemiddag, vrienden. In aanloop naar de start van een nieuwe editie van de cursus «DevOps-praktijken en -tools» delen we een nieuwe vertaling met jullie. Laten we beginnen.

Testen van infrastructuur als code met Pulumi. Deel 1

Het gebruik van Pulumi en algemene programmeertalen voor Infrastructure as Code biedt veel voordelen: de beschikbaarheid van vaardigheden en kennis, het elimineren van boilerplate-code via abstractie, en de gebruiksvriendelijke tools voor uw team, zoals IDE's en linters. Al deze software-engineeringtools maken ons niet alleen productiever, maar verbeteren ook de codekwaliteit. Het is dan ook heel natuurlijk dat het gebruik van algemene programmeertalen een andere belangrijke praktijk in softwareontwikkeling mogelijk maakt — een test uitgevoerd.

In dit artikel zullen we onderzoeken hoe Pulumi helpt bij het testen van onze 'infrastructuur als code'.

Testen van infrastructuur als code met Pulumi. Deel 1

Waarom infrastructuur testen?

Voordat we in de details duiken, is het goed om de vraag te stellen: "Waarom eigenlijk infrastructuur testen?" Er zijn veel redenen hiervoor, en hier zijn enkele daarvan:

  • Modulair testen van individuele functies of fragmenten van de logica van uw programma.
  • Controleren of de gewenste staat van de infrastructuur voldoet aan bepaalde vereisten.
  • Het opsporen van veelvoorkomende fouten, zoals het ontbreken van encryptie op de storage bucket of onbeveiligde, openbare toegang tot virtuele machines vanuit het internet.
  • Controleren of de provisioning van de infrastructuur is uitgevoerd.
  • Uitvoeren van runtime-tests van de applicatielogica die draait binnen uw 'geprogrammeerde' infrastructuur om de functionaliteit na provisioning te controleren.
  • Zoals we zien, zijn er veel verschillende mogelijkheden voor het testen van infrastructuur. In Pulumi zijn er mechanismen voor testen op elk punt in dit spectrum. Laten we beginnen en kijken hoe dit werkt.

Modulair testen

Pulumi-programma's worden geschreven in algemene programmeertalen zoals JavaScript, Python, TypeScript of Go. Daarom heeft u toegang tot de volledige kracht van deze talen, inclusief hun tools en bibliotheken, waaronder testframeworks. Pulumi is multi-cloud, wat betekent dat elk cloudprovider voor tests gebruikt kan worden.

(In dit artikel, ondanks de meertaligheid en multi-cloudomgeving, gebruiken we JavaScript en Mocha en richten we ons op AWS. Python kan ook worden gebruikt. unittest, testframework Go of een andere favoriete testframework. En natuurlijk werkt Pulumi uitstekend met Azure, Google Cloud, Kubernetes.)

Zoals we hebben gezien, zijn er verschillende redenen waarom je je infrastructuurcode zou moeten testen. Een daarvan is gewoonlijk module testen. Aangezien jouw code functies kan hebben — bijvoorbeeld voor het berekenen van CIDR, dynamisch genereren van namen, tags, enz. — wil je ze waarschijnlijk testen. Dit is hetzelfde als het schrijven van gewone module tests voor applicaties in jouw favoriete programmeertaal.
Als we het iets complexer maken, kunnen we controleren hoe jouw programma middelen toewijst. Ter illustratie laten we ons een eenvoudige EC2-server voorstellen en willen we zeker zijn van het volgende:

  • Instanties hebben een tag Naam.
  • Instanties mogen geen inline-script gebruiken userData — we moeten een AMI (afbeelding) gebruiken.
  • Er mag geen SSH openstaan voor het internet.

Dit voorbeeld is geïnspireerd op mijn voorbeeld 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 ], // referentie naar het bovenstaande groepsobject
    ami: "ami-c55673a0"             // AMI voor us-east-2 (Ohio),
    userData: userData              // start een eenvoudige webserver
});
 
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;

Dit is een basisprogramma van Pulumi: het alloceert gewoon een beveiligingsgroep EC2 en een instantie. Het is echter belangrijk op te merken dat we hier alle drie de regels die hierboven zijn uiteengezet, overtreden. Laten we tests schrijven!

We schrijven tests

De algemene structuur van onze tests zal eruitzien als gewone Mocha-tests:

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): Er moet een tág Name zijn.
        // TODO(check 2): Er mag geen inline-script userData zijn.
    });
    let group = infra.group;
    describe("#group", function() {
        // TODO(check 3): Er mag geen SSH openstaan voor het internet.
    });
});

Laten we nu onze eerste test schrijven: we zorgen ervoor dat instanties een tag hebben Naam. Om dit te controleren, krijgen we eenvoudig het EC2-instantieobject en controleren we de juiste eigenschap tags:

 // 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();
                }
            });
        });

Het lijkt op een gewone test, maar met enkele kenmerken die de aandacht verdienen:

  • Omdat we de status van de bron opvragen voordat we deze implementeren, worden onze tests altijd uitgevoerd in de modus 'plan' (of 'preview'). Hierdoor zijn er veel eigenschappen waarvan de waarden simpelweg niet worden verkregen of gedefinieerd zijn. Dit omvat alle uitvoereigenschappen die door uw cloudprovider worden berekend. Voor onze tests is dit prima — we controleren alleen de invoer. Hier zullen we later op terugkomen als we het hebben over integratietests.
  • Aangezien alle eigenschappen van Pulumi-resources 'uitvoer' zijn en veel ervan asynchroon worden berekend, moeten we de apply-methode gebruiken om toegang te krijgen tot de waarden. Dit lijkt erg op promises en de functie async. then .
  • Omdat we verschillende eigenschappen gebruiken om de URN van de bron in het foutbericht te tonen, moeten we de functie gebruiken pulumi.all, om ze samen te voegen.
  • Tot slot, omdat deze waarden asynchroon worden berekend, moeten we de ingebouwde asynchrone functie van Mocha gebruiken met een callback klaar of het retourneren van een promise.

Zodra we alles hebben ingesteld, hebben we toegang tot de invoerwaarden als eenvoudige JavaScript-waarden. De eigenschap tags is een map (associatieve array), dus we zorgen ervoor dat dit (1) niet false is, en (2) er een sleutel voor is Naam. Dit is heel eenvoudig en nu kunnen we alles controleren wat we willen!

Laten we nu onze tweede controle schrijven. Dit is nog eenvoudiger:

 // 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();
                }
            });
        });

En tenslotte, laten we de derde test schrijven. Dit zal iets gecompliceerder zijn, omdat we zoeken naar inlogregels die verband houden met de beveiligingsgroep, die er meerdere kunnen zijn, en de CIDR-bereiken in deze regels, die ook meerdere kunnen zijn. Maar we hebben het voor elkaar gekregen:

    // 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();
                }
            });
        });

Dat is het. Laten we nu de tests uitvoeren!

Tests uitvoeren

Tests kunnen in de meeste gevallen op de gebruikelijke manier worden uitgevoerd, met behulp van het testframework van uw keuze. Maar er is één bijzonderheid van Pulumi waar we op moeten letten.
Gewoonlijk wordt de Pulumi-programma's uitgevoerd met de pulimi CLI (Command Line Interface), die de runtime van de taal configureert, de uitvoeringen van de Pulumi-engine beheert zodat resource-operaties kunnen worden vastgelegd en in een plan kunnen worden opgenomen, enzovoort. Er is echter één probleem. Wanneer je het uitvoert onder controle van je testframework, is er geen verbinding tussen de CLI en de Pulumi-engine.

Om dit probleem te omzeilen, hoeven we alleen maar het volgende op te geven:

  • De projectnaam, die in de omgevingsvariabele bevindt PULUMI_NODEJS_PROJECT (of, in algemenere termen, PULUMI__PROJECT voor andere talen).
    De stacknaam, die in de omgevingsvariabele staat PULUMI_NODEJS_STACK (of, in algemenere termen, PULUMI__STACK).
    Je stack-configuratievariabelen. Deze kan je krijgen via de omgevingsvariabele PULUMI_CONFIG en hun formaat is een JSON-map met sleutel/waarde-paren.

    Het programma geeft waarschuwingen weer die aangeven dat er tijdens het uitvoeren geen verbinding met de CLI/engine beschikbaar is. Dit is belangrijk, omdat je programma in werkelijkheid niets zal uitrollen en dit kan een verrassing zijn als dat niet is wat je wilde doen! Om Pulumi te vertellen dat dit precies is wat je nodig hebt, kun je aangeven PULUMI_TEST_MODE in true.

    Stel je voor dat we de projectnaam moeten opgeven in my-ws, de stacknaam , maar volgt geen wijzigingen), gebruikmakend van een andere configuratie., en de AWS-regio us-west-2. De opdrachtregel om Mocha-testen uit te voeren zal er als volgt uitzien:

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

    Het uitvoeren hiervan zal ons, zoals verwacht, laten zien dat we drie mislukte tests hebben!

    Infrastructure
        #server
          1) moet een naamlabel hebben
     	 2) mag geen userData gebruiken (gebruik in plaats daarvan een AMI)
        #group
          3) mag poort 22 (SSH) niet openstellen voor het internet
    
      0 passing (17ms)
      3 failing
     
     1) Infrastructure
           #server
             moet een naamlabel hebben:
         Error: Ontbrekend naamlabel op server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     2) Infrastructure
           #server
             mag geen userData gebruiken (gebruik in plaats daarvan een AMI):
         Error: Onwettig gebruik van userData op server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     3) Infrastructure
           #group
             mag poort 22 (SSH) niet openstellen voor het internet:
         Error: Onwettige openstelling SSH-poort 22 voor het internet (CIDR 0.0.0.0/0) op groep

    Laten we ons programma corrigeren:

    "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 ], // verwijzing naar de groep hierboven
        ami: "ami-c55673a0"             // AMI voor us-east-2 (Ohio),
    });
     
    exports.group = group;
    exports.server = server;
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    

    En vervolgens zullen we de tests opnieuw uitvoeren:

    Infrastructuur
        #server
          ✓ moet een naamlabel hebben
          ✓ mag geen userData gebruiken (gebruik in plaats daarvan een AMI)
        #groep
          ✓ mag poort 22 (SSH) niet naar het internet openen
     
     
     3 geslaagd (16ms)

    Alles is succesvol verlopen… Hoera! ✓✓✓

    Dat was het voor vandaag, en we zullen in het tweede deel van de vertaling over het testen van de implementatie praten 😉

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster