Testando infraestrutura como código com Pulumi. Parte 1

Boa tarde amigos. Antecipando o início de um novo fluxo à taxa "Práticas e ferramentas DevOps" Estamos compartilhando com vocês uma nova tradução. Ir.

Testando infraestrutura como código com Pulumi. Parte 1

Usar Pulumi e linguagens de programação de uso geral para código de infraestrutura (Infraestrutura como Código) oferece muitas vantagens: disponibilidade de habilidades e conhecimentos, eliminação de clichês no código por meio de abstração, ferramentas familiares à sua equipe, como IDEs e linters. Todas essas ferramentas de engenharia de software não apenas nos tornam mais produtivos, mas também melhoram a qualidade do nosso código. Portanto, é natural que o uso de linguagens de programação de uso geral nos permita introduzir outra importante prática de desenvolvimento de software - teste.

Neste artigo, veremos como Pulumi nos ajuda a testar nossa infraestrutura como código.

Testando infraestrutura como código com Pulumi. Parte 1

Por que testar a infraestrutura?

Antes de entrar em detalhes, vale a pena perguntar: “Por que testar a infraestrutura?” Existem muitas razões para isso e aqui estão algumas delas:

  • Teste unitário de funções individuais ou fragmentos da lógica do seu programa
  • Verifica o estado desejado da infraestrutura em relação a determinadas restrições.
  • Detecção de erros comuns, como falta de criptografia de um bucket de armazenamento ou acesso aberto desprotegido da Internet para máquinas virtuais.
  • Verificar a implementação do provisionamento de infraestrutura.
  • Realização de testes em tempo de execução da lógica do aplicativo em execução dentro de sua infraestrutura “programada” para verificar a funcionalidade após o provisionamento.
  • Como podemos ver, existe uma ampla gama de opções de testes de infraestrutura. Polumi possui mecanismos de teste em todos os pontos deste espectro. Vamos começar e ver como funciona.

Teste de unidade

Os programas Pulumi são escritos em linguagens de programação de uso geral, como JavaScript, Python, TypeScript ou Go. Portanto, todo o poder dessas linguagens, incluindo suas ferramentas e bibliotecas, incluindo estruturas de teste, está disponível para elas. Pulumi é multinuvem, o que significa que pode ser usado para testes em qualquer provedor de nuvem.

(Neste artigo, apesar de ser multilíngue e multicloud, usamos JavaScript e Mocha e focamos em AWS. Você pode usar Python unittest, estrutura de teste Go ou qualquer outra estrutura de teste de sua preferência. E, claro, Pulumi funciona muito bem com Azure, Google Cloud, Kubernetes.)

Como vimos, há vários motivos pelos quais você pode querer testar seu código de infraestrutura. Um deles são os testes unitários convencionais. Porque seu código pode ter funções - por exemplo, para calcular CIDR, calcular dinamicamente nomes, tags, etc. - você provavelmente desejará testá-los. Isso é o mesmo que escrever testes unitários regulares para aplicativos em sua linguagem de programação favorita.
Para ficar um pouco mais complicado, você pode verificar como seu programa aloca recursos. Para ilustrar, vamos imaginar que precisamos criar um servidor EC2 simples e queremos ter certeza do seguinte:

  • As instâncias têm uma tag Name.
  • As instâncias não devem usar script embutido userData - devemos usar uma AMI (imagem).
  • Não deve haver SSH exposto à Internet.

Este exemplo é baseado em meu exemplo 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;

Este é o programa Pulumi básico: ele simplesmente aloca um grupo de segurança EC2 e uma instância. No entanto, deve-se notar que aqui estamos quebrando todas as três regras mencionadas acima. Vamos escrever testes!

Escrevendo testes

A estrutura geral dos nossos testes será semelhante aos testes normais do 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, открытого в Интернет.
    });
});

