Testování infrastruktury jako kódu s Pulumi. Část 1

Dobré odpoledne přátelé. V očekávání začátku nového toku rychlostí "Postupy a nástroje DevOps" Sdílíme s vámi nový překlad. Jít.

Testování infrastruktury jako kódu s Pulumi. Část 1

Používání Pulumi a univerzálních programovacích jazyků pro kód infrastruktury (Infrastructure as Code) poskytuje mnoho výhod: dostupnost dovedností a znalostí, odstranění standardního kódu v kódu pomocí abstrakce, nástroje známé vašemu týmu, jako jsou IDE a linters. Všechny tyto nástroje softwarového inženýrství nás nejen činí produktivnějšími, ale také zlepšují kvalitu našeho kódu. Proto je přirozené, že použití univerzálních programovacích jazyků nám umožňuje zavést další důležitou praxi vývoje softwaru - Testování.

V tomto článku se podíváme na to, jak nám Pulumi pomáhá testovat naši infrastrukturu jako kód.

Testování infrastruktury jako kódu s Pulumi. Část 1

Proč testovat infrastrukturu?

Než půjdeme do detailů, stojí za to si položit otázku: „Proč vůbec testovat infrastrukturu? Důvodů je mnoho a zde jsou některé z nich:

  • Unit testování jednotlivých funkcí nebo fragmentů logiky vašeho programu
  • Ověřuje požadovaný stav infrastruktury vůči určitým omezením.
  • Detekce běžných chyb, jako je chybějící šifrování úložiště nebo nechráněný otevřený přístup z internetu k virtuálním strojům.
  • Kontrola implementace zajišťování infrastruktury.
  • Provádění runtime testování aplikační logiky běžící ve vaší „naprogramované“ infrastruktuře pro kontrolu funkčnosti po zřízení.
  • Jak vidíme, existuje široká škála možností testování infrastruktury. Polumi má mechanismy pro testování v každém bodě tohoto spektra. Začněme a uvidíme, jak to funguje.

Testování jednotek

Programy Pulumi jsou napsány v univerzálních programovacích jazycích, jako je JavaScript, Python, TypeScript nebo Go. Proto je jim k dispozici plná síla těchto jazyků, včetně jejich nástrojů a knihoven, včetně testovacích frameworků. Pulumi je multi-cloud, což znamená, že jej lze použít pro testování od jakéhokoli poskytovatele cloudu.

(V tomto článku, přestože je vícejazyčný a multicloudový, používáme JavaScript a Mocha a zaměřujeme se na AWS. Můžete použít Python unittest, Go testovací rámec nebo jakýkoli jiný testovací rámec, který se vám líbí. A samozřejmě Pulumi funguje skvěle s Azure, Google Cloud, Kubernetes.)

Jak jsme viděli, existuje několik důvodů, proč byste mohli chtít otestovat kód infrastruktury. Jedním z nich je konvenční testování jednotek. Protože váš kód může mít funkce – například pro výpočet CIDR, dynamický výpočet jmen, značek atd. - pravděpodobně je budete chtít otestovat. Je to stejné jako psaní běžných jednotkových testů pro aplikace ve vašem oblíbeném programovacím jazyce.
Chcete-li to trochu zkomplikovat, můžete zkontrolovat, jak váš program přiděluje zdroje. Pro ilustraci si představme, že potřebujeme vytvořit jednoduchý EC2 server a chceme si být jisti následujícím:

  • Instance mají značku Name.
  • Instance by neměly používat vložený skript userData - musíme použít AMI (image).
  • Žádné SSH by nemělo být vystaveno internetu.

Tento příklad je založen na můj příklad 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;

Toto je základní program Pulumi: jednoduše přiděluje bezpečnostní skupinu EC2 a instanci. Je však třeba poznamenat, že zde porušujeme všechna tři výše uvedená pravidla. Pojďme psát testy!

Psaní testů

Obecná struktura našich testů bude vypadat jako běžné Mocha testy:

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

Nyní napíšeme náš první test: ujistěte se, že instance mají značku Name. Abychom to ověřili, jednoduše získáme objekt instance EC2 a zkontrolujeme odpovídající vlastnost 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();
                }
            });
        });

Vypadá to jako běžný test, ale s několika funkcemi, které stojí za zmínku:

  • Protože se před nasazením dotazujeme na stav prostředku, naše testy se vždy spouštějí v režimu „plánování“ (nebo „náhledu“). Existuje tedy mnoho vlastností, jejichž hodnoty jednoduše nebudou načteny nebo nebudou definovány. To zahrnuje všechny výstupní vlastnosti vypočítané vaším poskytovatelem cloudu. To je u našich testů normální – kontrolujeme pouze vstupní data. K této problematice se vrátíme později, až přijde řeč na integrační testy.
  • Protože všechny vlastnosti prostředků Pulumi jsou výstupy a mnoho z nich je vyhodnocováno asynchronně, musíme pro přístup k hodnotám použít metodu Apply. To je velmi podobné slibům a funkcím then .
  • Protože k zobrazení URN zdroje v chybové zprávě používáme několik vlastností, musíme tuto funkci použít pulumi.allkombinovat je.
  • A konečně, protože se tyto hodnoty počítají asynchronně, musíme použít vestavěnou funkci zpětného volání Mocha. done nebo vrácení slibu.

Jakmile vše nastavíme, budeme mít přístup ke vstupům jako jednoduchým hodnotám JavaScriptu. Vlastnictví tags je mapa (asociativní pole), takže se jen ujistíme, že není (1) nepravda a (2) existuje klíč pro Name. Je to velmi jednoduché a nyní můžeme testovat cokoli!

Nyní napíšeme naši druhou kontrolu. Je to ještě jednodušší:

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

A nakonec si napíšeme třetí test. To bude trochu složitější, protože hledáme přihlašovací pravidla spojená s bezpečnostní skupinou, kterých může být mnoho, a CIDR se v těchto pravidlech pohybuje, kterých může být také mnoho. Ale zvládli jsme:

    // 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 vše. Nyní provedeme testy!

Průběžné testy

Ve většině případů můžete testy spustit obvyklým způsobem pomocí testovacího rámce podle vašeho výběru. Ale je tu jedna vlastnost Pulumi, která stojí za pozornost.
Ke spouštění programů Pulumi se obvykle používá pulimi CLI (rozhraní příkazového řádku), které konfiguruje běh jazyka, řídí spouštění enginu Pulumi tak, aby bylo možné zaznamenávat operace se zdroji a zahrnout je do plánu atd. Je tu však jeden problém. Při běhu pod kontrolou vašeho testovacího rámce nedojde k žádné komunikaci mezi CLI a Pulumi enginem.

Abychom tento problém vyřešili, musíme specifikovat následující:

  • Název projektu, který je obsažen v proměnné prostředí PULUMI_NODEJS_PROJECT (nebo obecněji, PULUMI__PROJECT для других языков).
    Název zásobníku, který je zadán v proměnné prostředí PULUMI_NODEJS_STACK (nebo obecněji, PULUMI__ STACK).
    Vaše konfigurační proměnné zásobníku. Lze je získat pomocí proměnné prostředí PULUMI_CONFIG a jejich formát je mapa JSON s páry klíč/hodnota.

    Program vydá varování, že připojení k CLI/engine není během provádění dostupné. To je důležité, protože váš program ve skutečnosti nic nenasazuje a může být překvapením, pokud to není to, co jste zamýšleli udělat! Chcete-li říct Pulumi, že je to přesně to, co potřebujete, můžete nainstalovat PULUMI_TEST_MODE в true.

    Představte si, že musíme zadat název projektu my-ws, název zásobníku deva region AWS us-west-2. Příkazový řádek pro spuštění mocha testů bude vypadat takto:

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

    Pokud to uděláte, podle očekávání nám ukáže, že máme tři neúspěšné 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

    Pojďme opravit náš 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 pak spusťte testy znovu:

    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)

    Všechno šlo dobře... Hurá! ✓✓✓

    To je pro dnešek vše, ale o testování nasazení si povíme v druhé části překladu 😉

Zdroj: www.habr.com

Přidat komentář