maemaewaterの日記

エンジニア兼ゲーマーの人の日記です。PHP/Python/JavaScript/C#/C++などによるプログラムに関することを主に書いています。

Azure Batchを使用してBlender 3.0でレンダリング

はじめに

Azure Batchがレンダリング環境としても良さそうと思い試してみることにしてみました。マイクロソフトからもレンダリングに関する記事もあり、更にストレージ のアクセスの高速化の方法などかなり大規模なレンダリング環境(HPC Cache)まで想定されているようでした。 実際には、Blenderがインストール済みの環境なども用意されているようですが、Azure Batchの調査も含めてBlenderの環境をAzure Batchで構築してみることに してみましたので、そのことをまとめていこうと思います。

Azure Batchについて

Azure Batchですが、一つの特徴として次のようなことが挙げられるのではと思います。

  1. 実行時間の制限がない
  2. Azure Storageからデータを読み出しやすい(.blendファイルなどを転送しておけます)
  3. 基本は起動しているVMに対してジョブを追加していく仕組みで分かりやすい

プールとジョブ

AzueのWebの操作画面からリソースの追加でBatchアカウントを追加すると、プールとジョブという2つの 設定がでてきます。それぞれ次のような役割になります。

  • プールとノード

    • プールにVMのサーバーを起動させておきます。起動させておくサーバーの数(ノード数)は自由に設定できます。
    • プールには名前をつけておきます(BlenderPoolなど)  - 今回はBlenderをインストールする必要があるので、このプールのサーバーにあらかじめインストールしておきます(プール作成時にインストール用のコマンドを実行することができます)
  • ジョブとタスク

    • ジョブは、指定したプール(プールの名前)に対して処理を実行します
    • ジョブに関連付けられたタスクごとに実行するコマンドを指定することができます。

Pythonでのスクリプト

事前にpipコマンドでAzure Batchのライブラリをインストールします。

pip install azure-batch

設定用のスクリプト(settings.py)

account = "{Azure Batchのアカウント名}"
key = "{Azure Batchのキー}"
batch_url = "{Azure BatchのURL}"
storage_connection_string = "{Azure Batchに関連付けているストレージの接続文字列}"

プールの作成とレンダリングを実行するためのコード(create_pool.py)

import azure.batch as batch
import azure.batch.batch_auth as batchauth
import azure.batch.models as batchmodels
import os
import sys
from azure.storage.blob import BlobServiceClient
from pathlib import Path
import time
import settings

account = settings.account
key = settings.key
batch_url = settings.batch_url
storage_connection_string = settings.storage_connection_string

pool_id = "LinuxNodesBlender"
vm_size = "STANDARD_D2_V3" # 用途に応じて変更
node_count = 1

def create_client():
  creds = batchauth.SharedKeyCredentials(account, key)
  config = batch.BatchServiceClientConfiguration(creds, batch_url)
  client = batch.BatchServiceClient(creds, batch_url)

  return client

def create_pool():
  client = create_client()
  new_pool = batchmodels.PoolAddParameter(id=pool_id, vm_size=vm_size)
  new_pool.target_dedicated_nodes = node_count

  user = batchmodels.UserIdentity(
    auto_user=batchmodels.AutoUserSpecification(
      elevation_level=batchmodels.ElevationLevel.admin,
      scope="pool"
    )
  )

  commands = [
    "pwd",
    "apt install -y -qq libxi-dev libxxf86vm-dev libxrender-dev",
    "wget https://mirror.clarkson.edu/blender/release/Blender3.0/blender-3.0.0-linux-x64.tar.xz", # 新しいバージョンがリリースされたら変更
    "tar Jxfv blender-3.0.0-linux-x64.tar.xz",
    "cp -r $AZ_BATCH_NODE_STARTUP_DIR/wd/blender-3.0.0-linux-x64 $AZ_BATCH_NODE_STARTUP_DIR/blender"
  ]

  command_line = "/bin/sh -c \"" + "; ".join(commands) + "\""

  print(command_line)

  start_task = batchmodels.StartTask(command_line=command_line, user_identity=user)
  start_task.run_elevated = True
  new_pool.start_task = start_task


  images = client.account.list_supported_images()
  #for img in images:
  #  print('[image]: {0}, {1}, {2}'.format(img.image_reference.publisher, img.image_reference.offer, img.image_reference.sku))

  ir = batchmodels.ImageReference(
    publisher="canonical",
    offer="0001-com-ubuntu-server-focal",
    sku="20_04-lts",
    version="latest"
    )

  vmc = batchmodels.VirtualMachineConfiguration(
    image_reference=ir,
    node_agent_sku_id="batch.node.ubuntu 20.04"
    )

  new_pool.virtual_machine_configuration = vmc

  client.pool.add(new_pool)


def create_task(n, blend_file_name):
  client = create_client()

  path = Path('files')
  files = list(path.glob("*"))

  input_file_paths = list(path.glob("*"))

  for f in input_file_paths:
    print("copy to storage: {0}".format(f))

  blob_service_client = BlobServiceClient.from_connection_string(storage_connection_string)
  container_client = blob_service_client.get_container_client('blender')

  input_files = []

  for i in input_file_paths:
    name = Path(i).name
    
    blob = blob_service_client.get_blob_client("blender", name)
    with open(i, "rb") as data:
      blob.upload_blob(data, overwrite=True)
      input_files.append(batchmodels.ResourceFile(auto_storage_container_name="blender"))

  job_id = 'render_job_' + n
  job = batchmodels.JobAddParameter(
    id=job_id,
    pool_info=batch.models.PoolInformation(pool_id=pool_id)
  )

  client.job.add(job)

  tasks = list()

  commands = "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$AZ_BATCH_NODE_STARTUP_DIR/blender/lib; $AZ_BATCH_NODE_STARTUP_DIR/blender/blender -b " + blend_file_name + " -o $AZ_BATCH_TASK_DIR/frame_##### -f 1"
  command = "/bin/sh -c \"" + commands + "\""

  tasks.append(batchmodels.TaskAddParameter(
    id="Task_" + n,
    command_line = command,
    resource_files=[input_files[0]]
  ))

  client.task.add_collection(job_id, tasks)
  print("[job_id]: {0}".format(job_id))



if __name__ == "__main__":
  args = sys.argv
  if len(args) < 2:
    print('please set command type: pool or task')
    exit()

  command_type = args[1]

  if command_type == "pool":
    create_pool()

  if command_type == "task":
    if len(args) < 3:
      print('please set .blend file name: task add_to_task.blend')
      exit()

    create_task(str(time.time_ns()), args[2])

  print('[ok]')

使い方

プール作成

python create_pool.py pool

レンダリングタスクの追加

ストレージに転送するファイルは、filesディレクトリに入れておきます。

python create_pool.py task test.blend

実行するとAzureのWebのインタフェースから処理が開始されていることが確認できます。

おわりに

まだまだ、スクリプトも改良できると思いますので改良していこうと思っております。

参考にさせていただいたページ:

docs.blender.org

docs.microsoft.com