Testowanie infrastruktury jako kodu za pomocą Pulumi. Część 1

Dzień dobry przyjaciele. W oczekiwaniu na rozpoczęcie nowego przepływu w tempie „Praktyki i narzędzia DevOps” Dzielimy się z Wami nowym tłumaczeniem. Iść.

Testowanie infrastruktury jako kodu za pomocą Pulumi. Część 1

Używanie Pulumi i języków programowania ogólnego przeznaczenia do kodu infrastruktury (Infrastructure as Code) zapewnia wiele korzyści: dostępność umiejętności i wiedzy, eliminację szablonów w kodzie poprzez abstrakcję, narzędzia znane Twojemu zespołowi, takie jak IDE i linters. Wszystkie te narzędzia inżynierii oprogramowania nie tylko zwiększają naszą produktywność, ale także poprawiają jakość naszego kodu. Dlatego naturalne jest, że użycie języków programowania ogólnego przeznaczenia pozwala nam wprowadzić kolejną ważną praktykę tworzenia oprogramowania - Testowanie.

W tym artykule przyjrzymy się, jak Pulumi pomaga nam testować naszą infrastrukturę jako kod.

Testowanie infrastruktury jako kodu za pomocą Pulumi. Część 1

Po co testować infrastrukturę?

Zanim przejdziemy do szczegółów, warto zadać sobie pytanie: „Po co w ogóle testować infrastrukturę?” Powodów jest wiele, a oto niektóre z nich:

  • Testowanie jednostkowe poszczególnych funkcji lub fragmentów logiki programu
  • Weryfikuje pożądany stan infrastruktury pod kątem określonych ograniczeń.
  • Wykrywanie typowych błędów, takich jak brak szyfrowania zasobnika pamięci czy niezabezpieczony, otwarty dostęp z Internetu do maszyn wirtualnych.
  • Sprawdzanie realizacji udostępnienia infrastruktury.
  • Przeprowadzanie testów wykonawczych logiki aplikacji działającej w „zaprogramowanej” infrastrukturze w celu sprawdzenia funkcjonalności po udostępnieniu.
  • Jak widzimy, istnieje szeroka gama opcji testowania infrastruktury. Polumi posiada mechanizmy testowania w każdym punkcie tego spektrum. Zacznijmy i zobaczmy, jak to działa.

Testów jednostkowych

Programy Pulumi pisane są w językach programowania ogólnego przeznaczenia, takich jak JavaScript, Python, TypeScript czy Go. Dlatego dostępna jest dla nich pełna moc tych języków, w tym ich narzędzia i biblioteki, w tym frameworki testowe. Pulumi obsługuje wiele chmur, co oznacza, że ​​można go używać do testowania od dowolnego dostawcy chmury.

(W tym artykule, mimo że jest wielojęzyczny i wielochmurowy, używamy JavaScript i Mocha i skupiamy się na AWS. Możesz użyć Pythona unittest, Przejdź do platformy testowej lub dowolnej innej platformy testowej, którą lubisz. I oczywiście Pulumi świetnie współpracuje z Azure, Google Cloud, Kubernetes.)

Jak widzieliśmy, istnieje kilka powodów, dla których warto przetestować kod infrastruktury. Jednym z nich są konwencjonalne testy jednostkowe. Ponieważ Twój kod może zawierać funkcje - na przykład obliczające CIDR, dynamicznie obliczające nazwy, tagi itp. - prawdopodobnie będziesz chciał je przetestować. Przypomina to pisanie regularnych testów jednostkowych dla aplikacji w ulubionym języku programowania.
Aby było trochę bardziej skomplikowanie, możesz sprawdzić, w jaki sposób Twój program przydziela zasoby. Aby to zilustrować, wyobraźmy sobie, że musimy stworzyć prosty serwer EC2 i chcemy mieć pewność, że:

  • Instancje mają tag Name.
  • Instancje nie powinny używać wbudowanego skryptu userData - musimy użyć AMI (obrazu).
  • Internet nie powinien mieć dostępu do protokołu SSH.

Ten przykład opiera się na mój przykładowy serwer WWW aws-js:

indeks.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;

To jest podstawowy program Pulumi: po prostu przydziela grupę bezpieczeństwa EC2 i instancję. Należy jednak zaznaczyć, że łamiemy tu wszystkie trzy zasady wymienione powyżej. Napiszmy testy!

Pisanie testów

Ogólna struktura naszych testów będzie wyglądać jak zwykłe testy Mocha:

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

Teraz napiszmy nasz pierwszy test: upewnij się, że instancje mają tag Name. Aby to sprawdzić, po prostu pobieramy obiekt instancji EC2 i sprawdzamy odpowiednią właściwość 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();
                }
            });
        });

Wygląda jak zwykły test, ale ma kilka funkcji, na które warto zwrócić uwagę:

  • Ponieważ przed wdrożeniem sprawdzamy stan zasobu, nasze testy są zawsze uruchamiane w trybie „planowania” (lub „podglądu”). Zatem istnieje wiele właściwości, których wartości po prostu nie zostaną odzyskane lub nie zostaną zdefiniowane. Obejmuje to wszystkie właściwości wyjściowe obliczone przez dostawcę usług w chmurze. Jest to normalne w przypadku naszych testów – sprawdzamy jedynie dane wejściowe. Do tej kwestii wrócimy później, jeśli chodzi o testy integracyjne.
  • Ponieważ wszystkie właściwości zasobów Pulumi są danymi wyjściowymi, a wiele z nich jest ocenianych asynchronicznie, aby uzyskać dostęp do wartości, musimy użyć metody Apply. Jest to bardzo podobne do obietnic i funkcji then .
  • Ponieważ używamy kilku właściwości, aby wyświetlić numer URN zasobu w komunikacie o błędzie, musimy użyć tej funkcji pulumi.allaby je połączyć.
  • Wreszcie, ponieważ wartości te są obliczane asynchronicznie, musimy skorzystać z wbudowanej funkcji asynchronicznego wywołania zwrotnego Mocha done lub zwrócenie obietnicy.

Kiedy już wszystko skonfigurujemy, będziemy mieli dostęp do danych wejściowych w postaci prostych wartości JavaScript. Nieruchomość tags jest mapą (tablicą asocjacyjną), więc upewnimy się, że (1) nie jest fałszywa i (2) istnieje klucz do Name. To bardzo proste i teraz możemy przetestować wszystko!

Teraz napiszmy nasz drugi czek. To jeszcze prostsze:

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

I na koniec napiszmy trzeci test. Będzie to trochę bardziej skomplikowane, ponieważ szukamy reguł logowania powiązanych z grupą bezpieczeństwa, których może być wiele, oraz zakresów CIDR w tych regułach, których również może być wiele. Ale udało nam się:

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

To wszystko. Teraz przeprowadźmy testy!

Uruchamianie testów

W większości przypadków testy można uruchamiać w zwykły sposób, korzystając z wybranego środowiska testowego. Ale jest jedna cecha Pulumi, na którą warto zwrócić uwagę.
Zazwyczaj do uruchamiania programów Pulumi wykorzystywany jest interfejs CLI pulimi (interfejs wiersza poleceń), który konfiguruje środowisko wykonawcze języka, kontroluje uruchamianie silnika Pulumi, dzięki czemu można rejestrować i uwzględniać operacje na zasobach w planie itp. Jednakże jest jeden problem. Podczas pracy pod kontrolą środowiska testowego nie będzie komunikacji pomiędzy interfejsem CLI a silnikiem Pulumi.

Aby obejść ten problem, wystarczy podać następujące informacje:

  • Nazwa projektu zawarta w zmiennej środowiskowej PULUMI_NODEJS_PROJECT (lub, bardziej ogólnie, PULUMI__PROJECT для других языков).
    Nazwa stosu określona w zmiennej środowiskowej PULUMI_NODEJS_STACK (lub, bardziej ogólnie, PULUMI__ STACK).
    Twoje zmienne konfiguracyjne stosu. Można je uzyskać za pomocą zmiennej środowiskowej PULUMI_CONFIG a ich format to mapa JSON z parami klucz/wartość.

    Program wyświetli ostrzeżenia wskazujące, że połączenie z CLI/silnikiem nie jest dostępne podczas wykonywania. Jest to ważne, ponieważ Twój program w rzeczywistości niczego nie będzie wdrażał i może Cię zaskoczyć, jeśli nie jest to zamierzeniem! Aby powiedzieć Pulumi, że właśnie tego potrzebujesz, możesz zainstalować PULUMI_TEST_MODE в true.

    Wyobraź sobie, że musimy określić nazwę projektu w my-ws, nazwa stosu devi regionu AWS us-west-2. Wiersz poleceń do uruchamiania testów Mocha będzie wyglądać następująco:

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

    Wykonanie tego zgodnie z oczekiwaniami pokaże nam, że mamy trzy nieudane testy!

    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

    Naprawmy nasz 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;
    

    A następnie ponownie uruchom testy:

    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)

    Wszystko poszło dobrze... Hurra! ✓✓✓

    To tyle na dziś, ale o testach wdrożeniowych porozmawiamy w drugiej części tłumaczenia 😉

Źródło: www.habr.com

Dodaj komentarz