Instruções: como testar roles do ansible e identificar problemas antes da produção

Olá a todos!

Eu trabalho como engenheiro DevOps em um serviço de reservas de hotéis Ostrovok.ru. 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.

Instruções: como testar roles do ansible e identificar problemas antes da produção

Molecule

Molecula — é 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 Driver), 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 utilizados yamllint e flake8,
  • destroy — remoção de instâncias da execução anterior do Molecule (se houver),
  • dependência? — установка ansible-зависимости тестируемой роли,
  • syntax — verificação de sintaxe da role usando ansible-playbook --syntax-check,
  • create — criação da instância,
  • prepare? — подготовка инстанса; например, проверка / установка python2
  • converge — 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 usando testinfra(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 playbook

A 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 driver

O 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 arquivo

Criou-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 arquivos

Vamos 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: flake8

dependência

Esta seção descreve a fonte das dependências.

Opções possíveis: galaxy, gilt, 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 documentação.

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: False

platforms

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 docker_container_module. Quais módulos são usados nos outros drivers podem ser encontrados em documentação.

E exemplos de uso de diferentes drivers podem ser encontrados nos testes do próprio Molecule.

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

provisioner:
  name: ansible
  config_options:
    defaults:
      fact_caching: jsonfile
    ssh_connection:
      scp_if_ssh: True

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: BAR

scenario

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
    - converge

verificador

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: bar

Vamos 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 test

Saí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

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster