Testarea infrastructurii ca cod cu Pulumi. Partea 1

Buna seara prieteni. În așteptarea începerii unui nou flux la rata „Practici și instrumente DevOps” Vă împărtășim o nouă traducere. Merge.

Testarea infrastructurii ca cod cu Pulumi. Partea 1

Utilizarea Pulumi și a limbajelor de programare de uz general pentru codul de infrastructură (Infrastructure as Code) oferă multe avantaje: disponibilitatea abilităților și cunoștințelor, eliminarea boilerplate din cod prin abstractizare, instrumente familiare echipei dvs., cum ar fi IDE-uri și linters. Toate aceste instrumente de inginerie software nu numai că ne fac mai productivi, ci și îmbunătățesc calitatea codului nostru. Prin urmare, este firesc ca utilizarea limbajelor de programare cu scop general să ne permită să introducem o altă practică importantă de dezvoltare de software - testarea.

În acest articol, vom analiza modul în care Pulumi ne ajută să testăm infrastructura ca cod.

Testarea infrastructurii ca cod cu Pulumi. Partea 1

De ce testați infrastructura?

Înainte de a intra în detalii, merită să puneți întrebarea: „De ce să testați infrastructura?” Există multe motive pentru aceasta și iată câteva dintre ele:

  • Testarea unitară a funcțiilor individuale sau a fragmentelor logicii programului dumneavoastră
  • Verifică starea dorită a infrastructurii în raport cu anumite constrângeri.
  • Detectarea erorilor comune, cum ar fi lipsa criptării unei găleți de stocare sau accesul deschis neprotejat de pe Internet la mașinile virtuale.
  • Verificarea implementării furnizării infrastructurii.
  • Efectuarea testării timpului de execuție a logicii aplicației care rulează în infrastructura „programată” pentru a verifica funcționalitatea după furnizare.
  • După cum putem vedea, există o gamă largă de opțiuni de testare a infrastructurii. Polumi are mecanisme de testare în fiecare punct al acestui spectru. Să începem și să vedem cum funcționează.

Testarea unitară

Programele Pulumi sunt scrise în limbaje de programare de uz general, cum ar fi JavaScript, Python, TypeScript sau Go. Prin urmare, toată puterea acestor limbi, inclusiv instrumentele și bibliotecile lor, inclusiv cadrele de testare, le este disponibilă. Pulumi este multi-cloud, ceea ce înseamnă că poate fi folosit pentru testare de la orice furnizor de cloud.

(În acest articol, deși este multilingv și multicloud, folosim JavaScript și Mocha și ne concentrăm pe AWS. Puteți folosi Python unittest, cadru de testare Go sau orice alt cadru de testare care vă place. Și, desigur, Pulumi funcționează excelent cu Azure, Google Cloud, Kubernetes.)

După cum am văzut, există mai multe motive pentru care ați putea dori să vă testați codul de infrastructură. Una dintre ele este testarea unitară convențională. Deoarece codul dvs. poate avea funcții - de exemplu, pentru a calcula CIDR, a calcula dinamic nume, etichete etc. - probabil că vei dori să le testezi. Este același lucru cu scrierea testelor unitare obișnuite pentru aplicații în limbajul dvs. de programare preferat.
Pentru a deveni puțin mai complicat, puteți verifica modul în care programul dvs. alocă resursele. Pentru a ilustra, să ne imaginăm că trebuie să creăm un server EC2 simplu și vrem să fim siguri de următoarele:

  • Instanțele au o etichetă Name.
  • Instanțele nu trebuie să utilizeze script inline userData - trebuie să folosim un AMI (imagine).
  • Nu ar trebui să existe niciun SSH expus la Internet.

Acest exemplu se bazează pe exemplul meu 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;

Acesta este programul Pulumi de bază: pur și simplu alocă un grup de securitate EC2 și o instanță. Cu toate acestea, trebuie menționat că aici încălcăm toate cele trei reguli menționate mai sus. Să scriem teste!

Teste de scriere

Structura generală a testelor noastre va arăta ca testele Mocha obișnuite:

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

Acum să scriem primul nostru test: asigură-te că instanțele au eticheta Name. Pentru a verifica acest lucru, pur și simplu obținem obiectul instanței EC2 și verificăm proprietatea corespunzătoare 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();
                }
            });
        });

