Русский
Русский
English
Статистика
Реклама

Тестирование ansible роли для RabbitMQ кластера с помощью molecule

Molecule это фреймворк, предназначенный для тестирования ролей в Ansible. На хабре довольно много статей про тестирование с помощью molecule и почти во всех статьях говорится о неких "сложных сценариях тестирования для ansible", и далее в примерах обычно идут какие-то простенькие роли и тесты. Мне стало интересно протестировать более сложную роль, например роль для создания RabbitMQ кластера.

Используемые версии программ на момент написания статьи. Не гарантируется корректная работа для molecule версии ниже 3.3

debian 10 Buster

ansible-3.4.0

molecule-3.3.0

docker-ce-20.10.6

yamllint-1.26.1

ansible-lint-5.0.8

Устанавливаем ansible и molecule.

pip3 install --user ansible (как именно устанавливать не столь важно, в приведенном примере установка идет в хоумдир пользователя).

pip3 install --user molecule[docker] (мы будем использовать драйвер докера)

Устанавливаем линтеры

pip3 install --user ansible-lint yamllint

Установка докера выходит за рамки этой статьи, стоит отметить только что докер вы можете установить на эту же машину, где будете запускать molecule или же установить докер на любую другую машину в сети (например если мощности локальной машины не хватает) или же использовать уже существующий докер сервер.

Во втором случае на локальную машину нужно установить только докер клиент и выставить переменную DOCKER_HOST="ssh://ansible@адрес_вашего_докер_сервера", где ansible - аккаунт, который имеет ssh доступ на сервер и под которым будут создаваться докер контейнеры. Аккаунт также должен состоять в группе docker на докер сервере.

Описание ансибл роли тоже выходит за рамки статьи, можно взять любую похожую роль, где есть логика создания кластера.

Переходим в нашу условную роль

cd roles/role_rabbitmq

Создаем конфиги для линтеров в текущей директории (дефолтные конфиги будут генерить много лишних алертов) или же создаем линки на общие конфиги.

.ansible-lint

---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-changed-when  - no-relative-paths  - risky-shell-pipe  - role-name  - unnamed-task

.yamllint

---extends: defaultignore: |  templates/  sites/  files/  old/  README.md  LICENSErules:  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:latestENV DEBIAN_FRONTEND noninteractiveENV 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 userRUN groupadd --system ansible \  && useradd --system --comment "Ansible remote management" --home-dir /home/ansible --create-home --gid ansible --shell /bin/bash --password "*" ansible && echo "%ansible ALL = (ALL) NOPASSWD:ALL" > /etc/sudoers.d/ansible# Add company repoRUN 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.targetVOLUME ["/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, node01, 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: yescerts_dir: /etc/rabbitmq/sslrabbitmq_ssl: yesrabbitmq_ssl_certs:  - "_.company.net"rabbitmq_cookie: NJWHJPAOPYKSGTRGDLTN# обратите внимание, здесь мы указываем короткие имена нод, заданные в нашем molecule.yml# все ноды из списка должны узнавать друг друга по этим коротким именамrabbitmq_nodes:  - node01  - node02  - node03rabbitmq_master: rabbit@node01rabbitmq_master_node: node01rabbitmq_vhosts:  - name: /testrabbitmq_users:  - user: test    password: test    vhost: /testrabbitmq_exchanges:  - name: test    type: direct    durable: yes    vhost: /testrabbitmq_queues:  - name: test    durable: yes    vhost: /testrabbitmq_bindings:  - name: test    destination: test    destination_type: queue    vhost: /testrabbitmq_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: Truedriver:  name: dockerplatforms:  - 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: defaultprovisioner:  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: 1verifier:  name: ansiblelint: |  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

Создаем файл molecule/cluster/verify.yml и добавим различные базовые проверки для нашего кластера (опять же ничто не ограничивает вашу фантазию).

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

Так как ansible lookup не очень хорошо работает в докер-контейнере, создаем files/molecule/scripts/consumer.py, небольшой скрипт на питоне, который печатает сообщения из очереди test.

#!/usr/bin/python3import pika, sysurl = 'amqp://test:test@localhost/%2ftest'params = pika.URLParameters(url)params.socket_timeout = 1connection = 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)

Проверяем side-effect

molecule converge -s clustermolecule side-effect -s cluster

Проверяем verify

molecule verify -s cluster

Если все хорошо, запускаем полный тест и проверяем лог.

molecule test -s cluster >/tmp/log 2>&1tail -f /tmp/log
Источник: habr.com
К списку статей
Опубликовано: 17.05.2021 20:19:35
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Devops

Ansible

Molecule

Категории

Последние комментарии

© 2006-2021, personeltest.ru