Testar infrastruktur som kod med Pulumi. Del 1

God eftermiddag vänner. I väntan på starten av ett nytt flöde i takt "DevOps praxis och verktyg" Vi delar med oss ​​av en ny översättning. Gå.

Testar infrastruktur som kod med Pulumi. Del 1

Att använda Pulumi och allmänna programmeringsspråk för infrastrukturkod (Infrastructure as Code) ger många fördelar: tillgången på färdigheter och kunskaper, eliminering av boilerplate i koden genom abstraktion, verktyg som är bekanta för ditt team, såsom IDE:er och linters. Alla dessa mjukvaruutvecklingsverktyg gör oss inte bara mer produktiva, utan förbättrar också kvaliteten på vår kod. Därför är det bara naturligt att användningen av allmänna programmeringsspråk tillåter oss att introducera en annan viktig praxis för mjukvaruutveckling - testning.

I den här artikeln ska vi titta på hur Pulumi hjälper oss att testa vår infrastruktur-som-kod.

Testar infrastruktur som kod med Pulumi. Del 1

Varför testa infrastruktur?

Innan vi går in i detalj är det värt att ställa frågan: "Varför testa infrastruktur överhuvudtaget?" Det finns många anledningar till detta och här är några av dem:

  • Enhetstestning av enskilda funktioner eller fragment av din programlogik
  • Verifierar det önskade tillståndet för infrastrukturen mot vissa begränsningar.
  • Upptäckt av vanliga fel, såsom brist på kryptering av en lagringshink eller oskyddad, öppnar åtkomst från Internet till virtuella maskiner.
  • Kontrollera implementeringen av infrastrukturförsörjning.
  • Utför runtime-testning av applikationslogik som körs i din "programmerade" infrastruktur för att kontrollera funktionaliteten efter provisionering.
  • Som vi kan se finns det ett brett utbud av testalternativ för infrastruktur. Polumi har mekanismer för att testa vid varje punkt på detta spektrum. Låt oss komma igång och se hur det fungerar.

Enhetstestning

Pulumi-program är skrivna i allmänna programmeringsspråk som JavaScript, Python, TypeScript eller Go. Därför är den fulla kraften hos dessa språk, inklusive deras verktyg och bibliotek, inklusive testramverk, tillgänglig för dem. Pulumi är multimoln, vilket innebär att den kan användas för testning från vilken molnleverantör som helst.

(I den här artikeln, trots att vi är flerspråkiga och multimoln, använder vi JavaScript och Mocha och fokuserar på AWS. Du kan använda Python unittest, Go test-ramverk eller något annat testramverk du gillar. Och, naturligtvis, fungerar Pulumi utmärkt med Azure, Google Cloud, Kubernetes.)

Som vi har sett finns det flera anledningar till varför du kanske vill testa din infrastrukturkod. En av dem är konventionell enhetstestning. Eftersom din kod kan ha funktioner - till exempel att beräkna CIDR, dynamiskt beräkna namn, taggar osv. - du kommer förmodligen vilja testa dem. Detta är samma sak som att skriva vanliga enhetstester för applikationer på ditt favoritprogrammeringsspråk.
För att bli lite mer komplicerad kan du kolla hur ditt program allokerar resurser. För att illustrera, låt oss föreställa oss att vi behöver skapa en enkel EC2-server och vi vill vara säkra på följande:

  • Förekomster har en tagg Name.
  • Instanser ska inte använda inline-skript userData - vi måste använda en AMI (bild).
  • Det bör inte finnas någon SSH exponerad för Internet.

Detta exempel är baserat på mitt exempel 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;

Detta är det grundläggande Pulumi-programmet: det tilldelar helt enkelt en EC2-säkerhetsgrupp och en instans. Det bör dock noteras att vi här bryter mot alla tre reglerna ovan. Låt oss skriva prov!

Att skriva prov

Den allmänna strukturen för våra tester kommer att se ut som vanliga Mocka-tester:

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, открытого в Интернет.
    });
});

Låt oss nu skriva vårt första test: se till att instanserna har taggen Name. För att kontrollera detta hämtar vi helt enkelt EC2-instansobjektet och kontrollerar motsvarande egenskap 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();
                }
            });
        });

Det ser ut som ett vanligt test, men med några funktioner som är värda att notera:

  • Eftersom vi frågar om tillståndet för en resurs före distribution, körs våra tester alltid i "plan" (eller "förhandsgranskning") läge. Således finns det många egenskaper vars värden helt enkelt inte kommer att hämtas eller inte kommer att definieras. Detta inkluderar alla utdataegenskaper som beräknats av din molnleverantör. Detta är normalt för våra tester - vi kontrollerar bara indata. Vi återkommer till denna fråga senare, när det gäller integrationstester.
  • Eftersom alla Pulumis resursegenskaper är utdata, och många av dem utvärderas asynkront, måste vi använda appliceringsmetoden för att komma åt värdena. Detta är mycket likt löften och afunction then .
  • Eftersom vi använder flera egenskaper för att visa resurs-URN i felmeddelandet måste vi använda funktionen pulumi.allatt kombinera dem.
  • Slutligen, eftersom dessa värden beräknas asynkront, måste vi använda Mochas inbyggda asynkrona återuppringningsfunktion done eller lämna tillbaka ett löfte.

När vi har ställt in allt kommer vi att ha tillgång till ingångarna som enkla JavaScript-värden. Fast egendom tags är en karta (associativ array), så vi ser bara till att den är (1) inte falsk och (2) att det finns en nyckel för Name. Det är väldigt enkelt och nu kan vi testa vad som helst!

Låt oss nu skriva vår andra check. Det är ännu enklare:

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

Och slutligen, låt oss skriva det tredje testet. Detta kommer att bli lite mer komplicerat eftersom vi letar efter inloggningsreglerna för säkerhetsgruppen, som det kan finnas många av, och CIDR-intervallen i dessa regler, som det också kan finnas många av. Men vi lyckades:

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

Det är allt. Nu kör vi testerna!

Löpande tester

I de flesta fall kan du köra tester på vanligt sätt med det testramverk du väljer. Men det finns en egenskap hos Pulumi som är värd att uppmärksamma.
Vanligtvis, för att köra Pulumi-program, används pulimi CLI (Command Line interface), som konfigurerar språkets körtid, styr lanseringen av Pulumi-motorn så att operationer med resurser kan registreras och inkluderas i planen, etc. Det finns dock ett problem. När du kör under kontroll av ditt testramverk kommer det inte att finnas någon kommunikation mellan CLI och Pulumi-motorn.

För att komma runt det här problemet behöver vi bara ange följande:

  • Projektnamn, som finns i miljövariabeln PULUMI_NODEJS_PROJECT (eller mer allmänt, PULUMI__PROJECT для других языков).
    Namnet på stacken som anges i miljövariabeln PULUMI_NODEJS_STACK (eller mer allmänt, PULUMI__ STACK).
    Dina stackkonfigurationsvariabler. De kan erhållas med hjälp av en miljövariabel PULUMI_CONFIG och deras format är JSON-karta med nyckel/värdepar.

    Programmet kommer att utfärda varningar som indikerar att anslutningen till CLI/motorn inte är tillgänglig under körning. Detta är viktigt eftersom ditt program faktiskt inte kommer att distribuera någonting och det kan komma som en överraskning om det inte är vad du tänkt göra! För att tala om för Pulumi att det är precis vad du behöver kan du installera PULUMI_TEST_MODE в true.

    Föreställ dig att vi måste ange projektnamnet i my-ws, stacknamn dev, och AWS-regionen us-west-2. Kommandoraden för att köra Mocha-tester kommer att se ut så här:

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

    Att göra detta, som förväntat, kommer att visa oss att vi har tre misslyckade test!

    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

    Låt oss fixa vårt program:

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

    Och kör sedan testerna igen:

    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)

    Allt gick bra... Hurra! ✓✓✓

    Det var allt för idag, men vi kommer att prata om implementeringstestning i den andra delen av översättningen 😉

Källa: will.com

Lägg en kommentar