Pagsubok sa Imprastraktura bilang Code sa Pulumi. Bahagi 1

Magandang hapon mga kaibigan. Sa pag-asa ng pagsisimula ng isang bagong daloy sa rate "Mga kasanayan at tool ng DevOps" Ibinabahagi namin sa iyo ang isang bagong pagsasalin. Pumunta ka.

Pagsubok sa Imprastraktura bilang Code sa Pulumi. Bahagi 1

Ang paggamit ng Pulumi at pangkalahatang layunin na mga programming language para sa code ng imprastraktura (Infrastructure bilang Code) ay nagbibigay ng maraming mga pakinabang: ang pagkakaroon ng mga kasanayan at kaalaman, pag-aalis ng boilerplate sa code sa pamamagitan ng abstraction, mga tool na pamilyar sa iyong koponan, tulad ng mga IDE at linter. Ang lahat ng mga tool sa software engineering na ito ay hindi lamang gumagawa sa amin na mas produktibo, ngunit pinapabuti din ang kalidad ng aming code. Samakatuwid, natural lamang na ang paggamit ng mga pangkalahatang-layunin na mga programming language ay nagpapahintulot sa amin na ipakilala ang isa pang mahalagang kasanayan sa pagbuo ng software - testing.

Sa artikulong ito, titingnan natin kung paano tayo tinutulungan ng Pulumi na subukan ang ating imprastraktura-bilang-code.

Pagsubok sa Imprastraktura bilang Code sa Pulumi. Bahagi 1

Bakit subukan ang imprastraktura?

Bago pumunta sa detalye, ito ay nagkakahalaga ng pagtatanong sa tanong na: "Bakit subukan ang imprastraktura sa lahat?" Maraming dahilan para dito at narito ang ilan sa mga ito:

  • Unit testing ng mga indibidwal na function o fragment ng logic ng iyong program
  • Bine-verify ang gustong estado ng imprastraktura laban sa ilang mga hadlang.
  • Ang pagtuklas ng mga karaniwang error, tulad ng kakulangan ng pag-encrypt ng isang storage bucket o hindi protektado, bukas na pag-access mula sa Internet patungo sa mga virtual machine.
  • Sinusuri ang pagpapatupad ng pagbibigay ng imprastraktura.
  • Pagsasagawa ng runtime testing ng application logic na tumatakbo sa loob ng iyong "naka-program" na imprastraktura upang suriin ang functionality pagkatapos ng provisioning.
  • Gaya ng nakikita natin, mayroong malawak na hanay ng mga opsyon sa pagsubok sa imprastraktura. Ang Polumi ay may mga mekanismo para sa pagsubok sa bawat punto sa spectrum na ito. Magsimula tayo at tingnan kung paano ito gumagana.

Pagsubok sa yunit

Ang mga programang Pulumi ay isinulat sa pangkalahatang layunin na mga programming language tulad ng JavaScript, Python, TypeScript o Go. Samakatuwid, ang buong kapangyarihan ng mga wikang ito, kasama ang kanilang mga tool at library, kabilang ang mga balangkas ng pagsubok, ay magagamit sa kanila. Ang Pulumi ay multi-cloud, na nangangahulugang maaari itong magamit para sa pagsubok mula sa anumang cloud provider.

(Sa artikulong ito, sa kabila ng pagiging multilingual at multicloud, gumagamit kami ng JavaScript at Mocha at tumutuon sa AWS. Maaari mong gamitin ang Python unittest, Go test framework, o anumang iba pang test framework na gusto mo. At, siyempre, mahusay na gumagana ang Pulumi sa Azure, Google Cloud, Kubernetes.)

Gaya ng nakita natin, may ilang dahilan kung bakit maaaring gusto mong subukan ang iyong code sa imprastraktura. Isa sa mga ito ay conventional unit testing. Dahil maaaring may mga function ang iyong code - halimbawa, para kalkulahin ang CIDR, dynamic na kalkulahin ang mga pangalan, tag, atbp. - malamang na gusto mong subukan ang mga ito. Ito ay kapareho ng pagsulat ng mga regular na unit test para sa mga application sa iyong paboritong programming language.
Upang maging mas kumplikado, maaari mong suriin kung paano naglalaan ang iyong programa ng mga mapagkukunan. Upang ilarawan, isipin natin na kailangan nating lumikha ng isang simpleng EC2 server at gusto nating makatiyak sa mga sumusunod:

  • May tag ang mga instance Name.
  • Ang mga pagkakataon ay hindi dapat gumamit ng inline na script userData - dapat tayong gumamit ng AMI (larawan).
  • Dapat ay walang SSH na nakalantad sa Internet.

Ang halimbawang ito ay batay sa ang aking halimbawa 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;

Ito ang pangunahing programa ng Pulumi: naglalaan lamang ito ng EC2 security group at isang instance. Gayunpaman, dapat tandaan na dito nilalabag natin ang lahat ng tatlong panuntunang nakasaad sa itaas. Sumulat tayo ng mga pagsubok!

Mga pagsusulit sa pagsulat

Ang pangkalahatang istraktura ng aming mga pagsubok ay magiging katulad ng mga regular na pagsusulit sa Mocha:

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

Ngayon ay isulat natin ang ating unang pagsubok: tiyaking may tag ang mga pagkakataon Name. Upang suriin ito, nakukuha lang namin ang EC2 instance object at suriin ang kaukulang property 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();
                }
            });
        });

Mukhang isang regular na pagsubok, ngunit may ilang mga tampok na dapat tandaan:

  • Dahil ni-query namin ang estado ng isang mapagkukunan bago ang pag-deploy, ang aming mga pagsubok ay palaging tumatakbo sa mode na "plano" (o "preview"). Kaya, maraming mga pag-aari na ang mga halaga ay hindi makukuha o hindi matutukoy. Kabilang dito ang lahat ng mga katangian ng output na kinakalkula ng iyong cloud provider. Normal ito para sa aming mga pagsubok - sinusuri lang namin ang data ng input. Babalik kami sa isyung ito sa ibang pagkakataon, pagdating sa mga pagsubok sa pagsasama.
  • Dahil ang lahat ng pag-aari ng mapagkukunan ng Pulumi ay mga output, at marami sa mga ito ay sinusuri nang asynchronously, kailangan nating gamitin ang paraan ng paglalapat upang ma-access ang mga halaga. Ito ay halos kapareho sa mga pangako at paggana then .
  • Dahil gumagamit kami ng ilang mga katangian upang ipakita ang mapagkukunang URN sa mensahe ng error, kailangan naming gamitin ang function pulumi.allupang pagsamahin ang mga ito.
  • Sa wakas, dahil ang mga halagang ito ay kinakalkula nang asynchronously, kailangan naming gamitin ang built-in na tampok na async callback ni Mocha done o pagbabalik ng pangako.

Kapag na-set up na namin ang lahat, magkakaroon kami ng access sa mga input bilang mga simpleng halaga ng JavaScript. Ari-arian tags ay isang mapa (associative array), kaya titiyakin lang namin na ito ay (1) hindi mali, at (2) mayroong isang susi para sa Name. Ito ay napaka-simple at ngayon ay maaari naming subukan ang anumang bagay!

Ngayon isulat natin ang ating pangalawang tseke. Ito ay mas simple:

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

At sa wakas, isulat natin ang ikatlong pagsubok. Ito ay magiging mas kumplikado dahil hinahanap namin ang mga panuntunan sa pag-log in na nauugnay sa pangkat ng seguridad, kung saan maaaring marami, at ang mga saklaw ng CIDR sa mga panuntunang iyon, kung saan maaari ding marami. Ngunit nagawa namin:

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

Iyon lang. Ngayon, patakbuhin natin ang mga pagsubok!

Pagpapatakbo ng mga pagsubok

Sa karamihan ng mga kaso, maaari kang magpatakbo ng mga pagsubok sa karaniwang paraan, gamit ang balangkas ng pagsubok na iyong pinili. Ngunit mayroong isang tampok ng Pulumi na dapat bigyang pansin.
Karaniwan, upang patakbuhin ang mga programang Pulumi, ginagamit ang pulimi CLI (Command Line interface), na nagko-configure sa runtime ng wika, kinokontrol ang paglulunsad ng Pulumi engine upang ang mga operasyon na may mga mapagkukunan ay maitala at maisama sa plano, atbp. Gayunpaman, mayroong isang problema. Kapag tumatakbo sa ilalim ng kontrol ng iyong test framework, walang magiging komunikasyon sa pagitan ng CLI at ng Pulumi engine.

Upang malutas ang isyung ito, kailangan lang nating tukuyin ang sumusunod:

  • Pangalan ng proyekto, na nakapaloob sa variable ng kapaligiran PULUMI_NODEJS_PROJECT (o, sa pangkalahatan, PULUMI__PROJECT для других языков).
    Ang pangalan ng stack na tinukoy sa variable ng kapaligiran PULUMI_NODEJS_STACK (o, sa pangkalahatan, PULUMI__ STACK).
    Ang iyong mga variable ng pagsasaayos ng stack. Maaari silang makuha gamit ang isang variable ng kapaligiran PULUMI_CONFIG at ang kanilang format ay JSON map na may key/value pairs.

    Magbibigay ang programa ng mga babala na nagpapahiwatig na ang koneksyon sa CLI/engine ay hindi magagamit sa panahon ng pagpapatupad. Ito ay mahalaga dahil ang iyong programa ay hindi talaga magde-deploy ng anuman at maaari itong maging isang sorpresa kung hindi iyon ang iyong nilayon na gawin! Upang sabihin kay Pulumi na ito mismo ang kailangan mo, maaari mong i-install PULUMI_TEST_MODE в true.

    Isipin na kailangan nating tukuyin ang pangalan ng proyekto sa my-ws, pangalan ng stack dev, at Rehiyon ng AWS us-west-2. Ang command line para sa pagpapatakbo ng mga pagsubok sa Mocha ay magiging ganito:

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

    Ang paggawa nito, gaya ng inaasahan, ay magpapakita sa atin na mayroon tayong tatlong nabigong pagsubok!

    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

    Ayusin natin ang ating programa:

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

    At pagkatapos ay patakbuhin muli ang mga pagsubok:

    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)

    Naging maayos ang lahat... Hurray! ✓✓✓

    Iyon lang para sa araw na ito, ngunit pag-uusapan natin ang tungkol sa pagsubok sa pag-deploy sa ikalawang bahagi ng pagsasalin 😉

Pinagmulan: www.habr.com

Magdagdag ng komento