2017年8月31日

ブロードバンドタワー緒方です。今回は、統計解析向け言語/開発実行環境の R のクラスタを Azure Batch を利用してお手軽に構築する方法をご紹介させて頂きます。

※ご注意※ この記事は、2017/08/10 時点での情報です。アップデートにより変わる可能性があります。

一時的なリソース不足にどう対応する ?

通常 R を利用する場合には特に問題はないのだけれども、一時的に大きなデータを扱う必要があったり、複雑な計算をさせたい場合があるかと思います。 一時的に計算リソースが不足している場合に CPU やメモリ等のリソースを追加するのはなかなか難しいかと思います。

このような場合に、手元の R 実行環境のスケールアップをさせることなく、一時的に外部に処理をオフロードさせることができれば嬉しいのではないでしょうか。

オフロード先にクラウドを利用することができれば、利用した時間だけのコスト負担で済ませることができます。

でも R クラスタ構築って大変でしょ ?

オフロード先にクラウドを利用して、利用した時間だけのコスト負担で済ませるコンセプトは良いのですが、そもそもクラスタ構築に手間暇 & 費用がかかるようでは意味がありません。

このクラスタ構築にかかる手間を R のパッケージの doAzureParallel を利用することにより、お手軽に Azure 上に R クラスタを構築することができます。

もちろん構築するだけではなく、簡単にクラスタの破棄もできます。これぞクラウド的な利用方法で、使いたいときに使いたいだけ使うことができます。

どんな処理でもクラスタにお任せできる ?

doAzureParallelforeach パッケージの拡張的な役割をしてくれます。そのため、ユーザは何にも考えることなくあらゆる処理を doAzureParallel が自動的に処理を並列分散処理してくれる訳ではありません。

初期設定作業 ( 初回のみ )

R クラスタを Azure 上に構築するためには、事前作業として下記の作業が必要になります。

  1. R パッケージ: doAzureParallel のインストール
  2. Azure Batch アカウントの設定
  3. Azure ストレージアカウントの設定
  4. doAzureParallel 設定ファイルの編集

ちなみに、上記作業は初回時に必要な作業になります。R クラスタを構築する都度必要になる作業ではありません。

1 doAzureParallel インストール

それでは、doAzureParallel のインストールを実行してみましょう。

今回も ( Mac が調達できていないので ) Windows 環境で試してみます。

C:> ver
Microsoft Windows [Version 6.1.7601]
  • R 環境: Microsoft R Open 3.3.2
  • RStudio: 1.0.143

インストールは簡単で、下記のコマンドを R 上で実行するだけです。

devtools::install_github(c("Azure/rAzureBatch", "Azure/doAzureParallel"))

以上で R 側での作業は完了です。簡単ですね。

2 Azure ストレージアカウントの設定

まず初めに、Azure ストレージアカウントを作成します。Azure ポータル上での作業を実行していきます。下記の公式ホームページを参考にアカウントを作成します。

Azure ストレージ アカウントについて

※ 既存のストレージアカウントを利用される場合には作業は不要です。

作業後、下記の情報をメモしておきます。

  • ストレージアカウント名
  • ストレージアクセスキー

3 Azure Batch アカウントの設定

R クラスタ構築には、Azure Batch のサービスを利用します。Azure Batch を利用するためにアカウントを作成します。こちらも Azure ポータル上での作業を実行していきます。下記の公式ホームページを参考にアカウントを作成します。

作業後、下記の情報をメモしておきます。

  • バッチアカウント名
  • バッチアカウント URL
  • バッチアカウントアクセスキー

4 doAzureParallel 設定ファイルの編集

Azure 上で設定し、メモした情報をもとに doAzureParallel 設定ファイルを編集します。

4.1 generateClusterConfig() の実行

まずは、初期設定ファイルを生成します。R 上で generateClusterConfig() の引数に作成したい設定ファイル名 ( 今回は cluster.json ) を指定して実行します。

> library(doAzureParallel)

> generateClusterConfig("cluster.json")
[1] "A cluster settings has been generated C:/Users/***/Documents/cluster.json. Please enter your cluster specification."
[1] "Note: To maximize all CPU cores, set the maxTasksPerNode property up to 4x the number of cores for the VM size."

