Prueba de infraestructura como código con Pulumi. Parte 1

Buenas tardes amigos. En previsión del inicio de un nuevo flujo al ritmo "Prácticas y herramientas de DevOps" Compartimos con ustedes una nueva traducción. Ir.

Prueba de infraestructura como código con Pulumi. Parte 1

El uso de Pulumi y lenguajes de programación de propósito general para el código de infraestructura (Infraestructura como código) ofrece muchas ventajas: disponibilidad de habilidades y conocimientos, eliminación del texto repetitivo en el código mediante la abstracción, herramientas familiares para su equipo, como IDE y linters. Todas estas herramientas de ingeniería de software no sólo nos hacen más productivos, sino que también mejoran la calidad de nuestro código. Por lo tanto, es natural que el uso de lenguajes de programación de propósito general nos permita introducir otra práctica importante de desarrollo de software: las pruebas.

En este artículo, veremos cómo Pulumi nos ayuda a probar nuestra infraestructura como código.

Prueba de infraestructura como código con Pulumi. Parte 1

¿Por qué probar la infraestructura?

Antes de entrar en detalles, vale la pena hacerse la pregunta: "¿Por qué probar la infraestructura?" Hay muchas razones para esto y estas son algunas de ellas:

  • Pruebas unitarias de funciones individuales o fragmentos de la lógica de su programa
  • Verifica el estado deseado de la infraestructura frente a ciertas restricciones.
  • Detección de errores comunes, como falta de cifrado de un depósito de almacenamiento o acceso abierto y desprotegido desde Internet a máquinas virtuales.
  • Comprobación de la implementación del aprovisionamiento de infraestructura.
  • Realizar pruebas en tiempo de ejecución de la lógica de la aplicación que se ejecuta dentro de su infraestructura "programada" para verificar la funcionalidad después del aprovisionamiento.
  • Como podemos ver, existe una amplia gama de opciones de prueba de infraestructura. Polumi tiene mecanismos para realizar pruebas en todos los puntos de este espectro. Comencemos y veamos cómo funciona.

Examen de la unidad

Los programas Pulumi están escritos en lenguajes de programación de propósito general como JavaScript, Python, TypeScript o Go. Por lo tanto, tienen a su disposición todo el poder de estos lenguajes, incluidas sus herramientas y bibliotecas, incluidos los marcos de prueba. Pulumi es multinube, lo que significa que puede usarse para realizar pruebas desde cualquier proveedor de nube.

(En este artículo, a pesar de ser multilingüe y multinube, utilizamos JavaScript y Mocha y nos centramos en AWS. Puedes utilizar Python unittest, Vaya al marco de prueba o cualquier otro marco de prueba que desee. Y, por supuesto, Pulumi funciona muy bien con Azure, Google Cloud y Kubernetes).

Como hemos visto, existen varias razones por las que es posible que desee probar su código de infraestructura. Uno de ellos son las pruebas unitarias convencionales. Porque su código puede tener funciones, por ejemplo, calcular CIDR, calcular dinámicamente nombres, etiquetas, etc. - probablemente querrás probarlos. Esto es lo mismo que escribir pruebas unitarias regulares para aplicaciones en su lenguaje de programación favorito.
Para complicarse un poco más, puede verificar cómo su programa asigna recursos. Para ilustrar, imaginemos que necesitamos crear un servidor EC2 simple y queremos estar seguros de lo siguiente:

  • Las instancias tienen una etiqueta Name.
  • Las instancias no deben utilizar secuencias de comandos en línea userData - debemos utilizar una AMI (imagen).
  • No debería haber ningún SSH expuesto a Internet.

Este ejemplo se basa en mi ejemplo aws-js-webserver:

índice.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;

Este es el programa Pulumi básico: simplemente asigna un grupo de seguridad EC2 y una instancia. Sin embargo, cabe señalar que aquí estamos incumpliendo las tres reglas expuestas anteriormente. ¡Escribamos pruebas!

Pruebas de escritura

La estructura general de nuestras pruebas se parecerá a las pruebas normales de 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, открытого в Интернет.
    });
});

