Testiranje infrastrukture kao koda uz pomoć Pulumija. Dio 1

Dobar dan, prijatelji. Uoči novog ciklusa tečaja „DevOps prakse i alati“ dijelimo s vama novi prijevod. Idemo.

Testiranje infrastrukture kao koda uz pomoć Pulumija. Dio 1

Korištenje Pulumi i jezika opće namjene za infrastrukturni kod (Infrastructure as Code) donosi mnoge prednosti: posjedovanje vještina i znanja, eliminaciju šablonskog koda putem apstrakcije, poznate alate vašem timu, kao što su IDE i linteri. Svi ovi alati inženjeringa softvera ne samo da nas čine produktivnijima, nego i poboljšavaju kvalitetu koda. Stoga nije iznenađujuće što korištenje jezika opće namjene omogućava uvođenje još jedne važne prakse razvoja softvera — testiranje.

U ovom članku ćemo razmotriti kako Pulumi pomaže u testiranju naše „infrastrukture kao kod“.

Testiranje infrastrukture kao koda uz pomoć Pulumija. Dio 1

Zašto testirati infrastrukturu?

Prije nego što se upustimo u detalje, vrijedi postaviti pitanje: „Zašto uopće testirati infrastrukturu?“ Ima mnogo razloga za to, a evo nekih od njih:

  • Modularno testiranje pojedinih funkcija ili dijelova logike vaše aplikacije.
  • Provjera željenog stanja infrastrukture prema određenim ograničenjima.
  • Otkrivanje uobičajenih pogrešaka, poput nedostatka enkripcije storage bucketa ili nezaštićenog, otvorenog pristupa s Interneta na virtualne mašine.
  • Provjera izvršenosti provisioninga infrastrukture.
  • Izvođenje runtime testiranja logike aplikacije koja se pokreće unutar vaše „programirane“ infrastrukture radi provjere funkcionalnosti nakon provisioninga.
  • Kako vidimo, postoji širok spektar opcija za testiranje infrastrukture. U Pulumi postoje mehanizmi za testiranje na svakoj točki tog spektra. Idemo dalje i pogledajmo kako to funkcionira.

Modularno testiranje

Pulumi programi se stvaraju na jezicima opće namjene, kao što su JavaScript, Python, TypeScript ili Go. Stoga je dostupna sva moć tih jezika, uključujući njihove alate i biblioteke, uključujući i testne okvire. Pulumi je multi-cloud, što znači mogućnost korištenja bilo kojeg cloud providera za testove.

(U ovom članku, unatoč višekratnosti jezika i multi-cloud pristupu, koristimo JavaScript i Mocha, fokusirajući se na AWS. Možete koristiti Python unittest, testni okvir Go ili bilo koji drugi omiljeni okvir za testiranje. I, naravno, Pulumi odlično radi s Azure, Google Cloud, Kubernetes.)

Kao što smo vidjeli, postoji nekoliko razloga zbog kojih može biti potrebno testirati vaš infrastrukturni kod. Jedan od njih je uobičajeno modularno testiranje. Budući da vaš kod može imati funkcije — na primjer, za izračun CIDR-a, dinamičko generiranje imena, oznaka itd. — vjerojatno ćete ih htjeti testirati. To je isto kao pisanje uobičajenih modularnih testova za aplikacije na vašem omiljenom programskom jeziku.
Ako malo zakompliciramo, možemo provjeriti kako vaša aplikacija raspodjeljuje resurse. Da bismo ilustrirali, zamislimo da trebamo stvoriti jednostavan EC2 poslužitelj i želimo biti sigurni u sljedeće:

  • Instanci imaju oznaku Name.
  • Instanci ne smiju koristiti inline-skriptu userData — moramo koristiti AMI (sliku).
  • Ne smije biti SSH-a otvorenog za internet.

Ovaj primjer je napisan inspiriran mojim primjerom 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 ], // referenca na gornji objekt grupe
    ami: "ami-c55673a0"             // AMI za us-east-2 (Ohio),
    userData: userData               // pokreće jednostavni web poslužitelj
});
 
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;

Ovo je osnovni program Pulumi: jednostavno alocira sigurnosnu grupu EC2 i instancu. Međutim, valja napomenuti da ovdje kršimo sva tri pravila navedena gore. Idemo pisati testove!

Pišemo testove

Opća struktura naših testova izgledati ć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("Infrastruktura", function() {
    let server = infra.server;
    describe("#server", function() {
        // TODO(check 1): Treba imati oznaku Name.
        // TODO(check 2): Ne smije biti inline-skripte userData.
    });
    let group = infra.group;
    describe("#group", function() {
        // TODO(check 3): Ne smije biti SSH-a otvorenog za internet.
    });
});

Sada idemo napisati naš prvi test: uvjerimo se da instanci imaju oznaku Name. Za provjeru toga jednostavno dobijemo objekt EC2 instance i provjerimo odgovarajuću 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 sa nekoliko značajnih osobina koje zaslužuju pažnju:

  • Budući da tražimo stanje resursa pre implementacije, naši testovi se uvek izvode u režimu „plan“ (ili „preview“). Tako postoji mnogo svojstava čije vrednosti jednostavno neće biti dobijene ili će biti neodređene. Ovo uključuje sve izlazne osobine koje izračunava vaš provajder u oblaku. Za naše testove to je u redu — proveravamo samo ulaze. O ovom pitanju ćemo se ponovo osvrnuti kasnije, kada dođemo do integracionih testova.
  • Budući da su sva svojstva Pulumi resursa „izlazi“, i mnogi od njih se izračunavaju asinhrono, treba da koristimo metodu apply da bismo pristupili vrednostima. Ovo je veoma slično promenljivama i funkcijama. then .
  • Budući da koristimo nekoliko svojstava za prikaz URN resursa u poruci o grešci, potrebno je da koristimo funkciju pulumi.all, kako bismo ih spojili.
  • Na kraju, budući da se ove vrednosti izračunavaju asinhrono, potrebno je da koristimo ugrađenu asinhronu mogućnost Mocha sa povratnim pozivom završeno ili vraćanjem promenljive.

Kada sve postavimo, imamo pristup ulaznim podacima kao jednostavnim JavaScript vrednostima. Osoba tags je mapa (asocijativni niz), tako da ćemo samo proveriti da je (1) ne false, i (2) da postoji ključ za Name. To je vrlo jednostavno i sada možemo proveriti sve što poželimo!

Sada hajde da napišemo naš drugi test. Ovo je još 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 složenije, jer tražimo pravila ulaska vezana za grupu bezbednosti, kojih može biti mnogo, kao i CIDR opsege u tim pravilima, kojih takođe može biti mnogo. Ali mi smo to uspeli:

    // 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 to. Sada hajde da pokrenemo testove!

Pokretanje testova

Testove možete pokrenuti na uobičajen način, koristeći vaš odabrani okvir za testiranje. Ali postoji jedna osobina Pulumi koju vredi naglasiti.
Obično se za pokretanje Pulumi programa koristi pulumi CLI (Command Line Interface), koji konfigurira runtime jezika, kontrolira pokretanje Pulumi motora kako bi se mogle zabilježiti operacije s resursima i uključiti ih u plan itd. Međutim, postoji jedan problem. Kada se pokreće pod kontrolom vašeg testnog okvira, neće biti veze između CLI-a i Pulumi motora.

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

  • Ime projekta koje se nalazi u varijabli okruženja PULUMI_NODEJS_PROJECT (ili, u općem slučaju, PULUMI__PROJECT za ostale jezike).
    Ime steka koje je navedeno u varijabli okruženja PULUMI_NODEJS_STACK (ili, u općem slučaju, PULUMI__STACK).
    Vaše varijable konfiguracije steka. Mogu se dobiti pomoću varijable okruženja PULUMI_CONFIG i njihov format predstavlja JSON mapu s parovima ključ/vrijednost.

    Program će ispisati upozorenja koja govore da tijekom izvođenja nije dostupna veza s CLI/motorom. To je važno, jer zapravo vaša aplikacija neće ništa raspoređivati i to može biti iznenađenje ako to nije ono što ste htjeli učiniti! Da biste rekli Pulumi-u da je to upravo ono što trebate, možete postaviti PULUMI_TEST_MODE u true.

    Zamislite da trebamo navesti naziv projekta u my-ws, naziv steka dev, i AWS regiju us-west-2. Komandna linija za izvođenje 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

    Izvršavanje ovoga, kako je i očekivano, 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

    Hajde da ispravimo 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 ], // referenca na objekt grupe iznad
        ami: "ami-c55673a0"             // AMI za us-east-2 (Ohio),
    });
     
    exports.group = group;
    exports.server = server;
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    

    A zatim ponovo pokrenemo testove:

    Infrastruktura
        #server
          ✓ mora imati oznaku imena
          ✓ ne smije koristiti userData (umjesto toga koristite AMI)
        #group
          ✓ ne smije otvoriti port 22 (SSH) za Internet
     
     
     3 prolazna (16ms)

    Sve je prošlo uspješno… Hura! ✓✓✓

    Danas je to sve, a o testiranju implementacije razgovarat ćemo u drugom dijelu prijevoda 😉

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster