Testen der Infrastruktur als Code mit Pulumi. Teil 1

Guten Tag, Freunde. Im Vorfeld des neuen Kurses „DevOps-Praktiken und Werkzeuge“ teilen wir mit Ihnen eine neue Übersetzung. Los geht's.

Testen der Infrastruktur als Code mit Pulumi. Teil 1

Die Verwendung von Pulumi und allgemeinen Programmiersprachen für Infrastructure as Code (IaC) bietet zahlreiche Vorteile: benötigte Fähigkeiten und Kenntnisse, die Beseitigung von Boilerplate-Code durch Abstraktion und vertraute Tools wie IDEs und Linter für Ihr Team. All diese Software-Ingenieurtechniken steigern nicht nur unsere Produktivität, sondern auch die Codequalität. Daher ist es nur logisch, dass die Verwendung allgemeiner Programmiersprachen eine weitere wichtige Praxis in der Softwareentwicklung ermöglicht — Testing.

In diesem Artikel werden wir uns ansehen, wie Pulumi uns hilft, unsere "Infrastructure as Code" zu testen.

Testen der Infrastruktur als Code mit Pulumi. Teil 1

Warum Infrastruktur testen?

Bevor wir ins Detail gehen, sollte die Frage gestellt werden: „Warum überhaupt Infrastruktur testen?“ Es gibt viele Gründe dafür, und hier sind einige davon:

  • Modultests einzelner Funktionen oder Logikfragmente Ihres Programms
  • Überprüfung des gewünschten Infrastrukturzustands auf Übereinstimmung mit bestimmten Einschränkungen.
  • Erkennung häufiger Fehler, wie fehlende Verschlüsselung des Storage Buckets oder ungesicherter, offener Zugang aus dem Internet zu virtuellen Maschinen.
  • Überprüfung der Durchführung der Infrastruktur-Provisionierung.
  • Durchführung von Runtime-Tests der Anwendung, die innerhalb Ihrer "programmierten" Infrastruktur ausgeführt wird, um die Funktionalität nach der Provisionierung zu überprüfen.
  • Wie wir sehen, gibt es ein breites Spektrum an Testmöglichkeiten für die Infrastruktur. In Pulumi gibt es Mechanismen, um an jedem Punkt dieses Spektrums zu testen. Lassen Sie uns beginnen und sehen, wie es funktioniert.

Modultests

Pulumi-Programme werden in allgemeinen Programmiersprachen wie JavaScript, Python, TypeScript oder Go erstellt. Daher steht ihnen die volle Leistungsfähigkeit dieser Sprachen zur Verfügung, einschließlich ihrer Werkzeuge und Bibliotheken, einschließlich Testframeworks. Pulumi ist multi-cloud, was bedeutet, dass Testmöglichkeiten für alle Cloud-Anbieter genutzt werden können.

(In diesem Artikel verwenden wir trotz Mehrsprachigkeit und Multi-Cloud-Journey JavaScript und Mocha und orientieren uns an AWS. Python kann ebenfalls genutzt werden, unittest, das Test-Framework Go oder ein beliebiges anderes Test-Framework Ihrer Wahl. Und natürlich funktioniert Pulumi hervorragend mit Azure, Google Cloud, Kubernetes.)

Wie wir gesehen haben, gibt es mehrere Gründe, warum Sie Ihren Infrastrukturcode testen sollten. Einer davon ist das reguläre Modultesting. Da Ihr Code Funktionen haben kann – zum Beispiel zur Berechnung von CIDR, dynamischen Berechnung von Namen, Tags usw. – möchten Sie diese wahrscheinlich testen. Das ist das gleiche wie das Schreiben regulärer Modultests für Anwendungen in Ihrer bevorzugten Programmiersprache.
Wenn wir es etwas komplizierter gestalten, können wir überprüfen, wie Ihre Anwendung Ressourcen verteilt. Stellen wir uns vor, wir müssen einen einfachen EC2-Server erstellen und wollen sicherstellen, dass:

  • Die Instanzen haben ein Tag Name.
  • Instanzen sollten kein Inline-Skript verwenden userData – wir sollten AMI (Abbild) verwenden.
  • Es sollte kein SSH ins Internet geöffnet sein.

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 ], // referenziere das obige Gruppenobjekt
    ami: "ami-c55673a0"             // AMI für us-east-2 (Ohio),
    userData: userData              // starte einen einfachen Webserver
});
 
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;

Dies ist ein grundlegendes Pulumi-Programm: Es allokiert einfach eine EC2-Sicherheitsgruppe und eine Instanz. Es ist jedoch wichtig zu beachten, dass wir hier gegen alle drei oben genannten Regeln verstoßen. Lassen Sie uns Tests schreiben!

Tests schreiben

Die allgemeine Struktur unserer Tests wird wie bei normalen Mocha-Tests aussehen:

ec2tests.js

test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
 
describe("Infrastruktur", function() {
    let server = infra.server;
    describe("#server", function() {
        // TODO(check 1): Es sollte ein Name-Tag geben.
        // TODO(check 2): Es sollte kein inline userData-Skript geben.
    });
    let group = infra.group;
    describe("#group", function() {
        // TODO(check 3): SSH sollte nicht für das Internet offen sein.
    });
});

Lassen Sie uns jetzt unseren ersten Test schreiben: Wir überprüfen, ob die Instanzen ein Tag haben. NameUm dies zu überprüfen, erhalten wir einfach das EC2-Instanzobjekt und überprü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();
                }
            });
        });

Das sieht aus wie ein gewöhnlicher Test, hat aber einige Besonderheiten, die beachtet werden sollten:

  • Da wir den Status der Ressource vor der Bereitstellung abfragen, werden unsere Tests immer im „Plan“-Modus (oder „Vorschau“) ausgeführt. Daher gibt es viele Eigenschaften, deren Werte einfach nicht abgerufen oder undefiniert sein werden. Dazu gehören alle Ausgangseigenschaften, die von Ihrem Cloud-Anbieter berechnet werden. Für unsere Tests ist das in Ordnung – wir überprüfen nur die Eingabewerte. Auf dieses Thema werden wir später zurückkommen, wenn es um Integrationstests geht.
  • Da alle Eigenschaften von Pulumi-Ressourcen „Ausgaben“ sind und viele von ihnen asynchron berechnet werden, müssen wir die apply-Methode verwenden, um auf die Werte zuzugreifen. Das ist sehr ähnlich wie bei Promises und Afunktionen. then .
  • Da wir mehrere Eigenschaften verwenden, um die URN der Ressource in der Fehlermeldung anzuzeigen, müssen wir die Funktion verwenden pulumi.all, um sie zu kombinieren.
  • Da diese Werte asynchron berechnet werden, müssen wir die eingebaute asynchrone Funktionalität von Mocha mit einem Callback nutzen. fertig oder durch Rückgabe eines Promises.

Nachdem wir alles eingerichtet haben, haben wir Zugriff auf die Eingabewerte als einfache JavaScript-Werte. Die Eigenschaft tags ist ein map (assoziatives Array), also müssen wir nur sicherstellen, dass dies (1) nicht false ist und (2) einen Schlüssel hat für Name. Das ist ganz einfach und jetzt können wir alles Mögliche überprüfen!

Jetzt lass uns unseren zweiten Test schreiben. Das 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 schließlich schreiben wir den dritten Test. Das wird etwas komplizierter, da wir nach Eingangsregeln suchen, die mit Security Groups verbunden sind, von denen es viele geben kann, und CIDR-Bereiche in diesen Regeln, die auch zahlreich sein können. 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 lass uns die Tests ausführen!

Tests ausführen

Die Ausführung von Tests kann in den meisten Fällen auf die übliche Weise erfolgen, indem das von Ihnen gewählte Test-Framework verwendet wird. Aber es gibt eine Besonderheit bei Pulumi, auf die man achten sollte.
In der Regel wird das Pulumi-Programm über die pulumi CLI (Command Line Interface) gestartet, die die Laufzeitumgebung der Sprache konfiguriert und die Ausführungen der Pulumi-Engine steuert, sodass Operationen mit Ressourcen protokolliert und in den Plan aufgenommen werden können usw. Allerdings gibt es ein Problem. Wenn das Programm unter Kontrolle Ihres Test-Frameworks ausgeführt wird, besteht keine Verbindung zwischen der CLI und der Pulumi-Engine.

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

  • Den Projektnamen, der in der Umgebungsvariable PULUMI_NODEJS_PROJECT (oder, im allgemeineren Fall, PULUMI__PROJECT für andere Sprachen).
    Den Stack-Namen, der in der Umgebungsvariable PULUMI_NODEJS_STACK (oder, im allgemeineren Fall, PULUMI__STACK) angegeben ist.
    Ihre Stack-Konfigurationsvariablen. Diese können über die Umgebungsvariable PULUMI_CONFIG abgerufen werden, und ihr Format ist eine JSON-Karte mit Schlüssel/Wert-Paaren.

    Das Programm wird Warnungen ausgeben, die darauf hinweisen, dass während der Ausführung keine Verbindung zu CLI/Engine hergestellt werden kann. Dies ist wichtig, da Ihre Anwendung tatsächlich nichts bereitstellen wird, was eine Überraschung sein kann, wenn das nicht Ihr Ziel war! Um Pulumi mitzuteilen, dass dies genau das ist, was Sie benötigen, können Sie PULUMI_TEST_MODE in true.

    stellen Sie sich vor, wir müssen den Projektnamen in my-wsangeben, den Stack-Namen devund die AWS-Region us-west-2. Die Befehlszeile zum Ausführen der Mocha-Tests würde folgendermaßen aussehen:

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

    Die Durchführung dieses Tests wird uns wie erwartet zeigen, dass wir drei fehlgeschlagene Tests haben!

    Infrastructure
        #server
          1) muss ein Namens-Tag haben
     	 2) darf userData nicht verwenden (verwenden Sie stattdessen ein AMI)
        #group
          3) darf Port 22 (SSH) nicht für das Internet öffnen
    
      0 bestanden (17ms)
      3 fehlgeschlagen
     
     1) Infrastructure
           #server
             muss ein Namens-Tag haben:
         Fehler: Fehlendes Namens-Tag auf dem Server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     2) Infrastructure
           #server
             darf userData nicht verwenden (verwenden Sie stattdessen ein AMI):
         Fehler: Illegale Verwendung von userData auf dem Server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     3) Infrastructure
           #group
             darf Port 22 (SSH) nicht für das Internet öffnen:
         Fehler: Illegale Öffnung von SSH-Port 22 für das Internet (CIDR 0.0.0.0/0) in der Gruppe

    Lassen Sie uns unser Programm korrigieren:

    "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 ], // referenzieren Sie das oben genannte Gruppenobjekt
        ami: "ami-c55673a0"             // AMI für us-east-2 (Ohio),
    });
    
    exports.group = group;
    exports.server = server;
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    

    Und dann werden wir die Tests erneut durchführen:

    Infrastruktur
        #server
          ✓ muss ein Namens-Tag haben
          ✓ darf kein userData verwenden (verwenden Sie stattdessen ein AMI)
        #group
          ✓ darf Port 22 (SSH) nicht für das Internet öffnen
    
     3 bestanden (16ms)

    Alles war erfolgreich… Hurra! ✓✓✓

    Das ist es für heute, und über das Testen der Bereitstellung sprechen wir im zweiten Teil der Übersetzung 😉

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster