Olá pessoal!
Trabalho como engenheiro de DevOps no serviço de reservas de hotĂ©is Ostrovok.ru . Neste artigo, quero falar sobre a nossa experiĂŞncia em testar funções ansĂveis.
No Ostrovok.ru, usamos o ansible como gerenciador de configuração. Recentemente, surgiu a necessidade de testar funções, mas, como se viu, não existem muitas ferramentas para isso - a estrutura Molecule é talvez a mais popular, então decidimos usá-la. Mas a documentação dele ficou em silêncio sobre muitas armadilhas. Como não conseguimos encontrar um guia suficientemente detalhado em russo, decidimos escrever este artigo.

Molécula
MolĂ©cula - uma estrutura para ajudar a testar papĂ©is ansĂveis.
Descrição simplificada: a molécula cria uma instância na plataforma especificada (nuvem, máquina virtual, contêiner; para mais detalhes, consulte a seção Driver ), executa sua função nela, executa os testes e exclui a instância. Em caso de falha em uma das etapas, a molécula informará você sobre isso.
Agora com mais detalhes.
Pouco de teoria
Considere as duas principais entidades da molécula: cenário e driver.
Cenário
O script contém uma descrição do que, onde, como e em qual sequência será executada. Uma função pode ter vários scripts e cada um é um diretório no caminho <role>/molecule/<scenario>
, que contém descrições das ações necessárias para o teste. O script default
deve estar presente, que será criado automaticamente se você inicializar a função usando a Molécula. Os nomes dos seguintes cenários são escolhidos a seu critério.
A sequĂŞncia de teste no script Ă© chamada de matriz e, por padrĂŁo, Ă© a seguinte:
(As etapas marcadas com ?
ignoradas por padrão, se não forem descritas pelo usuário)
lint
- execute o linter. Por padrĂŁo, yamllint
e flake8
,destroy
- remova instâncias do último lançamento da molécula (se deixado),dependency
? - instalação de dependência ansiável da função testada,syntax
- verifique a sintaxe de uma função usando ansible-playbook --syntax-check
,create
- cria uma instância,prepare
? - preparação da instância; por exemplo, verificação / instalação do python2converge
- lançamento do manual testado,idempotence
- reinicie o manual para o teste de idempotency,side_effect
? - ações não diretamente relacionadas ao papel, mas necessárias para testes,verify
- execute testes da configuração resultante usando testinfra
(padrĂŁo) / goss
/ inspec
,cleanup
? - (em novas versões) - grosso modo, a "limpeza" da infraestrutura externa afetada pela molécula,destroy
- excluir uma instância.
Essa sequência abrange a maioria dos casos, mas você pode alterá-la, se necessário.
Cada uma das etapas acima pode ser executada separadamente usando a molecule <command>
. Mas vale a pena entender que, para cada um desses comandos cli, pode existir sua própria sequência de ações, que pode ser reconhecida pela execução da molecule matrix <command>
. Por exemplo, quando vocĂŞ executa o comando converge
(executa o manual testado), as seguintes ações serão executadas:
$ molecule matrix converge ... └── default
A sequĂŞncia dessas ações pode ser editada. Se alguma coisa da lista já tiver sido concluĂda, ela será ignorada. O estado atual, bem como a configuração da instância, Ă© armazenado no diretĂłrio $TMPDIR/molecule/<role>/<scenario>
.
Adicionar etapas com ?
Você pode, após descrever as ações desejadas no formato do ansible-playbook, e criar o nome do arquivo de acordo com a etapa: prepare.yml
/ side_effect.yml
. A molécula aguardará esses arquivos na pasta de scripts.
Driver
Um driver é uma entidade na qual as instâncias de teste são criadas.
A lista de drivers padrão para os quais a molécula tem modelos prontos é a seguinte: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegado.
Na maioria dos casos, os modelos sĂŁo os destroy.yml
e destroy.yml
na pasta de script que descrevem a criação e a exclusão da instância, respectivamente.
Exceções são Docker e Vagrant, pois as interações com seus módulos podem ocorrer sem os arquivos acima.
Vale ressaltar o driver Delegado, pois se ele for usado nos arquivos para criar e excluir uma instância, apenas o trabalho com a configuração das instâncias será descrito, o restante deverá ser descrito pelo engenheiro.
O driver padrĂŁo Ă© o Docker.
Agora, voltamos à prática e consideramos outros recursos lá.
Introdução
Como "olá mundo", testamos o simples papel de instalar o nginx. Escolheremos o docker como o driver - acho que ele está instalado na maioria de vocês (e lembre-se de que o docker é o driver padrão).
Prepare o virtualenv
e instale a molecule
nele:
> pip install virtualenv > virtualenv -p `which python2` venv > source venv/bin/activate > pip install molecule docker
O próximo passo é inicializar uma nova função.
A inicialização de uma nova função, bem como de um novo cenário, é executada usando o comando molecule init <params>
da molecule init <params>
:
> molecule init role -r nginx --> Initializing new role nginx... Initialized role in <path>/nginx successfully. > cd nginx > tree -L 1 . ├── README.md ├── defaults ├── handlers ├── meta ├── molecule ├── tasks └── vars 6 directories, 1 file
O resultado foi um papel tĂpico e ansioso. AlĂ©m disso, todas as interações com as molĂ©culas de CLI sĂŁo feitas a partir da raiz do papel.
Vamos ver o que está no diretório de funções:
> tree molecule/default/ molecule/default/ ├── Dockerfile.j2
Vamos analisar a configuração da molecule/default/molecule.yml
(substituiremos 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: flake8
dependĂŞncia
Esta seção descreve a fonte das dependências.
Opções possĂveis: galáxia , dourado , concha.
Shell é apenas um shell de comando usado se galáxia e dourado não atenderem às suas necessidades.
Não pararei aqui por um longo tempo, está descrito o suficiente na documentação .
motorista
O nome do driver. NĂłs temos esta janela de encaixe.
cotĂŁo
Como um linter, yamllint Ă© usado.
Opções úteis nesta parte da configuração são a capacidade de especificar um arquivo de configuração para o yamllint, encaminhar variáveis ​​de ambiente ou desativar o linter:
lint: name: yamllint options: config-file: foo/bar env: FOO: bar enabled: False
Descreve a configuração de instâncias.
No caso da janela de encaixe como driver, a MolĂ©cula Ă© iterada nesta seção e cada item da lista está disponĂvel no Dockerfile.j2
como uma variável do item
.
No caso do driver, no qual create.yml
e destroy.yml
, a seção está disponĂvel neles como molecule_yml.platforms
, e as iterações já estão descritas nesses arquivos.
Como o Molecule fornece controle de instância para mĂłdulos ansible, uma lista de configurações possĂveis deve ser buscada lá. Para janela de encaixe, por exemplo, o mĂłdulo docker_container_module Ă© usado . Quais mĂłdulos sĂŁo usados ​​em outros drivers podem ser encontrados na documentação .
E também exemplos de uso de vários drivers podem ser encontrados nos testes da própria molécula .
Substitua centos: 7 no ubuntu aqui .
provisionador
"Provedor" Ă© a entidade que controla as instâncias. No caso da molĂ©cula, isso Ă© ansĂvel, o suporte a outros nĂŁo Ă© planejado; portanto, esta seção pode ser chamada com reserva de configuração estendida do ansible.
Aqui vocĂŞ pode especificar muitas coisas, vou destacar os principais, na minha opiniĂŁo, momentos:
- playbooks : você pode especificar quais playbooks devem ser usados ​​em determinados estágios.
provisioner: name: ansible playbooks: create: create.yml destroy: ../default/destroy.yml converge: playbook.yml side_effect: side_effect.yml cleanup: cleanup.yml
provisioner: name: ansible config_options: defaults: fact_caching: jsonfile ssh_connection: scp_if_ssh: True
- connection_options : parâmetros de conexão
provisioner: name: ansible connection_options: ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"
- options : opções possĂveis e variáveis ​​de ambiente
provisioner: name: ansible options: vvv: true diff: true env: FOO: BAR
cenário
O nome e a descrição das seqüências de scripts.
Você pode alterar a matriz de ação padrão de um <command>_sequence
adicionando a chave <command>_sequence
e determinando a lista de etapas que precisamos como um valor para ela.
Suponha que desejemos alterar a sequência de ações ao executar o comando playbook run: molecule converge
# : # - dependency # - create # - prepare # - converge scenario: name: default converge_sequence: - create - converge
verificador
Configurando a estrutura para testes e o link para ela. Por padrĂŁo, testinfra
e flake8
sĂŁo usados testinfra
flake8
. As opções possĂveis sĂŁo semelhantes Ă s 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: bar
Vamos voltar ao nosso papel. Edite o tasks/main.yml
para ficar assim:
--- - name: Install nginx apt: name: nginx state: present - name: Start nginx service: name: nginx state: started
E adicione os testes Ă 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")
Feito, resta apenas executar (da raiz do papel, eu lembro):
> molecule test
Escape longo sob o spoiler: --> Validating schema <path>/nginx/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy --> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in <path>/nginx/... Lint completed successfully. --> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/... Lint completed successfully. --> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml... Lint completed successfully. --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* ok: [localhost] => (item=None) ok: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file. --> Scenario: 'default' --> Action: 'syntax' playbook: <path>/nginx/molecule/default/playbook.yml --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) TASK [Create Dockerfiles from image names] ************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Discover local Docker images] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Build an Ansible compatible image] *************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Create docker network(s)] ************************************************ TASK [Create molecule instance(s)] ********************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) creation to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured. --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [instance] TASK [nginx : Install nginx] *************************************************** changed: [instance] TASK [nginx : Start nginx] ***************************************************** changed: [instance] PLAY RECAP ********************************************************************* instance : ok=3 changed=2 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully. --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured. --> Scenario: 'default' --> Action: 'verify' --> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/... ============================= test session starts ============================== platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0 rootdir: <path>/nginx/molecule/default, inifile: plugins: testinfra-1.16.0 collected 4 items tests/test_default.py .... [100%] ========================== 4 passed in 27.23 seconds =========================== Verifier completed successfully. --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0
Nosso papel simples foi testado sem problemas.
Vale lembrar que, se houve problemas durante a operação do molecule test
da molecule test
, se você não alterou a sequência padrão, a molécula excluirá a instância.
Os seguintes comandos são úteis para depuração:
> molecule --debug <command>
Função existente
A adição de um novo script a uma função existente ocorre no diretório de funções com os seguintes comandos:
Caso este seja o primeiro script na função, a -s
poderá ser omitida, pois o script default
será criado.
ConclusĂŁo
Como você pode ver, a Molécula não é muito complicada e, ao usar seus próprios modelos, você pode reduzir a implantação de um novo script para editar variáveis ​​em playbooks para criar e excluir instâncias. A molécula se integra perfeitamente aos sistemas de CI, o que permite aumentar a velocidade de desenvolvimento, reduzindo o tempo necessário para testar manualmente os manuais.
Obrigado pela atenção. Se vocĂŞ tem experiĂŞncia em testar papĂ©is ansĂveis, e nĂŁo está relacionado Ă molĂ©cula - conte-nos nos comentários!