Δοκιμαστική υποδομή ως κώδικας με Pulumi. Μέρος 1

Καλό απόγευμα φίλοι. Εν αναμονή της έναρξης μιας νέας ροής με το ρυθμό "Πρακτικές και εργαλεία DevOps" Μοιραζόμαστε μαζί σας μια νέα μετάφραση. Πηγαίνω.

Δοκιμαστική υποδομή ως κώδικας με Pulumi. Μέρος 1

Η χρήση Pulumi και γλωσσών προγραμματισμού γενικής χρήσης για τον κώδικα υποδομής (Infrastructure as Code) παρέχει πολλά πλεονεκτήματα: διαθεσιμότητα δεξιοτήτων και γνώσεων, εξάλειψη του boilerplate στον κώδικα μέσω αφαίρεσης, εργαλεία γνωστά στην ομάδα σας, όπως IDE και linters. Όλα αυτά τα εργαλεία μηχανικής λογισμικού όχι μόνο μας κάνουν πιο παραγωγικούς, αλλά βελτιώνουν και την ποιότητα του κώδικά μας. Επομένως, είναι φυσικό η χρήση γλωσσών προγραμματισμού γενικής χρήσης να μας επιτρέπει να εισάγουμε μια άλλη σημαντική πρακτική ανάπτυξης λογισμικού - δοκιμές.

Σε αυτό το άρθρο, θα δούμε πώς το Pulumi μας βοηθά να δοκιμάσουμε την υποδομή μας ως κώδικας.

Δοκιμαστική υποδομή ως κώδικας με Pulumi. Μέρος 1

Γιατί να δοκιμάσετε την υποδομή;

Πριν προχωρήσουμε σε λεπτομέρειες, αξίζει να θέσουμε το ερώτημα: "Γιατί να δοκιμάσουμε καθόλου την υποδομή;" Υπάρχουν πολλοί λόγοι για αυτό και εδώ είναι μερικοί από αυτούς:

  • Δοκιμή μονάδας μεμονωμένων συναρτήσεων ή τμημάτων της λογικής του προγράμματός σας
  • Επαληθεύει την επιθυμητή κατάσταση της υποδομής έναντι ορισμένων περιορισμών.
  • Εντοπισμός κοινών σφαλμάτων, όπως η έλλειψη κρυπτογράφησης ενός κάδου αποθήκευσης ή η μη προστατευμένη, ανοιχτή πρόσβαση από το Διαδίκτυο σε εικονικές μηχανές.
  • Έλεγχος υλοποίησης της παροχής υποδομής.
  • Εκτέλεση δοκιμών χρόνου εκτέλεσης της λογικής εφαρμογής που εκτελείται μέσα στην "προγραμματισμένη" υποδομή σας για έλεγχο της λειτουργικότητας μετά την παροχή.
  • Όπως μπορούμε να δούμε, υπάρχει ένα ευρύ φάσμα επιλογών δοκιμών υποδομής. Το Polumi διαθέτει μηχανισμούς για δοκιμή σε κάθε σημείο αυτού του φάσματος. Ας ξεκινήσουμε και ας δούμε πώς λειτουργεί.

Δοκιμή μονάδας

Τα προγράμματα Pulumi είναι γραμμένα σε γλώσσες προγραμματισμού γενικής χρήσης, όπως JavaScript, Python, TypeScript ή Go. Επομένως, η πλήρης ισχύς αυτών των γλωσσών, συμπεριλαμβανομένων των εργαλείων και των βιβλιοθηκών τους, συμπεριλαμβανομένων των δοκιμαστικών πλαισίων, είναι διαθέσιμη σε αυτούς. Το Pulumi είναι multi-cloud, πράγμα που σημαίνει ότι μπορεί να χρησιμοποιηθεί για δοκιμές από οποιονδήποτε πάροχο cloud.

(Σε αυτό το άρθρο, παρά το γεγονός ότι είναι πολύγλωσσο και πολυσύννεφο, χρησιμοποιούμε JavaScript και Mocha και εστιάζουμε στο AWS. Μπορείτε να χρησιμοποιήσετε την Python unittest, Go test framework ή οποιοδήποτε άλλο πλαίσιο δοκιμής σας αρέσει. Και, φυσικά, το Pulumi λειτουργεί εξαιρετικά με το Azure, το Google Cloud, το Kubernetes.)

Όπως είδαμε, υπάρχουν διάφοροι λόγοι για τους οποίους μπορεί να θέλετε να δοκιμάσετε τον κωδικό υποδομής σας. Ένα από αυτά είναι η δοκιμή συμβατικών μονάδων. Επειδή ο κώδικάς σας μπορεί να έχει λειτουργίες - για παράδειγμα, για τον υπολογισμό του CIDR, τον δυναμικό υπολογισμό ονομάτων, ετικετών κ.λπ. - μάλλον θα θέλετε να τα δοκιμάσετε. Αυτό είναι το ίδιο με τη σύνταξη κανονικών δοκιμών μονάδας για εφαρμογές στην αγαπημένη σας γλώσσα προγραμματισμού.
Για να γίνετε λίγο πιο περίπλοκοι, μπορείτε να ελέγξετε πώς το πρόγραμμά σας κατανέμει πόρους. Για παράδειγμα, ας φανταστούμε ότι πρέπει να δημιουργήσουμε έναν απλό διακομιστή EC2 και θέλουμε να είμαστε σίγουροι για τα εξής:

  • Οι περιπτώσεις έχουν μια ετικέτα Name.
  • Οι περιπτώσεις δεν πρέπει να χρησιμοποιούν ενσωματωμένη δέσμη ενεργειών 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();
                }
            });
        });

Μοιάζει με μια κανονική δοκιμή, αλλά με μερικά χαρακτηριστικά που αξίζει να σημειωθούν:

  • Επειδή ρωτάμε την κατάσταση ενός πόρου πριν από την ανάπτυξη, οι δοκιμές μας εκτελούνται πάντα σε λειτουργία "σχεδίου" (ή "προεπισκόπησης"). Έτσι, υπάρχουν πολλές ιδιότητες των οποίων οι τιμές απλά δεν θα ανακτηθούν ή δεν θα καθοριστούν. Αυτό περιλαμβάνει όλες τις ιδιότητες εξόδου που υπολογίζονται από τον πάροχο cloud. Αυτό είναι φυσιολογικό για τις δοκιμές μας - ελέγχουμε μόνο τα δεδομένα εισόδου. Θα επιστρέψουμε σε αυτό το ζήτημα αργότερα, όταν πρόκειται για δοκιμές ενοποίησης.
  • Δεδομένου ότι όλες οι ιδιότητες πόρων Pulumi είναι έξοδοι και πολλές από αυτές αξιολογούνται ασύγχρονα, πρέπει να χρησιμοποιήσουμε τη μέθοδο εφαρμογής για πρόσβαση στις τιμές. Αυτό μοιάζει πολύ με τις υποσχέσεις και τη λειτουργία then .
  • Δεδομένου ότι χρησιμοποιούμε πολλές ιδιότητες για να εμφανίσουμε το URN του πόρου στο μήνυμα σφάλματος, πρέπει να χρησιμοποιήσουμε τη συνάρτηση pulumi.allνα τα συνδυάσεις.
  • Τέλος, δεδομένου ότι αυτές οι τιμές υπολογίζονται ασύγχρονα, πρέπει να χρησιμοποιήσουμε την ενσωματωμένη δυνατότητα ασύγχρονης επανάκλησης της Mocha done ή επιστρέφοντας μια υπόσχεση.

Μόλις ρυθμίσουμε τα πάντα, θα έχουμε πρόσβαση στις εισόδους ως απλές τιμές JavaScript. Ιδιοκτησία tags είναι ένας χάρτης (συσχετικός πίνακας), οπότε απλώς θα βεβαιωθούμε ότι δεν είναι ψευδής (1) και (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 (διεπαφή γραμμής εντολών), το οποίο ρυθμίζει τον χρόνο εκτέλεσης της γλώσσας, ελέγχει την εκκίνηση του κινητήρα 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;
    

    Και μετά εκτελέστε ξανά τις δοκιμές:

    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)

    Όλα πήγαν καλά... Ούρα! ✓✓✓

    Αυτά είναι όλα για σήμερα, αλλά θα μιλήσουμε για τη δοκιμή ανάπτυξης στο δεύτερο μέρος της μετάφρασης 😉

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο