Test infrastructuur als code met Pulumi. Deel 1

Goede middag vrienden. In afwachting van het begin van een nieuwe stroom tegen het tarief "DevOps-praktijken en tools" We delen een nieuwe vertaling met u. Gaan.

Test infrastructuur als code met Pulumi. Deel 1

Het gebruik van Pulumi en algemene programmeertalen voor infrastructuurcode (Infrastructure as Code) biedt veel voordelen: de beschikbaarheid van vaardigheden en kennis, het elimineren van standaardteksten in de code door middel van abstractie, tools die bekend zijn bij uw team, zoals IDE's en linters. Al deze software-engineeringtools maken ons niet alleen productiever, maar verbeteren ook de kwaliteit van onze code. Daarom is het niet meer dan normaal dat het gebruik van programmeertalen voor algemene doeleinden ons in staat stelt een andere belangrijke praktijk voor softwareontwikkeling te introduceren: testen.

In dit artikel zullen we bekijken hoe Pulumi ons helpt onze infrastructuur-als-code te testen.

Test infrastructuur als code met Pulumi. Deel 1

Waarom infrastructuur testen?

Voordat we in detail treden, is het de moeite waard om de vraag te stellen: “Waarom überhaupt infrastructuur testen?” Daar zijn veel redenen voor en hier zijn er enkele:

  • Unit-testen van individuele functies of fragmenten van uw programmalogica
  • Verifieert de gewenste staat van de infrastructuur aan de hand van bepaalde beperkingen.
  • Detectie van veelvoorkomende fouten, zoals een gebrek aan codering van een opslagbucket of onbeschermde, open toegang vanaf internet tot virtuele machines.
  • Het controleren van de implementatie van de infrastructuurvoorzieningen.
  • Het uitvoeren van runtimetests van applicatielogica die binnen uw “geprogrammeerde” infrastructuur draait om de functionaliteit na de inrichting te controleren.
  • Zoals we kunnen zien, is er een breed scala aan opties voor het testen van de infrastructuur. Polumi beschikt over mechanismen om op elk punt van dit spectrum te testen. Laten we aan de slag gaan en kijken hoe het werkt.

Testen van een eenheid

Pulumi-programma's zijn geschreven in algemene programmeertalen zoals JavaScript, Python, TypeScript of Go. Daarom is de volledige kracht van deze talen, inclusief hun tools en bibliotheken, inclusief testframeworks, voor hen beschikbaar. Pulumi is multi-cloud, wat betekent dat het gebruikt kan worden voor testen van elke cloudprovider.

(In dit artikel gebruiken we, ondanks dat we meertalig en multicloud zijn, JavaScript en Mocha en concentreren we ons op AWS. Je kunt Python gebruiken unittest, Ga een testframework gebruiken, of een ander testframework dat je leuk vindt. En natuurlijk werkt Pulumi uitstekend met Azure, Google Cloud en Kubernetes.)

Zoals we hebben gezien, zijn er verschillende redenen waarom u uw infrastructuurcode zou willen testen. Eén daarvan is het conventionele testen van eenheden. Omdat uw code functies kan hebben, bijvoorbeeld om CIDR te berekenen, dynamisch namen, tags te berekenen, enz. - je wilt ze waarschijnlijk testen. Dit is hetzelfde als het schrijven van reguliere unit-tests voor applicaties in uw favoriete programmeertaal.
Om het iets ingewikkelder te maken, kunt u controleren hoe uw programma bronnen toewijst. Laten we ons ter illustratie voorstellen dat we een eenvoudige EC2-server moeten maken en dat we zeker willen zijn van het volgende:

  • Instanties hebben een tag Name.
  • Instanties mogen geen inline script gebruiken userData - we moeten een AMI (afbeelding) gebruiken.
  • Er mag geen SSH worden blootgesteld aan internet.

Dit voorbeeld is gebaseerd 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 ], // 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;

Dit is het basisprogramma van Pulumi: het wijst eenvoudigweg een EC2-beveiligingsgroep en een instantie toe. Er moet echter worden opgemerkt dat we hier alle drie de hierboven genoemde regels overtreden. Laten we tests schrijven!

Testen schrijven

De algemene structuur van onze tests ziet eruit als gewone Mokka-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): Должен быть тэг Name.
        // TODO(check 2): Не должно быть inline-скрипта userData.
    });
    let group = infra.group;
    describe("#group", function() {
        // TODO(check 3): Не должно быть SSH, открытого в Интернет.
    });
});

Laten we nu onze eerste test schrijven: zorg ervoor dat de instanties de tag hebben Name. Om dit te controleren, halen we eenvoudigweg het EC2-instantieobject op en controleren de bijbehorende 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 ziet eruit als een gewone test, maar met een paar kenmerken die het vermelden waard zijn:

  • Omdat we vóór de implementatie de status van een resource opvragen, worden onze tests altijd uitgevoerd in de “plan”- (of “preview”)-modus. Er zijn dus veel eigenschappen waarvan de waarden simpelweg niet worden opgehaald of niet worden gedefinieerd. Dit omvat alle uitvoereigenschappen die door uw cloudprovider zijn berekend. Dit is normaal voor onze tests: we controleren alleen de invoergegevens. We komen later op deze kwestie terug als het gaat om integratietests.
  • Omdat alle Pulumi-broneigenschappen uitvoer zijn en veel ervan asynchroon worden geëvalueerd, moeten we de methode apply gebruiken om toegang te krijgen tot de waarden. Dit lijkt erg op beloften en een functie then .
  • Omdat we verschillende eigenschappen gebruiken om de bron-URN in het foutbericht weer te geven, moeten we de functie gebruiken pulumi.allom ze te combineren.
  • Omdat deze waarden asynchroon worden berekend, moeten we ten slotte de ingebouwde asynchrone callback-functie van Mocha gebruiken done of een belofte teruggeven.

Zodra we alles hebben ingesteld, hebben we toegang tot de invoer als eenvoudige JavaScript-waarden. Eigendom tags is een kaart (associatieve array), dus we zorgen er gewoon voor dat deze (1) niet onwaar is, en (2) er een sleutel is voor Name. Het is heel eenvoudig en nu kunnen we alles testen!

Laten we nu onze tweede cheque schrijven. Het 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 tot slot, laten we de derde test schrijven. Dit zal iets ingewikkelder zijn omdat we zoeken naar de inlogregels die aan de beveiligingsgroep zijn gekoppeld, waarvan er veel kunnen zijn, en de CIDR-bereiken in die regels, waarvan er ook veel kunnen zijn. Maar het is ons gelukt:

    // 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 alles. Laten we nu de tests uitvoeren!

Testen uitvoeren

In de meeste gevallen kunt u op de gebruikelijke manier testen uitvoeren, met behulp van het testframework van uw keuze. Maar er is één kenmerk van Pulumi dat de moeite waard is om op te letten.
Om Pulumi-programma's uit te voeren, wordt doorgaans de pulimi CLI (Command Line Interface) gebruikt, die de taalruntime configureert, de lancering van de Pulumi-engine regelt, zodat bewerkingen met bronnen kunnen worden vastgelegd en in het plan kunnen worden opgenomen, enz. Er is echter één probleem. Wanneer u onder controle van uw testframework draait, zal er geen communicatie zijn tussen de CLI en de Pulumi-engine.

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

  • Projectnaam, die is opgenomen in de omgevingsvariabele PULUMI_NODEJS_PROJECT (of, algemener, PULUMI__PROJECT для других языков).
    De naam van de stapel die is opgegeven in de omgevingsvariabele PULUMI_NODEJS_STACK (of, algemener, PULUMI__ STACK).
    Uw stackconfiguratievariabelen. Ze kunnen worden verkregen met behulp van een omgevingsvariabele PULUMI_CONFIG en hun formaat is een JSON-kaart met sleutel/waarde-paren.

    Het programma zal waarschuwingen geven die aangeven dat de verbinding met de CLI/engine niet beschikbaar is tijdens de uitvoering. Dit is belangrijk omdat uw programma feitelijk niets zal implementeren en het kan een verrassing zijn als u dat niet van plan was! Om Pulumi te vertellen dat dit precies is wat u nodig heeft, kunt u het installeren PULUMI_TEST_MODE в true.

    Stel je voor dat we de projectnaam moeten opgeven my-ws, stapelnaam deven AWS-regio us-west-2. De opdrachtregel voor het uitvoeren van Mocha-tests ziet er als volgt uit:

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

    Als we dit doen, zoals verwacht, zullen we zien dat we drie mislukte tests hebben!

    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

    Laten we ons programma repareren:

    "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;
    

    En voer vervolgens de tests opnieuw uit:

    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)

    Alles ging goed... Hoera! ✓✓✓

    Dat is alles voor vandaag, maar we zullen het hebben over implementatietests in het tweede deel van de vertaling 😉

Bron: www.habr.com

Voeg een reactie