Istruzioni: come testare i ruoli Ansible e identificare i problemi prima del rilascio in produzione

Ciao a tutti!

Lavoro come ingegnere DevOps in un servizio di prenotazione alberghiera. Ostrovok.ru. In questo articolo voglio raccontare la nostra esperienza con il testing delle role Ansible.

In Ostrovok.ru, come manager della configurazione, utilizziamo Ansible. Recentemente ci siamo trovati nella necessità di testare le role, ma, a quanto pare, non esistono molti strumenti per farlo: il più popolare è senz'altro il framework Molecule, quindi abbiamo deciso di usarlo. Tuttavia, ci siamo resi conto che la sua documentazione omette molti dettagli critici. Non siamo riusciti a trovare una guida dettagliata in russo, quindi abbiamo deciso di scrivere questo articolo.

Istruzioni: come testare i ruoli Ansible e identificare i problemi prima del rilascio in produzione

Molecule

Molecule è un framework per facilitare il testing delle role Ansible.

Descrizione semplificata: Molecule crea un'istanza sulla piattaforma che hai specificato (cloud, macchina virtuale, container; per ulteriori dettagli, vedi la sezione Driver), esegue la tua role, quindi lancia i test e rimuove l'istanza. In caso di fallimento in uno dei passaggi, Molecule ti avviserà.

Ora in modo più dettagliato.

Un po' di teoria

Esaminiamo due entità chiave di Molecule: Scenario e Driver.

Scenario

Lo scenario contiene una descrizione di cosa, dove, come e in quale sequenza verrà eseguito. Un ruolo può avere più scenari, e ciascuno è una directory lungo il percorso /molecule/, contenente la descrizione delle azioni necessarie per il test. Deve essere presente uno scenario default, che verrà creato automaticamente se inizializzi il ruolo tramite Molecule. I nomi dei seguenti scenari possono essere scelti a tua discrezione.

La sequenza delle azioni di test nello scenario è chiamata matrix, e di default è la seguente:

(I passi contrassegnati ?, per impostazione predefinita vengono saltati se non descritti dall'utente)

  • lint — esecuzione dei linter. Di default si utilizzano yamllint e flake8,
  • destroy — eliminazione delle istanze dall'ultima esecuzione di Molecule (se presenti),
  • dependency? — установка ansible-зависимости тестируемой роли,
  • syntax — verifica della sintassi del ruolo con ansible-playbook --syntax-check,
  • create — creazione di un'istanza,
  • prepare? — подготовка инстанса; например, проверка / установка python2
  • converge — esecuzione del playbook da testare,
  • idempotence — ri-esecuzione del playbook per il test di idempotenza,
  • side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,
  • verify — esecuzione dei test della configurazione risultante con testinfra(per impostazione predefinita) /goss/inspec,
  • cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,
  • destroy — eliminazione dell'istanza.

Questa sequenza copre la maggior parte dei casi, ma, se necessario, può essere modificata.

Ognuno dei passaggi sopra elencati può essere eseguito separatamente utilizzando molecule. Ma è importante capire che per ciascun comando CLI potrebbero esistere sequenze di azioni proprie, che si possono scoprire eseguendo molecule matrix. Ad esempio, quando si esegue il comando converge (esecuzione del playbook in fase di test) verranno eseguite le seguenti azioni:

$ molecule matrix converge
...
└── default         # nome dello scenario
    ├── dependency  # installazione delle dipendenze
    ├── create      # creazione dell'istanza
    ├── prepare     # preparazione dell'istanza
    └── converge    # esecuzione del playbook

La sequenza di queste azioni può essere modificata. Se qualcosa della lista è già stato eseguito, verrà saltato. Lo stato attuale, così come la configurazione delle istanze, Molecule li conserva nella directory $TMPDIR/molecule//.

Aggiungere passaggi con ? è possibile descrivendo le azioni desiderate nel formato di un playbook ansible e dando al file un nome corrispondente al passaggio: prepare.yml/side_effect.yml. Molecule si aspetterà questi file nella cartella dello scenario.

Driver

Il driver è l'entità in cui vengono creati gli istanze per i test.
L'elenco dei driver standard per i quali Molecule ha pronti dei modelli è il seguente: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

Nella maggior parte dei casi, i modelli sono file create.yml e destroy.yml nella cartella degli scenari, che descrivono rispettivamente la creazione e la distruzione di un'istanza.
Le eccezioni sono Docker e Vagrant, poiché l'interazione con i loro moduli può avvenire senza i file suddetti.

Vale la pena sottolineare il driver Delegated, poiché nel caso del suo utilizzo nei file di creazione e distruzione dell'istanza è descritta solo l'interazione con la configurazione delle istanze; il resto deve essere descritto dall'ingegnere.

Il driver predefinito è Docker.

Ora passiamo alla pratica e analizziamo ulteriormente le caratteristiche lì.

Inizio

Come 'hello world', testeremo un semplice ruolo per l'installazione di nginx. Come driver sceglieremo Docker — credo sia installato per la maggior parte di voi (e ricordiamo che Docker è il driver predefinito).

Prepariamo virtualenv e installiamo al suo interno molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule installerà ansible come dipendenza; docker per il driver

Il passo successivo è inizializzare un nuovo ruolo.
L'inizializzazione di un nuovo ruolo, come quella di un nuovo scenario, avviene tramite il comando molecule init:

> molecule init role -r nginx
--> Inizializzazione del nuovo ruolo nginx...
Ruolo inizializzato in /nginx con successo.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directory, 1 file

Abbiamo ottenuto un tipico ruolo Ansible. Da questo punto, tutte le interazioni con l'interfaccia a riga di comando di Molecule avvengono dalla radice del ruolo.

Diamo un'occhiata a cosa si trova nella directory del ruolo:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Modello Jinja per Dockerfile
├── INSTALL.rst.   # Breve informazione sull'installazione delle dipendenze dello scenario
├── molecule.yml   # File di configurazione
├── playbook.yml   # Playbook per eseguire il ruolo
└── tests          # Directory con i test della fase di verifica
    └── test_default.py

1 directory, 6 file

Analizziamo il file di configurazione molecule/default/molecule.yml (sostituiamo solo l'immagine 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

dependency

Questa sezione descrive la fonte delle dipendenze.

Opzioni disponibili: galaxy, gilt, shell.

Shell è semplicemente una shell di comando utilizzata nel caso in cui galaxy e gilt non soddisfino le vostre esigenze.

Non mi soffermerò a lungo qui, è già stato ampiamente descritto in documentazione.

driver

Nome del driver. Da noi è docker.

lint

Per il linting si utilizza yamllint.

Opzioni utili in questa parte della configurazione: è possibile specificare un file di configurazione per yamllint, passare variabili d'ambiente oppure disattivare il linter:

lint:
  name: yamllint
  options:
    config-file: foo/bar
  env:
    FOO: bar
  enabled: False

piattaforme

Descrive la configurazione degli istanziamenti.
Nel caso di docker come driver, Molecule itera su questa sezione, e ogni elemento della lista è accessibile in Dockerfile.j2 come variabile item.

Nel caso del driver che richiede obbligatoriamente create.yml e destroy.yml, la sezione è accessibile in molecule_yml.platforms, e le iterazioni su di essa sono già descritte in questi file.

Poiché Molecule gestisce gli istanziamenti per i moduli ansible, anche l'elenco delle configurazioni disponibili deve essere cercato lì. Per docker, ad esempio, si usa il modulo docker_container_module. Quali moduli vengono utilizzati negli altri driver può essere trovato in documentazione.

E esempi di utilizzo di vari driver possono essere trovati nei test di Molecule stessa.

Sostituiremo qui centos:7 con ubuntu.

provisioner

«Provider» è l'entità che gestisce le istanze. Nel caso di Molecule, si tratta di ansible; non è prevista la supporto per altre, quindi questa sezione può essere considerata un'estensione della configurazione di ansible.
Qui puoi specificare molte cose. Metterò in evidenza i punti principali, a mio avviso:

  • playbooks: puoi specificare quali playbook devono essere utilizzati in determinate fasi.

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: parametri Ansible e variabili d'ambiente

provisioner:
  name: ansible  
  options:
    vvv: true
    diff: true
  env:
    FOO: BAR

scenario

Nome e descrizione delle sequenze di scenario.
Puoi modificare la matrice di azioni predefinita di un comando aggiungendo la chiave _sequence e definendo la lista di passaggi desiderata come valore per essa.
Supponiamo di voler modificare la sequenza di azioni durante l'esecuzione del comando per eseguire il playbook: molecule converge

# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
  name: default
  converge_sequence:
    - create
    - converge

verifier

Configurazione del framework per i test e il relativo linter. Di default, viene utilizzato come linter testinfra e flake8. Le opzioni possibili sono simili a quelle menzionate in precedenza:

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

Torniamo al nostro ruolo. Modifichiamo il file tasks/main.yml in questo modo:

---
- name: Installare nginx
  apt:
    name: nginx
    state: present

- name: Avviare nginx
  service:
    name: nginx
    state: started

E aggiungiamo i test in 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")

Fatto, rimane solo da eseguire (dalla radice del ruolo, ricordo):

> molecule test

Output lungo sotto spoiler:

--> Validazione dello schema /nginx/molecule/default/molecule.yml.
Convalida completata con successo.
--> Matrice di test

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

--> Scenario: 'default'
--> Azione: 'lint'
--> Esecuzione di Yamllint sui file trovati in /nginx/...
Lint completato con successo.
--> Esecuzione di Flake8 sui file trovati in /nginx/molecule/default/tests/...
Lint completato con successo.
--> Esecuzione di Ansible Lint su /nginx/molecule/default/playbook.yml...
Lint completato con successo.
--> Scenario: 'default'
--> Azione: 'destroy'

    GIOCO [Distruggi] *****************************************************************

    ATTIVITÀ [Distruggi le istanze di molecola] ********************************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Aspetta che la cancellazione dell'istanza sia completata] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    ATTIVITÀ [Elimina la rete docker] ************************************************

    RIEPILOGO GIOCO *********************************************************************
    localhost                  : ok=2    cambiato=1    irraggiungibile=0    fallito=0

--> Scenario: 'default'
--> Azione: 'dependency'
Salta, file di requisiti mancante.
--> Scenario: 'default'
--> Azione: 'syntax'

    playbook: /nginx/molecule/default/playbook.yml

--> Scenario: 'default'
--> Azione: 'create'

    GIOCO [Crea] ******************************************************************

    ATTIVITÀ [Accedi a un registro Docker] **********************************************
    salto: [localhost] => (item=None)

    ATTIVITÀ [Crea Dockerfiles dai nomi delle immagini] *************************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Scopri le immagini Docker locali] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    ATTIVITÀ [Crea un'immagine compatibile con Ansible] ***************************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Crea reti docker] ************************************************

    ATTIVITÀ [Crea istanze di molecola] *********************************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Aspetta che la creazione dell'istanza sia completata] *******************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    RIEPILOGO GIOCO *********************************************************************
    localhost                  : ok=5    cambiato=4    irraggiungibile=0    fallito=0

--> Scenario: 'default'
--> Azione: 'prepare'
Salta, playbook di preparazione non configurato.
--> Scenario: 'default'
--> Azione: 'converge'

    GIOCO [Converge] ****************************************************************

    ATTIVITÀ [Raccolta fatti] *********************************************************
    ok: [istanza]

    ATTIVITÀ [nginx : Installa nginx] ***************************************************
    cambiato: [istanza]

    ATTIVITÀ [nginx : Avvia nginx] *****************************************************
    cambiato: [istanza]

    RIEPILOGO GIOCO *********************************************************************
    istanza                   : ok=3    cambiato=2    irraggiungibile=0    fallito=0

--> Scenario: 'default'
--> Azione: 'idempotence'
Idempotenza completata con successo.
--> Scenario: 'default'
--> Azione: 'side_effect'
Salta, playbook per effetto collaterale non configurato.
--> Scenario: 'default'
--> Azione: 'verify'
--> Esecuzione dei test di Testinfra trovati in /nginx/molecule/default/tests/...
    ============================= inizio sessione test ==============================
    piattaforma darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: /nginx/molecule/default, inifile:
    plugin: testinfra-1.16.0
collettati 4 elementi

    tests/test_default.py ....                                               [100%]

    ========================== 4 superati in 27.23 secondi ===========================
Verifica completata con successo.
--> Scenario: 'default'
--> Azione: 'destroy'

    GIOCO [Distruggi] *****************************************************************

    ATTIVITÀ [Distruggi le istanze di molecola] ********************************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Aspetta che la cancellazione dell'istanza sia completata] *******************************
    cambiato: [localhost] => (item=None)
    cambiato: [localhost]

    ATTIVITÀ [Elimina la rete docker] ************************************************

    RIEPILOGO GIOCO *********************************************************************
    localhost                  : ok=2    cambiato=2    irraggiungibile=0    fallito=0

Il nostro ruolo semplice è stato testato senza problemi.
È importante ricordare che se hai problemi durante l'esecuzione molecule test, se non hai modificato la sequenza standard, Molecule eliminerà l'istanza.

Per il debug, sono utili i seguenti comandi:

> molecule --debug  # informazioni di debug. Durante l'esecuzione normale, Molecule nasconde i log.
> molecule converge          # Mantiene l'istanza dopo l'esecuzione del ruolo testato.
> molecule login             # Accedi all'istanza creata.
> molecule --help            # Lista completa dei comandi.

Ruolo esistente

L'aggiunta di un nuovo scenario a un ruolo esistente avviene dalla directory del ruolo con i seguenti comandi:

# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>

Se si tratta del primo scenario nel ruolo, il parametro -s può essere omesso, poiché verrà creato uno scenario. default.

Conclusione

Come puoi vedere, Molecule non è molto complessa e, utilizzando i propri modelli, puoi ridurre il deployment di un nuovo scenario a modificare le variabili nei playbook di creazione e rimozione delle istanze. Molecule si integra senza problemi con i sistemi CI, consentendo di aumentare la velocità di sviluppo riducendo il tempo dedicato al testing manuale dei playbook.

Grazie per la vostra attenzione. Se avete esperienza con il testing dei ruoli Ansible che non sia legata a Molecule, condividete le vostre esperienze nei commenti!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster