Cómo configurar el desarrollo impulsado por pruebas con Molécule en Ansible

Read this article in:

Molecule es un marco de pruebas diseñado para papeles Ansible, colecciones y juegos. En el ecosistema Ansible, proporciona un flujo de trabajo estructurado para desarrollar y verificar la infraestructura como código.

Desde una perspectiva más amplia, Molecule alienta desarrollo de la infraestructura impulsada por los ensayos (TDD). En lugar de desplegar funciones directamente a la producción y esperar que trabajen, los desarrolladores pueden validarlas localmente o en tuberías de CI con Molecule. Soporta diferentes estrategias de prueba, forro, idempotencia y un ecosistema de plugins.

Al ser un poco difícil de configurar, Molecule permite al desarrollador los formalismos del Pensamiento Correcto (TM). TDD permite al desarrollador validar un paso terminado, y marcarlo como completo. TDD permite pensar en piezas: en lugar de ser abrumado por una gran tarea, el desarrollador puede implementar y validar pequeñas piezas de la funcionalidad que se está implementando - y con tales cambios incrementales, todo el proyecto se puede completar.

~ ♪♪

Molecule sufrió cambios de ruptura entre algunas versiones, y cierta documentación en línea parece ser incorrecta. Este artículo funciona con la versión Molecule 25. Aquí, voy a configurar un pequeño andamio, para activar una prueba simple en una sola máquina de eliminación. El lector puede tomar desde allí, personalizando el andamio para sus propias necesidades específicas.

Supongamos que tenemos un proyecto ansible con la siguiente estructura de carpetas:

wco_ansible/
  inventory/
    ants.yml
    do.yml
  playbooks/
    setup_mongo.yml
    setup_ubuntu.yml
  requirements.yml
  roles/
    ror/
      files/
        root/
          _screenrc
      meta/
        main.yml
      molecule/
        ...
      requirements.yml
      tasks/
        packaged_app.yml
      templates/
        etc/
          mongo.conf
  vars/
    packaged_app_1.yml

Estaremos probando una tarea específica en el papel Ror. Primero, vamos a configurar la estructura wco ansible/roles/ror/molecule así:

wco_ansible/roles/ror/molecule/
  README.txt
  default/
    converge.yml
    create.yml
    destroy.yml
    molecule.yml
    verify.yml
  

Y el contenido de los archivos:

## requirements.yml
collections:
  - name: community.docker
    version: ">=3.10.4"
  - name: community.general

.

## converge.yml
- name: Fail if molecule group is missing
  hosts: localhost
  tasks:
    - name: Print some info
      ansible.builtin.debug:
        msg: "{{ groups }}"
    - name: Assert group existence
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}
- name: Converge
  hosts: molecule
  # We disable gather facts because it would fail due to our container not
  # having python installed. This will not prevent use from running 'raw'
  # commands. Most molecule users are expected to use containers that already
  # have python installed in order to avoid notable delays installing it.
  # gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false
    - name: Print some info
      ansible.builtin.assert:
        that: result.stdout is ansible.builtin.search("^Linux")
    - name: "+++ +++ Check Python version is >= 3.8"
      command: python3 --version
      register: py_ver
      changed_when: false
    - name: "+++ +++ Assert correct Python version"
      assert:
        that:
          - py_ver.stdout is match("Python 3\\.[189].*|Python [4-9].*")

Lo siguiente es importante. Específicamente, añadir contenedor a molevule inventory parece importar.

## create.yml
- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        hosts: {}
        # molecule: {}
  tasks:
    - name: Create a container
      community.docker.docker_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: sleep 1d
        log_driver: json-file
      register: result
      loop: "{{ molecule_yml.platforms }}"
    - name: Print some info
      ansible.builtin.debug:
        msg: "{{ result.results }}"
    - name: Fail if container is not running
      when: >
        item.container.State.ExitCode != 0 or
        not item.container.State.Running
      ansible.builtin.include_tasks:
        file: tasks/create-fail.yml
      loop: "{{ result.results }}"
      loop_control:
        label: "{{ item.container.Name }}"
    - name: Add container to molecule_inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: {{ item.ansible_connection }}
                    ansible_user: {{ item.ansible_user }}
                    ansible_host: {{ item.ansible_host }}
                    ansible_port: {{ item.ansible_port }}
                    ansible_ssh_common_args: {{ item.ansible_ssh_common_args }}
                    ansible_ssh_private_key_file: {{ item.ansible_ssh_private_key_file }}
      ansible.builtin.set_fact:
        molecule_inventory: >
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"
    - name: Dump molecule_inventory
      ansible.builtin.copy:
        content: |
          {{ molecule_inventory | to_yaml }}
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"
    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory
    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}
      run_once: true # noqa: run-once[task]
# we want to avoid errors like "Failed to create temporary directory"
- name: Validate that inventory was refreshed
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false
    - name: Display uname info
      ansible.builtin.debug:
        msg: "{{ result.stdout }}"

.

## destroy.yml
- name: Destroy molecule containers
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Stop and remove container
      delegate_to: localhost
      community.docker.docker_container:
        name: "{{ inventory_hostname }}"
        state: absent
        auto_remove: true

- name: Remove dynamic molecule inventory
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove dynamic inventory file
      ansible.builtin.file:
        path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        state: absent

En el siguiente archivo, desea tener ambos plataformas y provisioner llaves. Tienen los mismos datos para mí... El host, el puerto y otros argumentos en provisioner son definitivamente importantes. Los de las plataformas pueden ser opcionales - pero los uso en crear.yml Así que por ahora tienen que quedarse. Cabe destacar la estructura de plataformas puede ser simplificado:

## molecule.yml
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml
# driver:
#   name: ssh
platforms:
  - name: do-ubuntu24-3
    ansible_connection: ssh
    ansible_user: root
    ansible_host: "x.x.x.x"
    ansible_port: 22
    ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
    ansible_ssh_private_key_file: ~/.ssh/your-key
    groups:
      - molecule
    image: piousbox/ubuntu-sshd:0.0.0 ## not used, but presence is required

provisioner:
  name: ansible
  inventory:
    host_vars:
      do-ubuntu24-3:
        ansible_connection: ssh
        ansible_host: x.x.x.x
        ansible_port: 22
        ansible_user: root
        ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
        ansible_ssh_private_key_file: ~/.ssh/your-key

Ahora, un comentario sobre Ssh vs Docker Conductor. Yo uso ambos. Ya que conduzco docker con ansible, a menudo necesito docker interior, o algo similar, y al final es más fácil para mí girar una máquina virtual en otro lugar y hacer pruebas allí vía ssh. Por otro lado, el portátil de desarrollo definitivamente utiliza un montón de docker, por lo que el controlador de docker para la molécula es valioso y útil. Tengo mi propia imagen que permite sistematizar, y ssh. Puede leer el artículo sobre Cómo configurar la imagen del docker con sistema y sshd, para más información sobre el tema.

.^.

Please login to post comments: