Déploiement continu d’Azure Data Factory et Azure DevOps

Azure Data Factory est un service managé du cloud Azure, de type “low code”, aussi bien utile pour ses fonctionnalités d’ETL/ELT que d’ordonnanceur. On appréciera particulièrement d’y intégrer toute une chaîne de traitements et d’avoir ainsi un espace centralisé de lecture des logs d’exécution.

Si ce service commence à prendre de l’importance dans vos projets data, vous souhaiterez certainement développer dans une ressource qui n’est celle de production. Se posera donc alors la question du déploiement continu entre ressources.

Lier Data Factory à un repository

Avant de déployer, il est bien sûr nécessaire de versionner les développements ! Cela sera possible dans l’un des deux outils du monde Microsoft : GitHub ou Azure DevOps. Il est possible de définir le repository à la création de la ressource mais nous pouvons le faire, plus facilement, après l’instanciation.

A la première connexion au Studio Data Factory, nous configurons le repository, en choisissant ici une organisation et un projet Azure DevOps. Il n’est pas nécessaire d’importer le contenu de ce repository qui doit être vide pour l’instant.

Décocher la case “Import existing resource”

Notons tout de suite un fonctionnement propre à Data Factory que sera le travail sur deux branches :

  • une branche principale (nommée ici main), de collaboration
  • une branche de “publication” au nom réservé : adf_publish

Nous verrons lors du déploiement le rôle joué par cette seconde branche. Nous terminons la configuration en définissant la branche main comme la branche de travail.

Il sera possible par la suite de créer des branches apportant de nouvelles fonctionnalités ou réalisant des corrections puis d’effectuer des opérations de merge (fusion) avec la branche principale.

Terminons cette première partie sur une notion fondamentale : les environnements de destination lors du déploiement (pré-production, production…) ne doivent pas être liés au repository. C’est en effet une opération spécifique (un pipeline de release) qui viendra déposer le code, mais ce dernier ne doit pas pouvoir être modifié en dehors de l’environnement de développement.

Créer un premier service lié

Les services liés sont les premiers éléments à créer dans Data Factory car ils “hébergent” les datasets ou représentent une ressource de calcul comme c’est le cas pour le service de clusters Spark managé Azure Databricks. Ce sont aussi les services liés qui sont les plus dépendants des environnements (dev, uat, prod…) car ils pointent vers d’autres ressources auprès desquelles une authentification est bien souvent nécessaire.

Voici une bonne pratique : ne pas utiliser un nom spécifique à l’environnement (par exemple ls_DatabricksDev) car ce nom doit refléter la ressource dans chaque environnement et nous ne pourrons pas le modifier.

En revanche, plusieurs propriétés ainsi que l’authentification devront être modifiées lors du déploiement entre environnements.

Pour un service lié Databricks, nous choisissons de nous authentifier à l’aide d’une identité managée, qu’il faudra déclarer en tant que “Contributor” au niveau de l’access control (IAM) de la ressource Databricks.

Pour vérifier que l’authentification est bien réalisée, nous déroulons les listes “cluster version” et “cluster node type”, remplacées par un message “failed” si la connexion n’est pas valide.

Il s’agit là d’une autre bonne pratique : privilégier les identités managées car celles-ci ont un périmètre très bien défini, appartiennent à l’annuaire Azure Active Directory et ne demanderont pas de manipulation lors du déploiement continu, à l’inversion d’un token ou d’un password.

Lors de la création ou de la modification d’éléments dans le Studio Data Factory, nous pouvons sauvegarder ces changements à tout moment puis réaliser une publication à l’aide du bouton “Publish”.

L’action Publish correspond à un “commit – push” sur le repository lié.

Nous retrouvons dans le repository les deux branches évoquées lors de l’association à Azure DevOps.

La branche adf_publish contient en particulier deux fichiers nommés respectivement ARMTemplateForFactory.json et ARMTemplateParametersForFactory.json. Ceux-ci contiennent les noms propres à l’environnement et qui devront donc être remplacés lors du déploiement.

Le nom de la ressource ADF est le premier paramètre propre à l’environnement.

Afin de pousser la démonstration, nous ajoutons et démarrons un trigger dans le Studio Data Factory (nous verrons son importance un peu plus tard dans cet article).

Déployer par pipeline de release

Le pipeline de release convient à ce que nous cherchons à faire : déployer un environnement vers un autre, à partir d’un code versionné.

Nous démarrons le pipeline avec un “empty job”.

La première chose à faire est de lier l’artefact (le code versionné) au pipeline. Attention, c’est bien la branche main et non adf_publish qui est attendue ici.

La branche par défaut est la branche main.

Passons ensuite au paramétrage du stage, qui pourra être renommé par le nom de l’environnement cible (uat, prod…).

Nous recherchons dans la Market Place les tâches correspondant à Data Factory. Une installation sera nécessaire à la première utilisation.

Je vous recommande le produit développé par SQLPlayer. En effet, nous allons voir de multiples avantages (et un inconvénient…).

Autoriser le composant à accéder à une ressource Azure va demander de créer une service connection. Des droits suffisants sur Azure sont nécessaires pour réaliser cette opération.

Dans la boîte de dialogue ci-dessous, les champs attendus concernent l’environnement cible :

  • nom du groupe de ressource
  • nom de la ressource Azure Data Factory
  • chemin vers le répertoire des fichiers JSON (branche adf_publish)
  • nom de la région où est située la ressource
Le fichier de configuration sera précisé plus tard.

Pour indiquer le path du dossier contenant les fichiers du template ARM, nous utilisons une nouvelle boîte de dialogue, en prenant soin de descendre un cran au-dessous du repository.

Veillez également à cocher la case “Stop/Start triggers”. En effet, le déploiement ARM ne pourra avoir lieu si des triggers sont actifs. C’est ici un avantage de cette extension : il n’est pas nécessaire d’ajouter par exemple un script PowerShell réalisant le stop puis le start. Une limite de l’approche par PowerShell consiste dans le fait que l’intégralité des triggers sont redémarrés. Il serait donc nécessaire de supprimer les triggers qui ne sont pas utilisés, plutôt que de les mettre en pause.

A ce stade, une première release pourrait être lancée et elle déploiera à l’identique l’environnement de développement.

Modifier les paramètres propres à l’environnement

Nous avons évoqué ci-dessus les fichiers JSON de la branche adf_publish. Ceux-ci permettent de réaliser un déploiement de template ARM (Azure Resource Manager), qui correspond à une approche dite Infra as Code, propre à l’univers Azure de Microsoft.

Pour déployer les développements vers un autre environnement, nous devons remplacer certaines valeurs :

  • nom de la ressource Data Factory
  • chaines de connexion
  • identifiant et mot de passe
  • URL
  • certains paramétrages spécifiques (ex.: type et dimension d’un cluster Databricks)

Nous allons ici préciser toutes les modifications à apporter au moyen d’un fichier CSV qui sera stocké sur la branche main du repository, dans un répertoire deployment et nommé config-{stage}.csv où la valeur de stage indique l’environnement cible.

Voici un exemple de fichier CSV qui transforme les informations nécessaires pour changer de workspace Azure Databricks. Les noms de colonnes en première ligne doivent être respectés.

type,name,path,value
linkedService,AzureDatabricks,typeProperties.domain,"https://adb-xxx.x.azuredatabricks.net"
linkedService,AzureDatabricks,typeProperties.workspaceResourceId,"/subscriptions/xxx-xxx-xxx-xxx/resourceGroups/rg-dbx-prd/providers/Microsoft.Databricks/workspaces/dbxprd"

Une documentation complète, sur la page de l’extension, explique comment renseigner ce fichier. Nous serons particulièrement vigilants quant à la gestion des secrets (tokens, mots de passe, etc.). Une astuce pourra être d’utiliser des variables d’environnement dans ce fichier, elles-mêmes sécurisées par Azure DevOps. Une meilleure pratique consistera à utiliser un coffre-fort de secrets Azure Key Vault, lui-même déclaré comme un service lié.

EDIT : en cas d’utilisation d’un firewall sur la ressource Azure Key Vault, il sera nécessaire d’ajouter l’IP de l’agent DevOps à ce firewall. Or, cette adresse IP n’est pas fixe.

Nous pouvons maintenant lancer la release.

Nous vérifions enfin dans le Studio Data Factory que les éléments développés sont bien présents, en particulier les triggers. Ceux-ci sont alors dans l’état “démarré” ou “arrêté” de l’environnement de développement.

Cette approche pourrait être perturbante si tous les triggers de développement sont arrêtés (les faire tourner ne se justifie sans doute pas). Mais nous avons bénéficier d’une autre fonctionnalité pour améliorer notre déploiement.

Réaliser un déploiement sélectif

Une autre option de l’extension sera particulièrement intéressante : le déploiement sélectif. Une case de la boîte de dialogue permet de déclarer la liste des objets que l’on souhaite ou non déployer. La syntaxe est explicité sur la page GitHub de l’extension.

Par exemple, nous retirons le déploiement d’un autre service lié, qui ne sera pas utile en production.

Dans une architecture plus complète, intégrant un Data Lake, nous pouvons obtenir le schéma suivant. Chaque cluster Databricks disposera d’un secret scope, lié à un coffre-fort Azure Key Vault. Celui-ci contiendra la définition d’un principal de service (client ID et client secret) permettant de définir un point de montage vers la ressource Azure Data Lake gen2 de l’environnement.

Pour remplacer certains paramètres avancés des services liés, cet article pourra servir de ressource.

Webhooks pour MLFlow Registry

Une nouvelle fonctionnalité est disponible dans Databricks, en public preview, depuis février 2022 : les webhooks liés aux événements MLFlow. Il s’agit de lancer une action sur un autre service, déclenchable par API REST, lors d’un changement intervenant sur un modèle stocké dans MLFlow Registry (changement d’état, commentaire, etc.).

Cette fonctionnalité n’a rien d’anecdotique car elle vient mettre le liant nécessaire entre différents outils qui permettront de parfaire une approche MLOps. Mais avant d’évoquer un scénario concret d’utilisation, revenons sur un point de l’interface MLFlow Registry qui peut poser question. Nous disposons dans cette interface d’un moyen de faire évoluer le “stage” d’un modèle entre différentes valeurs :

  • None
  • Staging
  • Production
  • Archived

Ces valeurs résument le cycle de vie d’un modèle de Machine Learning. Mais “dans la vraie vie”, les étapes de staging et de production se réalisent sur des environnements distincts. Les pratiques DevOps de CI/CD viennent assurer le passage d’un environnement à un autre.

MLFlow étant intégré à un workspace Azure Databricks, ce service se retrouve lié à un environnement. Bien sûr, il existe quelques pistes pour travailler sur plusieurs environnements :

  • utiliser un workspace Azure Databricks uniquement dédié aux interactions avec MLFlow (en définissant les propriétés tracking_uri et registry_uri)
  • exporter des experiments MLFlow Tracking puis enregistrer les modèles sur un autre environnement comme décrit dans cet article

Aucune de ces deux approches n’est réellement satisfaisante car manquant d’automatisation. C’est justement sur cet aspect que les webhooks vont être d’une grande utilité.

Déclencher un pipeline de release

Dans Azure DevOps, l’opération correspondant au déploiement sur un autre environnement se nomme “release pipeline“.

Pour simplifier la démonstration, nous définissions ici une pipeline contenant uniquement un tâche de type bash, réalisant un “hello world”.

Dans un cas d’utilisation plus réalise, nous pourrons par exemple déployer un service web prédictif contenant un modèle passé en production, grâce à une ressource comme Azure Machine Learning.

Nous vérifierons par la suite qu’une modification dans MLFlow Registry déclenche bien une nouvelle release de cette pipeline.

Précisons ici un point important : les webhooks Databricks attendent une URL dont l’appel sera fait par la méthode POST. Il existe une API REST d’Azure DevOps qui permettrait de piloter la release mais le paramétrage de celle-ci s’avère complexe et verbeux.

Nous passons donc un ordonnanceur intermédiaire : Azure Logic App. En effet, cette ressource Azure nous permettra d’imaginer des scénarios plus poussés et bénéficie également d’une très bonne intégration avec Azure DevOps. Nous définissons le workflow ci-dessous comprenant deux étapes :

  • When a HTTP request is received
  • Create a new release
Veillez à bien sélectionner la méthode POST.

Le workflow nous fournit alors une URL que nous utiliserons à l’intérieur du webhook. Il ne nous reste plus qu’à définir celui-ci dans Databricks. La documentation officielle des webhooks se trouve sur ce lien.

Il existe deux manières d’utiliser les webhooks : par l’API REST de Databricks ou bien par un package Python. Nous installons cette librairie databricks-registry-webhooks sur le cluster.

Depuis un nouveau notebook, nous pouvons créer le webhook, tout d’abord dans un statut “TEST_MODE”, en l’associant à un modèle déjà présent dans MLFlow Registry.

Ceci nous permet de tester un appel par la commande .test_webhook() qui attend en paramètre l’identifiant du webhook préalablement créé. Une réponse 202 nous indique que cette URL est acceptée (bannissez les réponses 305, 403, 404, etc.).

Le webhook peut se déclencher sur les événements suivants :

  • MODEL_VERSION_CREATED
  • MODEL_VERSION_TRANSITIONED_STAGE
  • TRANSITION_REQUEST_CREATED
  • COMMENT_CREATED
  • MODEL_VERSION_TAG_SET
  • REGISTERED_MODEL_CREATED

Le webhook peut ensuite être mis à jour au statut ACTIVE par la commande ci-dessous.

Il ne reste plus qu’à réaliser une des actions sélectionnées sur le modèle stocké dans MLFlow Registry et automatiquement, une nouvelle release Azure DevOps se déclenchera.

Maintenant, il n’y a plus qu’à laisser libre cours à vos scénarios les plus automatisés !

Ci-dessous, le schéma résumant les différentes interactions mises en œuvre.

Utiliser les commandes Git avec les instances de calcul Azure ML

Les instances de calcul (“compute instance“) d’Azure Machine Learning sont très pratiques pour les Data Scientists souhaitant travailler dans un environnement Jupyter, déjà configuré, et disposant du SDK azureml.

Cette simplicité ne doit pas faire oublier la bonne pratique de la gestion de versions du code, au moyen par exemple d’un outil comme Git.

Il existe des extensions pour JupyterLab qui permettent de faciliter les interactions avec un repository git. La recherche du mot-clé “git” nous en montre quelques-unes dans l’image ci-dessous.

Malheureusement, à ce jour (janvier 2022), des incompatibilités de versions semblent rendre impossible l’installation de jupyterlab-git, qui nécessite en particulier une version de JupyterLab >= 3.0.

L’installation va donc échouer.

Nous devrions utiliser la version de l’extension définie pour JupyterLab 2.x disponible dans cette branche : https://github.com/jupyterlab/jupyterlab-git/tree/jlab-2, à l’aide des commandes ci-dessous :

pip install git+https://github.com/jupyterlab/jupyterlab-git.git@jlab-2
conda install -c conda-forge jupyterlab-git

Après lancement d’un “Rebuild”, nous obtenons une erreur 500.

*

Nous allons donc nous en remettre aux lignes de commandes Git. Pour cela, il sera nécessaire d’ouvrir un terminal sur l’instance de calcul. Nous vérifions tout d’abord la bonne installation de git ainsi que sa version.

git --version
Git en version 2.33.0 est disponible

Nous allons créer ici un répertoire local à l’instance de calcul, que nous connecterons ensuite à un repository à distance, hébergé par Azure Repos. Ce répertoire contient un premier notebook, fichier de format .ipynb.

mkdir my_git_repo
git init

Il sera possible de renommer la branche master en main, selon les standards actuels, par la commande :

git branch -M main

Nous allons maintenant définir le repository à distance dont nous pourrons obtenir l’URL dans l’interface Azure DevOps, en cliquant sur le bouton “Clone”.

git remote add origin <Azure Repos URL>

Il ne reste plus qu’à lancer le trio de commandes qui permettront successivement d’ajouter les fichiers du dossier à la liste de ce qui doit être versionné, de “commiter” cette version avec un commentaire puis de pousser les fichiers vers le répertoire à distance.

git add .
git commit -m "initialize repository"
git push -u origin --all

Lors de la commande git push, il sera demandé à l’utilisateur de s’authentifier. Pour cela, nous pouvons obtenir un password dans la fenêtre vue précédemment : “Clone Repository”.

Les logs de la commande nous montrent que les fichiers sont bien uploadés sur le repository à distance.

Nous vérifions également la présence des fichiers dans l’interface graphique d’Azure Repos.

Rappelons enfin que les notebooks sont très verbeux lorsqu’on les regarde sous l’angle de fichiers texte que le gestionnaire de versions cherchera à comparer. Mieux vaut limiter le nombre de cellules et privilégier l’appel à des librairies externes, par exemple packagées dans un fichier wheel qui sera mis à disposition par Azure Feed (voir cet article).

Verbosité des notebooks pour un gestionnaire de versions

Gestion des repos Git dans Azure Databricks

Au mois de mars 2021, une nouvelle fonctionnalité vient accompagner les espaces de travail Azure Databricks : les repos Git.

Pour l’activer, nous devons passer par la console d’administration, cliquer sur “Enable” puis rafraîchir la page.

Une nouvelle icône apparaît alors dans le menu latéral.

Jusqu’ici, le lien avec un gestionnaire de versions se faisait individuellement, par le biais de de la fenêtre “user settings“, comme expliqué dans cet article.

Chaque notebook devait alors être relié manuellement à un repository. Pour des actions de masse, il fallait employer les commandes en ligne (Databricks CLI).

Pour démarrer, nous cliquons sur l’icône Repos et le menu propose un bouton “Add Repo“.

Une boîte de dialogue s’ouvre alors.

Il faut comprendre ici qu’il s’agit de créer un repository local au sens de l’espace de travail (“Add Git remote later”) ou bien de cloner un reporemote“, c’est-à-dire relié à un des fournisseurs présents dans la liste. Ce dernier peut être vide si le projet débute.

Nous créons ici un premier notebook dans ce nouveau repository local.

Il faut alors s’habituer à une nouvelle façon de travailler. Les notebooks ne sont plus visibles depuis l’icône “Workspace”. Le lien “Git: Synced” que l’on trouvait en cliquant sur “Revision history” n’existe plus. Mais une icône Git apparaît maintenant en haut à gauche du notebook.

L’historique des modifications s’enregistre toujours bien automatiquement et des retours arrières (“Restore this version”) sont possibles.

Sans réaliser l’association avec un gestionnaire de versions tiers, nous ne disposons pas d’autres fonctionnalités.

Un clic sur l’icône Git nous propose de réaliser cette association, ici avec le service Azure DevOps.

Attention, on ne peut attacher qu’un dépôt qui ne contient pas de commit.

Pour un repository de type privé, il va être nécessaire de réaliser des manipulations de sécurité.

Il s’agit de fournir un Personal Access Token (PAT) généré depuis le gestionnaire de versions.

Une fois la configuration réalisée, la boîte de dialogues ci-dessous apparaît alors.

Nous retrouvons des notions familières aux utilisateurs de Git : branche (master ou main), pull, commit & push.

Il sera nécessaire (c’est même obligatoire) de saisir un résumé (summary) des modifications pour pouvoir cliquer sur le bouton “Commit & Push“.

Attention, vous pourriez rencontrer l’erreur suivante.

En effet, une liste de repositories autorisés doit être donné dans la console d’administration (seulement si cette option a été activée). Ce scénario s’inscrit dans le cadre de la licence Premium de Databricks où tous les utilisateurs ne sont pas obligatoirement administrateurs.

Notons que l’extension du fichier est .py mais il s’agit bien d’un notebook Databricks. Le contenu de celui-ci, en particulier les cellules Markdown, est arrangée pour “ressembler” à un script Python.

Attention, une commande comme display est propre à l’environnement des notebooks Databricks.

Si le repository est aussi utilisé pour conserver d’autres éléments que ceux propres à Databricks, nous verrons apparaître les dossiers du repo dans le menu de navigation de Databricks. Il pourrait être tentant de les supprimer depuis Databricks (ils sembleront vides puisque seules quelques extensions .py et .ipynb sont affichées) mais ne le faîtes surtout pas !

Une action “removed” serait alors être proposée au prochain commit. Il sera bien sûr possible de l’éviter en basculant sur “Discard changes“.

Ici, le repo est partagé avec des pipelines Azure Data Factory.

Le répertoire “vide” va alors réapparaître dans l’arborescence.

En conclusion, il serait préférable de dédier un repository à Databricks pour éviter les confusions.

EDIT 2021-08-23 :

La fonctionnalité originelle de liaison avec un repository Git est dorénavant une “legacy feature” et il est recommandé de ne plus l’utiliser.

En poussant les tests, on obtient un blocage pour commiter depuis Databricks lors du scénario suivant :

  • publication réalisée depuis un outil tiers dans le repository partagé (par exemple, création d’un nouveau pipeline dans Azure Data Factory)
  • modifications d’un notebook dans Azure Databricks
  • sauvegarde de ces modifications
  • tentative de commit & push depuis Databricks

Nous obtenons alors le message d’erreur ci-dessous.

Il est indispensable de réaliser une action Pull avant de modifier les notebooks et nous recommandons de le faire avant même de débuter vos travaux sous Databricks.

Un message d’alerte s’affiche alors.

Toutes les limitations liées à la fonctionnalité “Repos” dans Azure Databricks sont données sur ce lien officiel de la documentation Microsoft.

Wheel Python et Azure feed : centralisez et distribuez vos packages

Assurer la qualité d’un développement Python passe par le fait de packager du code (des classes, des fonctions…) dans des modules que l’on pourra ensuite simplement installer dans un nouvel environnement (avec un classique pip install...) et importer dans des scripts à l’aide des syntaxes habituelles que sont import package ou from package import function.

Créer le package Wheel

C’est bien sûr la toute première étape une fois que notre code a été écrit. Et bien écrit, c’est-à-dire en respectant par exemple la norme PEP8 (nous en reparlerons) et en intégrant des docstrings dans les fonctions.

Nous aurons besoin de quelques librairies, dont bien évidemment wheel et nous profiterons d’un environnement virtuel pour les installer (commandes Windows ci-dessous pour créer, activer et configurer cet environnement).

python -m venv env
env\Scripts\activate.bat
python -m pip install -U setuptools wheel twine

Notre exemple se basera sur du code simple générant un pandas dataframe avec des nombres aléatoires. Notons au passage (pour les puristes :)) que ce code poserait problème dès que nb_col dépasserait la valeur 26.

import pandas as pd
import numpy as np
import string


def generate_df(nb_col, nb_row):
     """
         Generate a pandas DataFrame
         with nb_col columns and nb_row rows
     """
     alphabet_string = string.ascii_uppercase
     columns_string = alphabet_string[:nb_col]
     columns_list = list(columns_string)
     df = pd.DataFrame(np.random.randint(0, 100, size=(nb_row, nb_col)), columns=columns_list)
 return df

Le script, nommé ici my_function.py, doit se trouver dans une arborescence de fichiers définie comme suit :

my_package
├── LICENSE
├── README.md
├── my_pkg
│   └── __init__.py
│   └── my_function.py
├── setup.py
└── tests

Le fichier __init__.py est tout simplement un fichier vide, seul le nom est obligatoire. Ce fichier doit être dans le répertoire dédié aux fichiers développés (sous-répertoire du répertoire principal). Il peut toutefois contenir également des fonctions qui seront chargées à l’appel du module. Pour réaliser des tests simples, voici ce qu’il contient.

def function_init():
     print('Successfully imported Init.py')


 def print_hello_iam(name):
     print(f'Hello, I am {name}')

Nous complétons avec un fichier d’informations README au format Markdown, un fichier texte contenant la licence (ci-dessous) et un répertoire, éventuellement vide dans un premier temps, qui contiendra des tests.

Copyright (c) 2018 The Python Packaging Authority
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.

Le dernier élément nécessaire est le fichier setup.py dont un modèle de contenu peut être retrouver sur cette page. Il faudra en particulier y préciser le nom souhaité pour le package.

Pour générer l’archive de distribution, nous lançons la commande suivante, au niveau du répertoire contenant le fichier setup.py :

python setup.py sdist bdist_wheel

Un répertoire dist est alors créé et contient deux fichiers : le fichier .whl et une archive .tar.gz.

Le nom du package wheel est normalisé de la sorte :

{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl

EDIT : vérifiez également que le dossier finissant par “egg-info” contient bien les fichiers suivants.

Le fichier top_level.txt contient en particulier le nom qui servira à appeler le package dans les syntaxes du type from package import …

Réalisons tout de suite un premier test pour valider que notre package est bien construit. Au niveau du dossier contenant le fichier .whl, nous lançons dans un terminal la commande suivante, depuis le répertoire dist :

python -m pip install my_pkg_byPaulPETON-0.0.1-py3-none-any.whl

Puis dans un prompt Python, nous vérifions que l’import des nos méthodes est bien reconnu :

from my_pkg.__init__ import *
from my_pkg.my_function import *

Tester le package depuis un cluster Azure Databricks

Nous allons maintenant installer manuellement le fichier .whl disponible dans le répertoire dist sur un cluster Databricks. Nous commençons par le télécharger sur un cluster interactif démarré.

Dès lors, les fonctions deviennent disponibles dans un notebook.

Notez au passage le “dark mode” des notebooks Databricks 🙂

Charger le package sur un feed Azure DevOps

Nous n’allons bien sûr pas utiliser le fichier .whl localement, celui-ci doit être hébergé sur une plateforme accessible de tous les développeurs de l’équipe.

Tout comme le code est placé dans un dépôt (repository) d’un gestionnaire de versions comme Azure DevOps, nous allons placer le package, ici nommé artefact, dans un feed, accessible aux personnes autorisées.

Nous réalisons tout d’abord la création du feed. Il sera ici public, donc accessible au travers d’Internet. Pour une pratique en entreprise, un projet privé est bien évidemment recommandé.

Le feed est maintenant bien actif.

Attention, il est préférable de vérifier vos options de facturation (dans le menu “Organization Settings”). Un compte gratuit présentera des limites de taille pour l’usage des artefacts stockés dans le feed.

Vérifions également le stockage associé aux artefacts (ici au niveau projet, puisque c’est le périmètre qui a été défini).

Il s’agit ensuite d’automatiser la création du package wheel par un pipeline d’intégration continue. Nous démarrons à partir d’un “starter pipeline”.

Le code complet de ce pipeline, enregistré automatiquement dans un fichier azure-pipelines.yml, ajouté au repository, est disponible par exemple sur ce GitHub (veillez à adapter si besoin la version de Python attendue ainsi que le nom de la branche – main pour master – si vous souhaitez un lancement automatique de la pipeline à chaque commit).

Ce script YAML reprend l’exécution du fichier setup.py pour créer l’artefact dans un conteneur dédié.

Nous avons ensuite besoin d’un pipeline de release qui déposera l’artefact dans le feed qui servira de point de distribution.

A partir d’un modèle vide de pipeline de release (“empty job“), nous attachons le résultat du pipeline de build réalisé précédemment.

Nous ajoutons trois activités qui sont :

  • Python twine upload authenticate
  • Command line pour l’installation de Twine
  • Command line pour la publication de l’artefact

L’étape 1 s’authentifie auprès du feed, dont il faut saisir le nom.

L’étape 2 réalise l’installation du package Twine par la commande pip install.

L’étape 3 utilise Twine pour télécharger le package.

Voici la ligne de code nécessaire :

twine upload -r <feed_name> --config-file $(PYPIRC_PATH) d:\a\r1\a\_<project_name>\dist\dist*

Cette étape est sans doute la plus délicate. En cas d’erreur pour localiser ce répertoire, je vous conseille de regarder les logs de la première étape du pipeline de build afin de visualiser l’artefact dans son arborescence.

Utiliser le package depuis un nouveau script

Nous allons avoir besoin d’une nouvelle librairie : artifacts-keyring..

pip install artifacts-keyring

Ensuite, nous pouvons faire appel à la commande classique “pip install” pointant vers notre feed.

pip install packageName --index-url https://pkgs.dev.azure.com/<organization_name>/<project_name>/_packaging/<feed_name>/pypi/simple/

Le “project_name” est facultatif si le feed est déclaré au niveau de l’organisation. Il sera possible de remplacer par l’option –index-url par –extra-index-url comme indiqué dans cet article mais ceci est à réaliser seulement si l’on utilise la méthode proposée dans le portail Azure DevOps : la création d’un fichier pip.ini.

En cas de project privé, une authentification sera alors demandée, de manière interactive, au travers d’un navigateur.

Ce mode n’est bien sûr pas envisageable pour une installation devant s’exécuter de manière autonome. Nous allons donc générer un Personal Access Token (PAT) qui permettra de nous authentifier. Ce jeton s’obtient dans Azure DevOps.

Nous donnons les droits au niveau “packaging“.

Dans l’environnement d’exécution, une variable d’environnement sera nécessaire pour désactiver l’authentification interactive et tenir compte du PAT. Cela se fait par exemple dans Windows par le menu “Modifier les variables d’environnement système” ou dans un dockerfile avec la ligne ci-dessous.

VAR ARTIFACTS_KEYRING_NONINTERACTIVE_MODE=true

Il sera alors possible d’appeler le package avec la syntaxe suivante :

pip install packageName --extra-index-url=https://<PAT>@pkgs.dev.azure.com/<organization_name>/<project_name>/_packaging/<feed_name>/pypi/simple/

Ca y est ! Le package est maintenant installé et nous pouvons nous appuyer sur les fonctions qu’il contient.

from my_pkg.my_function import *

En conclusion

Nous avons mis en place ici une approche dédiée à l’industrialisation d’un développement, accompagné d’un processus CI/CD. Cela demande un investissement en temps et en prise en main de cette procédure mais c’est une garantie de stabilité et de non régression sur le livrable en production.

Utiliser (encore) des notebooks sous Azure

Au 15 janvier 2021, le service Azure Notebooks disparaît complètement. Nous cherchons donc une alternative en version SaaS, toujours sur Azure et celle-ci se trouve dans le portail Azure Machine Learning.

Un article d’introduction à ce service est disponible dans les archives de ce blog.

Quelques nouveautés sont disponibles et annoncées à l’ouverture d’un premier notebook. Nous reviendrons sur ces nouveautés dans la suite de cet article.

Nous pouvons alors nommer un nouveau fichier, à l’extension classique .ipynb.

Nous retrouvons ce qui caractérise les notebooks : des cellules de code, suivies par les sorties des différentes instructions, ou bien des cellules de type formaté en Markdown.

Il n’est pourtant pas possible d’exécuter du code immédiatement, il faut au préalable relier ce notebook à une instance de calcul, déjà définie dans le portail (menu “Compute”).

Dans le menu dédié aux ressources de calcul, nous retrouvons des raccourcis vers l’utilisation d’une interface Jupyter, JupyterLab ou encore RStudio.

La nécessité d’une instance de calcul rend ce service payant, et facturé au temps d’utilisation de la machine virtuelle définie. Il sera très important de ne pas oublier d’éteindre la machine après utilisation, il n’existe à ce jour aucun système natif d’extinction planifiée !

Les utilisateurs férus de Visual Studio Code (VSC) peuvent travailler à distance avec l’environnement de calcul d’Azure Machine Learning. En installant conjointement les extensions Python et Jupyter, il était déjà possible de travailler avec un notebook (fichier reconnu par son extension .ipynb) dans l’IDE de Microsoft. En ajoutant l’extension Azure Machine Learning, nous pouvons nous connecter à la ressource Azure.

Les “objets” de l’espace Azure ML sont alors visibles depuis le menu Azure de VSC.

Il est possible de réaliser des actions à distance sur les instances de calcul.

Par défaut, un notebook dans VSC utilise le serveur Jupyter local. Mais il est possible de désigner un serveur à distance (“remote“) en cliquant sur le bouton en haut à droite indiquant “Jupyter Server : local”.

Nous obtenons la boîte de dialogues ci-dessous.

La succession de choix proposés permettra d’associer le notebook à une instance de calcul Azure ML.

Une composante indispensable de l’approche par le code est le gestion du versionnig, par un service compatible avec Git (GitHub, GitLab, Bitbucket, Azure DevOps, etc.), comme décrit sur ce lien.

Nous allons bénéficier ici de l’affichage en terminal présentés parmi les nouvelles (janvier 2021) fonctionnalités disponibles.

Nous devons tout d’abord créer une clé SSH au moyen de la commande ci-dessous :

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

La clé SSH doit être enregistrée dans le chemin /home/azureuser/.ssh qui est spécifique à l’instance de calcul (valider par Entrée le prompt proposé après la commande précédente). Il est alors possible de saisir une passphrase, facultative mais fortement conseillée. Un fichier id_rsa.pub est ainsi créé et son contenu peut être copié (ctrl + inser).

Nous utiliserons dans cet article Azure DevOps, où nous allons pouvoir saisir cette clé publique.

De retour dans le terminal, nous pouvons lancer la commande de test :

ssh -T git@ssh.dev.azure.com

Le résultat attendu est le suivant :

Depuis le repository Azure DevOps, nous obtenons la syntaxe SSH qui viendra compléter une instruction git remote, lancée depuis le terminal.

Si cela n’a pas fait, dans le terminal et depuis le chemin voulu, saisir la commande git init pour initialiser le répertoire. Définir également l’utilisateur qui réalisera les instructions de commit :

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

La commande git remote add origin peut alors être exécutée avec succès.

Il pourra être nécessaire de passer la commande git add . pour ajouter l’intégralité du contenu dans ce qui doit être géré par git, puis un git commit.

De même, le repository Azure doit avoir été initialisé, par exemple avec un fichier readme.md et disposer ainsi d’une branche (main ou master).

git push -u origin --all

Enfin, un rappel des commandes Git indispensables est donné ici : Git · GitHub

Une autre nouvelle intéressante consiste à “nettoyer” le notebook avant de le publier. C’est en effet un reproche souvent fait aux notebooks : ceux-ci mélangent code, sorties et commentaires et ne sont pas optimaux pour les tests unitaires et le déploiement dans un environnement de production.

La fonctionnalité “Gather” (documentation officielle) sur une cellule permet de créer un autre notebook qui conservera uniquement les cellules de code dépendant de la cellule sélectionnée.

Déploiement continu avec Azure Synapse Analytics – partie 1

S’il était bien une fonctionnalité attendue pour la nouvelle (2020) version d’Azure Synapse Analytics, c’était la gestion du versionning et du déploiement des scripts créés dans l’outil.

Depuis la disponibilité générale (fin novembre 2020), nous trouvons un menu Source control dans la fenêtre Manage.

Nous pouvons définir la Git configuration, c’est-à-dire l’outil qui servira à l’enregistrement des développements et à leur versionning. Nous pouvons choisir entre Azure DevOps ou GitHub.

Nous choisissons ici Azure DevOps mais la partie GitHub est documentée sur ce lien officiel.

Nous devons disposer au préalable d’un projet DevOps et d’un repository dont l’URL devra être renseignée dans Synapse.

The Dev Ops repository link must start with http or https and be in DevOps format. For example: https://account.visualstudio.com/project/_git/repository

Dès lors, le dépôt se remplie avec les premiers éléments faisant partie de la ressource Synapse Analytics.

Dans le studio Synapse, nous disposons de plusieurs actions :

La validation permet de contrôler le développement réalisé et des erreurs seront remontées le cas échéant.

Le bouton Commit permet de “sauvegarder” le développement réalisé sur la branche active. Il est recommandé de ne pas développer directement sur la branche master.

Le bouton Publish de l’interface Synapse permet de créer ou mettre à jour la branche workspace_publish.

Il est donc logique de les utiliser successivement, dans cet ordre. L’action Publish réalisera une sauvegarde si nécessaire. A noter que les sorties (outputs) des cellules des notebooks sont effacées au moment de la publication. C’est une bonne pratique pour le versionning mais peut déstabiliser l’utilisateur souhaitant conserver l’affichage de ses résultats sans relancer le notebook complet.

Au fur et à mesure de l’utilisation de Synapse Analytics, de nouveaux répertoires vont se créer sur la branche collaborative.

Manquent ici triggers, credentials et Spark job definiition

Dans la branche workspace_publish, nous retrouvons deux fichiers JSON dans une logique complètement similaire à celle d’Azure Data Factory, permettant un déploiement de type “template ARM“.

Le fichier TemplateParametersForWorkspace.json contient en particulier le nom des informations de connexion (nom du workspace, URL, etc.) et les mots de passe, secrets ou token, sont effacés. Il faudra écraser ou renseigner ces valeurs lors du déploiement dans un autre environnement.

Dans une seconde partie, nous développerons les approches possibles pour le déploiement continue à l’aide d’Azure Pipelines.