Instrucțiuni: cum să testăm rolurile ansible și să aflăm despre probleme înainte de producție

Salut tuturor!

Lucrez ca inginer DevOps într-un serviciu de rezervare a hotelurilor Ostrovok.ru. În acest articol, vreau să împărtășesc experiența noastră în testarea rolurilor ansible.

La Ostrovok.ru, utilizăm ansible ca manager de configurație. Recent, am ajuns la necesitatea testării rolurilor, dar, după cum s-a dovedit, nu există atât de multe instrumente pentru aceasta — cel mai popular, probabil, este cadrul Molecule, așa că am decis să-l folosim. Dar s-a dovedit că documentația sa omite multe capcane. Nu am reușit să găsim un ghid destul de detaliat în limba rusă, așa că am decis să scriem acest articol.

Instrucțiuni: cum să testăm rolurile ansible și să aflăm despre probleme înainte de producție

Molecule

Moleculă — cadrul care ajută la testarea rolurilor ansible.

Descriere simplificată: Molecula creează o instanță pe platforma pe care ați specificat-o (cloud, mașină virtuală, container; mai multe detalii în secțiunea Driver), rulează rolul dumneavoastră pe aceasta, apoi execută teste și șterge instanța. În cazul în care apare o eroare într-unul dintre pași, Molecula vă va informa despre aceasta.

Acum în detaliu.

Puțină teorie

Să analizăm două entități cheie ale Moleculei: Scenario și Driver.

Scenario

Scenariul conține descrierea a ceea ce, unde, cum și în ce ordine va fi executat. O rolă poate avea mai multe scenarii, iar fiecare este un director pe calea /molecule/, conținând descrierile acțiunilor necesare pentru test. Este obligatoriu să existe un scenariu default, care va fi creat automat dacă inițializați rolul folosind Molecula. Numele următoarelor scenarii se aleg la discreția dumneavoastră.

Secvența acțiunilor de testare într-un scenariu se numește matrix, și în mod implicit aceasta este:

(Pașii marcați ?, sunt omisi în mod implicit, dacă nu sunt descriși de utilizator)

  • lint — rularea linters. În mod implicit, se folosesc yamllint și flake8,
  • destroy — ștergerea instanțelor rămase de la ultima rulare a Moleculei (dacă există),
  • dependency? — установка ansible-зависимости тестируемой роли,
  • syntax — verificarea sintaxei rolului folosind ansible-playbook --syntax-check,
  • create — crearea instanței,
  • prepare? — подготовка инстанса; например, проверка / установка python2
  • converge — rularea playbook-ului testat,
  • idempotence — rerularea playbook-ului pentru testul de idempotentă,
  • side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,
  • verify — rularea testelor configurației obținute folosind testinfra(în mod implicit) /goss/inspec,
  • cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,
  • destroy — ștergerea instanței.

Această secvență acoperă majoritatea cazurilor, dar, dacă este necesar, poate fi modificată.

Fiecare dintre pașii menționați mai sus poate fi rulat separat folosind molecule. Dar este important de știut că pentru fiecare astfel de comandă cli poate exista o secvență specifică de acțiuni, pe care o puteți afla executând molecule matrix. De exemplu, la rularea comenzii converge (executarea playbook-ului testat) vor fi executate următoarele acțiuni:

$ molecule matrix converge
...
└── default         # numele scenariului
    ├── dependency  # instalarea dependențelor
    ├── create      # crearea instanței
    ├── prepare     # pregătirea instanței
    └── converge    # rularea playbook-ului

Secvența acestor acțiuni poate fi editată. Dacă ceva din listă a fost deja executat, va fi sărit. Starea curentă, precum și configurația instanțelor, Molecule le păstrează în directorul $TMPDIR/molecule//.

Adăugarea pașilor cu ? se poate face descriind acțiunile dorite în format de playbook ansible, iar numele fișierului să fie conform pasului: prepare.yml/side_effect.yml. Molecule va aștepta aceste fișiere în folderul scenariului.

Driver

Driverul este entitatea unde sunt create instanțele pentru teste.
Lista driverelor standard pentru care Molecule are template-uri gata este: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

În cele mai multe cazuri, template-urile sunt fișierele create.yml și destroy.yml din folderul scenariului, care descriu crearea și distrugerea instanței, respectiv.
Excepțiile sunt Docker și Vagrant, deoarece interacțiunile cu modulele lor pot avea loc fără fișierele menționate anterior.

Merită să menționăm driverul Delegated, deoarece în cazul utilizării sale, fișierele de creare și distrugere a instanței descriu doar lucrul cu configurația instanțelor, restul trebuie să fie descris de inginer.

Driverul implicit este Docker.

Acum să trecem la practică și vom discuta despre caracteristicile suplimentare acolo.

Începerea utilizării

Ca «hello world», vom testa un rol simplu pentru instalarea nginx. Ca driver, vom alege Docker – cred că este instalat la majoritatea dintre voi (și să ne amintim că Docker este driverul implicit).

Să pregătim virtualenv și să-l instalăm în acesta molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule va instala ansible ca dependență; docker pentru driver

Următorul pas este să inițializăm un rol nou.
Inițializarea unui nou rol, la fel ca și a unui nou scenariu, se face folosind comanda molecule init:

> molecule init role -r nginx
--> Inițializare nou rol nginx...
Rol inițializat cu succes în /nginx.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directoare, 1 fișier

Am obținut un rol tipic ansible. În continuare, toate interacțiunile cu CLI Molecule se fac din rădăcina rolului.

Hai să vedem ce se află în directorul rolului:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Șablon Jinja pentru Dockerfile
├── INSTALL.rst.   # Informații despre instalarea dependențelor scenariului
├── molecule.yml   # Fișier de configurare
├── playbook.yml   # Playbook pentru rularea rolului
└── tests          # Director cu teste pentru etapa de confirmare
    └── test_default.py

1 director, 6 fișiere

Să analizăm configurația molecule/default/molecule.yml (vom schimba doar imaginea 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

Această secțiune descrie sursa de dependențe.

Posibile variante: galaxy, gilt, shell.

Shell-ul este pur și simplu un terminal de comandă care este folosit în cazul în care galaxy și gilt nu acoperă nevoile tale.

Nu voi insista prea mult aici, este suficient descris în documentation.

driver

Numele driverului. La noi este docker.

lint

Pentru linters se folosește yamllint.

Opțiunile utile în această parte a configurației sunt posibilitatea de a specifica un fișier de configurare pentru yamllint, de a trece variabile de mediu sau de a dezactiva linter-ul:

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

platforms

Descrie configurația instanțelor.
În cazul docker-ului ca driver, Molecule iterează prin această secțiune, iar fiecare element din listă este disponibil în Dockerfile.j2 ca variabilă item.

În cazul driverului, în care este obligatorie create.yml și destroy.yml, secțiunea este disponibilă în ele ca molecule_yml.platforms, iar iterațiile prin ea sunt deja descrise în aceste fișiere.

Deoarece Molecule oferă gestionarea instanțelor modulului ansible, lista posibilelor configurații trebuie căutată acolo. De exemplu, pentru docker se folosește modulul docker_container_module. Modulele utilizate în celelalte drivere pot fi găsite în documentation.

De asemenea, exemple de utilizare a diferitelor drivere pot fi găsite în testele Moleculei.

Să înlocuim aici centos:7 pe ubuntu.

provisioner

„Furnizorul“ este o entitate care gestionează instanțele. În cazul Moleculei, acesta este ansible, suportul pentru altele nu este planificat, așa că această secțiune poate fi numită, cu o rezervă, configurație extinsă a ansible.
Aici se pot menționa multe lucruri, voi evidenția principalele, din punctul meu de vedere:

  • playbooks: se pot specifica ce playbook-uri trebuie utilizate în anumite etape.

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: parametrii Ansible și variabile de mediu

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

scenario

Numele și descrierea secvențelor de scenariu.
Se poate schimba matricea de acțiuni implicit a unei comenzi adăugând cheia <command>_sequence și ca valoare pentru aceasta definind lista de pași necesară.
De exemplu, dorim să modificăm secvența de acțiuni când se execută comanda de rulare a playbook-ului: molecule converge

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

verifier

Configurarea framework-ului pentru teste și linternă pentru acesta. În mod implicit, se utilizează ca linternă testinfra și flake8. Opțiunile posibile sunt asemănătoare cu cele menționate mai sus:

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

Să ne întoarcem la rolul nostru. Să edităm fișierul tasks\/main.yml în această formă:

---
- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Start nginx
  service:
    name: nginx
    state: started

Și să adăugăm teste în 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")

Gata, rămâne doar să rulăm (din rădăcina rolului, să ne amintim):

> molecule test

Vârful lung sub spoiler:

--> Validarea schemei /nginx/molecule/default/molecule.yml.
Validarea a fost finalizată cu succes.
--> Matrice de teste

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

--> Scenariul: 'default'
--> Acțiune: 'lint'
--> Executarea Yamllint pe fișierele găsite în /nginx/...
Lintul a fost finalizat cu succes.
--> Executarea Flake8 pe fișierele găsite în /nginx/molecule/default/tests/...
Lintul a fost finalizat cu succes.
--> Executarea Ansible Lint pe /nginx/molecule/default/playbook.yml...
Lintul a fost finalizat cu succes.
--> Scenariul: 'default'
--> Acțiune: 'destroy'

    PLAY [Distruge] *****************************************************************

    TASK [Distruge instanța moleculă] ********************************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Așteaptă finalizarea ștergerii instanței] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Șterge rețeaua docker] **************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    modificat=1    inaccesibil=0    eșuat=0

--> Scenariul: 'default'
--> Acțiune: 'dependency'
Sărind, fișierul de cerințe lipsește.
--> Scenariul: 'default'
--> Acțiune: 'syntax'

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

--> Scenariul: 'default'
--> Acțiune: 'create'

    PLAY [Creare] ******************************************************************

    TASK [Conectare la un registru Docker] *****************************************
    sărind: [localhost] => (item=None)

    TASK [Creare Dockerfiles din numele imaginilor] *******************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Descoperire imagini Docker locale] ***************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Construiește o imagine compatibilă Ansible] *****************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Creare rețea docker(e)] *************************************************

    TASK [Creare instanță moleculă(e)] ********************************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Așteaptă finalizarea creării instanței] *******************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    modificat=4    inaccesibil=0    eșuat=0

--> Scenariul: 'default'
--> Acțiune: 'prepare'
Sărind, playbook-ul de pregătire nu este configurat.
--> Scenariul: 'default'
--> Acțiune: 'converge'

    PLAY [Convergere] **************************************************************

    TASK [Adunare fapte] *********************************************************
    ok: [instanță]

    TASK [nginx : Instalează nginx] ***********************************************
    modificat: [instanță]

    TASK [nginx : Pornește nginx] ***************************************************
    modificat: [instanță]

    PLAY RECAP *********************************************************************
    instanță                   : ok=3    modificat=2    inaccesibil=0    eșuat=0

--> Scenariul: 'default'
--> Acțiune: 'idempotence'
Idempotenta a fost finalizată cu succes.
--> Scenariul: 'default'
--> Acțiune: 'side_effect'
Sărind, playbook-ul de efect secundar nu este configurat.
--> Scenariul: 'default'
--> Acțiune: 'verify'
--> Executarea testelor Testinfra găsite în /nginx/molecule/default/tests/...
    ============================= sesiune de testare începe ==============================
    platforma darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: /nginx/molecule/default, inifile:
    pluginuri: testinfra-1.16.0
colectate 4 elemente

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

    ========================== 4 trecute în 27.23 secunde ===========================
Verificatorul a fost finalizat cu succes.
--> Scenariul: 'default'
--> Acțiune: 'destroy'

    PLAY [Distruge] *****************************************************************

    TASK [Distruge instanța moleculă] ********************************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Așteaptă finalizarea ștergerii instanței] *******************************
    modificat: [localhost] => (item=None)
    modificat: [localhost]

    TASK [Șterge rețeaua docker] **************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    modificat=2    inaccesibil=0    eșuat=0

Rolul nostru simplu a fost testat fără probleme.
Este important să rețineți că, dacă apar probleme în timpul lucrului testul moleculei, dacă nu ați modificat secvența standard, Molecule va șterge instanța.

Pentru depanare, sunt utile următoarele comenzi:

> molecule --debug  # informații de depanare. La rularea obișnuită, Molecule ascunde jurnalele.
> molecule converge          # Lasă instanța după rularea rolului testat.
> molecule login             # Intră în instanța creată.
> molecule --help            # Lista completă a comenzilor.

Rol existent

Adăugarea unui nou scenariu la un rol existent se face din directorul rolului prin următoarele comenzi:

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

În cazul în care acesta este primul scenariu din rol, parametrul -s poate fi omis, deoarece va fi creat un scenariu. default.

Concluzie

După cum vedeți, Molecule nu este foarte complicat, iar folosind șabloanele proprii, se poate reduce desfășurarea unui nou scenariu la modificarea variabilelor în playbook-urile de creare și ștergere a instanțelor. Molecule se integrează fără probleme cu sistemele CI, ceea ce permite creșterea vitezei de dezvoltare prin reducerea timpului de testare manuală a playbook-urilor.

Vă mulțumesc pentru atenție. Dacă aveți experiență în testarea rolurilor ansible, care nu este legată de Molecule — povestiți despre ea în comentarii!

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster