Anleitung: Wie man Ansible-Rollen testet und Probleme vor der Produktion erkennt

Hallo an alle!

Ich arbeite als DevOps-Ingenieur in einem Hotelbuchungsdienst. Ostrovok.ru. In diesem Artikel möchte ich über unsere Erfahrungen beim Testen von Ansible-Rollen sprechen.

Bei Ostrovok.ru verwenden wir Ansible als Konfigurationsmanager. Vor kurzem kamen wir auf die Notwendigkeit, Rollen zu testen, aber wie sich herausstellte, gibt es dafür nicht so viele Tools – das beliebteste ist vielleicht das Molecule-Framework, also haben wir uns entschieden, es zu verwenden. Es stellte sich jedoch heraus, dass seine Dokumentation über viele Fallstricke schweigt. Da wir kein ausreichend detailliertes Handbuch auf Russisch finden konnten, haben wir uns entschieden, diesen Artikel zu schreiben.

Anleitung: Wie man Ansible-Rollen testet und Probleme vor der Produktion erkennt

Molekül

Molekül – ein Framework zum Testen von Ansible-Rollen.

Vereinfachte Beschreibung: Das Molekül erstellt eine Instanz auf der von Ihnen angegebenen Plattform (Cloud, virtuelle Maschine, Container; weitere Details finden Sie im Abschnitt LED Treiber), führt Ihre Rolle darauf aus, führt dann Tests durch und löscht die Instanz. Sollte einer der Schritte fehlschlagen, werden Sie vom Molecule darüber informiert.

Jetzt mehr.

Ein bisschen Theorie

Betrachten Sie zwei Schlüsseleinheiten des Moleküls: Szenario und Treiber.

Szenario

Das Skript enthält eine Beschreibung, was, wo, wie und in welcher Reihenfolge ausgeführt wird. Eine Rolle kann mehrere Skripte haben und jedes ist ein Verzeichnis entlang des Pfades <role>/molecule/<scenario>, das Beschreibungen der für den Test erforderlichen Aktionen enthält. Skript muss enthalten sein default, die automatisch erstellt wird, wenn Sie die Rolle mit einem Molekül initialisieren. Die Namen der folgenden Skripte sind Ihnen überlassen.

Die Abfolge von Testaktionen in einem Skript wird aufgerufen Matrix, und standardmäßig ist es:

(Schritte beschriftet ?, standardmäßig übersprungen, wenn nicht vom Benutzer angegeben)

  • lint - Lauf-Linters. Standardmäßig werden verwendet yamllint и flake8,
  • destroy - Löschen von Instanzen vom letzten Start des Molecule (falls vorhanden),
  • dependency? — Installation der Ansible-Abhängigkeit der getesteten Rolle,
  • syntax - Überprüfung der Syntax der Rolle mit ansible-playbook --syntax-check,
  • create - Erstellen einer Instanz,
  • prepare? — Vorbereitung der Instanz; z.B. Python2 prüfen/installieren
  • converge — Veröffentlichung des getesteten Playbooks,
  • idempotence - Neustart des Playbooks für den Idempotenztest,
  • side_effect? - Aktionen, die nicht direkt mit der Rolle zusammenhängen, aber für Tests notwendig sind,
  • verify - Ausführen von Tests der resultierenden Konfiguration mit testinfra(Default) /goss/inspec,
  • cleanup? - (in neuen Versionen) - grob gesagt, „Säubern“ der vom Molekül betroffenen externen Infrastruktur,
  • destroy - Löschen einer Instanz.

Diese Reihenfolge deckt die meisten Fälle ab, kann jedoch bei Bedarf geändert werden.

Jeder der oben genannten Schritte kann separat ausgeführt werden molecule <command>. Sie sollten sich jedoch darüber im Klaren sein, dass es für jeden dieser CLI-Befehle möglicherweise eine eigene Abfolge von Aktionen gibt, die Sie durch Ausführen herausfinden können molecule matrix <command>. Zum Beispiel beim Ausführen des Befehls converge (Ausführen des zu testenden Playbooks) werden die folgenden Aktionen ausgeführt:

$ molecule matrix converge
...
└── default         # название сценария
    ├── dependency  # установка зависимостей
    ├── create      # создание инстанса
    ├── prepare     # преднастройка инстанса
    └── converge    # прогон плейбука

Die Reihenfolge dieser Aktionen kann bearbeitet werden. Wenn etwas aus der Liste bereits erledigt ist, wird es übersprungen. Den aktuellen Zustand sowie die Konfiguration der Instanzen speichert das Molecule im Verzeichnis $TMPDIR/molecule/<role>/<scenario>.

Fügen Sie Schritte hinzu mit ? Sie können die gewünschten Aktionen im Ansible-Playbook-Format beschreiben und den Dateinamen entsprechend dem Schritt festlegen: prepare.yml/side_effect.yml. Erwarten Sie diese Dateien. Das Molekül befindet sich im Skriptordner.

LED Treiber

Ein Treiber ist eine Einheit, in der Testinstanzen erstellt werden.
Die Liste der Standardtreiber, für die Molecule Vorlagen bereithält, lautet wie folgt: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

In den meisten Fällen handelt es sich bei Vorlagen um Dateien create.yml и destroy.yml im Skriptordner, die das Erstellen bzw. Löschen einer Instanz beschreiben.
Ausnahmen bilden Docker und Vagrant, da Interaktionen mit deren Modulen ohne die oben genannten Dateien erfolgen können.

Hervorzuheben ist der Delegated-Treiber, denn wenn er in den Dateien zum Erstellen und Löschen einer Instanz verwendet wird, wird nur die Arbeit mit der Konfiguration von Instanzen beschrieben, der Rest sollte vom Ingenieur beschrieben werden.

Der Standardtreiber ist Docker.

Kommen wir nun zum Üben und betrachten dort weitere Funktionen.

Erste Schritte

Lassen Sie uns als „Hallo Welt“ eine einfache Nginx-Installationsrolle testen. Wählen wir Docker als Treiber – ich denke, die meisten von Ihnen haben es installiert (und denken Sie daran, dass Docker der Standardtreiber ist).

Vorbereiten virtualenv und darin installieren molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule установит ansible как зависимость; docker для драйвера

Der nächste Schritt besteht darin, die neue Rolle zu initialisieren.
Mit dem Befehl wird die Initialisierung einer neuen Rolle sowie eines neuen Skripts durchgeführt 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

Es stellte sich heraus, dass es sich um eine typische Ansible-Rolle handelte. Darüber hinaus erfolgen alle Interaktionen mit CLI-Molekülen von der Wurzel der Rolle aus.

Schauen wir mal, was sich im Rollenverzeichnis befindet:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

Lassen Sie uns die Konfiguration analysieren molecule/default/molecule.yml (nur Docker-Image ersetzen):

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

Abhängigkeit

In diesem Abschnitt wird die Quelle von Abhängigkeiten beschrieben.

Die Optionen sind: Galaxis, vergoldet, Hülse.

Shell ist lediglich eine Befehls-Shell, die für den Fall verwendet wird, dass Galaxy und Gil Ihre Anforderungen nicht erfüllen.

Ich werde hier nicht lange verweilen, es ist genug beschrieben in Dokumentation.

Fahrer

Der Name des Fahrers. Unserer ist Docker.

Band

Der Linter ist Yamlint.

Nützliche Optionen in diesem Teil der Konfiguration sind die Möglichkeit, eine Konfigurationsdatei für Yamllint anzugeben, Umgebungsvariablen weiterzuleiten oder den Linter zu deaktivieren:

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

Plattformen

Beschreibt die Konfiguration der Instanzen.
Im Fall von Docker als Treiber wird das Molecule über diesen Abschnitt iteriert und jedes Element der Liste ist in verfügbar Dockerfile.j2 als Variable item.

Im Falle eines Treibers, der dies erfordert create.yml и destroy.yml, der Abschnitt ist in ihnen verfügbar als molecule_yml.platformsund Iterationen darüber sind bereits in diesen Dateien beschrieben.

Da das Molecule die Kontrolle über Instanzen zu Ansible-Modulen bereitstellt, sollte dort auch nach der Liste der möglichen Einstellungen gesucht werden. Für Docker kommt beispielsweise das Modul zum Einsatz docker_container_module. Welche Module in anderen Treibern verwendet werden, finden Sie in Dokumentation.

Außerdem finden Sie Beispiele für die Verwendung verschiedener Treiber in den Tests des Moleküls selbst.

Hier ersetzen Centos:7 auf ubuntu.

Versorger

„Lieferant“ – eine Entität, die Instanzen verwaltet. Im Fall von Molecule ist dies ansible, eine Unterstützung für andere ist nicht geplant, daher kann dieser Abschnitt mit einer Einschränkung als erweiterte Ansible-Konfiguration bezeichnet werden.
Hier kann man vieles präzisieren, die wichtigsten Punkte möchte ich meiner Meinung nach hervorheben:

  • Spielbücher: Sie können angeben, welche Playbooks in bestimmten Phasen verwendet werden sollen.

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'"

  • Optionen: Ansible-Optionen und Umgebungsvariablen

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

Szenario

Name und Beschreibung von Skriptsequenzen.
Sie können die Standardaktionsmatrix jedes Befehls ändern, indem Sie den Schlüssel hinzufügen <command>_sequence und als Wert dafür, indem wir die Liste der Schritte definieren, die wir benötigen.
Nehmen wir an, wir möchten die Reihenfolge der Aktionen ändern, wenn wir den Befehl „playbook run“ ausführen: molecule converge

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

Gutachter

Einrichten eines Frameworks für Tests und eines Linters dazu. Der Standard-Linter ist testinfra и flake8. Die möglichen Optionen sind die gleichen wie oben:

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

Kehren wir zu unserer Rolle zurück. Lassen Sie uns die Datei bearbeiten tasks/main.yml zu dieser Art:

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

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

Und fügen Sie Tests hinzu 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")

Fertig, es bleibt nur noch die Ausführung (ich möchte Sie daran erinnern, vom Stamm der Rolle aus):

> molecule test

Langer Auspuff unter dem 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

Unsere einfache Rolle wurde ohne Probleme getestet.
Es lohnt sich, daran zu denken, wenn es während der Arbeit zu Problemen kommt molecule testWenn Sie die Standardsequenz nicht geändert haben, löscht Molecule die Instanz.

Die folgenden Befehle sind zum Debuggen nützlich:

> molecule --debug <command> # debug info. При обычном запуске Молекула скрывает логи.
> molecule converge          # Оставляет инстанс после прогона тестируемой роли.
> molecule login             # Зайти в созданный инстанс.
> molecule --help            # Полный список команд.

Vorhandene Rolle

Das Hinzufügen eines neuen Skripts zu einer vorhandenen Rolle ist aus dem Rollenverzeichnis mit den folgenden Befehlen:

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

Falls dies das erste Szenario in der Rolle ist, dann der Parameter -s kann weggelassen werden, da dadurch ein Skript erstellt wird default.

Abschluss

Wie Sie sehen, ist das Molecule nicht sehr komplex, und durch die Verwendung Ihrer eigenen Vorlagen kann die Bereitstellung eines neuen Skripts auf die Bearbeitung von Variablen in den Playbooks zum Erstellen und Löschen von Instanzen reduziert werden. Das Molekül lässt sich nahtlos in CI-Systeme integrieren, wodurch Sie die Entwicklungsgeschwindigkeit erhöhen können, indem Sie die Zeit für manuelle Tests von Playbooks reduzieren.

Vielen Dank für Ihre Aufmerksamkeit. Wenn Sie Erfahrung im Testen von Ansible-Rollen haben und diese nichts mit dem Molecule zu tun haben, teilen Sie uns dies in den Kommentaren mit!

Source: habr.com

Kommentar hinzufügen