Mes notes sur RabbitMQ

RabbitMQ est un bus de messages Open Source qui implémente le protocole Advanced Message Queuing (AMQP). Sa fonction est de faire communiquer entre eux des programmes différents, potentiellement écrits dans différents langages. Le serveur RabbitMQ est lui-même écrit dans le langage de programmation Erlang, ce qui est plutôt atypique. Aucune connaissance de Erlang n’est nécessaire pour l’utiliser. C’est un produit édité par Pivotal, un spin-off de VMWare et EMC, connu de tous les développeurs JAVA pour son fabuleux framework Spring.

RabbitMQ demande une complexité de configuration proportionnelle aux exigences demandées : queues de messages persistantes, dead letters, haute disponibilité, optimisation des performances… Pour les configurations compliquées et le support technique avancé dans des mises en oeuvre d’entreprises, il y a des experts (j’ai une adresse pour ceux intéressés). Pour une utilisation basique, dans un cadre de développement pépère à la maison, RabbitMQ est très accessible, bien documenté et permet d’avoir rapidement un bus de message pour faire communiquer ses applications.

Je m’en sers actuellement pour faire discuter mon petit éco-système hébergé. Dans ce cadre j’ai pris quelques notes sur sa mise en place, de l’installation à la configuration de base.

Installation

Choix de l’OS : CentOS 7

Page d’aide de référence pour l’installation : https://www.rabbitmq.com/install-rpm.html

Pivotal fournit une installation d’une version allégée de Erlang avec les dépendances nécessaires à RabbitMQ : https://github.com/rabbitmq/erlang-rpm

Et bien sûr, il fournissent aussi un RPM de RabbitMQ (actuellement en version 3.7.2-1) : https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.2/rabbitmq-server-3.7.2-1.el7.noarch.rpm

Gestion du service à la sauce CentOS :

# démarrer le service 
/sbin/service rabbitmq-server start

# stopper le service
/sbin/service rabbitmq-server stop

# démarrage automatique du service
chkconfig rabbitmq-server on

Après avoir mis le service en démarrage automatique, on n’utilisa plus que l’outil rabbitmqctl :

# démarrer le serveur rabbitmq
rabbitmqctl start_app

# stopper le serveur rabbitmq
rabbitmqctl stop_app

Droits et permissions

Par défaut, un utilisateur guest (mot de passe idem) est créé et il est attaché à l’interface réseau locale (localhost). Pour se connecter depuis une autre machine, en distant, il faut créer un nouvel utilisateur.

rabbitmqctl add_user admin <mot de passe dur>
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

Interface Web d’administration

Une belle interface permet de gérer la configuration et de visualiser des indicateurs de fonctionnement. C’est un plugin qu’on active en ligne de commande avec rabbitmq- plugins

rabbitmq-plugins enable rabbitmq_management

L’interface Web répond à l’adresse http://server-name:15672/cli/

L’utilisateur guest n’a accès à l’interface que par localhost. Le nouveau compte admin est nécessaire pour se connecter en distant.

Si l’interface Web est derrière un proxy NginX et qu’elle répond à une sous-URL du domaine, la config pour pour une sous-url /rabbitwebmin est la suivante :

location /rabbitwebmin/ {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    rewrite ^/rabbitwebmin/(.*)$ /$1 break;
    proxy_pass http://192.168.2.1:15672;
}

Optionnellement, on peut installer le CLI de rabbitmqadmin en téléchargeant le programme (Python) depuis https://raw.githubusercontent.com/rabbitmq/rabbitmq- management/v3.7.2/bin/rabbitmqadmin et en le plaçant dans /usr/local/bin.

Mise en oeuvre

On crée un utilisateur technique pour nos applications dans un virtual host spécifique.

rabbitmqctl add_vhost devhub
rabbitmqctl add_user techuser tech
rabbitmqctl set_permissions -p devhub techuser ".*" ".*" ".*"

A ce niveau, on peut essayer de faire communiquer deux applications à travers Rabbit avec un classique producteur-consommateur écrit en Python, utilisant la librairie Pika, dérivé du tutorial de RabbitMQ.

Code du producteur :

 1    #!/usr/bin/env python
 2    import pika
 3    import sys
 4
 5    credentials = pika.PlainCredentials('techuser', 'tech')
 6    connection = pika.BlockingConnection(pika.ConnectionParameters(host='192.168.2.1',credentials=credentials, virtual_host="devhub"))
 7    channel = connection.channel()
 8
 9    channel.exchange_declare(exchange='hub.topic',
10                             exchange_type='topic')
11
12    routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
13    message = ' '.join(sys.argv[2:]) or 'Hello World!'
14    channel.basic_publish(exchange='hub.topic',
15                          routing_key=routing_key,
16                          body=message)
17    print(" [x] Sent %r:%r" % (routing_key, message))
18    connection.close()

Code du consommateur :

 1    #!/usr/bin/env python
 2    import pika
 3    import sys
 4
 5    credentials = pika.PlainCredentials('techuser', 'tech')
 6    connection = pika.BlockingConnection(pika.ConnectionParameters(host='192.168.2.1',credentials=credentials, virtual_host="devhub"))
 7    channel = connection.channel()
 8
 9    channel.exchange_declare(exchange='hub.topic',
10                             exchange_type='topic')
11
12    result = channel.queue_declare(exclusive=True)
13    queue_name = result.method.queue
14    print("Queue => " + queue_name)
15
16    # on s'abonne aux topics : 
17    binding_keys = ['mail.message', 'mail.command.*']
18
19    for binding_key in binding_keys:
20        channel.queue_bind(exchange='hub.topic',
21                           queue=queue_name,
22                           routing_key=binding_key)
23
24    print(' [*] Waiting for logs. To exit press CTRL+C')
25
26    def callback(channel, method, properties, body):
27        print("=>  %r:%r" % (method.routing_key, body))
28
29    channel.basic_consume(callback,
30                          queue=queue_name,
31                          no_ack=True)
32
33    channel.start_consuming()

Quelques tests :

# le consommateur lancé dans un shell s'abonne aux topics mail.message et mail.command.*
$ python3 consumer.py 

le consommateur doit recevoir le message suivant :

# le producteur produit dans le topic 'mail.message'
$ python3 producer.py "mail.message"

le consommateur doit recevoir le message suivant :

# le producteur produit dans le topic 'mail.command.test'
$ python3 producer.py "mail.command.test"

le consommateur ne doit pas recevoir le message suivant :

# le producteur produit dans le topic 'mail.rate'
$ python3 producer.py "mail.rate"
Votre commentaire
Le site Web est optionel
Le message peut être rédigé au format Markdown