Testando sua infraestrutura como código com Pulumi. Parte 1

Boa tarde amigos Antecipando o início de um novo fluxo no curso "DevOps Practices and Tools", estamos compartilhando com você uma nova tradução. Vamos lá



O uso de Pulumi e linguagens de programação de uso geral para Infraestrutura como código oferece muitas vantagens: ter habilidades e conhecimentos, eliminar clichês no código por meio da 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 código. Portanto, é natural que o uso de linguagens de programação de uso geral permita introduzir outra prática importante no desenvolvimento de software - o teste .

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



Por que testar a infraestrutura?


Antes de entrar em detalhes, vale a pena fazer a pergunta: "Por que precisamos testar a infraestrutura?" Há muitas razões para isso, e aqui estão algumas delas:

  • Unidade de teste de funções individuais ou peças de lógica em seu programa
  • Verifique o estado desejado da infraestrutura quanto à conformidade com certas restrições.
  • A detecção de erros comuns, como falta de criptografia de balde de armazenamento ou segurança, abre o acesso da Internet a máquinas virtuais.
  • Verificação de provisionamento de infraestrutura.
  • Executando testes em tempo de execução da lógica do aplicativo em execução na sua infraestrutura "programada" para verificar a integridade após o provisionamento.
  • Como podemos ver, há uma ampla variedade de opções de teste de infraestrutura. Polumi possui mecanismos para testar em todos os pontos deste espectro. Vamos começar e ver como funciona.

Teste de unidade


Os programas Pulumi são criados em linguagens de programação de uso geral, como JavaScript, Python, TypeScript ou Go. Portanto, o poder total dessas linguagens está disponível para eles, incluindo suas ferramentas e bibliotecas, incluindo estruturas de teste. Pulumi é multi-nuvem, o que significa a capacidade de usar qualquer provedor de nuvem para teste.

(Neste artigo, apesar de ser multilíngüe e com várias nuvens, usamos JavaScript e Mocha e focamos na AWS. Você pode usar o Python mais unittest , a estrutura de teste Go ou qualquer outra estrutura de teste que você desejar. E, é claro, a Pulumi funciona muito bem com o Azure, o Google Cloud, Kubernetes.)

Como vimos, há várias razões pelas quais você pode precisar testar seu código de infraestrutura. Um deles é o habitual teste de unidade. Como seu código pode ter funções - por exemplo, para calcular o CIDR, calcule dinamicamente nomes, tags etc. - você provavelmente quer testá-los. É o mesmo que escrever testes de unidade regulares para aplicativos em sua linguagem de programação favorita.
Se você complicar um pouco as coisas, poderá verificar como o seu programa aloca recursos. Para ilustrar, vamos imaginar que precisamos criar um servidor EC2 simples e queremos ter certeza do seguinte:

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

Este exemplo foi escrito com base no meu exemplo do 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 o grupo e a instância de segurança do EC2. No entanto, deve-se notar que aqui violamos todas as três regras estabelecidas acima. Vamos escrever testes!

Testes de escrita


A estrutura geral de nossos testes será semelhante a testes Mocha regulares:

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: verifique se as instâncias possuem uma tag Name . Para verificar isso, simplesmente obtemos o objeto de instância EC2 e verificamos a propriedade tags correspondente:

  // 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 regular, mas com alguns recursos dignos de atenção:

  • Como solicitamos o estado do recurso antes da implantação, nossos testes são sempre executados no modo "plano" (ou "visualização"). Assim, existem muitas propriedades cujos valores simplesmente não serão recebidos ou não serão determinados. Isso inclui todas as propriedades de saída calculadas seu provedor de nuvem. Para nossos testes, isso é normal - apenas verificamos os dados de entrada. Voltaremos a esse problema mais tarde, quando se trata de testes de integração.
  • Como todas as propriedades dos recursos Pulumi são "saídas" e muitas são calculadas de forma assíncrona, precisamos usar o método apply para acessar os valores. Isto é muito semelhante às promessas e then .
  • Como usamos várias propriedades para mostrar o URN do recurso na mensagem de erro, precisamos usar a função pulumi.all para combiná-las.
  • Finalmente, como esses valores são calculados de forma assíncrona, precisamos usar o recurso assíncrono interno do Mocha com um retorno de chamada done ou um retorno de promessa.

Depois de configurar tudo, teremos acesso aos dados de entrada como valores simples de JavaScript. A propriedade tags é um mapa (matriz associativa), portanto, garantiremos que (1) não seja falso e (2) haja uma chave para Name . É muito simples e agora podemos verificar qualquer coisa!

Agora vamos escrever nosso segundo cheque. Isso é 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, escreveremos o terceiro teste. Isso será um pouco mais complicado, porque estamos procurando regras de login associadas a um grupo de segurança, que pode ser muitas, e o CIDR varia nessas regras, que também podem ser muitas. Mas nós 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 executar os testes!

Executando testes


Na maioria dos casos, você pode executar testes da maneira usual usando a estrutura de teste de sua escolha. Mas há um recurso Pulumi ao qual você deve prestar atenção.
Normalmente, o Pulimi CLI (interface de linha de comando, interface de linha de comando) é usado para iniciar programas Pulumi, que configuram o tempo de execução do idioma, controlam o arranque do mecanismo Pulumi, para que seja possível registrar operações com recursos e incluí-las no plano, etc. No entanto, há um problema. Quando iniciado sob o controle de sua estrutura de teste, não haverá conexão entre a CLI e o mecanismo Pulumi.

Para contornar esse problema, basta especificar o seguinte:

  • O 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 da pilha. Eles podem ser obtidos usando a PULUMI_CONFIG ambiente PULUMI_CONFIG e seu formato é um mapa JSON com pares de chave / valor.

    O programa emitirá avisos indicando que, no tempo de execução, uma conexão com a CLI / mecanismo não está disponível. Isso é importante, porque, na realidade, seu programa não implementará nada e isso pode ser uma surpresa se não for o que você queria fazer! Para dizer à Pulumi que é exatamente isso que você precisa, você pode definir PULUMI_TEST_MODE como true .

    Imagine que precisamos especificar o nome do projeto em my-ws , o nome da pilha de desenvolvimento e a região AWS us-west-2 . A linha de comando para executar os testes do Mocha ficará assim:

     $ 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 caídos!

     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, em seguida, execute novamente os testes:

     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) 

    Tudo correu bem ... Viva! ✓ ✓ ✓

    Isso é tudo por hoje, mas falaremos sobre o teste de implantação na segunda parte da tradução ;-)

Source: https://habr.com/ru/post/pt463001/


All Articles