Testiranje infrastrukture kao koda s Pulumijem. 1. dio

Dobar dan prijatelji. U iščekivanju početka novog toka po stopi "DevOps prakse i alati" Dijelimo s vama novi prijevod. Ići.

Testiranje infrastrukture kao koda s Pulumijem. 1. dio

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 putem apstrakcije, 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 kvalitetu našeg koda. Stoga je sasvim prirodno da nam korištenje programskih jezika opće namjene omogućuje uvođenje još jedne važne prakse razvoja softvera - ispitivanje.

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

Testiranje infrastrukture kao koda s Pulumijem. 1. dio

Zašto testirati infrastrukturu?

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

  • Jedinično testiranje pojedinačnih funkcija ili fragmenata vaše programske logike
  • Provjerava željeno stanje infrastrukture u odnosu na određena ograničenja.
  • Otkrivanje uobičajenih pogrešaka, poput nedostatka enkripcije spremnika za pohranu ili nezaštićenog, otvorenog pristupa s Interneta virtualnim strojevima.
  • Provjera provedbe pružanja infrastrukture.
  • Izvođenje testiranja tijekom izvođenja logike aplikacije koja se izvodi unutar vaše "programirane" infrastrukture kako bi se provjerila funkcionalnost nakon pružanja.
  • Kao što vidimo, postoji širok raspon mogućnosti testiranja infrastrukture. Polumi ima mehanizme za testiranje na svakoj točki ovog spektra. Započnimo i vidimo kako funkcionira.

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 sva moć ovih jezika, uključujući njihove alate i biblioteke, uključujući testne okvire. Pulumi je multi-cloud, što znači da se može koristiti za testiranje od bilo kojeg cloud providera.

(U ovom članku, unatoč tome što je višejezičan i multicloud, koristimo JavaScript i Mocha i fokusiramo se na AWS. Možete koristiti Python unittest, Go test framework ili bilo koji drugi test framework koji želite. I, naravno, Pulumi radi odlično s Azureom, Google Cloudom, Kubernetesom.)

Kao što smo vidjeli, postoji nekoliko razloga zašto biste mogli testirati svoj infrastrukturni kod. Jedan od njih je konvencionalno testiranje jedinica. Budući da vaš kod može imati funkcije - na primjer, izračunati CIDR, dinamički izračunati imena, oznake itd. - vjerojatno ćete ih htjeti testirati. To je isto kao da pišete obične jedinične testove za aplikacije u vašem omiljenom programskom jeziku.
Da postanemo malo kompliciraniji, možete provjeriti kako vaš program dodjeljuje resurse. Za ilustraciju, zamislimo da trebamo kreirati jednostavan EC2 poslužitelj i želimo biti sigurni u sljedeće:

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

Ovaj se primjer temelji 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. Pišimo testove!

Pisanje testova

Opća struktura naših testova izgledat će kao uobičajeni 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 imaju li instance oznaku Name. Da bismo to provjerili, jednostavno dobijemo objekt instance EC2 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 značajki vrijednih pažnje:

  • Budući da ispitujemo stanje resursa prije implementacije, naši se testovi uvijek izvode u načinu rada "plan" (ili "pretpregled"). Dakle, postoje mnoga svojstva čije vrijednosti jednostavno neće biti dohvaćene ili neće biti definirane. Ovo uključuje sva svojstva izlaza koje je izračunao vaš pružatelj usluga oblaka. To je normalno za naše testove - provjeravamo samo ulazne podatke. Ovom pitanju ćemo se vratiti kasnije, kada je riječ o integracijskim testovima.
  • Budući da su sva svojstva Pulumi resursa izlazi, a mnogi od njih se procjenjuju asinkrono, moramo koristiti metodu primjene za pristup vrijednostima. Ovo je vrlo slično obećanjima i funkciji then .
  • Budući da koristimo nekoliko svojstava za prikaz URN-a resursa u poruci pogreške, moramo koristiti funkciju pulumi.allda ih kombiniram.
  • Konačno, budući da se ove vrijednosti izračunavaju asinkrono, moramo koristiti Mochinu ugrađenu značajku asinkronog povratnog poziva done ili vraćanje obećanja.

Nakon što sve postavimo, imat ćemo pristup ulazima kao jednostavnim JavaScript vrijednostima. Vlasništvo tags je mapa (asocijativni niz), pa ćemo samo provjeriti je li (1) netočna i (2) postoji ključ za Name. Vrlo je jednostavno i sada možemo testirati bilo što!

Sada napišimo 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, napišimo treći test. Ovo će biti malo kompliciranije jer tražimo pravila prijave povezana sa sigurnosnom grupom, kojih može biti mnogo, i CIDR raspone 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. Sada pokrenimo testove!

Izvođenje 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 naredbenog retka), koje konfigurira runtime jezika, kontrolira pokretanje Pulumi motora tako da se operacije s resursima mogu zabilježiti i uključiti u plan, itd. Međutim, postoji jedan problem. Kada se izvodi pod kontrolom vašeg testnog okvira, neće biti komunikacije između CLI-ja i Pulumi motora.

Da bismo riješili ovaj problem, samo trebamo navesti sljedeće:

  • Naziv projekta koji je sadržan u varijabli okoline PULUMI_NODEJS_PROJECT (ili, općenitije, PULUMI__PROJECT для других языков).
    Naziv stoga koji je naveden u varijabli okoline PULUMI_NODEJS_STACK (ili, općenitije, PULUMI__ STACK).
    Vaše varijable konfiguracije hrpa. Mogu se dobiti pomoću varijable okruženja PULUMI_CONFIG a njihov format je JSON mapa s parovima ključ/vrijednost.

    Program će izdati upozorenja koja pokazuju da veza s CLI/motorom nije dostupna tijekom izvođenja. Ovo je važno jer vaš program zapravo neće ništa implementirati i moglo bi vas iznenaditi ako to nije ono što ste namjeravali učiniti! Da kažeš Pulumi da je to baš ono što ti treba, možeš instalirati PULUMI_TEST_MODE в true.

    Zamislite da trebamo navesti naziv projekta u my-ws, ime hrpe dev, i AWS regija us-west-2. Naredbeni redak za pokretanje Mocha testova izgledat će ovako:

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

    Učinivši ovo, kao što se i očekivalo, pokazat će nam da imamo tri neuspjela 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;
    

    Zatim ponovno 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 dobro prošlo... Hura! ✓✓✓

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

Izvor: www.habr.com

Dodajte komentar