Agora vamos escrever nosso primeiro teste: certifique-se de que as instâncias possuem a tag Name. Para verificar isso, simplesmente pegamos o objeto da instância EC2 e verificamos a propriedade correspondente 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 um teste normal, mas com alguns recursos dignos de nota:

  • Como consultamos o estado de um recurso antes da implantação, nossos testes são sempre executados no modo “planejar” (ou “visualização”). Assim, existem muitas propriedades cujos valores simplesmente não serão recuperados ou não serão definidos. Isso inclui todas as propriedades de saída calculadas pelo seu provedor de nuvem. Isso é normal em nossos testes – verificamos apenas os dados de entrada. Voltaremos a esse assunto mais adiante, quando se tratar de testes de integração.
  • Como todas as propriedades dos recursos Pulumi são saídas e muitas delas são avaliadas de forma assíncrona, precisamos usar o método apply para acessar os valores. Isso é muito semelhante a promessas e funções then .
  • Como estamos usando diversas propriedades para mostrar o recurso URN na mensagem de erro, precisamos usar a função pulumi.allpara combiná-los.
  • Finalmente, como esses valores são calculados de forma assíncrona, precisamos usar o recurso de retorno de chamada assíncrono integrado do Mocha done ou retornando uma promessa.

Depois de configurar tudo, teremos acesso às entradas como valores JavaScript simples. Propriedade tags é um mapa (array associativo), então vamos apenas ter certeza de que (1) não é falso e (2) há uma chave para Name. É muito simples e agora podemos testar qualquer coisa!

Agora vamos preencher nosso segundo cheque. É ainda mais simples:

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

E finalmente, vamos escrever o terceiro teste. Isso será um pouco mais complicado porque estamos procurando as regras de login associadas ao grupo de segurança, que podem ser muitas, e os intervalos CIDR nessas regras, que também podem ser muitas. Mas conseguimos:

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

Isso é tudo. Agora vamos fazer os testes!

Executando testes

Na maioria dos casos, você pode executar testes da maneira usual, usando a estrutura de teste de sua preferência. Mas há uma característica do Pulumi que vale a pena prestar atenção.
Normalmente, para executar programas Pulumi, é utilizada a CLI (interface de linha de comando) pulimi, que configura o tempo de execução da linguagem, controla o lançamento do motor Pulumi para que as operações com recursos possam ser registradas e incluídas no plano, etc. No entanto, há um problema. Ao executar sob o controle de sua estrutura de teste, não haverá comunicação entre a CLI e o mecanismo Pulumi.

Para contornar esse problema, só precisamos especificar o seguinte:

  • Nome do projeto, que está contido na variável de ambiente PULUMI_NODEJS_PROJECT (ou, mais geralmente, PULUMI__PROJECT для других языков).
    O nome da pilha especificada na variável de ambiente PULUMI_NODEJS_STACK (ou, mais geralmente, PULUMI__ STACK).
    Suas variáveis ​​de configuração de pilha. Eles podem ser obtidos usando uma variável de ambiente PULUMI_CONFIG e seu formato é mapa JSON com pares chave/valor.

    O programa emitirá avisos indicando que a conexão com a CLI/mecanismo não está disponível durante a execução. Isso é importante porque seu programa não irá realmente implantar nada e pode ser uma surpresa se não for isso que você pretendia fazer! Para dizer ao Pulumi que é exatamente isso que você precisa, você pode instalar PULUMI_TEST_MODE в true.

    Imagine que precisamos especificar o nome do projeto em my-ws, nome da pilha deve região da AWS us-west-2. A linha de comando para executar testes Mocha será semelhante a esta:

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

    Fazer isso, como esperado, nos mostrará que temos três testes que falharam!

    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

    Vamos consertar nosso 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;
    

    E então execute os testes novamente:

    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)

    Correu tudo bem... Viva! ✓✓✓

    Por hoje é tudo, mas falaremos sobre testes de implantação na segunda parte da tradução 😉

Fonte: habr.com

Adicionar um comentário