DatabricksにおけるCI/CDの自動化

[How to Use Databricks Labs CI/CD Tools to Automate Development Pipelines \- The Databricks Blog](https://databricks.com/blog/2020/06/05/automate-continuous-integration-and-continuous-delivery-on-databricks-using-databricks-labs-ci-cd-templates.html)の翻訳です。

**CI/CDテンプレートのリポジトリ**

https://github.com/databrickslabs/cicd-templates

# 概要

Databricks Labsの継続的インテグレーション、継続的デプロイメント(CI/CD)テンプレートは、ソフトウェア開発チームが既存のCIツール、Databricksジョブを容易に利用できる様にするためにオープンソースツールです。さらに、これにはDatabricksのベストプラクティスと共にAzure、AWSで動作するパイプラインのテンプレートが含まれているので、開発者は完全なテスト、インテグレーション、デプロイメントシステムを最初からセットアップする必要なしに、本業のコード開発に集中することができます。

CI/CDテンプレートは3ステップで準備できます。

1. `pip install cookiecutter`
1. `cookiecutter https://github.com/databrickslabs/cicd-templates.git`
    - 完全に動作するパイプラインを実行する際に使用するクラウドは何かといった質問にターミナル上で回答します。
    - `pip install databricks_cli && databricks configure --token`
    - プロジェクトのメインディレクトリで`./run_pipeline.py pipelines`を実行して、Databricks上のパイプラインを起動します。
1. お使いのDatabricksトークンとワークスペースのURLをgithubのシークレットに追加し、パイプラインをgithubのリポジトリにコミットします。

これで、Databricks LabsのCI/CDパイプラインは、あなたがリポジトリに新たなコミットをするたびに、自動でDatabricksに対してテストを実行します。コードをデプロイする準備ができたら、githubのリリースを作成することで、テンプレートはあなたのパイプラインを自動でパッケージし、Databricksのジョブとしてデプロイします。

これで全てです！あなたは開発チームが利用できるスケーラブルなパイプラインを手に入れたことになります。さらに、みなさまのチームの固有のユースケースに応じて、いつでもテンプレートを変更できるので、しょうらのプロジェクトも容易にセットアップできる様になります。

この記事の残りの部分では、なぜこのDatabricks Labs CI/CDテンプレートを作成したのか、このプロジェクトの今後はどの様なものか、どの様にプロジェクトに貢献できるのかに関してディープダイブします。

# なぜ、さらに別の開発フレームワークを必要とするのでしょうか？

Databricksにおけるプロジェクトが拡大すると、DatabricksのユーザーはETL、データサイエンスの実験、ダッシュボードなどを含む大量のノートブックを管理することに苦労していることに気づくことでしょう。現在のノートブックから他のノートブックを呼び出すために`%run`を使うなどの短期的なワークアラウンドはいくつか存在しますが、コードを呼び出すパイプラインから再利用可能なコードを分離するという、従来型のソフトウェアエンジニアリングのベストプラクティスに従うことは有用です。さらに、パイプラインが動作していることを検証するためのパイプライン周りのテストの構築もまた、プロダクションレベルの開発プロセスを確立するためには重要な一歩となります。

最後に、新規のコードの変更があった際に、手動でジョブをトリガーしたり、手動でクラスターにライブラリをインストールすることなしに、自動でジョブを実行できることは、あなたのパイプライン全体におけるスケーラビリティ、安定性を実現するためにも重要なこととなります。まとめると、自身のプロダクションパイプラインをスケール、安定させるためには、ノートブック上で手動でコードを実行するのではなく、コードのパッケージング、テスト、デプロイメントをIDEや継続的インテグレーションツールなどの従来型のソフトウェアエンジニアリングツールを用いて自動化する方向に進むべきです。

実際のところ、Databricksをワークロードの実行環境として使っている多くのデータチームは、従来型のソフトウェアエンジニアリングのプラクティスを用いてパイプラインを構築することを好んでいます。これには、IDE、GIT、従来型のCI/CDパイプラインが含まれます。これらのチームは通常、自身の処理ロジックをユニットテストでカバーし、彼らのバージョン管理システムにおける変更後にインテグレーションテストを実行します。

リリースプロセスもまた、バージョン管理システムによって管理されます。PRがリリースブランチにマージされた後、インテグレーションテストが実行され、結果がポジティブだった場合には、デプロイメントパイプラインもアップデートされます。プロダクションワークスペースに新たなバージョンのパイプラインを持ち込むことは、設定アーティファクトやpython/mavenライブラリや他の依存関係など異なる依存関係を持っていることから、これもまた複雑なプロセスとなります。多くの場合、異なるパイプラインが同じアーティファクトの異なるバージョンに依存していることがあります。

# 再利用可能なテンプレートを用いてDatabricksにおけるCI/CDをシンプルに

多くの企業は、異なるプロジェクトにおいて自身のCI/CDパイプラインを構築するために、多くのリソースを投入しています。これらすべてのパイプラインには共通する部分が多くあります。基本的には、これらは何かしらのアーティファクトを構築、デプロイ、テストするものとなります。かつてCIツールが多くのタスクを時代遅れにするまでは、開発者はアプリケーションの構築、テスト、デプロイのために異なるスクリプトを開発するのに長い時間を費やしていました。CIツールによってもたらされた慣習によって、これらの慣習に従う限りあらゆるプロジェクトに適用できる様に抽象化した方法で、これらのタスクのほとんどを実装できるフレームワークを開発者に提供できることになりました。例えば、大規模なantスクリプトで実装されたMavenは、Javaの開発にこの様な慣習をもたらし、構築プロセスの大部分を自動化することが可能となりました。

Databricks LabsのCI/CDテンプレートによって、Jenkinsのような既存のCI/CDツールをDatabricksで容易に利用できる様になります。テンプレートには、Databricksのベストプラクティスに沿って作成されたパイプラインコードが含まれています。さらに、テンプレートによって、チームは自身のCI/CDパイプラインを他のプロジェクトでも再利用できる様に再利用可能なコードにパッケージすることができます。Databricks LabsのCI/CDテンプレートは、データエンジニアリング、データサイエンスプロジェクトに対して同様の慣習を提供します。Databricksを使っているデータ実践者に対して、自身のデータアプリケーションに対するCI/CDパイプラインを実装するために抽象化されたツールを提供します。

我々が導入した慣習を深く見ていきましょう。データ変換、特徴量生成ロジック、モデルトレーニングなどのデータ処理ロジックの大部分は、pythonパッケージとして開発される必要があります。このロジックは、スケジュールされたジョブなど数多くのプロダクションパイプラインで利用されることになります。また、前述のロジックは個々の変換関数をテストするローカルのユニットテスト、インテグレーションテストでテストされます。インテグレーションテストはDatabricksのワークスペースで実行され、全体としてのデータパイプラインがテストされます。

# Databricksデプロイメントを用いた開発ライフサイクル

データエンジニア、データサイエンティストは、Databricksにおいて、自身のローカルのIDEで開発したコードのテスト、デプロイにDatabricks LabsのCI/CDテンプレートを活用することができます。Databricks LabsのCI/CDテンプレートはユーザーに対して、新たなデータのユースケースの開発を急速立ち上げする際に活用できる再利用可能なデータプロジェクトテンプレートを提供します。このプロジェクトは以下の構造を持ちます。
![](https://databricks.com/wp-content/uploads/2020/05/ImageCICD-og.png)

データ取り込み、検証、変換ロジック、特徴量エンジニアリング、機械学習モデルはすべてpythonパッケージとして開発することができます。このロジックはプロダクションパイプラインで使用され、開発テスト(developer tests/Dev-test)、インテグレーションテストによってテストされます。**Databricks LabsのCI/CDテンプレートは、プロダクションパイプラインをすべての依存関係を含むDatabricksのジョブとして自動でデプロイすることができます。**これらのパイプラインは`pipeline`ディレクトリに格納される必要があり、それら自身は異なるライブラリや設定アーティファクトなどの依存関係を持ちます。開発者は自身のラップトップにインストールされたIDE上で開発する際に、テストを行うためにApache SparkのローカルモードあるいはDatabricks Connectを使用することができます。これらのパイプラインをDatabricksで実行したい場合には、Databricks LabsのCI/CDテンプレートのCLIを活用することができます。また、開発者はDatabricks上の現状のプロジェクトの状態に対してインテグレーションテストをキックするためにCLIを使うこともできます。

この後で、ユーザーは変更をGitHubにプッシュすると、GitHubのアクション設定を用いて、Databricks上で自動でテスト行います。それぞれのプッシュの後でGitHubのアクションはプロジェクトのコードをチェックするVMを起動し、このVM内でローカルのpytestを実行します。これらのテストが成功すると、pythonのwheelをビルドし、他の依存関係と共にDatabricksにデプロイし、Databricks上で開発テストを実行します。

開発サイクルの最後では、GitHubリリースを作成することでプロジェクト全体がデプロイされ、DatabricksにおけるインテグレーションテストとDatabricksジョブとしてプロダクションパイプラインのデプロイをキックします。この場合、CI/CDパイプラインは以前のものと同じ様に見えますが、開発テストの代わりにインテグレーションテストがDatabricks上で実行され、これらにパスした場合に、Databricksにおけるプロダクションジョブの定義が更新されます。

# Databricks LabsのCI/CDテンプレートを用いた新規データプロジェクトの作成およびデプロイの方法

## Databricks Labs CI/CDテンプレートのプロジェクトテンプレートを用いた新規プロジェクトの作成

- Cookiecutter pythonパッケージのインストール: `pip install cookiecutter`
- cookiecutterテンプレートを用いたプロジェクトの作成: `cookiecutter  https://github.com/databrickslabs/cicd-templates.git`
- 質問に答えていきます。

この後にあなた向けの新規プロジェクトが作成されます。以下の様な構造となっています。

```
  .
    ├── cicd_demo
    │   ├── __init__.py
    │   ├── data
    │   │   ├── __init__.py
    │   │   └── make_dataset.py
    │   ├── features
    │   │   ├── __init__.py
    │   │   └── build_features.py
    │   ├── models
    │   │   ├── __init__.py
    │   │   ├── predict_model.py
    │   │   └── train_model.py
    │   └── visualization
    │       ├── __init__.py
    │       └── visualize.py
    ├── create_cluster.py
    ├── deployment
    │   └── databrickslabs_mlflowdepl-0.2.0-py3-none-any.whl
    ├── deployment.yaml
    ├── dev-tests
    │   ├── pipeline1
    │   │   ├── job_spec_aws.json
    │   │   ├── job_spec_azure.json
    │   │   └── pipeline_runner.py
    │   └── pipeline2
    │       ├── job_spec_aws.json
    │       ├── job_spec_azure.json
    │       └── pipeline_runner.py
    ├── docs
    │   ├── Makefile
    │   ├── commands.rst
    │   ├── conf.py
    │   ├── getting-started.rst
    │   ├── index.rst
    │   └── make.bat
    ├── integration-tests
    │   ├── pipeline1
    │   │   ├── job_spec_aws.json
    │   │   ├── job_spec_azure.json
    │   │   └── pipeline_runner.py
    │   └── pipeline2
    │       ├── job_spec_aws.json
    │       ├── job_spec_azure.json
    │       └── pipeline_runner.py
    ├── notebooks
    ├── pipelines
    │   ├── pipeline1
    │   │   ├── job_spec_aws.json
    │   │   ├── job_spec_azure.json
    │   │   └── pipeline_runner.py
    │   └── pipeline2
    │       ├── job_spec_aws.json
    │       ├── job_spec_azure.json
    │       └── pipeline_runner.py
    ├── requirements.txt
    ├── run_pipeline.py
    ├── runtime_requirements.txt
    ├── setup.py
    └── tests
    └── test_smth.py
```

ここで作成したプロジェクトの名前は`cicd_demo`であり、pythonパッケージの名前も同様に`cicd_demo`です。我々の変換ロジックは`cicd_demo`ディレクトリ配下に作成されます。これは、`pipelines`ディレクトリに配置されるパイプラインから使用されます。`pipelines`ディレクトリ内に数多くのパイプラインを作成し、それぞれのパイプラインは自身のディレクトリを持ちます。

それぞれのパイプラインには`pipeline_runner.py`という名前のエントリーポイントのpythonスクリプトが必要となります。このプロジェクトにおいては、2つのサンプルパイプラインが作成されていることを確認できます。それぞれのパイプラインは、それぞれのサポートするクラウドに対するpythonスクリプトとジョブの定義を有しています。これらのファイルは、クラスター定義(ノード数、インスタンスタイプなど)やジョブのスケジュール設定を作成する際に使用されます。

`Dev-tests`と`integration-tests`ディレクトリはDatabricks上でパイプラインをテストするインテグレーションテストを定義するために使用されます。これらもまた、pythonパッケージで定義されたロジックを使用し、変換結果を検証します。

## ターゲットのDatabricksワークスペースにプロジェクトをデプロイしましょう

DatabricksのデプロイメントはGitHubのアクションと密接にインテグレーションされています。コードをプッシュし、パイプラインをテストするために自動でテスト、デプロイする様にGitHubアクションを使用するために、新たなGitHubリポジトリを作成する必要があります。GitHubリポジトリをDatabricksワークスペースと連携するためには、GitHubシークレットとしてワークスペースURLとパーソナル認証トークン(PAT)を設定する必要があります。ワークスペースURLは`DATABRICKS_HOST`シークレット、トークンは`DATABRICKS_TOKEN`として設定される必要があります。

これでプロジェクトディレクトリ内に新たなgitリポジトリを初期化することができます。この後で、全てのファイルをgitに追加し、リモートのGitHubリポジトリにプッシュすることができます。トークンを設定し、最初のプッシュを行うことでGitHubアクションはターゲットのDatabricksワークスペースに対して開発テストを自動で実行し、テストをパスした場合最初のコミットはグリーンとマークされます。

run_pipeline.pyスクリプトを実行することで、ローカルの環境からプロダクションパイプラインやDatabricks上の個々のテストを実行することもできます。

```
./run_pipeline.py pipelines --pipeline-name test_pipeline
```

このコマンドによって、Databricksのpipelinesフォルダーのtest_pipelineが実行されます。

## Databricksデプロイメントを用いたテストの自動化

新規に作成されたプロジェクトには、2つの標準的なCI/CDパイプラインが定義されています。そのうちの1つはプッシュのたびに実行され、Databricksワークスペースに対して開発テストを行います。
![](https://databricks.com/wp-content/uploads/2020/05/CICD4ML-PushFlow-og.png)

もう一つはそれぞれの作成されたGitHubリリースごとに実行され、Databricksワークスペースに対してインテグレーションテストを実行します。インテグレーションテストの結果がポジティブだった場合にはプロダクションパイプラインがDatabricksワークスペースのジョブとしてデプロイされます。
![](https://databricks.com/wp-content/uploads/2020/05/CICD4ML-ReleaseFlow-og.png)

## Databricksデプロイメントを用いたプロダクションパイプラインのデプロイ

パイプラインをプロダクションのワークスペースにデプロイするために、GitHubリリースが作成されます。自動でインテグレーションを開始し、結果がポジティブだった場合には、プロダクションパイプラインはジョブとしてDatabricksワークスペースにデプロイされます。最初の実行でDatabricksワークスペースにジョブが作成されます。以降のリリースでは、既存のジョブ定義が更新されます。

## 依存関係、設定の管理

Databricksのデプロイメントでは、2つのレベルの依存関係管理をサポートしています。

- プロジェクトレベル
    - プロダクションの実行時に必要となるプロジェクトレベルのPythonパッケージの依存関係は、runtime_requiremnets.txtに記述されます。
    - プロジェクトレベルのJARやPython Whlの依存関係を用いることもできます。これらは、dependencies/jars、dependencies/wheelsディレクトリに格納されます。
- パイプラインの依存関係
    - パイプラインレベルのPython/mavenなどの依存関係は、ジョブ定義のjsonのライブラリセクションに直接指定することができます。
    - パイプラインフォルダのdependencies/jars、dependencies/wheelsにJarやwheelを配置することもできます。

設定ファイルはパイプラインディレクトリに格納されます。これらはpythonスクリプトともにMLflowに記録されます。Databricksでの実行時、ジョブスクリプトは最初のパラメーターとしてパイプラインフォルダのパスを受け取ります。このパラメーターは、パイプラインディレクトリに存在するあらゆるファイルをオープンする際に使用されます。

以下の例を用いて、Databricksデプロイメントを使ってどの様に依存関係を管理できるのかを見ていきましょう。

```
    .
    ├── dependencies
    │   ├── jars
    │   │   └── direct_dep.jar
    │   └── wheels
    │       └── someotherwheel-0.1.0-py3-none-any.whl
    ├── job_spec_aws.json
    ├── job_spec_azure.json
    ├── pipeline_runner.py
    └── train_config.yaml
```

このパイプラインには、2つのパイプラインレベルの依存関係があります。一つのjarファイルと一つのwheelです。Train_config.yamlファイルには、以下のコードを用いてパイプラインが読み込む設定パラメーターが含まれています。

```py:Python
    def read_config(name, root):
       try:
           filename = root.replace('dbfs:', '/dbfs') + '/' + name
           with open(filename) as conf_file:
               conf = yaml.load(conf_file, Loader=yaml.FullLoader)
               return conf
       except FileNotFoundError as e:
           raise FileNotFoundError(
               f"{e}. Please include a config file!")
    
    conf = read_config('train_config.yaml', sys.argv[1])
```

# 展望および次のステップ

Databricksデプロイメントの開発の方向性はいくつかあります。テンプレートを提供している一連のCI/CDツールを拡張すること検討しています。現時点ではGitHubアクションのみですが、CircleCIやAzure DevOpsとインテグレーションするテンプレートを追加することができます。

他の方向性としては、Scalaによるパイプライン開発のサポートです。

# どうしたら貢献できますか？

Databricks Labs CI/CDテンプレートはオープンソースツールです。貢献いただける方は大歓迎です。ぜひ、PRをサブミットしてください！

### Databricks 無料トライアル

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