[MLOps] Azure Machine Learning のイベントを外部に送信して GitHub/Azure DevOps パイプラインをトリガーする

# はじめに
[Azure Machine Learning](https://azure.microsoft.com/products/machine-learning/) (AML) を使うと誰でも簡単にクラウド上に機械学習環境を用意でき、モデル作成や推論用 API をスピーディに公開することができます。この AML ですが、[MLOps](https://www.youtube.com/watch?v=KdbuWpRGwNk) を実現するプラットフォームとしても有用でして、[Azure DevOps](https://azure.microsoft.com/products/devops) や [GitHub Actions](https://github.co.jp/features/actions) などの CI/CD ツールと簡単に連携することができます。今回は AML のプレビュー中の機能である外部へのイベント送信機能とその実装方法について紹介します。

# AML から送信できるイベント
では早速、AML からどんなイベントを送信することができるのでしょうか。[こちら](https://learn.microsoft.com/azure/machine-learning/how-to-use-event-grid)に示すように、現時点では 5 つのイベントを利用することができます。ふむふむ、MLOps で使えそうですね。

|  イベントの種類  |  説明  |
| ---- | ---- |
|  Microsoft.MachineLearningServices.RunCompleted  |  機械学習実験の実行が完了したときに発生します  |
|  Microsoft.MachineLearningServices.ModelRegistered  |  機械学習モデルがワークスペースに登録されたときに発生します  |
|  Microsoft.MachineLearningServices.ModelDeployed  |  1 つ以上のモデルを持つ推論サービスのデプロイが完了したときに発生します  |
|  Microsoft.MachineLearningServices.DatasetDriftDetected  |  2 つのデータセットのデータ ドリフト検出ジョブが完了したときに発生します  |
|  Microsoft.MachineLearningServices.RunStatusChanged  |  実行状態が変化したときに発生します  |


# MLOps リファレンスアーキテクチャと今回のテーマ
この [MLOps リファレンスアーキテクチャ](https://learn.microsoft.com/azure/architecture/reference-architectures/ai/mlops-python)は、エンジニアが既に慣れているツールを使用して、プロジェクトのさまざまな段階をエンドツーエンドで自動化する方法を示しています。リファレンスアーキテクチャの図から、今回のテーマとなるデプロイ（リリース）パイプラインの部分を取り出したのが以下の図になります。

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/f01b9dfb-a4eb-e8a9-448c-20307b5c92e4.png)

今回は新しいモデルが利用可能になるたびに、デプロイパイプラインがトリガーされる仕組みを実装します。AML モデルレジストリに新しく登録されるモデルは、リリース成果物として扱われます。AML と Event Grid との連携によって、以下の図のように AML ワークスペース内で発生したイベントを外部イベントハンドラに送信することができます。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/caa0deb0-ac90-ebcb-c4bd-84767c1c4f63.png)

ちなみにモデルのトレーニングは AML の[機械学習パイプライン](https://learn.microsoft.com/azure/machine-learning/how-to-create-component-pipelines-cli)にて、モデルの管理は AML [モデルレジストリ](https://learn.microsoft.com/azure/machine-learning/how-to-manage-models?tabs=use-local%2Ccli)を利用しています。

# 事前準備
1. Azure Machine Learning [ワークスペース](https://learn.microsoft.com/azure/machine-learning/quickstart-create-resources)
1. Azure CLI を使用して[サービスプリンシパル](https://learn.microsoft.com/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#use-the-azure-login-action-with-a-service-principal-secret)が作成できること
1. Azure LogicApps および Azure Event Grid リソースを作成できること
    Azure サブスクリプションで 一度も LogicApps/Event Grid を使用したことがない場合は、リソース プロバイダーを登録する必要があります。[こちらの手順](https://learn.microsoft.com/azure/azure-resource-manager/management/resource-providers-and-types)にしたがって 以下のリソースを Registered （登録済み）にしてください。
    `Microsoft.EventGrid`, `Microsoft.Web`
1. Github アカウント or Azure DevOps
1. AML CLI v2 の最新かつエラーの出ないバージョン(2.10.0)を利用

:::note warn
無料アカウントもしくは Azure Pass で Azure DevOps を使用している場合、パイプライン実行時に[申請](https://learn.microsoft.com/azure/devops/release-notes/2021/sprint-184-update)が必要となり承認に数日かかります。すでにパイプラインが実行可能な方のみ選択してください。
:::

今回は GitHub Actions と Azure DevOps どちらでも試せるように作成しています。

# 1. AML から GitHub Actions のワークフローをトリガーする
## 1.1. Github Actions を使用したデプロイパイプライン構築
1. GitHub にリポジトリを作成します。リポジトリファイルの準備のしかたは省略しますが、[こちら](https://learn.microsoft.com/azure/machine-learning/how-to-deploy-online-endpoints?tabs=azure-cli)が参考になります。
1. Azure CLI を使用して[サービスプリンシパルを作成](https://learn.microsoft.com/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#use-the-azure-login-action-with-a-service-principal-secret)し、クレデンシャル`AZURE_CREDENTIALS` を シークレットとして GitHub に保存します。
1. GitHub Actions ワークフロー中で使用するリポジトリシークレット `AZURE_RESOURCE_GROUP_NAME`, `AZURE_ML_WORKSPACE_NAME` も同様に登録します。
1. GitHub Actions で デプロイパイプラインのワークフローを作成します。例：
    ```yaml
    name: Azure ML Trigger Deploy Pipeline
    
    on:
     repository_dispatch:
       types: [deploy_pipeline_action]
       
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v3
          - name: Log in with Azure
            uses: azure/login@v1
            with:
              creds: '${{secrets.AZURE_CREDENTIALS}}'
          - name: Install ML extension for az command
            run: az extension add --name ml --version 2.10.0
          - name: Replace modelName and modelVersion in YAML
            run: yq -i '.model = "azureml:${{github.event.client_payload.model_name}}:${{github.event.client_payload.model_version}}"' managed_deployment.yml
          - name: Trigger AML Pipeline Job
            run: az ml online-deployment update -f managed_deployment.yml -g ${{ secrets.AZURE_RESOURCE_GROUP_NAME }} -w ${{ secrets.AZURE_ML_WORKSPACE_NAME }}
    ```
1. 作成したワークフローのトリガーは `on: repository_dispatch` [イベント](https://docs.github.com/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-dispatch-event) を使用して、外部から起動できるようにします。
1. `on: repository_dispatch` イベントは、`${{github.event.client_payload.model_name}}` のようにして送信されたパラメータを抽出することができます。

##  1.2. Logic Apps によるモデル登録トリガーの作成
1. [Azure Portal](https://ms.portal.azure.com/) を開き Azure Machine Learning ワークスペースに移動し、[こちら](https://learn.microsoft.com/azure/machine-learning/how-to-use-event-grid#example-send-email-alerts)を参考にLogic Apps 連携によるイベント受信トリガーを作成します。
1. Azure Machine Learning から送信される ModelRegistered イベントのデータは JSON 形式なので次のステップは「[JSON の解析](https://learn.microsoft.com/azure/logic-apps/logic-apps-perform-data-operations#parse-json-action)」アクションを作成します。JSON のスキーマには以下をコピー&ペーストします。[ドキュメント](https://learn.microsoft.com/azure/event-grid/event-schema-machine-learning?tabs=event-grid-event-schema#example-events)にあるスキーマではうまく動きませんでした。
    ```json
    {"properties":{"data":{"properties":{"modelName":{"type":"string"},"modelProperties":{"properties":{"azureml.artifactPrefix":{"type":"string"},"flavors":{"type":"string"},"flavors.python_function":{"type":"string"},"flavors.sklearn":{"type":"string"},"mlflow.modelSourceUri":{"type":"string"},"model_json":{"type":"string"}},"type":"object"},"modelTags":{"properties":{},"type":"object"},"modelVersion":{"type":"string"}},"type":"object"},"dataVersion":{"type":"string"},"eventTime":{"type":"string"},"eventType":{"type":"string"},"id":{"type":"string"},"metadataVersion":{"type":"string"},"subject":{"type":"string"},"topic":{"type":"string"}},"type":"object"}
    ```
1. ModelRegistered イベントの JSON データから登録された**モデル名**と**モデルバージョン番号**を取得し、それらの値をパイプラインエンドポイントのパラメータとして指定して「[リポジトリ ディスパッチ イベントを作成する](https://learn.microsoft.com/connectors/github/#%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA-%E3%83%87%E3%82%A3%E3%82%B9%E3%83%91%E3%83%83%E3%83%81-%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B-(%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC))」アクションを作成します。初めて作成したときは GitHub へのサインインとアプリ承認が必要です。これによって Azure Logic Apps から OAuth 経由で GitHub へアクセスすることができるようになります。
    ![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/e18c27ea-eb46-8fe5-2071-f4abe08148ed.png)
    - イベント名: `deploy_pipeline_action`
    - イベント ペイロード: 以下の未完成の JSON をコピー&ペーストし、`modelName` と `modelVersion` は画像のように動的メニューから選択します。前のステップの「JSON の解析」アクションが JSON をパースしてくれているので、このように特定のキーを当てはめることができるようになります。
    ```json
    {
     "model_name": ,
     "model_version": 
    }
    ```
    ちなみに `ModelRegistered` イベントはモデルのタグやプロパティ情報も取得できます。
1. AML で[モデルを登録](https://learn.microsoft.com/azure/machine-learning/how-to-manage-models-mlflow#registering-new-models-in-the-registry)して動作を検証します。
1. Logic Apps の実行履歴は、概要ページから確認することができます。
    ![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/5babe32d-4e19-090d-e643-34496cfe9287.png)
1. 状態が「正常」であればデプロイパイプラインが起動され、パイプラインジョブが実行されます。実行履歴画面から、ステップごとに実際に流れたデータを確認することができます。以下の図では、AML から ModelRegistered イベントと共に送られてきたモデル名とモデルバージョン番号を正常に Github Actions へ送信できたことを示しています。
    ![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/442bfcf7-2750-2b82-07ae-6f8908e88cc5.png)
1. Github Actions 側のデプロイパイプラインが正常に起動していることを確認します。
    ![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/a1965508-0b0e-c185-ff3a-494351df4064.png)
1. Azure Machine Learning で新しいモデルがエンドポイントにデプロイされたことを確認できれば完了です。
    ![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/3f55d849-5aa0-f0ad-a7a0-d9b1f53c7053.png)

# 2. AML から Azure DevOps のパイプラインをトリガーする
## 2.1. Azure DevOps を使用したデプロイパイプライン構築
[こちら](https://learn.microsoft.com/azure/machine-learning/how-to-devops-machine-learning)を参考にパイプラインを構築します。
1. Azure Pipelines にサインインし組織を作成
1. Azure Resource Manager の接続を作成。`machine-learning-connection`
1. Azure Repos Git リポジトリの作成（コードは GitHub ではなく、Azure Repos Git に配置します）
1. 変数を作成。`RESOURCE_GROUP`, `AML_WORKSPACE_NAME`
1. YAML パイプラインを構築。例：
    ```yaml
    trigger: none  
    pr: none 
    
    parameters:  
      - name: "model_name"  
        type: string  
        displayName: "ModelName"  
      - name: "model_version"  
        type: number  
        default: 0  
        displayName: "ModelVersion"  
    
    pool:
      vmImage: ubuntu-latest
    
    steps:
    - task: AzureCLI@2
      displayName: Azure CLI
      inputs:
        azureSubscription: machine-learning-connection
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
          az extension add --name ml --version 2.10.0
          yq -i '.model = "azureml:${{parameters.model_name}}:${{parameters.model_version}}"' managed_deployment.yml
          az ml online-deployment update -f managed_deployment.yml -g $(RESOURCE_GROUP) -w $(AML_WORKSPACE_NAME)
    ```
1. [OAuth によるサードパーティアプリケーションのアクセス許可](https://learn.microsoft.com/azure/devops/organizations/accounts/change-application-access-policies?view=azure-devops)
Azure Pipelines で構築したパイプラインは、外部サービスから REST API エンドポイントを通じてトリガーすることができます。エンドポイントにリクエスト送信する際にはユーザー名とパスワードではなく、OAuth による認証を使用します。

## 2.2. Logic Apps によるモデル登録トリガーの作成
こちらは「JSON の解析」アクションまでは GitHub の時と同じです。
1. 新しいステップをクリックし、検索ボックスに「devops」と入力して「[Azure DevOps に HTTP 要求を送信する](https://learn.microsoft.com/connectors/visualstudioteamservices/#http-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92-azure-devops-%E3%81%AB%E9%80%81%E4%BF%A1%E3%81%97%E3%81%BE%E3%81%99)」アクションをクリックします。
1. 「Azure DevOps に HTTP 要求を送信する」アクションでは、以下のとおりに入力します。さらに下部の「Add new parameter」ドロップダウンをクリックし、「ボディ」チェックをオンにします。オンにしたら一旦キャンバス部分をクリックしてフォーカスを外します。
    ![001re.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/75040e6d-5f3d-bf93-ac7b-db8a2a1c63b2.png)
    - 組織名: ご自分の組織名をドロップダウンから選択
    - 方法: `POST`
    - 相対 URI: `https://dev.azure.com/{Organization}/{ProjectName}/_apis/pipelines/{PipelineID}/runs?api-version=7.0`

:::note warn
Azure Pipelines のパイプライン管理画面から作成したパイプラインをクリックしたときの URL からマッピングします。`https://dev.azure.com/{Organization}/{ProjectName}/_build?definitionId={PipelineID}&view=runs` のような対応になります。PipelineID は整数値であることに注意してください。
:::

- 本文: 以下の未完成の JSON をコピー&ペーストし、`modelName` と `modelVersion` は画像のように動的メニューから選択します。`"model_name"` はダブルクォーテーションの間にカーソルを合わせて挿入します。
    ```json
    {
      "resources": {
        "repositories": {
          "self": {
            "refName": "refs/heads/main"
          }
        }
      },
      "templateParameters": {
        "model_name": "",
        "model_version": 
      }
    }
    ```
1. AML で[モデルを登録](https://learn.microsoft.com/azure/machine-learning/how-to-manage-models-mlflow#registering-new-models-in-the-registry)して動作を検証します。
1. Logic Apps の実行履歴は、概要ページから確認することができます。
1. 状態が「正常」であればデプロイパイプラインが起動され、パイプラインジョブが実行されます。実行履歴画面から、ステップごとに実際に流れたデータを確認することができます。
1. Azure Pipelines 側のデプロイパイプラインが正常に起動していることを確認します。下のようにパイプラインに送信されたパラメーターが受信できていることも確認できます。
    ![011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/577b8a4a-1942-5946-f816-227cc31db7cd.png)
1. Azure Machine Learning で新しいモデルがエンドポイントにデプロイされたことを確認できれば完了です。

# 承認ゲート
今回単純化して記事を書いていますが、[MLOps リファレンスアーキテクチャ](https://learn.microsoft.com/azure/architecture/reference-architectures/ai/mlops-python)には、検証環境と運用環境の間に承認ゲートがあります。これは GitHub Actions や Azure DevOps でも簡単に導入することができます。
1. GitHub Actions のワークフローに[ジョブ承認](https://docs.github.com/actions/managing-workflow-runs/reviewing-deployments)を導入
1. Azure DevOps のパイプラインに[手動検証タスク](https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/manual-validation-v0?view=azure-pipelines&viewFallbackFrom=azure-devops)を導入

このタスクをパイプラインに導入すると、モデルのデプロイ前に人手による承認が必要になります。承認者を設定して、承認催促メールを送信することができます。

# まとめ
Azure Logic Apps で繋ぐところさえうまくできれば、あとは簡単。AML イベント駆動での MLOps ライフが可能に！
あと AML CLI v2 とっても良くないですか？ Python などの特定の言語に依存せず、`az extension add --name ml` 一発で使用可能になりますので DevOps/インフラ系エンジニアとの親和性抜群です。もちろん、Python SDK v2 でも同じ機能が使えますので、エンジニアの好みで分けてもいいかと思います。使い分けについては[参考](https://learn.microsoft.com/azure/machine-learning/concept-v2)。

# さいごに
Microsoft では [OpenHack](https://zine.qiita.com/event/202112-microsoft/) というイベントを開催しています。AI や Modern Data Warehouse などのワークロードごとに、数日間にわたりチームで課題を解決する非常に満足度の高いハックイベントです。これに準ずる形で MLOps をテーマにしたハッカソンやハンズオンを実施しています。AML × MLOps に興味がある方はぜひ Microsoft 担当までご一報を！　※参加にはサブスクリプションや人数などの制限がございます。