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

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

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

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

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

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

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

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

  • Модулно тестване на отделни функции или фрагменти от логиката на вашата програма.
  • Проверка на желаното състояние на инфраструктурата, за да отговаря на определени ограничения.
  • Откритие на често срещани грешки, като липса на криптиране на storage bucket или незасигурен, отворен достъп от Интернет до виртуални машини.
  • Проверка на изпълнението на провиженинга на инфраструктурата.
  • Времево тестване на логиката на приложението, работещо в рамките на вашата 'програмирана' инфраструктура, за да проверите работоспособността след провиженинга.
  • Както виждаме, има широк спектър от опции за тестване на инфраструктурата. В 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 ], // 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;

Това е базова програма 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();
                }
            });
        });

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

  • Тъй като искаме да проверим състоянието на ресурса преди разгръщането, нашите тестове винаги се изпълняват в режим 'plan' (или 'preview'). По този начин има много свойства, които просто няма да бъдат получени или ще бъдат неопределени. Това включва всички изходни свойства, изчислявани от вашия облачен доставчик. За нашите тестове това е наред — ние проверяваме само входните данни. За този въпрос ще се върнем по-късно, когато стигнем до интеграционните тестове.
  • Тъй като всички свойства на 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 програми се използва pulimi CLI (интерфейс на командния ред), който конфигурира runtime на езика, управлява стартирания на Pulumi двигателя, за да могат операциите с ресурсите да се фиксират и включат в плана и т.н. Въпреки това, има един проблем. При стартиране под контрола на вашата тестова рамка, няма да има връзка между CLI и Pulumi двигателя.

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

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

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

    Представете си, че трябва да зададем названието на проекта на 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) трябва да има име на таг
     	 2) не трябва да използва userData (използвайте AMI вместо това)
        #group
          3) не трябва да отваря порт 22 (SSH) към Интернет
    
      0 минаващи (17ms)
      3 неуспешни
     
     1) Infrastructure
           #server
             трябва да има име на таг:
         Error: Липсва име на таг на сървъра
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     2) Infrastructure
           #server
             не трябва да използва userData (използвайте AMI вместо това):
         Error: Незаконно използване на userData на сървъра
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     3) Infrastructure
           #group
             не трябва да отваря порт 22 (SSH) към Интернет:
         Error: Незаконно SSH порт 22 отворен за Интернет (CIDR 0.0.0.0/0) на групата

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

    "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 ], // референция към обекта група по-горе
        ami: "ami-c55673a0"             // AMI за us-east-2 (Охайо),
    });
     
    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