Testiranje infrastrukture kao koda sa Pulumijem. Dio 1

Dobar dan prijatelji. U iščekivanju početka novog toka po stopi "DevOps prakse i alati" S vama dijelimo novi prijevod. Idi.

Testiranje infrastrukture kao koda sa Pulumijem. Dio 1

Korištenje Pulumija i programskih jezika opće namjene za infrastrukturni kod (Infrastructure as Code) pruža mnoge prednosti: dostupnost vještina i znanja, eliminaciju šablona u kodu kroz apstrakciju, alate poznate vašem timu, kao što su IDE i linteri. Svi ovi alati za softversko inženjerstvo ne samo da nas čine produktivnijima, već i poboljšavaju kvalitet našeg koda. Stoga je sasvim prirodno da nam upotreba programskih jezika opće namjene omogućava uvođenje još jedne važne prakse razvoja softvera - testiranje.

U ovom članku ćemo pogledati kako nam Pulumi pomaže da testiramo našu infrastrukturu kao kod.

Testiranje infrastrukture kao koda sa Pulumijem. Dio 1

Zašto testirati infrastrukturu?

Prije nego što uđemo u detalje, vrijedi postaviti pitanje: "Zašto uopće testirati infrastrukturu?" Postoji mnogo razloga za to, a evo nekih od njih:

  • Jedinično testiranje pojedinačnih funkcija ili fragmenata logike vašeg programa
  • Provjerava željeno stanje infrastrukture u odnosu na određena ograničenja.
  • Otkrivanje uobičajenih grešaka, kao što je nedostatak enkripcije spremnika za pohranu ili nezaštićeni, otvoreni pristup sa Interneta virtuelnim mašinama.
  • Provjera implementacije infrastrukturnog obezbjeđenja.
  • Izvođenje testiranja logike aplikacije koja se izvodi unutar vaše „programirane“ infrastrukture radi provjere funkcionalnosti nakon obezbjeđenja.
  • Kao što vidimo, postoji širok spektar opcija za testiranje infrastrukture. Polumi ima mehanizme za testiranje u svakoj tački ovog spektra. Hajde da počnemo i vidimo kako to funkcioniše.

Jedinično testiranje

Pulumi programi su napisani u programskim jezicima opće namjene kao što su JavaScript, Python, TypeScript ili Go. Stoga im je dostupna puna moć ovih jezika, uključujući njihove alate i biblioteke, uključujući okvire za testiranje. Pulumi je multi-cloud, što znači da se može koristiti za testiranje od bilo kojeg cloud provajdera.

(U ovom članku, uprkos tome što smo višejezični i multicloud, koristimo JavaScript i Mocha i fokusiramo se na AWS. Možete koristiti Python unittest, Go test framework ili bilo koji drugi okvir za testiranje koji vam se sviđa. I, naravno, Pulumi odlično radi sa Azure, Google Cloud, Kubernetes.)

Kao što smo vidjeli, postoji nekoliko razloga zašto biste možda željeli testirati svoj infrastrukturni kod. Jedno od njih je konvencionalno testiranje jedinica. Zato što vaš kod može imati funkcije - na primjer, za izračunavanje CIDR-a, dinamički izračunavanje imena, oznaka itd. - verovatno ćete želeti da ih testirate. Ovo je isto kao pisanje redovnih jediničnih testova za aplikacije u vašem omiljenom programskom jeziku.
Da budete malo komplikovaniji, možete provjeriti kako vaš program dodjeljuje resurse. Za ilustraciju, zamislimo da trebamo kreirati jednostavan EC2 server i želimo biti sigurni u sljedeće:

  • Instance imaju oznaku Name.
  • Instance ne bi trebale koristiti inline skriptu userData - moramo koristiti AMI (sliku).
  • Ne bi trebao biti SSH izložen Internetu.

Ovaj primjer je zasnovan na moj primjer 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;

Ovo je osnovni Pulumi program: on jednostavno dodjeljuje EC2 sigurnosnu grupu i instancu. Međutim, treba napomenuti da ovdje kršimo sva tri gore navedena pravila. Hajde da pišemo testove!

Pisanje testova

Opća struktura naših testova će izgledati kao redovni Mocha testovi:

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

Sada napišimo naš prvi test: provjerite da li instance imaju oznaku Name. Da bismo to provjerili, jednostavno dobijemo EC2 objekt instance i provjerimo odgovarajuće svojstvo 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();
                }
            });
        });