generateClusterConfig() コマンドの実行により作成されたファイルは次のようになります。

  • 設定ファイル ( cluster.json )
{
  "pool": {
    "name": "myPoolName",
    "vmSize": "Standard_D2_v2",
    "maxTasksPerNode": 1,
    "poolSize": {
      "dedicatedNodes": {
        "min": 3,
        "max": 3
      },
      "lowPriorityNodes": {
        "min": 3,
        "max": 3
      },
      "autoscaleFormula": "QUEUE"
    }
  },
  "rPackages": {
    "cran": [],
    "github": []
  }
}

今回はこの設定ファイルの name を toe_pool と設定してみました。名称だけの変更になりますので、必須の設定ではありません。

cluster.json の項目に、lowPriorityNodes という記載があります。実は、この記事の下書きを作成している段階では lowPriorityNodes という設定がありませんでした。調べてみると、下記の公式ホームページに記載がありました。

クラスタを構成する VM を通常の VM と比べて割安に利用できるとのことです。AWS でいうスポットインスタンス的なもののようです。

4.2 generateCredentialsConfig() の実行

次に、ストレージアカウントやBatch アカウント情報を保存するファイルを生成します。generateCredentialsConfig() の引数に生成したいファイル名 ( 今回は credentials.json ) を指定して実行します。

generateCredentialsConfig("credentials.json")

generateCredentialsConfig() コマンドの実行により生成されたファイルは次のようになります。

{
  "batchAccount": {
    "name": "batch_account_name",
    "key": "batch_account_key",
    "url": "batch_account_url"
  },
  "storageAccount": {
    "name": "storage_account_name",
    "key": "storage_account_key"
  }
}

設定が必要なところは下記の 5 点になります。

{
  "batchAccount": {
    "name": "batch_account_name",  <- バッチアカウント名を入力
    "key": "batch_account_key", <- バッチアカウントアクセスキーを入力
    "url": "batch_account_url", <- バッチアカウント URLを入力
     },
   "storageAccount": {
    "name": "storage_account_name", <- ストレージアカウント名を入力
    "key": "storage_account_key" <- ストレージアクセスキーを入力
  }
 }

とりあえず動かしてみる

初期設定も無事終わりましたので、早速動かしてみましょう。

と、そのまえに Azure ポータル上でプール状況を確認してみましょう。

該当する Batch アカウント ( 今回は、tsgbatchplayground ) を選択して、プール の項目を選択します。この状態ではプールは空の状態です。

1 ストレージアカウント/Batch アカウント設定ファイルの読み込み

setCredentials() コマンドを実行します。

setCredentials("credentials.json")
[1] "Your azure credentials have been set."

2 クラスタの作成

makeCluster() コマンドを実行して、Azure 上にクラスタを作成します。

pool <- doAzureParallel::makeCluster("cluster.json")
[1] "Booting compute nodes. . . "
  |=====================================================|
   100%[1] "Your pool has been registered."
[1] "Dedicated Node Count: 3"
[1] "Low Priority Node Count: 3"

クラスタ作成途中の状態を Azure ポータルで確認してみます。

割り当ての状態サイズ変更中 になっていることが見て取れます。

ちなみに、makeCluster() 実行後、処理が完了するまで、約 6 分ほどかかりました。 コマンド一発でさっくりクラスタが作成できるのは本当に楽ちんですね。

作成が完了した状態を Azure ポータルで確認してみます。

割り当ての状態安定 になっています。この状態で、プール ID ( 今回だと toe_pool ) をクリックすると、プールの詳細情報が確認できます。

6 個の箱がそれぞれ VM を表しているようです。この状態だと、どの VM でも処理が走っていないのでアイドル状態になっていることが箱の色から判断できます。

3 クラスタの登録と状況確認

先ほど作成したクラスタを利用できるように、registerDoAzureParallel() コマンドを実行します。

doAzureParallel::registerDoAzureParallel(pool)

クラスタの登録台数を調べてみましょう。getDoParWorkers() コマンドを実行してみます。

foreach::getDoParWorkers()
[1] 6

6 台登録されていることが確認できます。先ほど確認した Azure ポータルの情報と一致しています。

4 処理の実行

それでは、クラスタを利用した処理を実行してみましょう。

> library(foreach)
> number_of_iterations <- 100
> results <- foreach(i = 1:number_of_iterations) %dopar% { sqrt(i) }
[1] "Job Summary: "
[1] "Id: job20170419061416"
[1] "Waiting for tasks to complete. . ."
  |==================================================================| 100%[1] "Number of errors: 0"

ちゃんと処理が終わりました。

処理が完了するまで、どれくらい時間がかかるのか計ってみました。

> system.time(results <- foreach(i = 1:number_of_iterations) %dopar% { sqrt(i) })
No encoding supplied: defaulting to UTF-8.
No encoding supplied: defaulting to UTF-8.
[1] "Job Summary: "
[1] "Id: job20170810053408"
[1] "Waiting for tasks to complete. . ."
  |==========================================================================================| 100%[1] "Number of errors: 0"
   ユーザ   システム       経過
      9.80       1.86      82.28

約 82 秒かかっていますね。

処理実行中の Azure ポータル画面は下記のようになっていました。4 VM で処理実行されていることが見て取れます。

ちなみに、実行コマンド中の %dopar%%do% に変えれば、ローカル環境でコマンドが実行されます。

> system.time(results <- foreach(i = 1:number_of_iterations) %do% { sqrt(i) })
   ユーザ   システム       経過
    0.03       0.00       0.03

うん、0.03 秒で完了しましたね。 当然といえば当然なのですが、sqrt() しか実行していないので、クラスタで処理するよりも、ローカル環境で処理した方が早く終わります。

5 ちょっと寄り道

クラスタを構築している VM がどんな OS で動いているのか調べてみました。。sessionInfo()$running と実行すると、R 実行環境の情報を取得することができます。 試しに、自分の Windows 環境で実行してみると下記の様に出力されました。

> sessionInfo()$running
[1] "Windows 7 x64 (build 7601) Service Pack 1"

sessionInfo()$running をクラスタ上で実行した結果を取得してみます。

> foreach(i = 1:6) %dopar% { sessionInfo()$running }
[1] "Job Summary: "
[1] "Id: job20170810051424"
[1] "Waiting for tasks to complete. . ."
  |==========================================================================================| 100%[1] "Number of errors: 0"
[[1]]
[1] "CentOS Linux 7 (Core)"
  :
<途中省略>

上記の出力結果から、VM が CentOS7 で動いていることがわかりました。

ほかにも情報が取れないか試してみましょう。system() コマンドを利用して、OS 上のコマンドを実行してみます。

> foreach(i = 1:6) %dopar% { system("hostname") }
[1] "Job Summary: "
[1] "Id: job20170810052939"
[1] "Waiting for tasks to complete. . ."
  |==========================================================================================| 100%[1] "Number of errors: 0"
[[1]]
[1] 0
  :
<途中省略>

う~ん、結果が 0 になってしまいます。"whoamiuname -a などのコマンドを試してみたのですが、こちらも 0 で返ってきました。 どうやら、隠蔽工作(?)されているような感じがします。

6 後片付け

一通り doAzureParallel の確認が取れたため、最後にクラスタの破棄を実施しましょう。

この作業を忘れてしまうと、Azure Virtual Machines の時間課金が発生し続けることになってしまうので、忘れずに実施しましょう。

doAzureParallel::stopCluster(pool)

本当に削除されているか Azure ポータルから確認してみましょう。

削除しています... とありますので、大丈夫そうですね。

最後に

以上で doAzureParallel の初期設定から簡単な使い方までご説明させていただきました。記事の途中にも書いていますが、下書きを書き始めた当初には優先順位の低い VM が存在していませんでした。また、R のパッケージの doAzureParallel もバージョンが上がっていました。当時は設定ファイルが 1 ファイルだったのですが、( 2017/08/10 現在) 2 ファイルに分かれるようになっていました。記事を書く身としては、アップデートが激しいと現状に追従するのが大変なのですが、クラウドを利用する立場からするとできることが増えるのはありがたい事だと思っています。

本ブログの情報につきましては、自社の検証に基づいた結果からの情報提供であり、
品質保証を目的としたものではございません。

投稿者: 緒方 亮

主に Azure や AWS などの Public Cloud を担当しています。機械学習/深層学習で R を利用しているため、投稿する記事に R がよく登場します。