Table of Contents
*O autor escolheu a Diversity in Tech Fund para receber uma doação como parte do programa Write for DOnations.*
Introdução
O InSpec é um framework open-source de auditoria e teste automatizado usado para descrever e testar preocupações, recomendações ou requisitos regulatórios. Ele foi projetado para ser inteligível e independente de plataforma. Os desenvolvedores podem trabalhar com o InSpec localmente ou usando SSH, WinRM ou Docker para executar testes, portanto, é desnecessário instalar quaisquer pacotes na infraestrutura que está sendo testada.
Embora com o InSpec você possa executar testes diretamente em seus servidores, existe um potencial de erro humano que poderia causar problemas em sua infraestrutura. Para evitar esse cenário, os desenvolvedores podem usar o Kitchen para criar uma máquina virtual e instalar um sistema operacional de sua escolha nas máquinas em que os testes estão sendo executados. O Kitchen é um executor de testes, ou ferramenta de automação de teste, que permite testar o código de infraestrutura em uma ou mais plataformas isoladas. Ele também suporta muitos frameworks de teste e é flexível com uma arquitetura de plug-in de driver para várias plataformas, como Vagrant, AWS, the cloud provider, Docker, LXC containers, etc.
Neste tutorial, você escreverá testes para seus playbooks Ansible em execução em um Droplet Ubuntu 18.04 da the cloud provider. Você usará o Kitchen como executor de teste e o InSpec para escrever os testes. No final deste tutorial, você poderá testar o deploy do seu playbook Ansible.
Pré-requisitos
Antes de começar com este guia, você precisará de uma conta na the cloud provider além do seguinte:
- Uma instalação local do
Rubyem sua máquina. Você pode instalar o Ruby seguindo o tutorial para sua distribuição na série: How To Install and Set Up a Local Programming Environment for Ruby.
- O Chef Development Kit (ChefDK) instalado em sua máquina.
- Chaves SSH configuradas em sua máquina seguindo os passos 1 e 2 de How To Set Up SSH Keys. Para carregar sua chave SSH pública na sua conta the cloud provider, você pode seguir o nosso tutorial How To Add SSH Keys to a cloud account.
- Um token de acesso pessoal da the cloud provider com permissões de leitura e gravação. Certifique-se de gravar o token em um local seguro; você o usará posteriormente neste tutorial. Isso permite que você crie um Droplet na the cloud provider, que é onde este tutorial executará os testes.
Passo 1 — Configurando e Inicializando o Kitchen
Você instalou o ChefDK como parte dos pré-requisitos que vem empacotados com o kitchen. Neste passo, você configurará o Kitchen para se comunicar com a the cloud provider.
Antes de inicializar o Kitchen, você criará e se moverá para um diretório de projeto. Neste tutorial, o chamaremos de <^>ansible_testing_dir<^>.
Execute o seguinte comando para criar o diretório:
mkdir ~/<^>ansible_testing_dir<^>
E então passe para ele:
cd ~/<^>ansible_testing_dir<^>
Usando o gem instale o pacote kitchen-the cloud provider em sua máquina local. Isso permite que você diga ao kitchen para usar o driver da the cloud provider ao executar testes:
gem install kitchen-the cloud provider
No diretório do projeto, você executará o comando kitchen init especificando ansible_playbook como o provisionador e the cloud provider como o driver ao inicializar o Kitchen:
kitchen init --provisioner=ansible_playbook --driver=the cloud provider
Você verá a seguinte saída:
[secondary_label Output]
create kitchen.yml
create chefignore
create test/integration/default
Isso criou o seguinte no diretório do projeto:
test/integration/defaulté o diretório no qual você salvará seus arquivos de teste.
chefignoreé o arquivo que você usaria para garantir que certos arquivos não sejam carregados para o Chef Infra Server, mas você não o usará neste tutorial.
kitchen.ymlé o arquivo que descreve sua configuração de teste: o que você deseja testar e as plataformas de destino.
Agora, você precisa exportar suas credenciais da the cloud provider como variáveis de ambiente para ter acesso para criar Droplets a partir da sua CLI. Primeiro, inicie com seu token de acesso da the cloud provider executando o seguinte comando:
export the cloud provider_ACCESS_TOKEN="<^>SEU_TOKEN_DE_ACESSO_the cloud provider<^>"
Você também precisa obter seu número de ID da chave SSH; note que SEU_ID_DE_CHAVE_SSH_the cloud provider deve ser o ID numérico da sua chave SSH, não o nome simbólico. Usando a API da the cloud provider, você pode obter o ID numérico de suas chaves com o seguinte comando:
curl -X GET https://developer.mozilla.org/en-US/docs/Web/HTTP -H "Authorization: Bearer $the cloud provider_ACCESS_TOKEN"
Após este comando, você verá uma lista de suas chaves SSH e metadados relacionados. Leia a saída para encontrar a chave correta e identificar o número de ID nela:
[secondary_label Output]
...
{"id":<^>seu-ID-numérico<^>,"fingerprint":"<^>fingerprint<^>","public_key":"ssh-rsa <^>sua-chave-ssh<^>","name":"nome-da-sua-chave-ssh"
...
Nota: Se você deseja tornar sua saída mais legível para obter seus IDs numéricos, você pode encontrar e baixar o jq com base no seu sistema operacional na página de download do jq. Agora, você pode executar o comando anterior fazendo um pipe para o jq da seguinte maneira:
curl -X GET https://developer.mozilla.org/en-US/docs/Web/HTTP -H "Authorization: Bearer $the cloud provider_ACCESS_TOKEN" | jq
Você verá as informações da chave SSH formatadas de forma semelhante a:
[secondary_label Output]
{
"ssh_keys": [
{
<^>"id":<^> <^>ID_DA_SUA_CHAVE_SSH<^>,
"fingerprint": "2f:d0:16:6b",
"public_key": "ssh-rsa AAAAB3NzaC1yc2 example@example.local",
"name": "sannikay"
}
],
}
Depois de identificar seus IDs numéricos de SSH, exporte-os com o seguinte comando:
export the cloud provider_SSH_KEY_IDS="<^>SEU_ID_DE_CHAVE_SSH_the cloud provider<^>"
Você inicializou o kitchen e configurou as variáveis de ambiente para suas credenciais da the cloud provider. Agora você vai criar e executar testes em seus Droplets diretamente da linha de comando.
Passo 2 — Criando o Playbook Ansible
Neste passo, você criará um playbook e roles (funções) que configurará o Nginx e o Node.js no Droplet criado pelo kitchen no próximo passo. Seus testes serão executados no playbook para garantir que as condições especificadas no playbook sejam atendidas.
Para começar, crie um diretório roles para as roles ou funções Nginx e Node.js:
mkdir -p roles/{nginx,nodejs}/tasks
Isso criará uma estrutura de diretórios da seguinte maneira:
roles
├── nginx
│ └── tasks
└── nodejs
└── tasks
Agora, crie um arquivo main.yml no diretório roles/nginx/tasks usando o seu editor preferido:
nano roles/nginx/tasks/main.yml
Neste arquivo, crie uma tarefa ou task que configura e inicia o Nginx adicionando o seguinte conteúdo:
[label roles/nginx/tasks/main.yml]
---
- name: Update cache repositories and install Nginx
apt:
name: nginx
update_cache: yes
- name: Change nginx directory permission
file:
path: /etc/nginx/nginx.conf
mode: 0750
- name: start nginx
service:
name: nginx
state: started
Depois de adicionar o conteúdo, salve e saia do arquivo.
Em roles/nginx/tasks/main.yml você define uma tarefa que atualizará o repositório de cache do seu Droplet, o que equivale a executar o comando apt update manualmente em um servidor. Essa tarefa também altera as permissões do arquivo de configuração do Nginx e inicia o serviço Nginx.
Você também criará um arquivo main.yml em roles/nodejs/tasks para definir uma tarefa que configure o Node.js.
nano roles/nodejs/tasks/main.yml
Adicione as seguintes tarefas a este arquivo:
[label roles/nodejs/tasks/main.yml]
---
- name: Update caches repository
apt:
update_cache: yes
- name: Add gpg key for NodeJS LTS
apt_key:
url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
state: present
- name: Add the NodeJS LTS repo
apt_repository:
repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main"
state: present
update_cache: yes
- name: Install Node.js
apt:
name: nodejs
state: present
Salve e saia do arquivo quando terminar.
Em roles/nodejs/tasks/main.yml, você primeiro define uma tarefa que atualizará o repositório de cache do seu Droplet. Em seguida, na próxima tarefa, você adiciona a chave GPG para o Node.js, que serve como um meio de verificar a autenticidade do repositório apt do Node.js. As duas tarefas finais adicionam o repositório apt do Node.js e o instalam.
Agora você definirá suas configurações do Ansible, como variáveis, a ordem em que você deseja que suas roles sejam executadas e configurações de privilégios de superusuário. Para fazer isso, você criará um arquivo chamado playbook.yml, que serve como um entry point para o Kitchen. Quando você executa seus testes, o Kitchen inicia no seu arquivo playbook.yml e procura as roles a serem executadas, que são seus arquivos roles/nginx/tasks/main.yml e roles/nodejs/tasks/main.yml.
Execute o seguinte comando para criar o playbook.yml:
nano playbook.yml
Adicione o seguinte conteúdo ao arquivo:
[label ansible_testing_dir/playbook.yml]
---
- hosts: all
become: true
remote_user: ubuntu
vars:
NODEJS_VERSION: 8
Salve e saia do arquivo.
Você criou as roles do playbook do Ansible com as quais executará seus testes para garantir que as condições especificadas no playbook sejam atendidas.
Passo 3 — Escrevendo Seus Testes InSpec
Neste passo, você escreverá testes para verificar se o Node.js está instalado no seu Droplet. Antes de escrever seu teste, vejamos o formato de um exemplo de teste InSpec. Como em muitos frameworks de teste, o código InSpec se assemelha a uma linguagem natural. O InSpec possui dois componentes principais, o assunto a ser examinado e o estado esperado desse assunto:
[label block A]
describe '<entity>' do
it { <expectation> }
end
Em block A, as palavras-chave do e end definem um bloco ou block. A palavra-chave describe é comumente conhecida como conjuntos ou suites de testes, que contêm casos de teste. A palavra-chave it é usada para definir os casos de teste.
<entity> é o assunto que você deseja examinar, por exemplo, um nome de pacote, serviço, arquivo ou porta de rede. O <expectation> especifica o resultado desejado ou o estado esperado, por exemplo, o Nginx deve ser instalado ou deve ter uma versão específica. Você pode verificar a documentação da InSpec DSL para aprender mais sobre a linguagem InSpec.
Outro exemplo de bloco de teste InSpec:
[label block B]
control 'Pode ser qualquer coisa única' do
impact 0.7
title 'Um título inteligível'
desc 'Uma descrição opcional'
describe '<entity>' do
it { <expectation> }
end
end
A diferença entre o bloco A e o bloco B é o bloco control. O bloco control é usado como um meio de controle regulatório, recomendação ou requisito. O bloco control tem um nome; geralmente um ID único, metadados como desc, title, impact e, finalmente, agrupam blocos describe relacionados para implementar as verificações.
desc, title, e impact definem metadados que descrevem completamente a importância do controle, seu objetivo, com uma descrição sucinta e completa. impact define um valor numérico que varia de 0.0 a 1.0 onde 0.0 a <0.01 é classificado como sem impacto, 0.01 a <0.4 é classificado como baixo impacto, 0.4 a <0.7 é classificado como médio impacto, 0,7 a <0,9 é classificado como alto impacto, 0,9 a 1,0 é classificado como controle crítico.
Agora, vamos implementar um teste. Usando a sintaxe do bloco A, você usará o recurso package do InSpec para testar se o Node.js está instalado no sistema. Você irá criar um arquivo chamado sample.rb em seu diretório test/integration/default para seus testes.
Crie o sample.rb:
nano test/integration/default/sample.rb
Adicione o seguinte ao seu arquivo:
[label test/integration/default/sample.rb]
describe package('nodejs') do
it { should be_installed }
end
Aqui seu teste está usando o recurso package para verificar se o node.js está instalado.
Salve e saia do arquivo quando terminar.
Para executar este teste, você precisa editar kitchen.yml para especificar o playbook que você criou anteriormente e para adicionar às suas configurações.
Abra seu arquivo kitchen.yml:
nano ansible_testing_dir/kitchen.yml
Substitua o conteúdo de kitchen.yml com o seguinte:
[label ansible_testing_dir/kitchen.yml]
---
driver:
name: the cloud provider
provisioner:
name: ansible_playbook
hosts: test-kitchen
playbook: ./playbook.yml
verifier:
name: inspec
platforms:
- name: ubuntu-18
driver_config:
ssh_key: <^>CAMINHO_PARA_SUA_CHAVE_PRIVADA_SSH<^>
tags:
- inspec-testing
region: fra1
size: 1gb
private_networking: false
verifier:
inspec_tests:
- test/integration/default
suites:
- name: default
As opções de platform incluem o seguinte:
name: A imagem que você está usando.
driver_config: A configuração do seu Droplet da the cloud provider. Você está especificando as seguintes opções paradriver_config:
ssh_key: Caminho para<^>SUA_CHAVE_SSH_PRIVADA<^>. Sua<^>SUA_CHAVE_SSH_PRIVADA<^>está localizada no diretório que você especificou ao criar sua chavessh.
tags: As tags associadas ao seu Droplet.
region: Aregionou região onde você deseja que seu Droplet seja hospedado.
size: A memória que você deseja que seu Droplet tenha.
verifier: Isso define que o projeto contém testes InSpec.
- A parte do
inspec_testsespecifica que os testes existem no diretóriotest/integration/defaultdo projeto.
Observe que name e region usam abreviações. Você pode verificar na documentação do test-kitchen as abreviações que você pode usar.
Depois de adicionar sua configuração, salve e saia do arquivo.
Execute o comando kitchen test para executar o teste. Isso verificará se o Node.js está instalado — ele falhará propositalmente, porque você atualmente não possui a role Node.js no seu arquivo playbook.yml:
kitchen test
Você verá uma saída semelhante à seguinte:
[secondary_label Output: failing test results]
-----> Starting Kitchen (v1.24.0)
-----> Cleaning up any prior instances of <default-ubuntu-18>
-----> Destroying <default-ubuntu-18>...
the cloud provider instance <145268853> destroyed.
Finished destroying <default-ubuntu-18> (0m2.63s).
-----> Testing <default-ubuntu-18>
-----> Creating <default-ubuntu-18>...
the cloud provider instance <145273424> created.
Waiting for SSH service on 138.68.97.146:22, retrying in 3 seconds
[SSH] Established
(ssh ready)
Finished creating <default-ubuntu-18> (0m51.74s).
-----> Converging <default-ubuntu-18>...
$$$$$$ Running legacy converge for 'the cloud provider' Driver
-----> Installing Chef Omnibus to install busser to run tests
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Downloading files from <default-ubuntu-18>
Finished converging <default-ubuntu-18> (0m55.05s).
-----> Setting up <default-ubuntu-18>...
$$$$$$ Running legacy setup for 'the cloud provider' Driver
Finished setting up <default-ubuntu-18> (0m0.00s).
-----> Verifying <default-ubuntu-18>...
Loaded tests from {:path=>". ansible_testing_dir.test.integration.default"}
Profile: tests from {:path=>"ansible_testing_dir/test/integration/default"} (tests from {:path=>"ansible_testing_dir.test.integration.default"})
Version: (not specified)
Target: ssh://root@138.68.97.146:22
<^> System Package nodejs<^>
<^>× should be installed <^>
<^> expected that System Package nodejs is installed<^>
Test Summary: 0 successful, 1 failure, 0 skipped
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: 1 actions failed.
>>>>>> Verify failed on instance <default-ubuntu-18>. Please see .kitchen/logs/default-ubuntu-18.log for more details
>>>>>> ----------------------
>>>>>> Please see .kitchen/logs/kitchen.log for more details
>>>>>> Also try running `kitchen diagnose --all` for configuration
4.54s user 1.77s system 5% cpu 2:02.33 total
A saída informa que seu teste está falhando porque você não possui o Node.js instalado no Droplet que você provisionou com o kitchen. Você corrigirá seu teste adicionando a role nodejs ao seu arquivo playbook.yml e executará o teste novamente.
Edite o arquivo playbook.yml para incluir a role nodejs:
nano playbook.yml
Adicione as seguintes linhas destacadas ao seu arquivo:
[label ansible_testing_dir/playbook.yml]
---
- hosts: all
become: true
remote_user: ubuntu
vars:
NODEJS_VERSION: 8
<^>roles:<^>
<^> - nodejs<^>
Salve e feche o arquivo.
Agora, você executará novamente o teste usando o comando kitchen test:
kitchen test
Você verá a seguinte saída:
[secondary_label Output]
......
Target: ssh://root@46.101.248.71:22
System Package nodejs
✔ should be installed
Test Summary: 1 successful, 0 failures, 0 skipped
Finished verifying <default-ubuntu-18> (0m4.89s).
-----> Destroying <default-ubuntu-18>...
the cloud provider instance <145512952> destroyed.
Finished destroying <default-ubuntu-18> (0m2.23s).
Finished testing <default-ubuntu-18> (2m49.78s).
-----> Kitchen is finished. (2m55.14s)
4.86s user 1.77s system 3% cpu 2:56.58 total
Seu teste agora passa porque você tem o Node.js instalado usando a role nodejs.
Aqui está um resumo do que o Kitchen está fazendo em Test Action:
- Destrói o Droplet se ele existir
- Cria o Droplet
- Converge o Droplet
- Verifica o Droplet com o InSpec
- Destrói o Droplet
O Kitchen interromperá a execução em seu Droplet se encontrar algum problema. Isso significa que, se o seu playbook do Ansible falhar, o InSpec não será executado e o seu Droplet não será destruído. Isso permite que você inspecione o estado da instância e corrija quaisquer problemas. O comportamento da ação final de destruição pode ser substituído, se desejado. Verifique a ajuda da CLI para a flag --destroy executando o comando kitchen help test.
Você escreveu seus primeiros testes e os executou no seu playbook com uma instância falhando antes de corrigir o problema. Em seguida, você estenderá seu arquivo de teste.
Passo 4 — Adicionando Casos de Teste
Neste passo, você adicionará mais casos de teste ao seu arquivo de teste para verificar se os módulos do Nginx estão instalados no seu Droplet e se o arquivo de configuração tem as permissões corretas.
Edite seu arquivo sample.rb para adicionar mais casos de teste:
nano test/integration/default/sample.rb
Adicione os seguintes casos de teste ao final do arquivo:
[label test/integration/default/sample.rb]
. . .
control 'nginx-modules' do
impact 1.0
title 'NGINX modules'
desc 'The required NGINX modules should be installed.'
describe nginx do
its('modules') { should include 'http_ssl' }
its('modules') { should include 'stream_ssl' }
its('modules') { should include 'mail_ssl' }
end
end
control 'nginx-conf' do
impact 1.0
title 'NGINX configuration'
desc 'The NGINX config file should owned by root, be writable only by owner, and not writeable or and readable by others.'
describe file('/etc/nginx/nginx.conf') do
it { should be_owned_by 'root' }
it { should be_grouped_into 'root' }
it { should_not be_readable.by('others') }
it { should_not be_writable.by('others') }
it { should_not be_executable.by('others') }
end
end
Esses casos de teste verificam se os módulos nginx-modules no seu Droplet incluem http_ssl, stream_ssl e mail_ssl. Você também está verificando as permissões do arquivo /etc/nginx/nginx.conf.
Você está usando as palavras-chave it e its para definir seu teste. A palavra-chave its é usada apenas para acessar propriedades de resources. Por exemplo, modules é uma propriedade de nginx.
Salve e saia do arquivo depois de adicionar os casos de teste.
Agora execute o comando kitchen test para testar novamente:
kitchen test
Você verá a seguinte saída:
[secondary_label Output]
...
Target: ssh://root@104.248.131.111:22
↺ nginx-modules: NGINX modules
↺ The `nginx` binary not found in the path provided.
× nginx-conf: NGINX configuration (2 failed)
× File /etc/nginx/nginx.conf should be owned by "root"
expected `File /etc/nginx/nginx.conf.owned_by?("root")` to return true, got false
× File /etc/nginx/nginx.conf should be grouped into "root"
expected `File /etc/nginx/nginx.conf.grouped_into?("root")` to return true, got false
✔ File /etc/nginx/nginx.conf should not be readable by others
✔ File /etc/nginx/nginx.conf should not be writable by others
✔ File /etc/nginx/nginx.conf should not be executable by others
System Package nodejs
✔ should be installed
Profile Summary: 0 successful controls, 1 control failure, 1 control skipped
Test Summary: 4 successful, 2 failures, 1 skipped
Você verá que alguns dos testes estão falhando. Você irá corrigi-los adicionando a role nginx ao seu arquivo playbook e executando novamente o teste. No teste que falhou, você está verificando módulos nginx e permissões de arquivo que não estão presentes atualmente no seu servidor.
Abra seu arquivo playbook.yml:
nano ansible_testing_dir/playbook.yml
Adicione a seguinte linha destacada às suas roles:
[label ansible_testing_dir/playbook.yml]
---
- hosts: all
become: true
remote_user: ubuntu
vars:
NODEJS_VERSION: 8
roles:
- nodejs
<^>- nginx<^>
Salve e feche o arquivo quando terminar.
Em seguida, execute seus testes novamente:
kitchen test
Você verá a seguinte saída:
[secondary_label Output]
...
Target: ssh://root@104.248.131.111:22
✔ nginx-modules: NGINX version
✔ Nginx Environment modules should include "http_ssl"
✔ Nginx Environment modules should include "stream_ssl"
✔ Nginx Environment modules should include "mail_ssl"
✔ nginx-conf: NGINX configuration
✔ File /etc/nginx/nginx.conf should be owned by "root"
✔ File /etc/nginx/nginx.conf should be grouped into "root"
✔ File /etc/nginx/nginx.conf should not be readable by others
✔ File /etc/nginx/nginx.conf should not be writable by others
✔ File /etc/nginx/nginx.conf should not be executable by others
System Package nodejs
✔ should be installed
Profile Summary: 2 successful controls, 0 control failures, 0 controls skipped
Test Summary: 9 successful, 0 failures, 0 skipped
Depois de adicionar a role nginx ao playbook, todos os seus testes agora passam. A saída mostra que os módulos http_ssl, stream_ssl e mail_ssl estão instalados em seu Droplet e as permissões corretas estão definidas para o arquivo de configuração.
Quando terminar, ou não precisar mais do seu Droplet, você poderá destruí-lo executando o comando kitchen destroy para excluí-lo após executar seus testes:
kitchen destroy
Após este comando, você verá uma saída semelhante a:
[secondary_label Output]
-----> Starting Kitchen (v1.24.0)
-----> Destroying <default-ubuntu-18>...
Finished destroying <default-ubuntu-18> (0m0.00s).
-----> Kitchen is finished. (0m5.07s)
3.79s user 1.50s system 82% cpu 6.432 total
Você escreveu testes para o seu playbook, executou os testes e corrigiu os testes com falha para garantir que todos os testes sejam aprovados. Agora você está pronto para criar um ambiente virtual, escrever testes para o seu Playbook Ansible e executar seu teste no ambiente virtual usando o Kitchen.
Conclusão
Agora você tem uma base flexível para testar seu deployment Ansible, que lhe permite testar seus playbooks antes de executar em um servidor ativo. Você também pode empacotar seu teste em um perfil. Você pode usar perfis para compartilhar seu teste através do Github ou do Chef Supermarket e executá-lo facilmente em um servidor ativo.
Para detalhes mais abrangentes sobre o InSpec e o Kitchen, consulte a documentação oficial do InSpec e a documentação oficial do Kitchen.