Тестване на инфраструктурата като код с помощта на Pulumi. Част 1

Добър ден, приятели. В навечерието на старта на нов поток по курса „DevOps практики и инструменти“ споделяме с вас нов превод. Да започваме.

Тестване на инфраструктурата като код с помощта на Pulumi. Част 1

Използването на Pulumi и езици за общо предназначение за инфраструктурен код (Infrastructure as Code) предлага много предимства: наличие на умения и знания, премахване на бойлерплейт кода чрез абстракция, познати на вашия екип инструменти, като IDE и линтери. Всички тези инструменти за софтуерно инженерство не само че правят нас по-продуктивни, но и подобряват качеството на кода. Поради това, е напълно естествено, че използването на езици за общо предназначение позволява внедряване на още една важна практика в разработката на софтуер — тестиране.

В тази статия ще разгледаме как Pulumi помага за тестването на нашата „инфраструктура като код“.

Тестване на инфраструктурата като код с помощта на Pulumi. Част 1

Защо да тестваме инфраструктурата?

Преди да навлезем в подробности, трябва да зададем въпроса: „Защо изобщо да тестваме инфраструктурата?“ Има много причини за това и ето някои от тях:

  • Модулно тестване на отделни функции или фрагменти от логиката на вашата програма.
  • Проверка на желаното състояние на инфраструктурата спрямо определени ограничения.
  • Откриване на често срещани грешки, като липса на криптиране на storage bucket или незащитен, публичен достъп от интернет до виртуални машини.
  • Проверка на успешното провизиране на инфраструктурата.
  • Изпълнение на тестове по време на работа (runtime) на логиката на приложението, работещо вътре в нашата „запрограмирана“ инфраструктура, за да проверим работоспособността след провизирането.
  • Както виждаме, има широк спектър от възможности за тестване на инфраструктурата. В Pulumi съществуват механизми за тестване на всяка точка от този спектър. Нека започнем и да видим как работи.

Модулно тестване

Pulumi програмите се създават на езици за общо предназначение, като JavaScript, Python, TypeScript или Go. Затова те имат достъп до пълната мощ на тези езици, включително техните инструменти и библиотеки, включително тестови фреймворкове. Pulumi е мултиоблачно решение, което означава възможност за използване на всякакви облачни доставчици за тестване.

(В тази статия, въпреки мултиезичността и мултиоблачността, използваме JavaScript и Mocha и се ориентираме към AWS. Може да се използва Python unittest, тестовият фреймворк Go или всеки друг любим от вас тестов фреймворк. И, разбира се, Pulumi работи отлично с Azure, Google Cloud, Kubernetes.)

Както видяхме, има няколко причини, поради които може да се наложи да тествате вашия инфраструктурен код. Една от тях е обичайното модулно тестване. Тъй като вашият код може да има функции — например, за изчисляване на CIDR, динамично изчисляване на имена, тагове и т.н. — вероятно ще искате да ги тествате. Това е същото като написването на обичайни модулни тестове за приложения на любимия ви език за програмиране.
Ако малко усложним ситуацията, можем да проверим как вашата програма разпределя ресурсите. За илюстрация, нека си представим, че трябва да създадем прост EC2 сървър и искаме да се уверим в следното:

  • Инстансите имат таг Name.
  • Инстансите не трябва да използват inline скрипт userData — трябва да използваме AMI (образ).
  • Не трябва да има SSH, отворен за интернет.

Този пример е написан вдъхновен от моя пример 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 ], // референция към горния обект на групата
    ami: "ami-c55673a0"             // AMI за us-east-2 (Охайо),
    userData: userData              // стартирайте прост уеб сървър
});
 
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;

Това е основната програма Pulumi: тя просто алоцирва група за сигурност EC2 и инстанс. Обаче трябва да се отбележи, че тук нарушаваме всичките три правила, изложени по-горе. Нека започнем да пишем тестове!

Пишем тестове

Общата структура на нашите тестове ще изглежда като обичайните 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, отворен за интернет.
    });
});

Сега нека напишем нашия първи тест: да се уверим, че инстансите имат таг Name. За да проверим това, просто получаваме обект на EC2 инстанса и проверяваме съответното свойство 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();
                }
            });
        });

Изглежда като обикновен тест, но с няколко особености, които заслужават внимание:

  • Тъй като проверяваме състоянието на ресурса преди разгръщането, нашите тестове винаги се изпълняват в режим „план“ (или „предварителен преглед“). Следователно, има много свойства, стойностите на които просто няма да бъдат получени или ще бъдат неопределени. Това включва всички изходящи свойства, изчислявани от вашия облачен доставчик. За нашите тестове това е нормално – проверяваме само входните данни. По този въпрос ще се върнем по-късно, когато стигнеш до интеграционните тестове.
  • Тъй като всичките свойства на Pulumi ресурсите са „изходи” и много от тях се изчисляват асинхронно, трябва да използваме метода apply, за да получим достъп до стойностите. Това е много подобно на промиси и асинхронни функции. тогава .
  • Тъй като използваме няколко свойства, за да покажем URN на ресурса в съобщението за грешка, трябва да използваме функцията pulumi.all, за да ги комбинираме.
  • Накрая, тъй като тези стойности се изчисляват асинхронно, трябва да използваме вградената асинхронна функционалност на Mocha с обратен повик. готово или с връщане на промис.

След като всичко е настроено, ще имаме достъп до входните данни като обикновени JavaScript стойности. Свойството tags е map (асоциативен масив), така че просто ще се уверим, че е (1) не false и (2) има ключ за Name. Това е много просто и сега можем да проверим всичко, което пожелаем!

Сега нека напишем нашето второ проверка. Това е още по-лесно:

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

И накрая, нека напишем третия тест. Той ще бъде малко по-сложен, защото търсим правила за вход, свързани с групи за сигурност, които могат да бъдат много, и CIDR диапазони в тези правила, които също могат да бъдат много. Но се справихме:

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

Това е всичко. Сега нека стартираме тестовете!

Стартиране на тестове

Стартирането на тестовете в повечето случаи може да се направи по обикновения начин, използвайки избраната тестова рамка. Но има една особеност на Pulumi, на която си заслужава да се обърне внимание.
Обикновено за стартиране на Pulumi програми се използва pulumi CLI (Command Line interface, интерфейс на командния ред), който конфигурира времето на изпълнение на езика, контролира стартиранията на Pulumi движка, за да може да се фиксират операциите с ресурсите и да се включат в плана и т.н. Въпреки това, има един проблем. При стартиране под контрола на вашата тестова рамка няма да има свързаност между CLI и Pulumi движка.

За да заобиколим този проблем, трябва просто да посочим следното:

  • Името на проекта, което е зададено в променливата на средата PULUMI_NODEJS_PROJECT (или, в по-общия случай, PULUMI__PROJECT за други езици).
    Името на стека, което е зададено в променливата на средата PULUMI_NODEJS_STACK (или, в по-общия случай, PULUMI__STACK).
    Вашите променливи за конфигурация на стека. Те могат да бъдат получени чрез променливата на средата PULUMI_CONFIG и тяхният формат представлява JSON карта с двойки ключ/стойност.

    Програмата ще издаде предупреждения, говорещи за това, че по време на изпълнение няма свързаност с CLI/движка. Това е важно, защото всъщност, вашата програма няма да разгръща нищо и това може да бъде изненада, ако не е това, което искате да направите! За да кажете на Pulumi, че точно това ви е необходимо, можете да зададете PULUMI_TEST_MODE в true.

    Представете си, че трябва да зададем името на проекта в my-ws, името на стека dev, и региона на AWS us-west-2. Командният ред за изпълнение на Mocha тестовете ще изглежда по следния начин:

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

    Изпълнението на това, както и се очакваше, ще ни покаже, че имаме три провалени теста!

    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

    Нека коригираме нашата програма:

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

    А след това ще стартираме тестовете отново:

    Инфраструктура
        #сървър
          ✓ трябва да има етикет с име
          ✓ не трябва да използва userData (използвайте вместо това AMI)
        #група
          ✓ не трябва да отваря порт 22 (SSH) за интернет
     
     
     3 преминаващи (16ms)

    Всичко приключи успешно… Ура! ✓✓✓

    За днес е всичко, а за тестването на внедряването ще говорим във втората част на превода 😉

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster