ノートブック型 Spark サービス（Databricks）における DevOps の実践

## 概要

ノートブック型 Spark サービス（主に Databricks）における DevOps の実践について説明する。Databricks と Azure DevOps の組み合わせにより、次ののような CIパイプラインにてテスト結果やコードカバレッジを継続的に管理するまでの考え方と実施方法を記述する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/c7b4d7bc-01c5-57b4-6171-2ce27421e6d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/ffa4c331-4adc-1d3e-9b89-085eee9bb643.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/acc91528-bed1-bb4e-f104-6fe02cc5e97e.png)


成果物の一部を、Azure DevOps のパブリックプロジェクトとして公開しており、下記の URL にてパイプラインの実行結果を確認できる。

https://dev.azure.com/azuredevopssub/public_databricks_cicd_pipelines/_build/results?buildId=7&view=results

本記事のコードを含むノートブックを以下のリンクに保存してあり、Databricks 等の環境にインポートして実行が可能。

https://github.com/manabian-/databricks_development_practices/blob/main/docs/jp/notebooks/T70__L100__010_%E3%83%8E%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF%E5%9E%8B%20Spark%20%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%20DevOps%20%E3%81%AE%E5%AE%9F%E8%B7%B5.ipynb

## 本記事の位置付け

次の開発ガイドシリーズにおけるDevOps分野の1記事であり、リンク先には記事にて記事の全体像を整理している。

| GroupID | 分野               |
| ------- | ---------------------- |
| T10     | Spark概要                  |
| T20     | データエンジニアリング |
| T30     | データ品質チェック     |
| T40     | データサイエンス       |                                         |
| T50     | メタデータデプロイ     |
| T60     | テスト                 |
| **T70**     | **DevOps**                 |

https://qiita.com/manabian/items/67bcb6c5823bc4d5d944


## ノートブック型 Spark サービスにおける DevOps の概要

ノートブック型 Spark サービスにおける CI/CD（Continuous Integration（継続的インテグレーション）/ Continuous Delivery（継続的デリバリー））の実施方法を説明する。CI/CD とは、コード開発から本番環境への統合までの品質の保証と期間の最小化を担保しつつ、変更点の統合を行いながらシステム運用を行うプロセスである。

CI/CD は次のようなプロセスを実施することが多い。CI にてリリース可能なリソース（一般的には main ブランチのコード）の準備を行い、 CD にて他リソースと統合した本番相当の環境で検証を行ったうえで本番環境へリリースする。リソースの管理は Git で行い、 Git の運用ルールは Git-flow と呼ぶ。Git-flow はチームのスキルセットに応じて方針を定める必要があり、Git の機能を多用するような複雑な設計を行うと運用コストが高くなる可能性がある。継続的な開発と運用を行えるように、シンプルな設計が望ましい。CI/CD の処理群を CI/CD パイプラインと呼び、Github Actions や Azure Pipelines などのサービスで実行する。

- CI
  - Code
    - Git レポジトリ（ main ブランチ）を準備。
    - main ブランチから派生した開発用レポジトリー（フォークしたレポジトリ、あるいは、ブランチ）を準備。
    - プログラムコードとテストコードを開発。
    - 開発用レポジトリーへ変更内容をコミット。
    - Main ブランチへ変更要求（Pull Requet）を実施。
  - Build
    - main ブランチをベースとして PR の変更内容を反映したコードを生成。
    - ライブラリ等のビルド。
    - テスト用環境の準備。
    - テストを実行。
    - リリースの実施有無を判断。
  - Release
    - 環境へデプロイ可能なリソースを生成。
- CD
  - Deploy
    - 環境へリソースを配布。
  - Test
    - 環境にてテスト実行（主に他システムとの連携テストやE2Eテストを実施）。
  - Opeate
    - システムの運用。

Code や Build では、開発支援の観点、及び、コードの品質管理の観点により、次のような項目の実施が求められる。

1. Linter ツールによる静的解析
1. Formatter ツールによるコード整形
1. テスト結果の取得、及び、テスト結果の継続的な管理
1. テストのコードカバレッジの取得、及び、テストのコードカバレッジの継続的な管理

参考リンク
- [Azure DevOps を使用した Azure Databricks での継続的インテグレーションとデリバリー - Azure Databricks | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/databricks/dev-tools/ci-cd/ci-cd-azure-devops)

## Databricks での開発

### Databricks の開発方針

Databricks Repos のファイル管理仕様、Databricks Workspace 上ではノートブックであるが Git 上では Python ファイルとして管理される仕様、を考慮して、次のような開発指針とする。

- 共通機能を保持したノートブックを`%run`により呼び出して開発を行うこと
- テストケースを unittest で記載し、ノートブック上で正常終了することを確認すること
- ローカル環境での動作を考慮したコードを記載すること

ローカル環境での動作を行う目的は、ノートブック型環境で実施できないことを補うためである。ノートブック型環境には、Visual Studio Code (VS Code) などのローカル環境と比較すると、次のような特徴がある。ノートブック型環境の短所を克服するために、ノートブック型環境とローカル環境の併用がおすすめ。

- Pros
  1. 実行ノートブックにおけるコードと結果を合わせて保存できること
  1. 開発環境の準備が最小限となり、実行環境とコードの共有ができること
  1. 処理をセルごとに分割することで処理フローの解釈が容易となること
- Cons
  1. デバック実行が不可
  1. Linter（静的解析）ツールの利用不可
  1. Formatter （コード整形）ツールの利用不可
  1. テストのコードカバレッジの取得不可

### ローカル環境（Visual Studio Code） 上での開発

#### ローカル環境での開発方針

ローカル環境（Visual Studio Code）の開発環境の構築方法として下記表の方法が考えられるが、ノートブック型環境の短所を補うための補足的な利用であるため、1 の方法を採用。CI/CD パイプラインの実行時 Databricks 上で実施するため、Databricks に近い環境を選択。

| #    | 方法                                      | Pros                                                         | Cons                                                         |
| ---- | ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 1    | Databricks Connect により開発する方法     | - VS Code 上でテスト実施でき、コードカバレッジも取得可能     | - 一部機能を利用することができず、新機能の開発の計画がない                           |
| 2    | dbx by Databricks Labs により開発する方法 | - Databricks Workspace ローカル上のコードを、ローカル環境と同期が可能 | - ベンダーのサポートがない<br />- コードカバレッジを取得する方法を確立できていない |
| 3    | ローカル環境の Spark 環境にて開発する方法 | - VS Code 上でテスト実施でき、コードカバレッジも取得可能     | - Spark プロバイダー固有機能のテストを実施できない           |

Databricks Connect 環境の構築方法としては下記表の方法が考えられるが、１の方法を採用。ローカル環境の OS が Windows の場合には、Databricks で利用されている OS との仕様差異を最小限とするために、Databricks Runtime と同じバージョンの OS (Databricks Runtime 10.4 LTS を利用する場合には Ubuntu 20.04 LTS) を用いることを推奨。

| #    | 方法                                                         | Pros                                             | Cons                                                         |
| ---- | ------------------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------ |
| 1    | Conda （無償：Miniconda、有償：Anaconda）を用いて構築する方法 | - 初期環境の統一ができ、環境を柔軟に切り替え可能 | - 環境の統一が困難                                           |
| 2    | Poetry を用いて構築する方法                                  | - 環境の統一が容易                               | - Databricks Connect のバージョンを切り替えることができない |
| 3    | コンテナ（無償：Rancher Desktop、有償：Docker Desktop）を用いて構築するする方法             | - 初期環境の統一ができ、環境を柔軟に切り替え可能  | - コンテナーに関する知識が必要 |
| 4    | Python 仮想環境を用いずに直接構築する方法             | - 特になし                                       | - Databricks Connect のバージョンを切り替えることができない |

Visual Studio Code の設定値を開発メンバーで共有するために、次のファイルを`.vscode`ディレクトリに配置。

| #    | ファイル名      | 概要                                                      |
| ---- | --------------- | --------------------------------------------------------- |
| 1    | settings.json   | Visual Studio Code の設定を保持。                         |
| 2    | extensions.json | Visual Studio Code に追加推奨の拡張機能のリストを保持。   |
| 3    | launch.json     | Visual Studio Code にてデバッグ実行する際のの設定を保持。 |

Python の Linter と Formatter として次のライブラリを利用。

| #    | 分類      | ライブラリ名 | 目的                               |
| ---- | --------- | ------------ | ---------------------------------- |
| 1    | Formatter | black        | コードに対するフォーマットツール。             |
| 2    | Formatter | isort        | import文に対するフォーマットツール。           |
| 3    | Linter    | flake8       | コードに対する静的解析ツール。     |
| 4    | Linter    | pydocstyle   | Docstring に対する静的解析ツール。 |
| 5    | Linter    | mypy         | 型に対する静的解析ツール。         |

ライブラリの設定値は、次のファイルにて設定。

| #    | ファイル名     | 概要                                                         |
| ---- | -------------- | ------------------------------------------------------------ |
| 1    | pyproject.toml | 基本的な設定値を設定。                                       |
| 2    | .vscode/settings.json  | ローカル環境でのみ有効な設定値を設定。テスト実行時の並列数の設定など。 |
| 3    | .vscode/launch.json    | デバッグ時にのみ有効な設定値を設定。カバレッジレポートを取得しない設定など。 |


Conda 環境情報を`environment.yml`に記述しており、Databricks Connect に対応した Python バージョンをベースに`requirements.txt`を pip install する構成。`requirements.txt`では、Databricks Connect や Linter などの開発時に必要なライブラリだけでなく、Databricks Runtime にインストールされている多くのライブラリを保持。

```requirements.txt
# Formatting libraries
black
isort

# Linter libraries
flake8
pydocstyle
mypy

# test libraries
pytest
coverage
pytest-cov
pytest-xdist

# Sphinx Libraries
sphinx
sphinx-markdown-builder

# databricks Connect
databricks-connect==10.4.*
```

Databricks Runtime インストールされているライブラリのバージョンを取得するために、次のような手順で `requirements.txt` を作成している。Conda 環境構築時にエラーとなったライブラリーを除外。

1. `%pip freeze`をノートブック上で実行することで、クラスターにインストールされているライブラリ一覧を取得
2. `requirements.txt`に、１の結果を貼り付け、不要なライブラリを削除
3. `conda env create -f environment.yml`により環境が構築できることを確認
4. `.environments` ディレクトリ下に Databricks Connect のバージョンごとの環境情報を保存

#### ローカル環境の構築

詳細は、[Databricks Connect（Python）環境を Windows Subsystem for Linux (WSL)上に特定の目的のみに利用する Linux 環境に構築する手順 - Qiita](https://qiita.com/manabian/items/e72832277c0aa00735dc) の記事にて記載。

## Azure DevOps (Pipelines) による CI/CD パイプライン

### Azure Pipelines 概要

Azure Pipelines とは、Azure DevOps の１機能であり、 CI/CD（Continuous Integration（継続的インテグレーション）/ Continuous Delivery（継続的デリバリー））を自動化させる pipeline を構築できるサービスである。pipeline は、stage -> job -> task の階層構造となってり、Trigger により実行され、 Stage により論理的境界線を定義し、 job にエージェントを紐づけて task で指定した処理を行う。

-  [Azure Pipelines New User Guide - Key concepts - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/get-started/key-pipelines-concepts?view=azure-devops)

Azure Pipelines では、エージェントの並列実行数が主な課金対象であり、複数人での開発を行う際には無償で並列数に制限なく利用できるエージェントレスジョブを利用するなどのエージェントの利用方法が重要となる。処理が完了するまでの待機処理を、通常のジョブで実行した場合にはエージェントを占有してしまうが、エージェントレスジョブで実行することで他パイプライン（他ジョブ）がエージェントを利用することができる。

- [Azure Pipelines エージェント - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser)
- [エージェントレス ジョブでサポートされているタスク - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/phases?view=azure-devops-2020&tabs=yaml#agentless-tasks)

Azure Pipelines では、YAML 形式で記述を行う。YAMLで記述できる項目は、Azure DevOps のドキュメントにて整理されており、タスク等の拡張機能をインストールすることも可能である。[Command Line task](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/utility/command-line?view=azure-devops&tabs=yaml) では`script`と記述できるなど、一部のタスクではシンタックスを利用することもできる。以前から Azure DevOps （Visual Studio Team Services） で利用できたクラシックパイプラインという開発方法もあるが、本記事では利用対象としない。クラシックパイプラインの機能をベースに解説されている記事もあるため、どちらの開発方法の記事であるかを確認する必要がある。

-   [YAML schema reference | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema/?view=azure-pipelines&viewFallbackFrom=azure-devops)
-   [Catalog of the built-in tasks for build-release and Azure Pipelines & TFS - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/?view=azure-devops)
-  [Azure Pipelines Extensions - Azure DevOps Services (visualstudio.com)](https://marketplace.visualstudio.com/search?target=AzureDevOps&category=Azure%20Pipelines&sortBy=Installs)

stages 、 jobs 、 tasks 、variables を別の YAML ファイルで定義し、テンプレートとして呼び出すことも可能。

- [Templates - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/templates?view=azure-devops)
  -   [stages.template definition | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema/stages-template?view=azure-pipelines)
  -   [jobs.template definition | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema/jobs-template?view=azure-pipelines)
  -   [steps.template definition | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema/steps-template?view=azure-pipelines)
  -   [variables.template definition | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/yaml-schema/variables-template?view=azure-pipelines)


機微な情報をパイプラインで利用する際には、シークレット変数とする方法、および、Azure Key Vault を利用する方法が推奨されており、YAML 上に直接記述するべきではない。

-   [シークレット変数を設定する - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/set-secret-variables?view=azure-devops&tabs=yaml%2Cbash)
-   [Azure Pipelines で Azure Key Vault シークレットを使用する - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/release/azure-key-vault?view=azure-devops&tabs=yaml)


パイプライン実行の承認（Approvals）や同時実行の制御（Exclusive lock）も実施できる。

-   [Approvals - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&preserve-view=true&tabs=check-pass#approvals)
-   [Exclusive lock - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&preserve-view=true&tabs=check-pass#exclusive-lock)


ジョブが異なる場合には異なるエージェント環境となるため、変数の受け渡しを行う際には次の記事で記載されているような記述を行う。`dependsOn`により変数取得元のジョブ間に依存関係を明示する必要がある。

- [Support for output variables - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops#support-for-output-variables)


ビルド番号などの定義済み変数については、次のドキュメントに記載されている。

- [実行 (ビルド) 番号 - Azure Pipelines | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml)

Azure pipelins の実装方法を検討する際には、次のドキュメントの方法を試すことが参考になる。

- [Python アプリのビルドとテスト - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/devops/pipelines/ecosystems/python?view=azure-devops)
- [Azure DevOps を使用した Azure Databricks での継続的インテグレーションとデリバリー - Azure Databricks | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/databricks/dev-tools/ci-cd/ci-cd-azure-devops)

Python アプリのビルドとテストのコードにて次の YAML により、 Python におけるシンプな CI パイプラインの構築が可能。

```yaml
trigger: none
pr: none

jobs:
- job:
  displayName: execute and publish test
  pool:
    vmImage: 'ubuntu-latest'
  steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.7'
  - script: python -m pip install --upgrade pip setuptools wheel
    displayName: 'Install tools'
  - script: pip install -r requirements.txt
    displayName: 'Install requirements'
  - script: |
      pip install pytest pytest-azurepipelines
      pip install pytest-cov
      pytest --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=xml
    displayName: 'pytest'
  - task: PublishTestResults@2
    condition: succeededOrFailed()
    inputs:
      testResultsFiles: '**/test-*.xml'
      testRunTitle: 'Publish test results for Python $(python.version)'
  - task: PublishCodeCoverageResults@1
    inputs:
      codeCoverageTool: Cobertura
      summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
```

### Databricks における CI/CD パイプラインの構築

詳細は、 [Databricks の CI （Continuous Integration（継続的インテグレーション）） パイプラインを Azure DevOps (Pipelines) にて構築する手順 - Qiita](https://qiita.com/manabian/items/5ea34757cacda0df98a2) の記事にて記載。