Vous avez un projet ?

Comment automatiser son architecture de Micro-Services ?

Publié le 24 janvier 2024
scroll
Image d'un groupe autour d'une table holographique réflissant à un sujet commun
N’oubliez pas
de partager
cet article

Dans le domaine des réseaux et de la sécurité, l’automatisation est devenue essentielle pour gérer efficacement les équipements et les tâches récurrentes. Les micro-services offrent une approche flexible pour développer des applications modulaires et évolutives.

Introduction

Dans cet article, nous allons explorer comment créer une stack docker complète pour automatiser l’exécution de scripts Python sur des équipements réseau et stocker les résultats dans une base de données PostgreSQL.

Nous utiliserons une API Python Flask pour exposer les fonctionnalités de notre application, un bus de messages Redis pour la communication asynchrone, et un worker pour traiter les tâches de manière distribuée.

Comprendre l’automatisation réseaux et sécurité

L’automatisation des réseaux et de la sécurité est une approche essentielle dans l’évolution des infrastructures modernes. Elle vise à simplifier la gestion des équipements réseau et des tâches de sécurité en réduisant l’intervention humaine, en augmentant l’efficacité opérationnelle et en améliorant la fiabilité du réseau.

L’automatisation consiste à utiliser des outils et des scripts pour exécuter automatiquement des tâches de configuration, de gestion et de surveillance des équipements réseau. Plutôt que d’effectuer ces tâches manuellement, l’automatisation permet aux administrateurs réseau de définir des politiques et des procédures une fois, puis de les appliquer de manière cohérente sur l’ensemble du réseau.

Les tâches couramment automatisées comprennent la configuration des routeurs, des commutateurs, des pare-feu et d’autres appareils réseau, la gestion des VLAN (Virtual Local Area Network), la mise à jour du firmware, la surveillance des performances réseau, la détection d’incidents de sécurité, etc.

L’automatisation présente de nombreux avantages pour les organisations, notamment :

  • Gain de temps et d’efficacité : L’automatisation permet d’exécuter des tâches complexes en quelques secondes ou minutes, ce qui prendrait normalement beaucoup plus de temps si elles étaient effectuées manuellement.
  • Réduction des erreurs humaines : En minimisant l’intervention humaine, l’automatisation réduit les risques d’erreurs humaines, améliorant ainsi la fiabilité et la stabilité du réseau.
  • Évolutivité et flexibilité : L’automatisation permet de gérer facilement des réseaux en expansion et de s’adapter rapidement aux changements d’exigences ou de topologies.
  • Consistance : L’application de politiques et de configurations cohérentes sur l’ensemble du réseau garantit un fonctionnement uniforme et évite les incohérences.
  • Sécurité améliorée : L’automatisation permet de déployer rapidement des correctifs de sécurité et d’appliquer des mesures de sécurité de manière proactive.

Mise en place de l’environnement de développement et des dépendances

Nous installerons et configurerons Docker pour créer notre stack de micro-services. Nous mettrons en place une API Flask avec Restx pour définir les endpoints permettant de soumettre des tâches d’exécution de scripts.

De plus, nous installerons et configurerons Redis pour le bus de messages, ainsi qu’une base de données PostgreSQL pour stocker les jobs et les résultats.

Enfin nous installerons un worker Python RQ, écoutant sur le bus de message pour exécuter réellement le script et ainsi mettre à jour la base de données PostgreSQL avec le résultat.

Dockerfile de l’API Python Flask avec Gunicorn

Créez un fichier Dockerfile.api pour l’API Flaks avec Gunicorn :

 

# Utilisez une image Python de base

FROM python:3.9

# Définit le répertoire de travail dans le conteneur

WORKDIR /app

# Copie du fichier de dépendances

COPY requirements.txt .

# Installation des dépendances

RUN pip install --no-cache-dir -r requirements.txt

# Copie des fichiers de l'application dans le conteneur

COPY app.py .

# Exposition du port de l'API

EXPOSE 7770

# Commande pour démarrer l'API Flask avec Gunicorn

CMD ["gunicorn", "-b", "0.0.0.0:7770", "app:app", "--workers", "1"]

Dockerfile du worker Python RQ

Créez un fichier Dockerfile.worker pour le worker Python RQ :

 

# Utilisez une image Python de base

FROM python:3.9

# Définit le répertoire de travail dans le conteneur

WORKDIR /app

# Copie du fichier de dépendances

COPY requirements.txt .

# Installation des dépendances

RUN pip install --no-cache-dir -r requirements.txt

# Copie du fichier du worker Python RQ dans le conteneur

COPY worker.py .

# Commande pour démarrer le worker RQ

CMD ["python", "worker.py"]

 

Dockerfile pour Redis

Créez un fichier Dockerfile.redis pour Redis :

 

# Utilisez une image Redis officielle

FROM redis:latest

# Expose le port par défaut de Redis

EXPOSE 6379

 

Dockerfile pour PostgreSQL

Créez un fichier Dockerfile.postgres pour PostgreSQL :

 

# Utilisez une image PostgreSQL officielle

FROM postgres:latest

# Expose le port par défaut de PostgreSQL

EXPOSE 5432

 

Docker Compose pour l’ensemble de la stack

Créez un fichier docker-compose.yml pour définir l’ensemble de la stack avec les services Flask, Redis, PostgreSQL et le worker RQ :

 

version: '3'

services:

  # Service pour l'API Python Flask avec Gunicorn

  api:

    build:

      context: ./path/to/api

      dockerfile: Dockerfile.api

    ports:

      - "7770:7770"

    depends_on:

      - redis

      - postgres

  # Service pour le bus de messages Redis

  redis:

    build:

      context: ./path/to/redis

      dockerfile: Dockerfile.redis

    ports:

      - "6379:6379"

  # Service pour la base de données PostgreSQL

  postgres:

    build:

      context: ./path/to/postgres

      dockerfile: Dockerfile.postgres

    ports:

      - "5432:5432"

    environment:

      POSTGRES_USER: your_db_user

      POSTGRES_PASSWORD: your_db_password

      POSTGRES_DB: your_db_name

  # Service pour le worker Python RQ

  worker:

    build:

      context: ./path/to/worker

      dockerfile: Dockerfile.worker

    depends_on:

      - redis

      - postgres

Assurez-vous d’ajuster le chemin ./path/to/… dans le fichier docker-compose.yml pour pointer vers les emplacements réels de chaque Dockerfile. Vous pouvez également personnaliser les noms d’utilisateur, mots de passe et noms de base de données pour PostgreSQL selon vos besoins.

Avec ces modifications, nous avons maintenant un Dockerfile distinct pour chaque élément de la stack (API Flask, worker Python RQ, Redis, PostgreSQL), et le fichier docker-compose.yml permet de définir l’ensemble de la stack pour le déploiement. Chaque service est défini indépendamment et peut interagir avec les autres services via les noms de service spécifiés (par exemple, l’API Flask peut communiquer avec Redis et PostgreSQL en utilisant leurs noms de service respectifs “redis” et “postgres”).

 

Création de l’API Python Flask avec Restx

Nous développerons une API Flask qui permettra aux utilisateurs de soumettre des tâches d’exécution de scripts. Nous définirons des endpoints pour soumettre une nouvelle tâche, vérifier l’état d’une tâche en cours d’exécution, et récupérer les résultats des tâches terminées.

https://palletsprojects.com/p/flask/

Flask est un micro framework open-source de développement web en Python. Il est classé comme microframework car il est très léger.

https://flask-restx.readthedocs.io/en/latest/

Flask-RESTX est une extension pour Flask qui ajoute la prise en charge de la création rapide d’API REST

https://swagger.io/

Swagger est un langage de description d’interface permettant de décrire des API RESTful exprimées à l’aide de JSON

https://gunicorn.org/

Gunicorn « Green Unicorn », est un serveur web HTTP WSGI écrit en Python et disponible pour Unix

https://redis.com/

https://pypi.org/project/redis/

https://python-rq.org/

RQ (Redis Queue) est une bibliothèque Python simple pour mettre en file d’attente les tâches et les traiter en arrière-plan avec les workers

https://www.postgresql.org/

PostgreSQL est un système de gestion de base de données relationnelle et objet.

 

Pour lancer votre serveur Gunicornhttps://docs.gunicorn.org/en/stable/run.html

Vous pouvez commencer votre APIhttps://flask-restx.readthedocs.io/en/latest/quickstart.html

 

# Import de l'application Flask

from flask import Flask, request

from flask_restx import Api, Resource, fields

import redis

from rq import Queue

import psycopg2

from worker import executer_tache  # Import de la fonction d'exécution des tâches depuis le worker




# Création de l'application Flask

app = Flask(__name__)




# Initialisation de la connexion à Redis

redis_conn = redis.StrictRedis(host='redis', port=6379, db=0)




# Création de la file d'attente RQ 'queue_name'

q = Queue('queue_name', connection=redis_conn)




# Initialisation de l'API avec Restx

api = Api(app, version='1.0', title='API d\'automatisation réseau et sécurité', description='Endpoints pour soumettre des tâches d\'exécution de scripts')




# Exemple de définition d'un modèle de données (identique à l'exemple précédent)




# Exemple d'un endpoint pour soumettre une nouvelle tâche

@api.route('/tache')

class Tache(Resource):

    def post(self):

        # Code ici pour récupérer les données soumises par l'utilisateur

        donnees = request.get_json()

        script_a_executer = donnees['script']

        equipement_cible = donnees['equipement']




        # Vérification si la tâche existe déjà dans la base de données

        job_id = recherche_job(script_a_executer, equipement_cible)




        if job_id is None:

            # Si la tâche n'existe pas, on l'ajoute à la base de données

            job_id = ajouter_job(script_a_executer, equipement_cible)




            # Envoi de la tâche à la file d'attente Redis

            tache = q.enqueue(executer_tache, script_a_executer, equipement_cible)

        else:

            # Si la tâche existe déjà, on récupère son ID

            tache = q.fetch_job(job_id)




        return {'message': 'Tâche soumise avec succès', 'tache_id': tache.get_id()}, 200




# Exemple d'un endpoint pour récupérer l'état d'une tâche en cours d'exécution (identique à l'exemple précédent)




# Exemple d'un endpoint pour récupérer les résultats d'une tâche terminée (identique à l'exemple précédent)




# Fonction pour rechercher un job dans la base de données

def recherche_job(script, equipement):

    conn = psycopg2.connect(

        host='postgres',

        port='5432',

        dbname='votre_db_name',

        user='votre_db_user',

        password='votre_db_password'

    )

    cur = conn.cursor()

    cur.execute("SELECT id FROM jobs WHERE script=%s AND equipement=%s", (script, equipement))

    row = cur.fetchone()

    cur.close()

    conn.close()

    return row[0] if row else None




# Fonction pour ajouter un job dans la base de données

def ajouter_job(script, equipement):

    conn = psycopg2.connect(

        host='postgres',

        port='5432',

        dbname='votre_db_name',

        user='votre_db_user',

        password='votre_db_password'

    )

    cur = conn.cursor()

    cur.execute("INSERT INTO jobs (script, equipement) VALUES (%s, %s) RETURNING id", (script, equipement))

    job_id = cur.fetchone()[0]

    conn.commit()

    cur.close()

    conn.close()

    return job_id




# Démarrage de l'application Flask

if __name__ == '__main__':

    # L'utilisation de Gunicorn nécessite de spécifier le nombre de workers pour la gestion des requêtes

    # Ici, nous utilisons 1 worker, mais vous pouvez ajuster cette valeur en fonction de vos besoins

    app.run(debug=False, host='0.0.0.0', port=7770, workers=1)