Izgleda kao običan test, ali s nekoliko karakteristika koje vrijedi napomenuti:

  • Budući da ispitujemo stanje resursa prije implementacije, naši se testovi uvijek izvode u načinu "planiranja" (ili "pregled"). Dakle, postoji mnogo svojstava čije vrijednosti jednostavno neće biti dohvaćene ili neće biti definirane. Ovo uključuje sva izlazna svojstva izračunata od strane vašeg dobavljača oblaka. To je normalno za naše testove - provjeravamo samo ulazne podatke. Kasnije ćemo se vratiti na ovo pitanje kada su u pitanju integracijski testovi.
  • Budući da su sva svojstva Pulumi resursa izlazi, a mnoga od njih se evaluiraju asinhrono, moramo koristiti metodu primjene za pristup vrijednostima. Ovo je vrlo slično obećanjima i funkciji then .
  • Budući da koristimo nekoliko svojstava da prikažemo URN resursa u poruci o grešci, moramo koristiti funkciju pulumi.allda ih kombinujemo.
  • Konačno, budući da se ove vrijednosti izračunavaju asinhrono, moramo koristiti Mocha-inu ugrađenu funkciju asinhronog povratnog poziva done ili vraćanje obećanja.

Nakon što sve podesimo, imat ćemo pristup ulazima kao jednostavnim JavaScript vrijednostima. Nekretnina tags je mapa (asocijativni niz), pa ćemo se samo pobrinuti da (1) nije lažna i (2) da postoji ključ za Name. Vrlo je jednostavno i sada možemo testirati bilo šta!

Hajde da napišemo naš drugi ček. Još je jednostavnije:

 // 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 kraju, hajde da napišemo treći test. Ovo će biti malo složenije jer tražimo pravila za prijavu povezana sa sigurnosnom grupom, kojih može biti mnogo, a CIDR rasponi u tim pravilima, kojih također može biti mnogo. Ali uspjeli smo:

    // 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 je sve. Hajde sada da izvršimo testove!

Pokretanje testova

U većini slučajeva možete pokrenuti testove na uobičajeni način, koristeći okvir za testiranje po vašem izboru. Ali postoji jedna karakteristika Pulumija na koju vrijedi obratiti pažnju.
Obično se za pokretanje Pulumi programa koristi pulimi CLI (sučelje komandne linije) koji konfigurira vrijeme izvođenja jezika, kontrolira pokretanje Pulumi motora tako da se operacije s resursima mogu snimiti i uključiti u plan itd. Međutim, postoji jedan problem. Kada radite pod kontrolom vašeg testnog okvira, neće biti komunikacije između CLI i Pulumi motora.

Da bismo zaobišli ovaj problem, samo trebamo navesti sljedeće:

  • Naziv projekta, koji se nalazi u varijabli okruženja PULUMI_NODEJS_PROJECT (ili, uopštenije, PULUMI__PROJECT для других языков).
    Ime steka koje je navedeno u varijabli okruženja PULUMI_NODEJS_STACK (ili, uopštenije, PULUMI__ STACK).
    Vaše varijable konfiguracije steka. Mogu se dobiti pomoću varijable okruženja PULUMI_CONFIG a njihov format je JSON mapa sa parovima ključ/vrijednost.

    Program će izdati upozorenja koja ukazuju da veza sa CLI/motorom nije dostupna tokom izvršavanja. Ovo je važno jer vaš program zapravo neće ništa implementirati i može biti iznenađenje ako to nije ono što ste namjeravali učiniti! Da biste rekli Pulumiju da je to upravo ono što vam treba, možete instalirati PULUMI_TEST_MODE в true.

    Zamislite da treba da navedemo naziv projekta my-ws, ime steka devi AWS Region us-west-2. Komandna linija za pokretanje Mocha testova će izgledati ovako:

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

    Ovo će nam, kao što se i očekivalo, pokazati da imamo tri pala testa!

    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

    Popravimo naš 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;
    

    I onda ponovo pokrenite testove:

    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)

    Sve je prošlo dobro... Ura! ✓✓✓

    To je sve za danas, ali o testiranju implementacije ćemo govoriti u drugom dijelu prijevoda 😉

izvor: www.habr.com

Dodajte komentar