Infrastructure as Code Testing with Pulumi. Part 1

Hello, friends. As we approach the start of a new flow for the course «DevOps պրակտիկաներ և գործիքներ» we're excited to share a new translation with you. Let's go.

Infrastructure as Code Testing with Pulumi. Part 1

Using Pulumi and general-purpose programming languages for Infrastructure as Code offers numerous advantages: the possession of skills and knowledge, the elimination of boilerplate code through abstraction, and familiar tools for your team such as IDEs and linters. All these software engineering tools not only make us more productive but also improve code quality. Therefore, it's quite natural that the use of general-purpose programming languages allows for the adoption of another important software development practice — testing.

In this article, we will explore how Pulumi helps to test our 'infrastructure as code.'

Infrastructure as Code Testing with Pulumi. Part 1

Why test infrastructure?

Before delving into the details, it's worth asking: 'Why test infrastructure at all?' There are many reasons for this, and here are some of them:

  • Unit testing individual functions or logic fragments of your program.
  • Checking the desired state of the infrastructure against certain constraints.
  • Identifying common errors such as missing encryption on a storage bucket or unsecured, open internet access to virtual machines.
  • Verifying the provisioning of infrastructure.
  • Performing runtime testing of application logic running within your 'programmed' infrastructure to ensure functionality after provisioning.
  • As we can see, there is a wide range of options for testing infrastructure. In Pulumi, there are mechanisms for testing at every point on this spectrum. Let's begin and see how it works.

Unit Testing

Pulumi programs are created in general-purpose programming languages such as JavaScript, Python, TypeScript, or Go. Therefore, they have access to the full power of these languages, including their toolkit and libraries, including testing frameworks. Pulumi is multi-cloud, meaning you can use any cloud providers for testing.

(In this article, despite its multilinguality and multi-cloud nature, we will use JavaScript and Mocha and focus on AWS. You can use Python unittest, the Go testing framework, or any other testing framework you prefer. And, of course, Pulumi works great with Azure, Google Cloud, Kubernetes.)

Ինչպես տեսանք, կան մի քանի պատճառներ, թե ինչու կարող է անհրաժեշտ լինել փորձարկել Ձեր ենթակառուցվածքների կոդը: Արդյունավետության սովորական մոդուլային փորձարկումը մի մասն է: Քանի որ Ձեր կոդը կարող է ունենալ ֆունկցիաներ՝ օրինակ, CIDR հաշվարկելու, անունները դինամիկ հաշվարկելու, պիտակները և այլն, դա կարող է լինել պատճառ, որ Դուք դրանք փորձարկեք: Դա նման է սովորական մոդուլային փորձարկումների գրելուց Ձեր սիրելի ծրագրավորման լեզվով:
Եթե մի փոքր ավելի բարդացնենք, մենք կարող ենք ստուգել, թե ինչպես է Ձեր ծրագիրը բաշխում ռեսուրսները: Եվ որպես illüstrasi, եկեք պատկերացնենք, որ մեզ անհրաժեշտ է ստեղծել պարզ EC2 սերվեր և մենք ուզում ենք համոզվել, որ հետևյալն է կատարվում:

  • Խոսակցական շրջադարձեր ունեն պիտակներ: Name.
  • Խոսակցական շրջադարձերը չպետք է օգտագործեն inline-script: 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-script 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 մեթոդը՝ արժեքներին հասնելու համար: Սա շատ նման է խոստումների և ֆունկցիաների օգտագործմանը: then .
  • Քանի որ մենք օգտագործում ենք մի քանի հատկություն, որպեսզի ցույց տանք ռեսուրսի URN սխալ հաղորդագրության մեջ, մեզ անհրաժեշտ է օգտագործել ֆունկցիա: pulumi.all, որպեսզի դրանք միավորենք:
  • Վերջում, քանի որ այս արժեքները հաշվի են առնվում ոչ սինխրոն կերպով, մեզ անհրաժեշտ է օգտագործել Mocha-ի կառուցվածքային ոչ սինխրոն հնարավորությունը հետադարձ կապով done կամ խոստման վերադարձով:

