Testando o papel ansible para o cluster RabbitMQ usando molécula

Molecule é uma estrutura para testar funçÔes no Ansible. Existem alguns artigos sobre Habré sobre testes com a ajuda de uma molécula, e quase todos os artigos falam sobre alguns "scripts de teste complexos para ansible" e, mais adiante nos exemplos, geralmente hå alguns papéis e testes simples. Fiquei interessado em testar uma função mais complexa, por exemplo, uma função para criar um cluster RabbitMQ.





As versĂ”es dos programas usados ​​no momento em que este livro foi escrito. A operação correta nĂŁo Ă© garantida para a versĂŁo da molĂ©cula abaixo de 3.3





debian 10 buster





ansible-3.4.0





molécula-3.3.0





docker-ce-20.10.6





yamllint-1.26.1





ansible-lint-5.0.8





Instale o ansible e a molécula.





pip3 install --user ansible



(como exatamente instalar não é tão importante, no exemplo dado, a instalação vai para a casa do usuårio).





pip3 install --user molecule[docker]



(usaremos o driver docker)





Instale linters





pip3 install --user ansible-lint yamllint







A instalação do docker estĂĄ alĂ©m do escopo deste artigo, Ă© importante notar que vocĂȘ pode instalar o docker na mesma mĂĄquina em que executa a molĂ©cula ou instalar o docker em qualquer outra mĂĄquina da rede (por exemplo, se a mĂĄquina local nĂŁo tiver energia suficiente) ou use um servidor docker existente.





No segundo caso, vocĂȘ precisa instalar apenas o cliente docker na mĂĄquina local e definir a variĂĄvel DOCKER_HOST = "ssh: // ansible @ your_docker_server_address", onde ansible Ă© uma conta que tem acesso ssh ao servidor e em que contĂȘineres docker SerĂĄ criado. A conta tambĂ©m deve ser membro do grupo docker no servidor docker.





, , .









cd roles/role_rabbitmq
      
      



( ) .





.ansible-lint (Disclaimer: , skip_list , )





---
exclude_paths:
  - .cache/
  - .git/
  - molecule/
skip_list:
  - command-instead-of-module
  - git-latest
  - no-handler
  - package-latest
  - empty-string-compare
  - command-instead-of-shell
  - meta-no-info
  - no-relative-paths
  - risky-shell-pipe
  - role-name
  - unnamed-task
      
      



.yamllint





---
extends: default
ignore: |
  templates/
  sites/
  files/
  old/
  README.md
  LICENSE

rules:
  braces:
    min-spaces-inside: 0
    max-spaces-inside: 1

  brackets:
    min-spaces-inside: 0
    max-spaces-inside: 1

  comments:
    require-starting-space: false
    level: error

  indentation:
    spaces: 2
    indent-sequences: consistent

  line-length: disable
  truthy: disable
      
      



molecule/cluster .





mkdir molecule/cluster
      
      



molecule/cluster/Dockerfile.j2. . - .





FROM registry.company.net/debian/buster:latest

ENV DEBIAN_FRONTEND noninteractive
ENV pip_packages "ansible"
ENV http_proxy "http://10.10.0.1:8888"
ENV https_proxy "http://10.0.0.1:8888"
ENV no_proxy "127.0.0.1,localhost,*.company.net,10.0.0.0/8,192.168.0.0/16,172.0.0.0/8"

# Install dependencies.
RUN apt update \
  && apt-get install -y --no-install-recommends \
      sudo systemd systemd-sysv \
      build-essential wget libffi-dev libssl-dev \
      python3-apt python3-cryptography python3-pip python3-dev python3-setuptools python3-wheel \
      procps passwd curl lsof netcat gnupg ca-certificates openssh-client less vim iputils-ping iproute2 \
      debian-archive-keyring dnsutils \
  && rm -rf /var/lib/apt/lists/* \
  && rm -Rf /usr/share/doc && rm -Rf /usr/share/man \
  && apt-get clean

# Create ansible user
RUN groupadd --system ansible \
  && useradd --system --comment "Ansible remote management" --home-dir /home/ansible --create-home --gid ansible --shell /bin/bash --password "*" an
sible && echo "%ansible ALL = (ALL) NOPASSWD:ALL" > /etc/sudoers.d/ansible

# Add company repo
RUN curl -k "https://certs.company.net/ca.pem" > /usr/local/share/ca-certificates/ca.crt && update-ca-certificates \
  && curl -k "https://company.net/repos/keys/company_repo_key.gpg" | apt-key add \
  && echo "deb https://company.net/repos/buster buster-local main > /etc/apt/sources.list.d/company.list && apt-get update && pip3 install $pip_packages

# Install Ansible inventory file.
RUN mkdir -p /etc/ansible && echo "[local]\nlocalhost ansible_connection=local" > /etc/ansible/hosts

# Exclude /usr/share/doc
#      /usr/share/doc,        dpkg,    
RUN sed -i 's/path-exclude \/usr\/share\/doc/#path-exclude \/usr\/share\/doc/' /etc/dpkg/dpkg.cfg.d/docker

# Make sure systemd doesn't start agettys on tty[1-6].
RUN rm -f /lib/systemd/system/multi-user.target.wants/getty.target

VOLUME ["/sys/fs/cgroup"]
CMD ["/lib/systemd/systemd"]
      
      



molecule/cluster/prepare.yml. - pre-tasks. pika RabbitMQ.





---
- name: prepare
  hosts: all
  gather_facts: no  #       
  tasks:
    - name: update apt cache
      block:
        - name: update apt cache
          apt:
            update_cache: yes
        - name: perform upgrade of all packages to the latest version
          apt:
            upgrade: dist
            force_apt_get: yes
    - name: install python pika
      pip:
        name:
          - pika
        executable: pip3

      
      



molecule/cluster/converge.yml. . hosts, molecule.yml





---
- name: Converge
  hosts: rabbitmq_cluster

  roles:
    - role: role_rabbitmq
      
      



molecule/cluster/molecule.yml. , . cluster 192.168.0.0/24 - node01, node02, node03 192.168.0.1/2/3. RabbitMQ , .









inventory:
  links:
    group_vars: ../../../../files/molecule/group_vars/
      
      







groups:
  - rabbitmq_cluster
      
      



files/molecule/group_vars/rabbitmq_cluster.yml role_rabbitmq





---
rabbitmq_cluster: yes
certs_dir: /etc/rabbitmq/ssl
rabbitmq_ssl: yes
rabbitmq_ssl_certs:
  - "_.company.net"
rabbitmq_cookie: NJWHJPAOPYKSGTRGDLTN
#  ,      ,    molecule.yml
#            
rabbitmq_nodes:
  - node01
  - node02
  - node03

rabbitmq_master: rabbit@node01
rabbitmq_master_node: node01

rabbitmq_vhosts:
  - name: /test

rabbitmq_users:
  - user: test
    password: test
    vhost: /test

rabbitmq_exchanges:
  - name: test
    type: direct
    durable: yes
    vhost: /test

rabbitmq_queues:
  - name: test
    durable: yes
    vhost: /test

rabbitmq_bindings:
  - name: test
    destination: test
    destination_type: queue
    vhost: /test

rabbitmq_policies:
  - name: ha-replica
    vhost: /test
    tags:
      ha-mode: exactly
      ha-params: 2
      ha-sync-mode: automatic
      
      



molecule.yml





---
dependency:
  name: galaxy
  options:
    ignore-certs: True
driver:
  name: docker
platforms:
  - name: node01
    image: registry.company.net/debian/buster:latest
    # pre_build_image: true
    privileged: True
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
    capabilities:
      - SYS_ADMIN
    command: "/lib/systemd/systemd"
    dns_servers:
      - 10.0.0.1
    groups:
      - rabbitmq_cluster
    docker_networks:
      - name: cluster
        ipam_config:
          - subnet: "192.168.0.0/24"
            gateway: "192.168.0.254"
    networks:
      - name: cluster
        ipv4_address: "192.168.0.1"
    network_mode: default
  - name: node02
    image: registry.company.net/debian/buster:latest
    privileged: True
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
    capabilities:
      - SYS_ADMIN
    command: "/lib/systemd/systemd"
    dns_servers:
      - 10.0.0.1
    groups:
      - rabbitmq_cluster
    networks:
      - name: cluster
        ipv4_address: "192.168.0.2"
    network_mode: default
  - name: node03
    image: registry.company.net/debian/buster:latest
    privileged: True
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
    capabilities:
      - SYS_ADMIN
    command: "/lib/systemd/systemd"
    dns_servers:
      - 10.0.0.1
    groups:
      - rabbitmq_cluster
    networks:
      - name: cluster
        ipv4_address: "192.168.0.3"
    network_mode: default
provisioner:
  name: ansible
  config_options:
    defaults:  
      interpreter_python: auto_silent
      host_key_checking: False
      gathering: smart
      callback_whitelist: profile_tasks, timer, yaml
    ssh_connection:
      pipelining: True
  inventory:
    links:
      group_vars: ../../../../files/molecule/group_vars/
  ansible_args:
    - -e molecule_run=True
    - -e use_proxy=False
  env:
    MOLECULE_NO_LOG: 0
    ANSIBLE_VERBOSITY: 1
verifier:
  name: ansible
lint: |
  set -e
  ansible-lint .
scenario:
  name: cluster
  test_sequence:
    - dependency
    - lint
    - cleanup
    - destroy
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - cleanup
    - destroy
      
      



ansible_args





  ansible_args:
    - -e molecule_run=True
    - -e use_proxy=False
      
      



env environment. ( -v) ANSIBLE_VERBOSITY.





MOLECULE_NO_LOG , no_log=no ( no_log yes). no_log: "{{ molecule_no_log|d(False)|ternary(False, True) }}". molecule_no_log=0, no_log: no, no_log: yes. , .





  env:
    MOLECULE_NO_LOG: 0
    ANSIBLE_VERBOSITY: 1
      
      



ansible-lint yamllint, ansible-lint





lint: |
  set -e
  ansible-lint .
      
      



scenario cluster , . molecule matrix test.





side_effect verify, , - , molecule matrix.





scenario:
  name: cluster
  test_sequence:
    - dependency
    - lint
    - cleanup
    - destroy
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - cleanup
    - destroy
      
      



cluster, -s default





molecule test -s cluster > /tmp/log 2>&1





test_sequence , idempotence. , . ( , ), changed_when: no





/tmp/log "Idempotence completed successfully", ;). , -.





, converge, molecule converge -s cluster. , converge.yml destroy. "docker exec -it container_id /bin/bash" .





side-effect verify. side-effect (- chaos monkey). verify .





rabbitmq ( ).





molecule/cluster/side_effect.yml





---
- name: Side Effect
  serial: 1
  hosts: all
  gather_facts: no  #    
  tasks:
    - name: restart rabbitmq service
      block:
        - name: stop rabbitmq service
          systemd:
            name: rabbitmq-server
            state: stopped
          failed_when: no
        - name: pause
          pause:
            seconds: 15
        - name: start rabbitmq service
          systemd:
            name: rabbitmq-server
            state: started
          failed_when: no
      
      



Crie um arquivo molécula / cluster / verify.yml e adicione vårias verificaçÔes båsicas para nosso cluster (novamente, nada limita sua imaginação).





---
- name: Verify
  hosts: all
  gather_facts: no
  tasks:
  - name: cluster status
    block:
      - name: get cluster status
        command: "rabbitmqctl cluster_status --formatter json"
        register: output
      - name: set facts
        set_fact:
          cluster_output: "{{ output.stdout|from_json }}"
      - name: print nodes
        debug:
          var: cluster_output.disk_nodes
      - name: verify fail
        fail:
          msg: "FAIL: number of nodes is less than 3"
        when:
          - cluster_output.disk_nodes | length < 3
    run_once: yes

  - name: check vhosts
    block:
      - name: get vhosts
        command: "rabbitmqctl list_vhosts --formatter json"
        register: output
      - name: set facts
        set_fact:
          vhost_output: "{{ output.stdout|from_json }}.name"
      - name: print vhosts
        debug:
          var: vhost_output
      - name: verify fail
        fail:
          msg: "FAIL: vhost is missing"
        when:
          - "'/test' not in vhost_output"
    run_once: yes

  - name: check users
    block:
      - name: get users
        command: "rabbitmqctl list_users --formatter json"
        register: output
      - name: set facts
        set_fact:
          user_output: "{{ output.stdout|from_json }}.user"
      - name: print users
        debug:
          var: user_output
      - name: verify fail
        fail:
          msg: "FAIL: user is missing"
        when:
          - "'test' not in user_output"
    run_once: yes

  - name: check queues
    block:
      - name: get queues
        command: "rabbitmqctl -p /test list_queues --formatter json"
        register: output
      - name: set facts
        set_fact:
          queue_output: "{{ output.stdout|from_json }}.name"
      - name: print queues
        debug:
          var: queue_output
      - name: verify fail
        fail:
          msg: "FAIL: queue is missing"
        when:
          - "'test' not in queue_output"
    run_once: yes

  - name: check exchanges
    block:
      - name: get exchanges
        command: "rabbitmqctl -p /test list_exchanges --formatter json"
        register: output
      - name: set facts
        set_fact:
          exchange_output: "{{ output.stdout|from_json }}.name"
      - name: print exchanges
        debug:
          var: exchange_output
      - name: verify fail
        fail:
          msg: "FAIL: exchange is missing"
        when:
          - "'test' not in exchange_output"
    run_once: yes

  - name: check bindings
    block:
      - name: get bindings
        command: "rabbitmqctl -p /test list_bindings --formatter json"
        register: output
      - name: set facts
        set_fact:
          binding_output: "{{ output.stdout|from_json }}.source_name"
      - name: print bindings
        debug:
          var: binding_output
      - name: verify fail
        fail:
          msg: "FAIL: binding is missing"
        when:
          - "'test' not in binding_output"
    run_once: yes

  - name: check policies
    block:
      - name: get policies
        command: "rabbitmqctl -p /test list_policies --formatter json"
        register: output
      - name: set facts
        set_fact:
          policy_output: "{{ output.stdout|from_json }}.name"
      - name: print policies
        debug:
          var: policy_output
      - name: verify fail
        fail:
          msg: "FAIL: policy is missing"
        when:
          - "'ha-replica' not in policy_output"
    run_once: yes

  - name: check publish
    block:
      - name: install consumer script
        copy:
          src: ../../../../files/molecule/scripts/consumer.py
          dest: /usr/local/bin/consumer.py
          owner: root
          mode: 0755
      - name: publish a message to a queue
        rabbitmq_publish:
          url: "amqp://test:test@localhost:5672/%2Ftest"
          queue: test
          body: "Test message"
          content_type: "text/plain"
          durable: yes
      - name: receive a message from the queue
        command: /usr/local/bin/consumer.py
    run_once: yes
      
      



Como a pesquisa ansible nĂŁo funciona muito bem em um contĂȘiner docker, vamos criar arquivos / molĂ©cula / scripts / consumidor.py, um pequeno script python que imprime mensagens da fila de teste.





#!/usr/bin/python3

import pika, sys

url = 'amqp://test:test@localhost/%2ftest'
params = pika.URLParameters(url)
params.socket_timeout = 1

connection = pika.BlockingConnection(params)
channel = connection.channel()
channel.queue_declare(queue='test', durable=True)
method_frame, header_frame, body = channel.basic_get(queue = 'test')
if method_frame is None:
    connection.close()
    sys.exit('Queue is empty!')
else:
    channel.basic_ack(delivery_tag=method_frame.delivery_tag)
    connection.close()
    print(body)
      
      



Verificando o efeito colateral





molecule converge -s cluster
molecule side-effect -s cluster
      
      



Verificando verificar





molecule verify -s cluster
      
      



Se tudo estiver bem, execute o teste completo e verifique o log.





molecule test -s cluster >/tmp/log 2>&1
tail -f /tmp/log
      
      






All Articles