Executer du Python dans un workflow Oozie

Executer du Python dans un workflow Oozie

By BEREZOWSKI César

7 mars 2018

Catégories : Data Engineering | Tags : Oozie, Elasticsearch, Python, REST

Les workflows Oozie permettent d’utiliser plusieurs actions pour exécuter du code, cependant il peut être délicat d’exécuter du Python, nous allons voir comment faire.

J’ai récemment implémenté un workflow qui interagit avec ElasticSearch. Celui-ci contient les actions suivantes :

  • Création d’un index.
  • Ingestion d’un data set.
  • Création d’un alias si tout s’est bien passé.
  • Suppression de l’index en cas d’échec.

Il y a plusieurs manières d’interagir avec ElasticSearch : en transport binaire Java ou avec l’API REST. La plupart des langages disposent de librairies. Pour que le workflow Oozie fonctionne comme attendu, nous avons défini plusieurs conditions nécessaires :

  • Le code doit être portable, c’est à dire embarquer toutes les dépendances nécéssaires, car le cluster n’est pas connecté à internet.
  • Le code doit être facilement compréhensible et écrit dans un langage populaire pour éviter une dette technique.
  • Donner priorité à un langage dans lequel REST et JSON sont facilement manipulables.
  • L’application doit pouvoir offrir plusieurs options CLI, au moins une pour chaque action Oozie.

L’idée d’utiliser Bash s’est présentée mais le parsing des JSON renvoyés par ElasticSearch aurait été trop complexe. Nous avons donc choisi le Python.

ElasticSearch & Python

Bon à savoir : le Python est un langage très adapté pour l’interaction avec ElasticSearch.

La librairie supporte les versions 2.x à 6.x d’ElasticSearch (cela nous arrange étant en 2.x) et est très simple d’utilisation.

Voici un exemple de code permettant d’ouvrir une connexion sécurisée et de créer un index :

from elasticsearch import Elasticsearch 

client = Elasticearch(["https://user:pwd@elastic.host:port"]) 
response = client.indices.create("my_index") 

if "acknowledged" in response and response["acknowledged"] is True: 
  print("my_index created !") 
else: 
  print("Uh oh, there was an error...") 
  print(response)

Packager le code Python

Maintenant que le code est prêt, il faut le packager avec toutes ses dépendances. Le workflow doit pouvoir fonctionner sans accès internet. Seul le binaire Python est un prérequis et il est présent par défaut sur l’OS que nous utilisons, CentOS 7.

Le Python offre plusieurs possibilités pour packager des applications (Wheel, Egg (déprécié pour Wheel), Zip…) avec leurs ressources et HOWTOs. Cependant lorsqu’il s’agit de packager, faire le bon choix peut s’avérer compliqué pour un débutant. Heureusement Python supporte de manière native la possibilité de packager le répertoire de code dans un fichier zip pour pourvoir l’exécuter sous cette forme. L’archive obtenue se lance comme un fichier .pyclassique.

Ensuite, il nous faut télécharger toutes les dépendances en local et les inclure dans le package.

Par exemple, avec cette architecture de projet :

my_python_project/
├── EsUtil.py
├── create_index.py
├── set_alias.py
└── rollback.py

Nous pouvons tout packager de la manière suivante :

cd my_python_project

# Locally install the dependencies
pip install -t ./ [dependency list]

# Compress everything
zip --recurse-paths --quiet -9 ../my_python_dist.zip ./*

Et l’exécuter comme ceci :

PYTHONPATH=/path/to/my_python_dist.zip python -m [filename without extension] [args]
PYTHONPATH=/path/to/my_python_dist.zip python -m create_index [args]

Workflow Oozie

Maintenant que nous avons un package Python contenant nos scripts, nous pouvons l’intégrer à notre workflow Oozie.

Il n’y a malheureusement pas d’action Python dans Oozie. Nous utiliserons donc celle qui s’en rapproche le plus, l’action Shell.

Comme pour tous les types d’action, Oozie prépare un container, y injecte les fichiers spécifiés et lance une commande.

Voici comment définir l’action :

<action name="python-action">
    <shell xmlns="uri:oozie:shell-action:0.2">
        <job-tracker>${clusterJobtracker}</job-tracker>
        <name-node>${clusterNamenode}</name-node>
        <configuration>
            <property>
                <name>mapred.job.queue.name</name>
                <value>${jobQueue}</value>
            </property>
        </configuration>
        <exec>python</exec> <!-- python2 if necessary -->
        <argument>-m</argument>
        <argument>create_index</argument>
        <argument>arg2</argument>
        <argument>arg3</argument>
        <env-var>PYTHONPATH=pyBundle</env-var>
        <file>my_python_dist.zip#pyBundle</file>
    </shell>
    <ok to="end"/>
    <error to="end"/>
</action>
  • La balise configuration spécifie une file YARN dans laquelle sera lancée le container.
  • env-var définit les variables d’environnement du bundle Python.
  • file injecte le bundle Python dans le container de l’action Shell d’Oozie.

Bien sûr, il faut avoir Python installé sur tous les noeuds YARN (il est très souvent présent de base dans les distributions Linux, mais il est préférable d’installer une version packagée avec Anaconda par exemple).

Une fonctionnalité utile d’Oozie dans l’action Shell est le tag <capture-output/>. Il permet de capturer les logs présentés sous la même forme property=value pour ensuite les réutiliser dans les actions suivantes du workflow de cette manière : ${wf:actionData('python-action')['property']}.

Sources

Canada - Morocco - France

International locations

10 rue de la Kasbah
2393 Rabbat
Canada

Nous sommes une équipe passionnées par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Sciencem le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.