Ahora escribamos nuestra primera prueba: asegúrese de que las instancias tengan la etiqueta Name. Para verificar esto simplemente obtenemos el objeto de instancia EC2 y verificamos la propiedad correspondiente. 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();
                }
            });
        });

Parece una prueba normal, pero con algunas características dignas de mención:

  • Debido a que consultamos el estado de un recurso antes de la implementación, nuestras pruebas siempre se ejecutan en modo "plan" (o "vista previa"). Por tanto, hay muchas propiedades cuyos valores simplemente no se recuperarán o no se definirán. Esto incluye todas las propiedades de salida calculadas por su proveedor de nube. Esto es normal en nuestras pruebas: solo verificamos los datos de entrada. Volveremos a este tema más adelante, cuando se trate de pruebas de integración.
  • Dado que todas las propiedades de los recursos de Pulumi son salidas y muchas de ellas se evalúan de forma asincrónica, debemos utilizar el método de aplicación para acceder a los valores. Esto es muy similar a las promesas y una función. then .
  • Dado que estamos usando varias propiedades para mostrar la URN del recurso en el mensaje de error, necesitamos usar la función pulumi.allpara combinarlos.
  • Finalmente, dado que estos valores se calculan de forma asincrónica, debemos usar la función de devolución de llamada asíncrona incorporada de Mocha. done o devolver una promesa.

Una vez que hayamos configurado todo, tendremos acceso a las entradas como valores simples de JavaScript. Propiedad tags es un mapa (matriz asociativa), por lo que nos aseguraremos de que (1) no sea falso y (2) haya una clave para Name. ¡Es muy sencillo y ahora podemos probar cualquier cosa!

Ahora escribamos nuestro segundo cheque. Es aún más sencillo:

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

Y finalmente, escribamos la tercera prueba. Esto será un poco más complicado porque buscamos las reglas de inicio de sesión asociadas al grupo de seguridad, que pueden ser muchas, y los rangos de CIDR en esas reglas, que también pueden ser muchas. Pero logramos:

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

Eso es todo. ¡Ahora hagamos las pruebas!

Ejecución de pruebas

En la mayoría de los casos, puede ejecutar pruebas de la forma habitual, utilizando el marco de prueba de su elección. Pero hay una característica de Pulumi a la que vale la pena prestarle atención.
Normalmente, para ejecutar programas Pulumi se utiliza la CLI (interfaz de línea de comando) de pulimi, que configura el tiempo de ejecución del lenguaje, controla el lanzamiento del motor Pulumi para que las operaciones con recursos puedan registrarse e incluirse en el plan, etc. Sin embargo, hay un problema. Cuando se ejecuta bajo el control de su marco de prueba, no habrá comunicación entre la CLI y el motor Pulumi.

Para solucionar este problema, sólo necesitamos especificar lo siguiente:

  • Nombre del proyecto, que está contenido en la variable de entorno. PULUMI_NODEJS_PROJECT (o, más generalmente, PULUMI__PROJECT для других языков).
    El nombre de la pila que se especifica en la variable de entorno. PULUMI_NODEJS_STACK (o, más generalmente, PULUMI__ STACK).
    Variables de configuración de su pila. Se pueden obtener usando una variable de entorno. PULUMI_CONFIG y su formato es un mapa JSON con pares clave/valor.

    El programa emitirá advertencias indicando que la conexión a la CLI/motor no está disponible durante la ejecución. ¡Esto es importante porque su programa en realidad no implementará nada y puede ser una sorpresa si eso no es lo que pretendía hacer! Para decirle a Pulumi que esto es exactamente lo que necesitas, puedes instalar PULUMI_TEST_MODE в true.

    Imaginemos que necesitamos especificar el nombre del proyecto en my-ws, nombre de la pila devy región de AWS us-west-2. La línea de comando para ejecutar pruebas de Mocha se verá así:

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

    ¡Hacer esto, como era de esperar, nos mostrará que tenemos tres pruebas fallidas!

    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

    Arreglemos nuestro 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;
    

    Y luego ejecute las pruebas nuevamente:

    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)

    Todo salió bien... ¡Hurra! ✓ ✓ ✓

    Eso es todo por hoy, pero hablaremos sobre las pruebas de implementación en la segunda parte de la traducción 😉

Fuente: habr.com

Añadir un comentario