Testen von Infrastructure as Code mit Pulumi. Teil 1

Guten Nachmittag Freunde. In Erwartung des Beginns eines neuen Flusses mit der Rate „DevOps-Praktiken und -Tools“ Wir teilen Ihnen eine neue Übersetzung mit. Gehen.

Testen von Infrastructure as Code mit Pulumi. Teil 1

Die Verwendung von Pulumi und allgemeinen Programmiersprachen für Infrastrukturcode (Infrastructure as Code) bietet viele Vorteile: Verfügbarkeit von Fähigkeiten und Wissen, Eliminierung von Boilerplates im Code durch Abstraktion, Ihrem Team vertraute Tools wie IDEs und Linters. All diese Software-Engineering-Tools machen uns nicht nur produktiver, sondern verbessern auch die Qualität unseres Codes. Daher ist es nur natürlich, dass die Verwendung allgemeiner Programmiersprachen es uns ermöglicht, eine weitere wichtige Praxis der Softwareentwicklung einzuführen – Testen.

In diesem Artikel schauen wir uns an, wie Pulumi uns beim Testen unserer Infrastructure-as-Code hilft.

Testen von Infrastructure as Code mit Pulumi. Teil 1

Warum Infrastruktur testen?

Bevor wir ins Detail gehen, lohnt es sich, die Frage zu stellen: „Warum überhaupt Infrastruktur testen?“ Dafür gibt es viele Gründe und hier sind einige davon:

  • Unit-Testing einzelner Funktionen oder Fragmente Ihrer Programmlogik
  • Überprüft den gewünschten Zustand der Infrastruktur anhand bestimmter Einschränkungen.
  • Erkennung häufiger Fehler, wie z. B. fehlende Verschlüsselung eines Storage-Buckets oder ungeschützter, offener Zugriff aus dem Internet auf virtuelle Maschinen.
  • Überprüfung der Umsetzung der Infrastrukturbereitstellung.
  • Durchführen von Laufzeittests der Anwendungslogik, die in Ihrer „programmierten“ Infrastruktur ausgeführt wird, um die Funktionalität nach der Bereitstellung zu überprüfen.
  • Wie wir sehen, gibt es eine große Auswahl an Möglichkeiten zum Testen der Infrastruktur. Polumi verfügt über Mechanismen zum Testen an jedem Punkt dieses Spektrums. Lass uns anfangen und sehen, wie es funktioniert.

Unit-Tests

Pulumi-Programme werden in allgemeinen Programmiersprachen wie JavaScript, Python, TypeScript oder Go geschrieben. Daher steht ihnen die volle Leistungsfähigkeit dieser Sprachen, einschließlich ihrer Tools und Bibliotheken, einschließlich Test-Frameworks, zur Verfügung. Pulumi ist Multi-Cloud, was bedeutet, dass es für Tests von jedem Cloud-Anbieter verwendet werden kann.

(In diesem Artikel verwenden wir, obwohl er mehrsprachig und multicloudfähig ist, JavaScript und Mocha und konzentrieren uns auf AWS. Sie können Python verwenden unittest, Go-Test-Framework oder ein anderes Test-Framework, das Ihnen gefällt. Und natürlich funktioniert Pulumi hervorragend mit Azure, Google Cloud und Kubernetes.)

Wie wir gesehen haben, gibt es mehrere Gründe, warum Sie Ihren Infrastrukturcode testen möchten. Eine davon ist das konventionelle Unit-Testen. Weil Ihr Code möglicherweise Funktionen hat – zum Beispiel zur Berechnung von CIDR, zur dynamischen Berechnung von Namen, Tags usw. - Sie werden sie wahrscheinlich testen wollen. Dies entspricht dem Schreiben regelmäßiger Unit-Tests für Anwendungen in Ihrer bevorzugten Programmiersprache.
Um es etwas komplizierter zu machen, können Sie überprüfen, wie Ihr Programm Ressourcen zuweist. Stellen wir uns zur Veranschaulichung vor, dass wir einen einfachen EC2-Server erstellen müssen und Folgendes sicherstellen möchten:

  • Instanzen haben ein Tag Name.
  • Instanzen sollten kein Inline-Skript verwenden userData - Wir müssen ein AMI (Bild) verwenden.
  • Es sollte keine SSH-Verbindung zum Internet bestehen.

Dieses Beispiel basiert auf mein Beispiel 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;

Dies ist das grundlegende Pulumi-Programm: Es weist lediglich eine EC2-Sicherheitsgruppe und eine Instanz zu. Es ist jedoch zu beachten, dass wir hier gegen alle drei oben genannten Regeln verstoßen. Lasst uns Tests schreiben!

Schreibtests

Die allgemeine Struktur unserer Tests wird wie normale Mokka-Tests aussehen:

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

Schreiben wir nun unseren ersten Test: Stellen Sie sicher, dass die Instanzen das Tag haben Name. Um dies zu überprüfen, holen wir uns einfach das EC2-Instanzobjekt und prüfen die entsprechende Eigenschaft 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();
                }
            });
        });

Es sieht aus wie ein normaler Test, weist jedoch einige erwähnenswerte Funktionen auf:

  • Da wir den Status einer Ressource vor der Bereitstellung abfragen, werden unsere Tests immer im „Plan“- (oder „Vorschau“)-Modus ausgeführt. Daher gibt es viele Eigenschaften, deren Werte einfach nicht abgerufen oder nicht definiert werden. Dazu gehören alle von Ihrem Cloud-Anbieter berechneten Ausgabeeigenschaften. Das ist bei unseren Tests normal – wir prüfen lediglich die Eingabedaten. Wir werden später auf dieses Thema zurückkommen, wenn es um Integrationstests geht.
  • Da es sich bei allen Pulumi-Ressourceneigenschaften um Ausgaben handelt und viele davon asynchron ausgewertet werden, müssen wir die Methode „Apply“ verwenden, um auf die Werte zuzugreifen. Dies ist den Versprechen und einer Funktion sehr ähnlich then .
  • Da wir mehrere Eigenschaften verwenden, um den Ressourcen-URN in der Fehlermeldung anzuzeigen, müssen wir die Funktion verwenden pulumi.allum sie zu kombinieren.
  • Da diese Werte schließlich asynchron berechnet werden, müssen wir die integrierte asynchrone Rückruffunktion von Mocha verwenden done oder ein Versprechen erwidern.

Sobald wir alles eingerichtet haben, haben wir Zugriff auf die Eingaben als einfache JavaScript-Werte. Eigentum tags ist eine Karte (assoziatives Array), also stellen wir nur sicher, dass sie (1) nicht falsch ist und (2) es einen Schlüssel dafür gibt Name. Es ist ganz einfach und jetzt können wir alles testen!

Jetzt schreiben wir unseren zweiten Scheck. Es ist noch einfacher:

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

Und zum Schluss schreiben wir den dritten Test. Dies wird etwas komplizierter, da wir nach den Anmelderegeln suchen, die mit der Sicherheitsgruppe verknüpft sind, von denen es viele geben kann, und nach den CIDR-Bereichen in diesen Regeln, von denen es auch viele geben kann. Aber wir haben es geschafft:

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

Das ist alles. Jetzt lasst uns die Tests durchführen!

Laufende Tests

In den meisten Fällen können Sie Tests wie gewohnt mit dem Test-Framework Ihrer Wahl durchführen. Aber es gibt eine Besonderheit von Pulumi, die es wert ist, beachtet zu werden.
Typischerweise wird zum Ausführen von Pulumi-Programmen die Pulimi-CLI (Befehlszeilenschnittstelle) verwendet, die die Sprachlaufzeit konfiguriert, den Start der Pulumi-Engine steuert, sodass Vorgänge mit Ressourcen aufgezeichnet und in den Plan einbezogen werden können usw. Es gibt jedoch ein Problem. Bei der Ausführung unter der Kontrolle Ihres Test-Frameworks findet keine Kommunikation zwischen der CLI und der Pulumi-Engine statt.

Um dieses Problem zu umgehen, müssen wir lediglich Folgendes angeben:

  • Projektname, der in der Umgebungsvariablen enthalten ist PULUMI_NODEJS_PROJECT (oder allgemeiner: PULUMI__PROJECT для других языков).
    Der Name des Stapels, der in der Umgebungsvariablen angegeben ist PULUMI_NODEJS_STACK (oder allgemeiner: PULUMI__ STACK).
    Ihre Stack-Konfigurationsvariablen. Sie können über eine Umgebungsvariable abgerufen werden PULUMI_CONFIG und ihr Format ist eine JSON-Karte mit Schlüssel/Wert-Paaren.

    Das Programm gibt Warnungen aus, die darauf hinweisen, dass die Verbindung zur CLI/Engine während der Ausführung nicht verfügbar ist. Dies ist wichtig, da Ihr Programm eigentlich nichts bereitstellt und es eine Überraschung sein könnte, wenn dies nicht das ist, was Sie beabsichtigt haben! Um Pulumi mitzuteilen, dass dies genau das ist, was Sie benötigen, können Sie es installieren PULUMI_TEST_MODE в true.

    Stellen Sie sich vor, wir müssen den Projektnamen angeben my-ws, Stapelname devund AWS-Region us-west-2. Die Befehlszeile zum Ausführen von Mocha-Tests sieht folgendermaßen aus:

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

    Wenn wir dies wie erwartet tun, werden wir sehen, dass wir drei fehlgeschlagene Tests haben!

    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

    Lassen Sie uns unser Programm reparieren:

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

    Und dann führen Sie die Tests erneut durch:

    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 hat gut geklappt... Hurra! ✓ ✓ ✓

    Das ist alles für heute, aber wir werden im zweiten Teil der Übersetzung über Bereitstellungstests sprechen 😉

Source: habr.com

Kommentar hinzufügen