L’API Flask repose sur le serveur HTTP WSGI Gunicorn pour garantir une gestion efficace des requêtes HTTP. Gunicorn permet à notre application d’être performante, lui permettant de répondre aux requêtes simultanées de manière robuste et stable.

Nous avons également mis en place un bus de messages Redis, qui joue un rôle clé dans la communication asynchrone entre l’API et le worker Python RQ. La file d’attente Redis permet de soumettre et de stocker les tâches en attente d’exécution, assurant ainsi une gestion fluide des tâches tout en évitant les interruptions potentielles du processus d’exécution.

De plus, nous utilisons une base de données PostgreSQL pour enregistrer les jobs lancés par l’API et stocker les résultats des tâches exécutées. Cela nous permet de conserver un historique des tâches réalisées, ainsi que de faciliter la récupération des résultats pour les utilisateurs.

 

Communication asynchrone avec Redis

Nous mettrons en œuvre un mécanisme de communication asynchrone en utilisant Redis comme bus de messages. Lorsqu’un utilisateur soumet une tâche via l’API, celle-ci sera placée dans la file d’attente Redis pour être traitée ultérieurement par le worker Python RQ.

Exemple de code pour l’initialisation de la connexion Redis dans l’API Flask (inclus dans l’API) :

 

import redis


# Initialisation de la connexion à Redis

redis_conn = redis.StrictRedis(host='redis', port=6379, db=0)

Création du worker Python RQ

Nous créerons un worker Python RQ qui écoutera en permanence la file d’attente Redis « queue_name ».

Lorsqu’une nouvelle tâche est détectée, le worker récupérera les informations de la tâche, exécutera le script Python souhaité sur l’équipement réseau ciblé, et stockera les résultats dans la base de données PostgreSQL.

Importation des dépendances nécessaires

Tout d’abord, nous importons les dépendances nécessaires pour le bon fonctionnement du worker Redis RQ. Ces dépendances incluent redis pour la communication avec le serveur Redis, rq pour gérer la file d’attente Redis, ainsi que les fonctions personnalisées que nous avons définies dans le fichier worker.py.

Connexion à Redis et création de la file d’attente RQ

Nous établissons une connexion à l’instance Redis en utilisant redis.StrictRedis avec les informations d’hôte et de port appropriées. Ensuite, nous créons une instance de la file d’attente RQ en utilisant Queue(connection=redis_conn). Cette file d’attente sera utilisée pour récupérer les tâches soumises par l’API Flask.

Fonction d’exécution des tâches

Dans le worker, nous définissons une fonction appelée executer_tache qui sera responsable de l’exécution réelle des tâches soumises. À l’intérieur de cette fonction, vous pouvez inclure votre code personnalisé pour exécuter le script Python sur l’équipement réseau ciblé. Dans notre exemple, nous avons supposé l’existence d’une fonction executer_script pour effectuer cette tâche.

Après avoir exécuté le script, le résultat obtenu est stocké dans la base de données PostgreSQL en utilisant la fonction enregistrer_resultat. Cette dernière fonction est également supposée être personnalisée selon vos besoins pour effectuer l’enregistrement dans la base de données.

Création du worker RQ

Enfin, nous créons le worker RQ avec Worker(q), où q est l’instance de la file d’attente que nous avons créée précédemment. Le worker est démarré en utilisant worker.work(), ce qui signifie qu’il commencera à écouter la file d’attente Redis et exécutera les tâches soumises dès qu’elles seront disponibles.

Exemple de code python pour le worker :

 

import redis

from rq import Worker, Queue, Connection


# Connexion à Redis

redis_conn = redis.StrictRedis(host='redis', port=6379, db=0)

q = Queue('queue_name’, connection=redis_conn)


# Fonction d'exécution des tâches

def executer_tache(script, equipement):

    try:

        # Code pour exécuter le script Python sur l'équipement réseau

        # Ici, nous supposons que vous avez une fonction d'exécution de script personnalisée

        resultat = executer_script(script, equipement)


        # Code pour stocker le résultat dans la base de données PostgreSQL

        # Ici, nous supposons que vous avez une fonction pour stocker les résultats

        enregistrer_resultat(resultat)

    except Exception as e:

        # En cas d'erreur lors de l'exécution de la tâche, vous pouvez gérer les exceptions ici

        # Par exemple, vous pouvez journaliser l'erreur ou effectuer une action de récupération

        print(f"Erreur lors de l'exécution de la tâche : {e}")


# Création du worker RQ

with Connection(redis_conn):

    worker = Worker(q)

worker.work()

 

Déploiement et mise à l’échelle

Déploiement initial

Pour déployer notre architecture de micro-services, nous allons utiliser Docker Compose, qui nous permet de spécifier et d’orchestrer facilement les différents services nécessaires pour notre application. Avec Docker Compose, nous pouvons définir l’environnement complet de notre application dans un fichier docker-compose.yml.

Chaque service sera basé sur une image Docker contenant le code et les dépendances nécessaires vu plus haut.

Vous pouvez créer ces images en utilisant Dockerfile pour chaque service.

Une fois que le fichier docker-compose.yml est prêt, nous pouvons déployer toute l’application en utilisant la commande suivante :

docker-compose up -d

L’option -d permet de lancer les conteneurs en mode détaché (démon), ce qui signifie qu’ils s’exécuteront en arrière-plan.

Après avoir exécuté cette commande, Docker Compose va créer et lancer les conteneurs pour chaque service défini dans le fichier docker-compose.yml. L’API Flask, Redis, PostgreSQL et le worker seront tous opérationnels et fonctionneront ensemble pour offrir une solution d’automatisation réseau et de sécurité complète.

Mise à l’échelle de l’API Flask

Lorsque l’application gagne en popularité et que le nombre de requêtes à l’API Flask augmente, il peut être nécessaire de mettre à l’échelle l’API pour gérer efficacement la charge. Heureusement, Docker Compose facilite également la mise à l’échelle de nos services.

Pour mettre à l’échelle l’API Flask, nous pouvons utiliser l’option –scale de Docker Compose lors de la commande up. Par exemple, pour démarrer trois instances de l’API Flask pour une meilleure tolérance aux pannes et une gestion de charge améliorée, nous pouvons exécuter :

docker-compose up -d –scale api=3

Cela lancera trois instances de l’API Flask en parallèle, chacune écoutant les requêtes sur le même port. Docker Compose s’occupera automatiquement de répartir la charge entre ces instances.

Mise à l’échelle du worker Redis RQ

De même, lorsque le nombre de tâches en file d’attente devient important, il peut être nécessaire de mettre à l’échelle le worker pour traiter efficacement les tâches soumises.

Pour mettre à l’échelle le worker, nous pouvons également utiliser l’option –scale de Docker Compose lors de la commande up. Par exemple, pour démarrer deux instances du worker pour améliorer le traitement des tâches en file d’attente, nous pouvons exécuter :

docker-compose up -d –scale worker=2

Cela lancera deux instances du worker, permettant de paralléliser le traitement des tâches et d’accélérer l’exécution des scripts sur les équipements.

Gestion des volumes

Pour que les données de Redis et PostgreSQL soient persistantes et ne soient pas perdues lorsque les conteneurs sont arrêtés, il est important de gérer les volumes Docker.

 

Dans le fichier docker-compose.yml, nous pouvons aussi définir des volumes pour les services Redis et PostgreSQL en utilisant la section volumes. Par exemple :

 

services:

  redis:

    image: redis:latest

    volumes:

      - redis_data:/data

    # Autres configurations du service Redis

  postgres:

    image: postgres:latest

    volumes:

      - postgres_data:/var/lib/postgresql/data

    # Autres configurations du service PostgreSQL

Ici, nous avons défini des volumes nommés redis_data et postgres_data pour stocker les données de Redis et PostgreSQL respectivement. Ces volumes sont persistants et seront automatiquement conservés même si les conteneurs sont redémarrés ou recréés.

En utilisant les volumes, nous pouvons nous assurer que les données importantes, comme les jobs en file d’attente Redis et les résultats stockés dans PostgreSQL, sont conservées même en cas d’interruptions temporaires du système.

En résumé, grâce à Docker Compose, nous pouvons facilement déployer notre architecture de micro-services, assurer une mise à l’échelle efficace de l’API Flask et du worker en fonction de la demande, et garantir que nos données sont persistantes et bien gérées. Cela nous permet de fournir une solution robuste et évolutive pour l’automatisation des tâches réseau et de sécurité, répondant ainsi aux besoins de nos utilisateurs en constante évolution.

Conclusion

Dans cet article, nous avons exploré la mise en place d’une architecture de micro-services performante et évolutive pour l’automatisation des tâches réseau et de sécurité. En utilisant Docker et Python, nous avons créé une stack complète qui offre une solution flexible et robuste pour répondre aux besoins complexes de gestion des équipements réseau.

Notre stack de micro-services repose sur une combinaison puissante de technologies :

  • API Python Flask avec Restx : Notre API offre une interface pour soumettre des tâches d’exécution de scripts Python sur des équipements réseau ciblés. Grâce à Flask, nous avons créé des endpoints pour la soumission de tâches, la récupération de l’état d’exécution et la récupération des résultats. De plus, en utilisant Gunicorn comme serveur HTTP WSGI, notre API est capable de gérer efficacement les requêtes simultanées, assurant une expérience utilisateur fluide.
  • Bus de message Redis : Avec Redis, nous avons établi un mécanisme de communication asynchrone entre l’API Flask et le worker Redis RQ. La file d’attente Redis permet de stocker les tâches en attente d’exécution, garantissant ainsi un traitement fluide des tâches sans interruption.
  • Base de données PostgreSQL : En utilisant PostgreSQL, nous avons mis en place une base de données robuste pour stocker les jobs lancés par l’API Flask et les résultats des tâches exécutées. Cela nous permet de conserver un historique des tâches réalisées et de fournir une méthode de récupération fiable des résultats pour les utilisateurs.
  • Worker : Notre worker RQ joue un rôle clé en récupérant les tâches de la file d’attente Redis et en les exécutant réellement sur les équipements réseau ciblés. Grâce à RQ, nous pouvons paralléliser le traitement des tâches, accélérant ainsi l’exécution des scripts et optimisant les performances globales.

En déployant notre architecture de micro-services à l’aide de Docker Compose, nous avons créé un environnement complet et portable qui facilite la gestion de notre application. Docker Compose nous a permis de déployer rapidement tous les services nécessaires, tout en garantissant la gestion des dépendances et la cohérence entre les différentes parties de notre stack.

Pour répondre aux exigences croissantes de notre application, nous avons également examiné la mise à l’échelle de l’API Flask et du worker.

Grâce à Docker Compose, nous avons pu facilement ajuster le nombre d’instances de chaque service pour gérer efficacement la charge accrue, assurant ainsi une performance optimale même lors de périodes de pointe.

En conclusion, notre architecture de micro-services utilisant Docker et Python offre une solution complète et évolutive pour l’automatisation des tâches réseau et de sécurité. Grâce à l’API Flask, les utilisateurs peuvent soumettre des tâches simplement et récupérer les résultats de manière transparente.

Le worker assure une exécution rapide des tâches, tandis que Redis et PostgreSQL fournissent une communication asynchrone robuste et une gestion fiable des données.

Avec cette solution, nous sommes prêts à relever les défis de l’automatisation réseau et de sécurité, offrant ainsi une expérience utilisateur fluide et performante.

Attention tout de même, cette architecture n’est pas recommandée pour un environnement de production sachant que tous les composants tourneront sur un seul serveur. Il est donc recommandé, pour un environnement de production, de répartir l’ensemble des conteneurs sur plusieurs nœuds, en utilisant par exemple Kubernetes.

Si vous souhaitez en savoir plus, contactez-nous.

– Arnaud Weiss, Consultant Automatisation Réseaux & Cloud