Molekul adalah kerangka kerja untuk menguji peran di Ansible. Ada beberapa artikel di Habré tentang pengujian dengan bantuan molekul, dan hampir semua artikel berbicara tentang beberapa "skrip pengujian yang kompleks untuk ansible", dan lebih jauh dalam contoh biasanya ada beberapa peran dan tes sederhana. Saya menjadi tertarik untuk menguji peran yang lebih kompleks, misalnya, peran untuk membuat klaster RabbitMQ.
Versi program yang digunakan pada saat penulisan ini. Operasi yang benar tidak dijamin untuk versi molekul di bawah 3.3
debian 10 Buster
ansible-3.4.0
molekul-3.3.0
buruh pelabuhan-ce-20.10.6
yamllint-1.26.1
ansible-lint-5.0.8
Pasang ansible dan molekul.
pip3 install --user ansible
(bagaimana tepatnya menginstal tidak begitu penting, dalam contoh yang diberikan, instalasi masuk ke homedir pengguna).
pip3 install --user molecule[docker]
(kami akan menggunakan driver buruh pelabuhan)
Pasang linter
pip3 install --user ansible-lint yamllint
Instalasi buruh pelabuhan berada di luar cakupan artikel ini, perlu dicatat bahwa Anda dapat menginstal buruh pelabuhan di mesin yang sama di mana Anda menjalankan molekul, atau menginstal buruh pelabuhan di mesin lain di jaringan (misalnya, jika mesin lokal tidak memiliki daya yang cukup) atau gunakan server buruh pelabuhan yang ada.
Dalam kasus kedua, Anda hanya perlu menginstal klien buruh pelabuhan di komputer lokal dan menyetel variabel DOCKER_HOST = "ssh: // ansible @ your_docker_server_address", di mana ansible adalah akun yang memiliki akses ssh ke server dan di bawah container buruh pelabuhan akan dibuat. Akun tersebut juga harus menjadi anggota grup buruh pelabuhan di server buruh pelabuhan.
, , .
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
Buat file molekul / cluster / verifikasi.yml dan tambahkan berbagai pemeriksaan dasar untuk cluster kita (sekali lagi, tidak ada yang membatasi imajinasi Anda).
---
- 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
Karena pencarian yang mungkin tidak berfungsi dengan baik dalam wadah buruh pelabuhan, mari buat file / molecule / scripts / consumer.py, skrip python kecil yang mencetak pesan dari antrian pengujian.
#!/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)
Memeriksa efek sampingnya
molecule converge -s cluster
molecule side-effect -s cluster
Memeriksa verifikasi
molecule verify -s cluster
Jika semuanya baik-baik saja, jalankan tes penuh dan periksa log.
molecule test -s cluster >/tmp/log 2>&1
tail -f /tmp/log