Arată ca un test obișnuit, dar cu câteva caracteristici care merită remarcate:

  • Deoarece interogăm starea unei resurse înainte de implementare, testele noastre sunt întotdeauna executate în modul „plan” (sau „previzualizare”). Astfel, există multe proprietăți ale căror valori pur și simplu nu vor fi preluate sau nu vor fi definite. Aceasta include toate proprietățile de ieșire calculate de furnizorul dvs. de cloud. Acest lucru este normal pentru testele noastre - verificăm doar datele de intrare. Vom reveni asupra acestei probleme mai târziu, când vine vorba de testele de integrare.
  • Deoarece toate proprietățile resurselor Pulumi sunt ieșiri și multe dintre ele sunt evaluate asincron, trebuie să folosim metoda de aplicare pentru a accesa valorile. Acest lucru este foarte asemănător cu promisiunile și cu o funcție then .
  • Deoarece folosim mai multe proprietăți pentru a afișa URN-ul resursei în mesajul de eroare, trebuie să folosim funcția pulumi.allpentru a le combina.
  • În cele din urmă, deoarece aceste valori sunt calculate asincron, trebuie să folosim funcția de apel invers asincronă încorporată a lui Mocha done sau returnarea unei promisiuni.

Odată ce am configurat totul, vom avea acces la intrări ca valori JavaScript simple. Proprietate tags este o hartă (matrice asociativă), așa că ne vom asigura că (1) nu este falsă și (2) există o cheie pentru Name. Este foarte simplu și acum putem testa orice!

Acum să scriem al doilea control. Este si mai simplu:

 // 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, în sfârșit, să scriem al treilea test. Acest lucru va fi puțin mai complicat deoarece căutăm regulile de conectare asociate cu grupul de securitate, dintre care pot fi multe, și intervalele CIDR în acele reguli, dintre care pot fi și multe. Dar ne-am descurcat:

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

Asta e tot. Acum haideți să facem testele!

Executarea testelor

În cele mai multe cazuri, puteți rula teste în mod obișnuit, folosind cadrul de testare la alegere. Dar există o caracteristică a lui Pulumi căreia merită să-i acordăm atenție.
În mod obișnuit, pentru a rula programe Pulumi, se folosește pulimi CLI (Command Line interface), care configurează durata de execuție a limbajului, controlează lansarea motorului Pulumi, astfel încât operațiunile cu resurse să poată fi înregistrate și incluse în plan etc. Cu toate acestea, există o problemă. Când rulați sub controlul cadrului dvs. de testare, nu va exista nicio comunicare între CLI și motorul Pulumi.

Pentru a rezolva această problemă, trebuie doar să specificăm următoarele:

  • Numele proiectului, care este conținut în variabila de mediu PULUMI_NODEJS_PROJECT (sau, mai general, PULUMI__PROJECT для других языков).
    Numele stivei care este specificat în variabila de mediu PULUMI_NODEJS_STACK (sau, mai general, PULUMI__ STACK).
    Variabilele de configurare a stivei dvs. Ele pot fi obținute folosind o variabilă de mediu PULUMI_CONFIG iar formatul lor este harta JSON cu perechi cheie/valoare.

    Programul va emite avertismente care indică faptul că conexiunea la CLI/motor nu este disponibilă în timpul execuției. Acest lucru este important deoarece programul dvs. nu va implementa nimic și poate fi o surpriză dacă nu este ceea ce intenționați să faceți! Pentru a-i spune lui Pulumi că acesta este exact ceea ce aveți nevoie, puteți instala PULUMI_TEST_MODE в true.

    Imaginează-ți că trebuie să specificăm numele proiectului my-ws, numele stivei devși Regiunea AWS us-west-2. Linia de comandă pentru rularea testelor Mocha va arăta astfel:

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

    Făcând acest lucru, așa cum era de așteptat, ne va arăta că avem trei teste nereușite!

    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

    Să ne reparăm programul:

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

    Și apoi rulați din nou testele:

    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)

    Totul a mers bine... Ura! ✓✓✓

    Asta e tot pentru astăzi, dar vom vorbi despre testarea implementării în a doua parte a traducerii 😉

Sursa: www.habr.com

Adauga un comentariu