Databricksクラスターノード初期化スクリプト

[Cluster node initialization scripts \| Databricks on AWS](https://docs.databricks.com/clusters/init-scripts.html) [2022/2/1時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

initスクリプトは、Apache SparkのドライバーやワーカーのJVMがスタートする*前に*それぞれのクラスターノードのスタートアップの際に実行されるシェルスクリプトです。

initスクリプトで実行されるタスクの例には以下のようなものがあります。

- Databricksランタイムに含まれていないパッケージ、ライブラリをインストールします。Pythonライブラリをインストールするには、システムのPython環境ではなく、DatabricksのPython仮想環境にインストールされるように、`/databricks/python/bin/pip`にある`pip`バイナリーを使用します。
- [特殊なケース](https://spark.apache.org/docs/latest/sql-programming-guide.html#troubleshooting)でJVMのシステムクラスパスを変更します。
- JVMで使用されるシステムプロパティ、環境変数を設定します。
- Spark設定パラメーターを変更します。

# initスクリプトのタイプ

Databricksには、クラスタースコープとグローバルの2種類のinitスクリプトが存在します。

- **クラスタースコープ**: スクリプトが設定された全てのクラスターで実行されます。initスクリプトを実行する際のお勧めの方法となります。
- **グローバル**: ワークスペースのすべてのクラスターで実行されます。これは、ワークスペース全体で一貫性のある設定を適用する際に役立ちます。しかし、ライブラリの競合など予期しないインパクトが起こり得るので、使用する際には注意してください。管理ユーザーのみがグローバルinitスクリプトを作成できます。グローバルinitスクリプトは[モデルサービング](https://qiita.com/taka_yayoi/items/b5a5f83beb4c532cf921)のクラスターでは実行されません。

> **注意**
利用を推奨しない古いinitスクリプトが2種類存在します。以下の種類のinitスクリプトは移行することをお勧めします。

> - **クラスター指定**: スクリプトと同じ名前のクラスターで実行されます。クラスター指定initスクリプトはベストエフォート(サイレントに失敗を無視します)であり、クラスター起動プロセスを続行しようとします。代わりにクラスタースコープを使用すべきであり、完全に置き換えるものとなっています。
- **レガシーグローバル**: すべてのクラスターで実行されます。新たなグローバルinitスクリプトフレームワークよりもセキュアではなく、サイレントにエラーを無視し、[環境変数](#環境変数)を参照することができません。既存のレガシーグローバルinitスクリプトを新たなinitスクリプトフレームワークに移行すべきです。[レガシーグローバルinitスクリプトから新しいグローバルinitスクリプトへの移行](#レガシーグローバルinitスクリプトから新しいグローバルinitスクリプトへの移行)を参照ください。

initスクリプトのタイプを変更した際には、スクリプトを有効にするために全てのクラスターを再起動する必要があります。

# initスクリプトの実行順序

initスクリプトの実行順序は以下のようになります。

1. レガシーグローバル (非推奨)
1. クラスター指定 (非推奨)
1. グローバル (新世代)
1. クラスタースコープ

# 環境変数

クラスタースコープ、グローバルinitスクリプト(新世代)では、以下の環境変数をサポートしています。

- `DB_CLUSTER_ID`: スクリプトが実行されrているクラスターのIDです。[Clusters API 2\.0](https://docs.databricks.com/dev-tools/api/latest/clusters.html)を参照ください。
- `DB_CONTAINER_IP`: Sparkが実行されているコンテナーのプライベートIPアドレスです。[SparkNode](https://docs.databricks.com/dev-tools/api/latest/clusters.html#clustersparkinfosparknode)を参照ください。
- `DB_IS_DRIVER`: スクリプトがドライバーノードで実行されているかどうかを示します。
- `DB_DRIVER_IP`: ドライバーノードのIPアドレスです。
- `DB_INSTANCE_TYPE`: ホストVMのインスタンスタイプです。
- `DB_CLUSTER_NAME`: スクリプトが実行されているクラスター名です。- `DB_PYTHON_VERSION`: クラスターで使用されているPythonのバージョンです。
- `DB_IS_JOB_CLUSTER`: クラスターがジョブを実行するために作成されたかどうかを示します。[ジョブの作成](https://qiita.com/taka_yayoi/items/b3275a1983c51a8bbe1a#%E3%82%B8%E3%83%A7%E3%83%96%E3%81%AE%E4%BD%9C%E6%88%90)を参照ください。
- `SPARKPASSWORD`: [シークレット](https://qiita.com/taka_yayoi/items/7e4eae0786b94cb7a5de#spark%E8%A8%AD%E5%AE%9A%E3%81%A8%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%81%AE%E3%83%91%E3%82%B9)へのパスです。

例えば、ドライバーノードでのみスクリプトのある部分を実行したい場合には、以下のようなスクリプトを記述します。

```bash:Bash
echo $DB_IS_DRIVER
if [[ $DB_IS_DRIVER = "TRUE" ]]; then
  <run this part only on driver>
else
  <run this part only on workers>
fi
<run this part on both driver and workers>
```

# ログ

initスクリプトの起動、終了のイベントはクラスターのイベントログで捕捉されます。詳細はクラスターログに記録されます。グローバルinitスクリプトの作成、編集、削除のイベントはアカウントレベルの監査ログでも記録されます。

## initスクリプトのイベント

[クラスターイベントログ](https://qiita.com/taka_yayoi/items/991aae376c089df58504#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B0)は2つのinitスクリプトのイベントを捕捉します。そのスクリプトの実行がスケジュールされたのか、どのスクリプトの処理が成功したのかを示す`INIT_SCRIPTS_STARTED`と`INIT_SCRIPTS_FINISHED`です。`INIT_SCRIPTS_FINISHED`では実行期間も記録されます。

グローバルinitスクリプトはログの詳細では`"global"`のキーで表示され、クラスタースコープinitスクリプトは`"cluster"`で表示されます。

> **注意**
クラスターイベントログにはそれぞれのクラスターノードのinitスクリプトのイベントは記録されません。1つのノードのみが選択され、それが全体を代表します。

## initスクリプトのログ

クラスターで[クラスターログデリバリー](https://qiita.com/taka_yayoi/items/8d951b660cd87c6c5f18#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%AD%E3%82%B0%E3%83%87%E3%83%AA%E3%83%90%E3%83%AA%E3%83%BC)が設定されると、initスクリプトのログは`/<cluster-log-path>/<cluster-id>/init_scripts`に書き込まれます。クラスターのそれぞれのコンテナーのログは`init_scripts/<cluster_id>_<container_ip>`というサブディレクトリに格納されます。例えば、`cluster-log-path`に`cluster-logs`が設定されると、特定のコンテナーに対応するログのパスは`dbfs:/cluster-logs/<cluster-id>/init_scripts/<cluster_id>_<container_ip>`となります。

クラスターがログをDBFSに書き込むように設定されている場合、[File system utility \(dbutils\.fs\)](https://docs.databricks.com/dev-tools/databricks-utils.html#dbutils-fs)や[DBFS CLI](https://docs.databricks.com/dev-tools/cli/dbfs-cli.html)を用いてログを参照することができます。例えば、クラスターIDが`1001-234039-abcde739`の場合、以下を実行します。

```bash:Bash
dbfs ls dbfs:/cluster-logs/1001-234039-abcde739/init_scripts
```

```
1001-234039-abcde739_10_97_225_166
1001-234039-abcde739_10_97_231_88
1001-234039-abcde739_10_97_244_199
```

```bash:Bash
dbfs ls dbfs:/cluster-logs/1001-234039-abcde739/init_scripts/1001-234039-abcde739_10_97_225_166
```

```
<timestamp>_<log-id>_<init-script-name>.sh.stderr.log
<timestamp>_<log-id>_<init-script-name>.sh.stdout.log
```

クラスターログデリバリーが設定されていない場合、ログは`/databricks/init_scripts`に書き込まれます。ログを一覧、参照するためにノートブックで標準シェルコマンドを使用することができます。

```bash:Bash
%sh
ls /databricks/init_scripts/
cat /databricks/init_scripts/<timestamp>_<log-id>_<init-script-name>.sh.stdout.log
```

クラスターが起動する度、initスクリプトのログフォルダにログが書き込まれます。

> **重要！**
クラスターを作成し、クラスターログデリバリーを有効化したユーザーはグローバルinitスクリプトの`stderr`と`stdout`出力を参照することができます。お使いのグローバルinitスクリプトから機微な情報を出力させないようにしてください。

## 監査ログ

Databricksの監査ログは、イベントタイプ`globalInitScripts`でグローバルinitスクリプトの作成、編集、削除イベントを捕捉します。[Configure audit logging](https://docs.databricks.com/administration-guide/account-settings/audit-logs.html)を参照ください。

# クラスタースコープinitスクリプト

クラスタースコープinitスクリプトはクラスター設定で定義されます。クラスタースコープinitスクリプトは作成したクラスター、ジョブを実行するクラスターの両方に適用されます。スクリプトはクラスター設定の一部なので、[クラスターアクセスコントロール](https://docs.databricks.com/security/access-control/cluster-acl.html)でスクリプトを変更できる人を制御することができます。

UI、CLIを用いて、あるいはClusters APIを呼び出すことでクラスタースコープinitスクリプトを設定することができます。このセクションではUIによる設定方法にフォーカスします。他の方法に関しては、[Databricks CLI](https://docs.databricks.com/dev-tools/cli/index.html)、[Clusters API 2\.0](https://docs.databricks.com/dev-tools/api/latest/clusters.html)を参照ください。

任意の数のスクリプトを追加することができ、指定された順にスクリプトは実行されます。

クラスタースコープinitスクリプトが非ゼロの結果を返した場合、クラスターの起動は*失敗*します。[クラスターログデリバリー](https://qiita.com/taka_yayoi/items/8d951b660cd87c6c5f18#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%AD%E3%82%B0%E3%83%87%E3%83%AA%E3%83%90%E3%83%AA%E3%83%BC)を設定し、[initスクリプトログ](#initスクリプトのログ)を検証することで、クラスタースコープinitスクリプトをデバッグすることができます。

# クラスタースコープinitスクリプトの場所

initスクリプトをクラスターがアクセスできるDBFS、S3のディレクトリに格納することができます。DBFS内のクラスターノードのinitスクリプトは[DBFSルート](https://qiita.com/taka_yayoi/items/897264c486e179d72247#dbfs%E3%83%AB%E3%83%BC%E3%83%88)に格納されている必要があります。Databricksでは、[オブジェクトストレージをマウント](https://qiita.com/taka_yayoi/items/897264c486e179d72247#dbfs%E3%81%B8%E3%81%AE%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%81%B8%E3%81%AE%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88)することで作成されたDBFSディレクトリへのinitスクリプトの格納を*サポートしていません*。

# クラスタースコープinitスクリプトのサンプル

このセクションではinitスクリプトの2つの例を説明します。

## サンプル：PostgreSQL JDBCドライバーのインストール

以下のスニペットをPythonノートブックで実行すると、PostgreSQL JDBCドライバーをインストールするinitスクリプトを作成します。

1. initスクリプトを格納するDBFSディレクトリを作成します。この例では`dbfs:/databricks/scripts`を使用します。

    ```py:Python
dbutils.fs.mkdirs("dbfs:/databricks/scripts/")
    ```

1. 上記のディレクトリに`postgresql-install.sh`というスクリプトを作成します。

    ```py:Python
dbutils.fs.put("/databricks/scripts/postgresql-install.sh","""
#!/bin/bash
wget --quiet -O /mnt/driver-daemon/jars/postgresql-42.2.2.jar https://repo1.maven.org/maven2/org/postgresql/postgresql/42.2.2/postgresql-42.2.2.jar""", True)
    ```

1. スクリプトが存在することを確認します。

    ```py:Python
display(dbutils.fs.ls("dbfs:/databricks/scripts/postgresql-install.sh"))
```

あるいは、ローカルでinitスクリプト`postgresql-install.sh`を作成することもできます。

```bash:Bash
#!/bin/bash
wget --quiet -O /mnt/driver-daemon/jars/postgresql-42.2.2.jar https://repo1.maven.org/maven2/org/postgresql/postgresql/42.2.2/postgresql-42.2.2.jar
```

そして、[DBFS CLI](https://docs.databricks.com/dev-tools/cli/dbfs-cli.html)を用いて`dbfs:/databricks/scripts`にコピーします。

```bash:Bash
dbfs cp postgresql-install.sh dbfs:/databricks/scripts/postgresql-install.sh
```

## サンプル：Pythonライブラリのインストールにcondaを使う

Databricksランタイム9.0以降では、Pythonライブラリのインストールにcondaを使うことができません。クラスターへのPythonパッケージのインストール方法については、[ライブラリ](https://qiita.com/taka_yayoi/items/d3a46efdc1ad01a581d0)を参照ください。

> **重要！**
> Anaconda, Inc.は2020年9月に[利用規約](https://www.anaconda.com/terms-of-service)をアップデートしています。Anacondaのパッケージ、ディストリビューションに依存している場合、新たな利用規約によりcommercial licenseが必要になる場合があります。詳細は[Anaconda Commercial Edition FAQ](https://www.anaconda.com/blog/anaconda-commercial-edition-faq)を参照ください。お使いのAnacondaチャンネルは[利用規約](https://www.anaconda.com/terms-of-service)によって管理されます。

> この変更によって、DatabricksはCondaパッケージマネージャーのデフォルトチャンネルを削除しました。これは破壊的変更です。initスクリプトでcondaコマンドを使用する際には、`-c`でチャンネルを指定するようにアップデートする必要があります。チャンネルを指定しない場合、condaコマンドは`PackagesNotFoundError`で失敗します。

Databricks 8.4ML以下では、Pythonパッケージのインストールに[Conda](https://conda.io/docs/)パッケージマネージャを使用します。クラスター初期化の際にPythonライブラリをインストールするには、以下のようなスクリプトを使用します。

```bash:Bash
#!/bin/bash
set -ex
/databricks/python/bin/python -V
. /databricks/conda/etc/profile.d/conda.sh
conda activate /databricks/python
conda install -c conda-forge -y astropy
```

# クラスタースコープinitスクリプトの設定

UIあるいはAPIを用いてinitスクリプトを実行するようにクラスターを設定することができます。

> **重要！**

> - スクリプトが指定された場所に存在する必要があります。スクリプトが存在しない場合、クラスターが起動する際、あるいはオートスケールする際に失敗します。
- initスクリプトを64KBより大きくすることはできません。このサイズを超えると、クラスターは起動に失敗し、クラスターログにエラーメッセージが表示されます。

## UIによるクラスタースコープinitスクリプトの設定

initスクリプトを実行するようにクラスター設定ページで設定を行うには、以下の手順を踏みます。

1. クラスター設定ページで**Advanced Options**を展開します。
1. ページの下部で**Init Scripts**タブをクリックします。
![](https://docs.databricks.com/_images/init-scripts-aws.png)
1. **Destination**ドロップダウンで、格納場所のタイプを選択します。上述した例では、格納場所は**DBFS**となります。
1. initスクリプトのパスを指定します。この例では、`dbfs:/databricks/scripts/postgresql-install.sh`となります。
1. 格納場所がS3の場合：
    1. リージョンを選択します。
    1. 以下のように、アクセスするバケットに対して`getObjectAcl`権限を持つ[インスタンスプロファイル](https://qiita.com/taka_yayoi/items/446c7971be354f88c679)がクラスターに設定されていることを確認します。

    ```json:JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:getObjectAcl"
      ],
      "Resource": [
        "arn:aws:s3:::<my-s3-bucket>/*"
      ]
    }
  ]
}
```

1. **Add**をクリックします。

クラスター設定からスクリプトを削除するには、スクリプトの右にある![](https://docs.databricks.com/_images/delete-icon.png)をクリックします。削除を確認すると、クラスターを再起動するように指示されます。任意で、アップロードした場所からスクリプトファイルを削除することができます。

## DBFS REST APIによるクラスタースコープinitスクリプトの設定

[Clusters API 2\.0](https://docs.databricks.com/dev-tools/api/latest/clusters.html)を用いて、クラスターID`1202-211320-brick1`のクラスターでinitスクリプトを実行するように設定するには以下のコマンドを実行します。

```bash:Bash
curl -n -X POST -H 'Content-Type: application/json' -d '{
  "cluster_id": "1202-211320-brick1",
  "num_workers": 1,
  "spark_version": "7.3.x-scala2.12",
  "node_type_id": "i3.2xlarge",
  "cluster_log_conf": {
    "dbfs" : {
      "destination": "dbfs:/cluster-logs"
    }
  },
  "init_scripts": [ {
    "dbfs": {
      "destination": "dbfs:/databricks/scripts/postgresql-install.sh"
    }
  } ]
}' https://<databricks-instance>/api/2.0/clusters/edit
```

# グローバルinitスクリプト

グローバルinitスクリプトはお使いのワークスペースのすべてのクラスターで実行されます。グローバルinitスクリプトは、企業全体でライブラリの設定やセキュリティ対策を強制したい場合に役立ちます。管理者のみがグローバルinitスクリプトを作成できます。UI、REST APIを用いて作成することができます。

> **重要！**
> グローバルinitは注意して使用してください。

> - 予期しないインパクトをもたらすライブラリの追加や変更を行えてしまいます。可能であれば、クラスタースコープinitスクリプトを使うようにしてください。
- クラスターを作成し、クラスターログデリバリーを有効化したユーザーはグローバルinitスクリプトの`stderr`と`stdout`出力を参照することができます。お使いのグローバルinitスクリプトから機微な情報を出力させないようにしてください。

[クラスターログデリバリー](https://qiita.com/taka_yayoi/items/8d951b660cd87c6c5f18#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%AD%E3%82%B0%E3%83%87%E3%83%AA%E3%83%90%E3%83%AA%E3%83%BC)を設定し、[initスクリプトログ](#initスクリプトのログ)を検証することで、グローバルinitスクリプトをデバッグすることができます。

## UIによるグローバルinitスクリプトの追加

Admin Consoleを用いてグローバルinitスクリプトを設定します。

1. Admin Consoleにアクセスして、**Global Init Scripts**タブをクリックします。
![](https://docs.databricks.com/_images/global-init-scripts-tab.png)

1. **+ Add**ボタンをクリックします。
1. スクリプト名を入力し、テキストファイルを**Script**フィールドにドラッグアンドドロップします。
![](https://docs.databricks.com/_images/global-init-add.png)

    > **注意**
このinitスクリプトを64KB以上にすることはできません。サイズを超えると、保存しようとした際にエラーメッセージが表示されます。

1. ワークスペースで2個以上のグローバルinitスクリプトを設定したい場合、実行順にスクリプトを並べます。
1. 保存後にすべての新規クラスター、再起動されたクラスターにスクリプトを適用したい場合には、**Enabled**スイッチを切り替えます。

    > **重要！**
実行順序、名前、有効化状態を含むグローバルinitスクリプトを反映させるには、稼働中のクラスターを再起動する必要があります。

1. **Add**をクリックします。

## UIによるグローバルinitスクリプトの設定

1. Admin Consoleに移動し、**Global Init Scripts**タブをクリックします。
1. スクリプトをクリックします。
1. スクリプトを編集します。
1. **Confirm**をクリックします。

## APIによるグローバルinitスクリプトの設定

[Global Init Scripts API 2\.0](https://docs.databricks.com/dev-tools/api/latest/global-init-scripts.html)を用いて、管理者はワークスペースのグローバルinitスクリプトの追加、削除、並び替え、情報の取得を行うことができます。

## レガシーグローバルinitスクリプトから新しいグローバルinitスクリプトへの移行

お使いのDatabricksワークスペースが2020年8月以前にデプロイされた場合、レガシーなグローバルinitが存在する可能性があります。次世代スクリプトフレームワークに含まれるセキュリティ、一貫性、可視性の機能を活用できるようにこれらのスクリプトを次世代グローバルinitスクリプトフレームワークに移行すべきです。

1. [UI](#uiによるグローバルinitスクリプトの追加)、[REST API](#apiによるグローバルinitスクリプトの設定)のいずれかを用いて、レガシーグローバルinitスクリプトをコピーし、次世代グローバルinitスクリプトフレームワークに追加します。

    次のステップを完了するまで、これらを無効化しておきます。

1. レガシーグローバルinitスクリプトを無効化します。

    Admin Consoleで**Global Init Scripts**に移動し、**Legacy Global Init Scripts**スイッチをオフにします。

    ![](https://docs.databricks.com/_images/disable-legacy-global-init-scripts.png)

1. 次世代グローバルinitスクリプトを有効にします。

    **Global Init Scripts**タブで、有効化したいinitスクリプトの**Enabled**スイッチをオンにします。
![](https://docs.databricks.com/_images/enable-global-scripts.png)

1. すべてのクラスターを再起動します。
    - 稼働中のクラスターにおける自動スケールアップで追加された新規ノードではレガシースクリプトは実行されません。これらの新規ノードでは次世代initスクリプトも実行されません。これらのノードで新規スクリプトが実行され、クラスターがグローバルスクリプトなしの新規ノードを追加しないようにするために、すべてのクラスターを再起動してください。
    - 冪等性のないスクリプトにおいては、次世代initグローバルスクリプトフレームワークに移行し、レガシーなスクリプトを無効化する際には変更が必要になる場合があります。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)