Olá a todos!
Eu trabalho como engenheiro DevOps em um serviço de reservas de hotéis . Neste artigo, quero compartilhar nossa experiência com o teste de roles do ansible.
Na Ostrovok.ru, como gerenciador de configurações, usamos o ansible. Recentemente, chegamos à necessidade de testar os roles, mas, como se viu, existem poucas ferramentas para isso — a mais popular, talvez, seja o framework Molecule, então decidimos usá-lo. No entanto, a documentação deixa de mencionar muitas armadilhas. Não conseguimos encontrar um guia suficientemente detalhado em português, então decidimos escrever este artigo.

Molecule
— é um framework que ajuda no teste de roles do ansible.
Descrição simplificada: Molecule cria uma instância na plataforma de sua escolha (nuvem, virtual, contêiner; mais detalhes na seção ), executa sua role sobre ela, depois executa os testes e remove a instância. Se ocorrer uma falha em uma das etapas, Molecule lhe informará.
Agora, mais detalhes.
Um pouco de teoria
Vamos considerar duas entidades-chave do Molecule: Scenario e Driver.
Scenario
Um cenário contém a descrição do que, onde, como e em que sequência será executado. Uma role pode ter vários cenários, e cada um é um diretório no caminho /molecule/, que contém descrições das ações necessárias para o teste. Deve haver um cenário padrão, que será criado automaticamente se você inicializar a role com o Molecule. Os nomes dos próximos cenários são escolhidos à sua escolha.
A sequência de ações de teste em um cenário é chamada de matrix, e por padrão é a seguinte:
(As etapas marcadas ?, por padrão, são puladas, a menos que sejam descritas pelo usuário)
lint— execução de linters. Por padrão, são utilizadosyamllinteflake8,destroy— remoção de instâncias da execução anterior do Molecule (se houver),dependência? — установка ansible-зависимости тестируемой роли,syntax— verificação de sintaxe da role usandoansible-playbook --syntax-check,create— criação da instância,prepare? — подготовка инстанса; например, проверка / установка python2converge— execução do playbook em teste,idempotence— execução do playbook novamente para teste de idempotência,side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,verify— execução dos testes da configuração resultante usandotestinfra(por padrão) /goss/inspec,cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,destroy— remoção da instância.
Essa sequência cobre a maioria dos casos, mas, se necessário, pode ser alterada.
Cada uma das etapas mencionadas pode ser executada separadamente usando molecule. Mas é importante entender que cada um desses comandos de CLI pode ter sua própria sequência de ações, que pode ser encontrada executando molecule matrix. Por exemplo, ao executar o comando converge (executar o playbook em teste) serão realizadas as seguintes ações:
$ molecule matrix converge
...
└── default # nome do cenário
├── dependency # instalação de dependências
├── create # criação da instância
├── prepare # pré-configuração da instância
└── converge # execução do playbookA sequência dessas ações pode ser editada. Se algo da lista já foi realizado, será ignorado. O estado atual, bem como a configuração das instâncias, a Molecule armazena no diretório $TMPDIR/molecule//.
Adicionar etapas com ? pode ser feito descrevendo as ações desejadas no formato de playbook do ansible, e o nome do arquivo deve corresponder à etapa: prepare.yml/side_effect.yml. A Molecule esperará esses arquivos na pasta do cenário.
Driver
O driver é a entidade onde as instâncias para testes são criadas.
A lista de drivers padrão para os quais a Molecule tem modelos prontos é a seguinte: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.
Na maioria dos casos, os modelos são arquivos create.yml e destroy.yml na pasta do cenário, que descrevem a criação e a remoção da instância, respectivamente.
As exceções são Docker e Vagrant, pois a interação com seus módulos pode ocorrer sem os arquivos mencionados anteriormente.
É importante destacar o driver Delegated, pois ao usá-lo, os arquivos de criação e remoção da instância descrevem apenas o trabalho com a configuração das instâncias; o restante deve ser descrito pelo engenheiro.
O driver padrão é o Docker.
Agora, vamos para a prática e examinaremos mais detalhes lá.
Início
Como um "hello world", testaremos um simples papel de instalação do nginx. Como driver, escolheremos o Docker – creio que a maioria de vocês já o tem instalado (e lembremos que o Docker é o driver padrão).
Prepararemos virtualenv e instalaremos nele molecule:
> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker # a molecule instalará o ansible como dependência; docker para o driverO próximo passo é inicializar um novo papel.
A inicialização de um novo papel, assim como de um novo cenário, é realizada com o comando molecule init:
> molecule init role -r nginx
--> Inicializando nova função nginx...
Função inicializada em /nginx com sucesso.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars
6 diretórios, 1 arquivoCriou-se uma função típica do Ansible. Em seguida, todas as interações com a CLI do Molecule são realizadas a partir da raiz da função.
Vamos ver o que está na diretoria da função:
> tree molecule/default/
molecule/default/
├── Dockerfile.j2 # Template Jinja para Dockerfile
├── INSTALL.rst. # Algumas informações sobre a instalação das dependências do script
├── molecule.yml # Arquivo de configuração
├── playbook.yml # Playbook que executa a função
└── tests # Diretório com os testes da fase de verificação
└── test_default.py
1 diretório, 6 arquivosVamos analisar a configuração molecule/default/molecule.yml (vamos substituir apenas a imagem do docker):
---
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
platforms:
- name: instance
image: centos:7
provisioner:
name: ansible
lint:
name: ansible-lint
scenario:
name: default
verifier:
name: testinfra
lint:
name: flake8dependência
Esta seção descreve a fonte das dependências.
Opções possíveis: , , shell.
Shell é apenas um shell de comandos que é usado quando galaxy e gilt não atendem às suas necessidades.
Não vou demorar muito aqui, está suficientemente descrito em .
driver
Nome do driver. O nosso é docker.
lint
O lint utilizado é o yamllint.
As opções úteis nesta parte da configuração são a capacidade de especificar um arquivo de configuração para o yamllint, passar variáveis de ambiente ou desativar o lint:
lint:
name: yamllint
options:
config-file: foo/bar
env:
FOO: bar
enabled: Falseplatforms
Descreve a configuração das instâncias.
No caso do docker como driver, o Molecule itera sobre esta seção, e cada elemento da lista está disponível em Dockerfile.j2 como variável item.
No caso do driver, em que é obrigatório create.yml e destroy.yml, a seção está disponível neles como molecule_yml.platforms, e as iterações sobre ela já estão descritas nesses arquivos.
Uma vez que o Molecule fornece gerenciamento de instâncias para os módulos ansible, a lista de configurações possíveis deve ser encontrada lá. Para o docker, por exemplo, é utilizado o módulo . Quais módulos são usados nos outros drivers podem ser encontrados em .
E exemplos de uso de diferentes drivers podem ser encontrados .
Vamos substituir aqui centos:7 para ubuntu.
provisioner
"Fornecedor" é uma entidade que gerencia instâncias. No caso do Molecule, é o ansible, e o suporte a outros não está planejado, portanto, esta seção pode ser considerada como uma configuração expandida do ansible.
Aqui você pode indicar muitas coisas, mas eu destacaria os principais pontos, na minha opinião:
- playbooks: você pode especificar quais playbooks devem ser usados em determinadas etapas.
provisioner:
name: ansible
playbooks:
create: create.yml
destroy: ..\/default\/destroy.yml
converge: playbook.yml
side_effect: side_effect.yml
cleanup: cleanup.yml- config_options:
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
ssh_connection:
scp_if_ssh: True- connection_options: parâmetros
provisioner:
name: ansible
connection_options:
ansible_ssh_common_args: "-o 'UserKnownHostsFile=\/dev\/null' -o 'ForwardAgent=yes'"- options: parâmetros do Ansible e variáveis de ambiente
provisioner:
name: ansible
options:
vvv: true
diff: true
env:
FOO: BARscenario
Nome e descrição das sequências do cenário.
Você pode alterar a matriz de ações padrão de qualquer comando adicionando a chave <command>_sequence e definindo a lista de etapas desejada como seu valor.
Vamos supor que queremos mudar a sequência de ações ao executar o comando para rodar o playbook: molecule converge
# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
name: default
converge_sequence:
- create
- convergeverificador
Configuração do framework para testes e seu linter. Por padrão, o linter usado é testinfra e flake8. As opções possíveis são semelhantes às mencionadas acima:
verifier:
name: testinfra
additional_files_or_dirs:
- ..\/path\/to\/test_1.py
- ..\/path\/to\/test_2.py
- ..\/path\/to\/directory\/*
options:
n: 1
enabled: False
env:
FOO: bar
lint:
name: flake8
options:
benchmark: True
enabled: False
env:
FOO: barVamos voltar ao nosso papel. Vamos editar o arquivo tasks\/main.yml para o seguinte formato:
---
- name: Install nginx
apt:
name: nginx
state: present
- name: Start nginx
service:
name: nginx
state: started
E adicionaremos os testes em molecule\/default\/tests\/test_default.py
def test_nginx_is_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
def test_nginx_running_and_enabled(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
def test_nginx_config(host):
host.run("nginx -t")
Pronto, só falta executar (lembrando que estamos na raiz do papel):
> molecule testSaída longa sob o spoiler:
--> Validando o esquema /nginx/molecule/default/molecule.yml.
Validação concluída com sucesso.
--> Matriz de teste
└── padrão
├── lint
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
└── destroy
--> Cenário: 'padrão'
--> Ação: 'lint'
--> Executando Yamllint em arquivos encontrados em /nginx/...
Lint concluído com sucesso.
--> Executando Flake8 em arquivos encontrados em /nginx/molecule/default/tests/...
Lint concluído com sucesso.
--> Executando Ansible Lint em /nginx/molecule/default/playbook.yml...
Lint concluído com sucesso.
--> Cenário: 'padrão'
--> Ação: 'destroy'
JOGAR [Destruir] *****************************************************************
TAREFA [Destruir instância(s) do molecule] ********************************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Aguardar a conclusão da exclusão da(s) instância(s)] *******************************
ok: [localhost] => (item=None)
ok: [localhost]
TAREFA [Excluir rede(s) do docker] ************************************************
RESUMO DO JOGO *********************************************************************
localhost : ok=2 altered=1 unreachable=0 failed=0
--> Cenário: 'padrão'
--> Ação: 'dependency'
Ignorando, arquivo de requisitos ausente.
--> Cenário: 'padrão'
--> Ação: 'syntax'
playbook: /nginx/molecule/default/playbook.yml
--> Cenário: 'padrão'
--> Ação: 'create'
JOGAR [Criar] ******************************************************************
TAREFA [Entrar em um registro Docker] **********************************************
ignorando: [localhost] => (item=None)
TAREFA [Criar Dockerfiles a partir de nomes de imagem] *************************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Descobrir imagens Docker locais] ********************************************
ok: [localhost] => (item=None)
ok: [localhost]
TAREFA [Construir uma imagem compatível com o Ansible] ***************************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Criar rede(s) do docker] ************************************************
TAREFA [Criar instância(s) do molecule] *********************************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Aguardar a conclusão da criação da(s) instância(s)] *******************************
alterado: [localhost] => (item=None)
alterado: [localhost]
RESUMO DO JOGO *********************************************************************
localhost : ok=5 altered=4 unreachable=0 failed=0
--> Cenário: 'padrão'
--> Ação: 'prepare'
Ignorando, playbook de preparação não configurado.
--> Cenário: 'padrão'
--> Ação: 'converge'
JOGAR [Convergir] ****************************************************************
TAREFA [Coletando Fatos] *********************************************************
ok: [instância]
TAREFA [nginx : Instalar nginx] ***************************************************
alterado: [instância]
TAREFA [nginx : Iniciar nginx] *****************************************************
alterado: [instância]
RESUMO DO JOGO *********************************************************************
instância : ok=3 altered=2 unreachable=0 failed=0
--> Cenário: 'padrão'
--> Ação: 'idempotence'
Idempotência concluída com sucesso.
--> Cenário: 'padrão'
--> Ação: 'side_effect'
Ignorando, playbook de efeito colateral não configurado.
--> Cenário: 'padrão'
--> Ação: 'verify'
--> Executando testes do Testinfra encontrados em /nginx/molecule/default/tests/...
============================= a sessão de teste começa ==============================
plataforma darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
rootdir: /nginx/molecule/default, inifile:
plugins: testinfra-1.16.0
coletados 4 itens
tests/test_default.py .... [100%]
========================== 4 passaram em 27.23 segundos ===========================
Verificador concluído com sucesso.
--> Cenário: 'padrão'
--> Ação: 'destroy'
JOGAR [Destruir] *****************************************************************
TAREFA [Destruir instância(s) do molecule] ********************************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Aguardar a conclusão da exclusão da(s) instância(s)] *******************************
alterado: [localhost] => (item=None)
alterado: [localhost]
TAREFA [Excluir rede(s) do docker] ************************************************
RESUMO DO JOGO *********************************************************************
localhost : ok=2 altered=2 unreachable=0 failed=0
O nosso fácil papel foi testado sem problemas.
É importante lembrar que, se você encontrar problemas durante a execução teste de molécula, se você não alterou a sequência padrão, a Molécula irá remover a instância.
Para depuração, os seguintes comandos são úteis:
> molecule --debug # informações de depuração. Durante a execução normal, a Molécula oculta logs.
> molecule converge # Mantém a instância após a execução do papel em teste.
> molecule login # Acesse a instância criada.
> molecule --help # Lista completa de comandos.Papel existente
Adicionar um novo cenário a um papel existente é feito da diretório do papel com os seguintes comandos:
# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>Caso este seja o primeiro cenário no papel, o parâmetro -s pode ser omitido, pois um cenário será criado. padrão.
Conclusão
Como você vê, a Molécula não é muito complicada, e ao usar seus próprios templates, você pode reduzir a implantação de um novo cenário a editar variáveis em playbooks de criação e remoção de instâncias. A Molécula se integra facilmente com sistemas CI, permitindo aumentar a velocidade de desenvolvimento ao reduzir o tempo de teste manual dos playbooks.
Obrigado pela sua atenção. Se você tem experiência em testar papéis ansible que não está relacionada à Molécula — compartilhe nos comentários!
Fonte: habr.com