Մենք հաջողապես ամեն ինչ կարգավորելուց հետո, կունենանք մուտքի տվյալներ որպես պարզ JavaScript արժեքներ: Իրականություն tags է ասոցիատիվ массив, այնպես որ մենք պարզապես կվստահեն, որ դա՝ (1) falso չէ, և (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 (Command Line interface, հրամանների շարքի ինտերֆեյս) կողմից, որը կարգավորում է լեզվի runtime-ը, վերահսկում է Pulumi շարժիչի գործարկումները, որպեսզի կարողանաք արձանագրել ռեսուրսների գործողությունները և ներառել դրանք մոդելով և այլն: Սակայն կա մեկ խնդիր: Ձեր փորձարկման շրջանակի վերահսկողության տակ գործարկելու ժամանակ CLI-ի և Pulumi շարժիչի միջև կապ չի լինի:

Այս խնդիրը շրջանցելու համար մեզ պարզապես անհրաժեշտ է նշել հետևյալը:

  • Նախագծի անունը, որը պարունակվում է միջավայրի փոփոխականում PULUMI_NODEJS_PROJECT (կամ, ավելի ընդհանուր դեպքում, PULUMI__PROJECT այլ լեզուների համար).
    Կույտի անունը, որը նշվում է միջավայրի փոփոխականում PULUMI_NODEJS_STACK (կամ, ավելի ընդհանուր դեպքում, PULUMI__STACK).
    Ձեր կույտի պարամետրական փոփոխականները: Դրանք կարող են ստացվել միջավայրի փոփոխականի միջոցով PULUMI_CONFIG և դրանց ֆորմատը ներկայացնում է JSON քարտեզ զույգերով բանալ/արժեք:

    Ծրագրանբգնի տպվող ծանուցումները վկայում են, որ ընթացքի ժամանակ CLI/engine-ի հետ կապ չի լինի: Սա կարևոր է, քանի որ իրականում, ձեր ծրագիրը ոչինչ չպետք է հատկացնի, և դա կարող է լինել զարմանալի, եթե դա չի այն, ինչ ցանկանում եք անել! 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

    Անխուսափելի է, որ դա ցույց կտա մեզ, թե եռակողմ ուսումնասիրություն է: Մեր մոտ եղել է երեք անկումային թեստ!

    Ներուժ
        #ծառ     
          1) պետք է ունենա անվան պիտակ
     	 2) չի կիրառել userData (ի փոխարեն օգտագործեք AMI)
        #խումբ     
          3) չի կարելի բացել 22-ն հայտակայանի (SSH) համար
    
      0 անցնում (17 մш)
      3 ձախողված
     
     1) Ներուժ
           #ծառ
             պետք է ունենա անվան պիտակ՝  
         Ошибка: Н_missing a name tag on server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     2) Ներուժ
           #ծառ
             չի կարելի կիրառել userData (ի փոխարեն օգտագործեք AMI):
         Ошибка: Н_illegal use of userData on server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     3) Ներուժ
           #խումբ
             չի կարելի բացել 22-ն հայտակայանի (SSH) համար:
         Ошибка: Н_illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group

    Եկեք ուղղենք մեր ծրագիրը:

    "կատեգորիա";
     
    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"             
    });
     
    exports.group = group;
    exports.server = server;
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    

    Եվ նորից կվերսկսենք թեստերը:

    Ներուժ
        #ծառ
          ✓ պետք է ունենա անվան պիտակ
          ✓ չի կարելի կիրառել userData (ի փոխարեն օգտագործեք AMI)
        #խումբ
          ✓ չի կարելի բացել 22-ն հայտակայանի (SSH) համար
     
     
     3 անցում (16 մш)

    Բոլոր հարցերն անցան... Ուրախություն! ✓✓✓

    Այսօր վերջացանք, իսկ վերլուծության մասին կխոսենք թարգմանության երկրորդ մասում 😉

Ընտանիք: habr.com

Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով 🔥 Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster