Azure BatchでPython 3.7のプログラムを実行する方法

# はじめに
今更感強めですが、仕事関連でAzureBatchを利用して動画解析を行うシステムを構築しました。
AzureBatchを使用するのが初だったため、色々と勝手がわからず苦労したのでその記録を残します。

今回はAzureBatchで簡単なスクリプトを実施するまでと、一番苦労したAzureBatch起動時の開始タスクの設定部分を主に記載します。

# 実行環境
#### ローカル側
 - OS：Windows10
 - 言語：Python 3.7.2
 - pip：19.0.2

#### AzureBatch側
 - OS：Canonical UbuntuServer 18.04-LTS (latest)
 
# AzureBatchってなんだろう
初めてAzureBatchを触ってみて、今の自分自身の理解的には、瞬間的にマシンスペックやリソースをすごく必要とする処理を実行させる際に活用するとコスト面などでメリットがあるサービスというイメージです。

決してスケジューラに登録して夜間等に実行させるスクリプトとかではないということがわかりました。
最初は夜間バッチ等々と呼ばれるものとのイメージ差異があって戸惑いがすごかったです。

詳しくはAzureの公式ページに記載されてますのでご参照ください。
[https://azure.microsoft.com/ja-jp/services/batch/](https://azure.microsoft.com/ja-jp/services/batch/)

# まずしたこと
公式のサンプルを活用して、とりあえずAzureBatchを動かしてみようと思いました。
[https://docs.microsoft.com/ja-jp/azure/batch/](https://docs.microsoft.com/ja-jp/azure/batch/)

クイックスタートに「AzureCLI」,「AzurePortal」,「.Net」,「Python」と選択肢があり、今回はBatchに処理させたいタスクとしてPythonでの動画解析スクリプトの実施を目的としていたため、これは渡りに船と「Python」を選択しました。

[クイック スタート:Python API で最初の Batch ジョブを実行する](https://docs.microsoft.com/ja-jp/azure/batch/quick-run-python)

上記のクイックスタートのページに従い、手順通りに実施し問題なく実行できました。

# クイックスタートを実施してわかったこと
クイックスタートを実施してみてわかったことは、AzureBatchというのはざっくり大きく下記のプロセスに分かれているということでした。

- プールの作成
 - ジョブやタスクが動くVM(仮想マシン)を作成する
- ジョブの作成
 - 上記プールにジョブを追加
- タスクの作成
 - 上記ジョブにタスクを追加
 - このタスクにBatchに実行させたい処理を登録する（今回はPythonのスクリプト）
- 上記のジョブ(タスク)、プールの削除
 - この削除を行うことで必要な時に必要な分だけリソースを使用することができ、コストの削減につながります

# 実現したいことに近づける
今回実現したかったことは、Pythonで作成した動画解析スクリプトをAzureBatch環境で動作させることでした。仮にこれをtask.py(仮称)します。
(※前提として、このtask.pyはローカル環境で正常動作の確認が済んでいるものとします）

前述のクイックスタートではタスクでコマンドを実行しており、pythonのスクリプトを実行させているものではなかったため、まずpythonのスクリプトを実施させているサンプルや情報は無いかと調べました。

### ベースとしたサンプル
Batchのサンプルリポジトリの中にちょうど、とてもシンプルでわかりやすいpythonの簡単なスクリプトをBatchで実行させるソースがあったのでそちらを参考にしました。

[https://github.com/Azure-Samples/azure-batch-samples/tree/master/Python/Batch#sample2_pools_and_resourcefilespy](https://github.com/Azure-Samples/azure-batch-samples/tree/master/Python/Batch#sample2_pools_and_resourcefilespy)

このサンプルでは、simple_task.pyというpythonスクリプトを実施し、Batchのプールで作成したVMのディレクトリの一覧を表示する非常にシンプルなものでした。

### 問題
キー等必要な情報は適宜置き換えをし、サンプル(simple_task.py)の実行には特に問題なく成功したのですが、このサンプル(simple_task.py)の呼び出しを作成していたtask.pyに変えたら何故か失敗してうまくいきませんでした。。。

何故うまくいかないのか考えた所…まぁ考えるまでもなかったのですが、
単純にプールで作成されたVMに必要なライブラリ等のインストールがされていないことが原因でした。
素のOSなので当然といえば当然なのですが、Batch内のログを確認する術を知らず原因がわからずに戸惑いました。

### 必要なもの
task.pyの実行には以下のものが必要でした。
これらをVM作成時にインストールしていかねばなりません。

- Python3.7
- OpenCV
 - 動画解析に使用 
- pyodbc
 - pythonからDBを扱うためのライブラリ
- Microsoft ODBC Driver 17 for SQL Server
 - SQLServerをpythonから扱うためのドライバ 
- ffmpeg
 - 動画読み込み(codec)と動画変換(ffmpegコマンド)に使用

# 開始タスクの設定
さて、それでは必要なものをインストールするためにBatchの開始タスクの設定部分について触れたいと思います。
上記でベースとして活用したサンプルソース(sample2_pools_and_resourcefiles.py)内のcreate_poolメソッドを改修していきます。原文は以下。

```python:sample2_pools_and_resourcefiles.py
def create_pool(batch_client, block_blob_client, pool_id, vm_size, vm_count):
    """Creates an Azure Batch pool with the specified id.
    :param batch_client: The batch client to use.
    :type batch_client: `batchserviceclient.BatchServiceClient`
    :param block_blob_client: The storage block blob client to use.
    :type block_blob_client: `azure.storage.blob.BlockBlobService`
    :param str pool_id: The id of the pool to create.
    :param str vm_size: vm size (sku)
    :param int vm_count: number of vms to allocate
    """
    # pick the latest supported 16.04 sku for UbuntuServer
    sku_to_use, image_ref_to_use = \
        common.helpers.select_latest_verified_vm_image_with_node_agent_sku(
            batch_client, 'Canonical', 'UbuntuServer', '16.04')

    block_blob_client.create_container(
        _CONTAINER_NAME,
        fail_on_exist=False)

    sas_url = common.helpers.upload_blob_and_create_sas(
        block_blob_client,
        _CONTAINER_NAME,
        _SIMPLE_TASK_NAME,
        _SIMPLE_TASK_PATH,
        datetime.datetime.utcnow() + datetime.timedelta(hours=1))

    pool = batchmodels.PoolAddParameter(
        id=pool_id,
        virtual_machine_configuration=batchmodels.VirtualMachineConfiguration(
            image_reference=image_ref_to_use,
            node_agent_sku_id=sku_to_use),
        vm_size=vm_size,
        target_dedicated_nodes=vm_count,
        start_task=batchmodels.StartTask(
            command_line="python " + _SIMPLE_TASK_NAME,
            resource_files=[batchmodels.ResourceFile(
                            file_path=_SIMPLE_TASK_NAME,
                            http_url=sas_url)]))

    common.helpers.create_pool_if_not_exist(batch_client, pool)
```

まずはVMのOSイメージの変更から、サンプルではUbuntuの16.04を使用するようになっていますがここを、18.04に変えます。

```python:task.py
    # pick the latest supported 18.04-LTS sku for UbuntuServer
    sku_to_use, image_ref_to_use = \
        common.helpers.select_latest_verified_vm_image_with_node_agent_sku(
            batch_client, 'Canonical', 'UbuntuServer', '18.04-LTS')
```
blobやsasについては今回のタスク実施には必要なかったため削除しました。
次にPoolAddParameterについて記載します。

```python:task.py
    pool = batchmodels.PoolAddParameter(
        id=pool_id,
        virtual_machine_configuration=batchmodels.VirtualMachineConfiguration(
            image_reference=image_ref_to_use,
            node_agent_sku_id=sku_to_use),
        vm_size=vm_size,
        target_dedicated_nodes=vm_count,
        start_task=batchmodels.StartTask(
            command_line=common.helpers.wrap_commands_in_shell('linux', task_commands),
            user_identity=batchmodels.UserIdentity(auto_user=user),
            wait_for_success=True)
        )
```
ここで注目して頂きたいのは、以下の2行です。
開始タスクで実行するコマンドを指定している部分とその実行ユーザを指定しています。
`command_line=common.helpers.wrap_commands_in_shell('linux', task_commands),`
`user_identity=batchmodels.UserIdentity(auto_user=user),`
このtask_commandsとuserの変数にはそれぞれ下記が代入されています。

```python:task.py
    task_commands = [
        # リポジトリ追加
        'apt update && apt -y install software-properties-common',
        'add-apt-repository -y ppa:deadsnakes/ppa',
        # python3.7　インストール
        'apt -y install python3.7',
        'ln -sfn /usr/bin/python3.7 /usr/bin/python',
        # pip インストール
        'curl -fSsL https://bootstrap.pypa.io/get-pip.py | python',
        # AzureBlobStorageライブラリ インストール
        'pip install azure-storage-blob==1.4.0',
        # Microsoft ODBC Driver 17 for SQL Server　インストール
        'curl -fSsL https://packages.microsoft.com/keys/microsoft.asc | apt-key add -',
        'curl -fSsL https://packages.microsoft.com/config/ubuntu/18.04/prod.list > /etc/apt/sources.list.d/mssql-release.list',
        'apt update && ACCEPT_EULA=Y apt install -y msodbcsql17',
        # pyodbc　インストール
        'apt install -y unixodbc-dev python3.7-dev build-essential',
        'pip install pyodbc==4.0.25',
        'pip install cffi',
        # opencv-python　インストール
        'pip install opencv-python==4.0.0.21',
        'apt install -y libsm6 libxrender-dev',
        # コーデック　インストール
        'apt -y install ffmpeg'
    ]
```
Batchでのインストール時はコマンドの応答要求に答えることができないため基本的に-yのオプションをつけてインストールしています。
また、Ubuntu18.04のデフォルトのPython 3系のバージョンが3.6ため、Python3.7をインストールの後シンボリックリンクを張り替えて3.7で実施されるようにしています。
（Pythonのバージョンが3.6のままでよい場合は実施不要）

```python:task.py
    user = batchmodels.AutoUserSpecification(
        scope=batchmodels.AutoUserScope.pool,
        elevation_level=batchmodels.ElevationLevel.admin)
```
諸々をインストールするために管理者の権限を与えています。

上記を開始タスクとして指定し、プールを作成することで目的のtask.pyをAzureBatchで動作させることができました。

以下修正後のcreate_poolメソッド全文です。

```python:task.py
def create_pool(batch_client, pool_id, vm_size, vm_count):
    # pick the latest supported 18.04-LTS sku for UbuntuServer
    sku_to_use, image_ref_to_use = \
        common.helpers.select_latest_verified_vm_image_with_node_agent_sku(
            batch_client, 'Canonical', 'UbuntuServer', '18.04-LTS')

    task_commands = [
        # リポジトリ追加
        'apt update && apt -y install software-properties-common',
        'add-apt-repository -y ppa:deadsnakes/ppa',
        # python3.7　インストール
        'apt -y install python3.7',
        'ln -sfn /usr/bin/python3.7 /usr/bin/python',
        # pip インストール
        'curl -fSsL https://bootstrap.pypa.io/get-pip.py | python',
        # AzureBlobStorageライブラリ インストール
        'pip install azure-storage-blob==1.4.0',
        # Microsoft ODBC Driver 17 for SQL Server　インストール
        'curl -fSsL https://packages.microsoft.com/keys/microsoft.asc | apt-key add -',
        'curl -fSsL https://packages.microsoft.com/config/ubuntu/18.04/prod.list > /etc/apt/sources.list.d/mssql-release.list',
        'apt update && ACCEPT_EULA=Y apt install -y msodbcsql17',
        # pyodbc　インストール
        'apt install -y unixodbc-dev python3.7-dev build-essential',
        'pip install pyodbc==4.0.25',
        'pip install cffi',
        # opencv-python　インストール
        'pip install opencv-python==4.0.0.21',
        'apt install -y libsm6 libxrender-dev',
        # コーデック　インストール
        'apt -y install ffmpeg'
    ]

    user = batchmodels.AutoUserSpecification(
        scope=batchmodels.AutoUserScope.pool,
        elevation_level=batchmodels.ElevationLevel.admin)

    pool = batchmodels.PoolAddParameter(
        id=pool_id,
        virtual_machine_configuration=batchmodels.VirtualMachineConfiguration(
            image_reference=image_ref_to_use,
            node_agent_sku_id=sku_to_use),
        vm_size=vm_size,
        target_dedicated_nodes=vm_count,
        start_task=batchmodels.StartTask(
            command_line=common.helpers.wrap_commands_in_shell('linux', task_commands),
            user_identity=batchmodels.UserIdentity(auto_user=user),
            wait_for_success=True)
        )

    common.helpers.create_pool_if_not_exist(batch_client, pool)
```

# 最後に
Batchのエラー出力が取れないため、開始タスクの登録の仕方は分かっても、最初何故うまくいってないのかがわからず、各々必要なものをインストールするのに本当に苦労しました。
どうにかできないかと考えたり調べたりしている途中で、Batchのプールも所詮はVMを立てているだけだと気づいてからは、一旦AzureVMでUbuntu18.04の環境を立てて、そのVM上でtask.pyを動かしてみて、エラーが出力されれば必要な対応を調べそのコマンドを実行し、それを開始タスクとして登録すれば良いということに気づき比較的楽にできるようになりました。

あと、今回の記事ではそこまでフォーカスを当てていませんが、ベースとしたサンプルソースの中に同梱されている`azure-batch-samples/Python/Batch/common/helpers.py`がかなり便利で処理を組む上で活用させて頂きました。一度中を覗いてみると良いと思います。

まだまだAzureBatchの入口に立ったくらいで奥が深そうなので今後とも勉強していきたいと思います。もっと使いこなせるようになったら、色々と処理の重い解析とかをできるようになりそうな感触。。。