Jenkins を使用した Azure Databricks の CI/CD

注意

この記事では、Databricks によって提供もサポートもされていない Jenkins について説明します。 プロバイダーに問い合わせるには、Jenkins のヘルプを参照してください。

CI/CD パイプラインの管理と実行に使用できる CI/CD ツールは多数あります。 この記事では、Jenkins オートメーション サーバーの使用方法について説明します。 CI/CD は設計パターンなので、この記事に記載されている手順とステージでは、各ツールのパイプライン定義言語にいくつかの変更を加えて転送する必要があります。 さらに、このサンプル パイプラインのコードの多くでは、標準の Python コードが実行されます。これは、他のツールで呼び出すことができます。 Azure Databricks の CI/CD の概要については、「Azure Databricks の CI/CD とは」を参照してください。

Azure Databricks での Azure DevOps の使用方法については、「Azure DevOps を使用した Azure Databricks での継続的インテグレーションとデリバリー」を参照してください。

CI/CD 開発ワークフロー

Databricks は、Jenkins を使用した CI/CD 開発の次のワークフローを提案します。

  1. サードパーティの Git プロバイダーでリポジトリを作成するか、既存のリポジトリを使用してください。
  2. ローカル開発マシンを同じサード パーティのリポジトリに接続します。 手順については、サードパーティの Git プロバイダーのドキュメントを参照してください。
  3. 既存の更新された成果物 (ノートブック、コード ファイル、ビルド スクリプトなど) をサード パーティのリポジトリからローカル開発マシンにプルします。
  4. 必要に応じて、ローカル開発マシンで成果物を作成、更新、テストしてください。 次に、ローカル開発マシンから、新しい成果物と変更された成果物をサード パーティのリポジトリにプッシュします。 手順については、サードパーティの Git プロバイダーのドキュメントを参照してください。
  5. 必要に応じて、手順 3 と手順 4 を繰り返します。
  6. Jenkins を定期的に使用することで、サード パーティのリポジトリから成果物をローカル開発マシンまたは Azure Databricks ワークスペースに自動的にプルし、ローカル開発マシンまたは Azure Databricks ワークスペースでコードをビルド、テスト、実行し、テストおよび実行結果を報告するための統合的なアプローチとして使用できます。 Jenkins は手動で実行できますが、実際の実装では、リポジトリのプル要求など、特定のイベントが発生するたびに Jenkins を実行するようにサードパーティの Git プロバイダーに指示しておくのが一般的です。

この記事の残りの部分では、プロジェクト例を使用して、Jenkins を使用して上記の CI/CD 開発ワークフローを実装する 1 つの方法について説明します。

Jenkins の代わりに、Azure DevOps を使用する方法については、「Azure DevOps を使用した Azure Databricks での継続的インテグレーションとデリバリー」を参照してください。

ローカル開発マシンのセットアップ

この記事の例では、Jenkins を使用して Databricks CLIDatabricks アセット バンドルに次の操作を行うよう指示 します。

  1. ローカル開発マシンで Python ホイール ファイルをビルドします。
  2. ビルドされた Python ホイール ファイルと、追加の Python ファイル、Python ノートブックをローカル開発マシンから Azure Databricks ワークスペースにデプロイします。
  3. アップロードした Python ホイール ファイルとノートブックを、そのワークスペースでテストして実行します。

Azure Databricks ワークスペースに、この例のビルド ステージとアップロード ステージを実行するように指示するためにローカル開発マシンを設定するには、ローカル開発マシンで次の操作を行います。

手順 1: 必要なツールをインストールする

この手順では、ローカル開発マシンに Databricks CLI、Jenkins、jq、Python ホイール ビルド ツールをインストールします。 この例を実行するには、これらのツールが必要です。

  1. Databricks CLI バージョン 0.205 以降をインストールします (まだインストールしていない場合)。 Jenkins では、Databricks CLI を使用して、この例のテストと実行命令をワークスペースに渡します。 「Databricks CLI のインストールまたは更新」を参照してください。

  2. Jenkins をインストールして起動します (まだインストールしていない場合)。 LinuxmacOS、またはWindows 用の Jenkins のインストールを参照してください。

  3. jq をインストールします。 この例では、JSON 形式のコマンド出力を解析するために jq を使用します。

  4. pip を使用して、次のコマンドで Python ホイール ビルド ツールをインストールします (一部のシステムでは、pip の代わりに pip3 を使用する必要があります)。

    pip install --upgrade wheel
    

手順 2: Jenkins パイプラインを作成する

この手順では、Jenkins を使用して、この記事の例の Jenkins パイプラインを作成します。 Jenkins には、CI/CD パイプラインを作成するための異なるプロジェクトの種類がいくつか用意されています。 パイプラインでは、Jenkins プラグインを呼び出して構成するために、Groovy コードを使用してパイプライン内のステージを定義するインターフェイスが提供されます。

Jenkins プロジェクトの種類

Jenkins で Jenkins パイプラインを作成するには、次の手順を実行します。

  1. Jenkins を起動したら、Jenkins ダッシュボード[新しい項目] をクリックします。
  2. [項目名の入力] に、Jenkins パイプラインの名前を入力します。例: jenkins-demo
  3. [パイプライン プロジェクト タイプ] アイコンをクリックします。
  4. [OK] をクリックします。 Jenkins パイプラインの [構成] ページが表示されます。
  5. [パイプライン] 領域にて、 [定義] ドロップダウン リストで、SCM から [パイプライン スクリプト] を選択します。
  6. [SCM] ドロップダウン リストで、[Git] を選択します。
  7. [リポジトリ URL] には、サードパーティの Git プロバイダーによってホストされているリポジトリの URL を入力します。
  8. [Branch Specifier]\(ブランチ指定子\) には、*/<branch-name> と入力し、<branch-name> は使用するリポジトリ内のブランチの名前です。例: */main
  9. [スクリプト パス] には、まだ設定されていない場合は Jenkinsfile と入力します。 この記事の後半で Jenkinsfile を作成します。
  10. [Lightweight checkout]\(ライトウェイト チェックアウト\) というタイトルのチェックボックスが既にオンになっている場合はオフにします。
  11. [保存] をクリックします。

手順 3: Jenkins にグローバル環境変数を追加する

この手順では、3 つのグローバル環境変数を Jenkins に追加します。 Jenkins は、これらの環境変数を Databricks CLI に渡します。 Databricks CLI では、Azure Databricks ワークスペースで認証するためにこれらの環境変数の値が必要になります。 この例では、サービス プリンシパルに対して OAuth マシン間 (M2M) 認証を使用します (ただし、他の認証タイプも使用できます)。 Azure Databricks ワークスペースに対して OAuth M2M 認証を設定するには、「サービス プリンシパルを使用して Azure Databricks(OAuth M2M) による認証を行う」を参照してください。

この例の 3 つのグローバル環境変数は次のとおりです。

  • DATABRICKS_HOSThttps:// で始まる Azure Databricks ワークスペースの URL に設定します。 「ワークスペースのインスタンス名、URL、および ID」を参照してください。
  • DATABRICKS_CLIENT_ID。サービス プリンシパルのクライアント ID (アプリケーション ID とも呼ばれます) に設定されます。
  • DATABRICKS_CLIENT_SECRET。サービス プリンシパルの Azure Databricks OAuth シークレットに設定されます。

Jenkins でグローバル環境変数を設定するには、Jenkins ダッシュボードから次の手順を実行します。

  1. サイドバーの [Jenkins の管理] をクリックします。
  2. [システム構成] セクションで、[システム] をクリックします。
  3. [グローバル プロパティ] セクションで、タイル化された [環境変数] のチェック ボックスをオンにします。
  4. [追加] をクリックし、環境変数の [名前][値] を 入力します。 追加の環境変数ごとにこれを繰り返します。
  5. 環境変数の追加が完了したら、[保存] をクリックして Jenkins ダッシュボードに戻ります。

Jenkins パイプラインを設計する

Jenkins には、CI/CD パイプラインを作成するための異なるプロジェクトの種類がいくつか用意されています。 この例では、Jenkins パイプラインを実装します。 パイプラインでは、Jenkins プラグインを呼び出して構成するために、Groovy コードを使用してパイプライン内のステージを定義するインターフェイスが提供されます。

Jenkins パイプライン定義をテキスト ファイル (Jenkinsfile と呼ばれる) で記述します。これは、プロジェクトのソース管理リポジトリにチェックインされます。 詳細については、Jenkins パイプラインに関するページを参照してください。 この記事の例の Jenkins パイプラインを次に示します。 この例の Jenkinsfile では、次のプレースホルダーを置き換えます。

  • <user-name><repo-name> はサードパーティの Git プロバイダーによってホストされているユーザー名とリポジトリ名に置き換えます。 この記事では、GitHub URL を例として使用します。
  • <release-branch-name> をリポジトリ内のリリース ブランチの名前に置き換えます。 たとえば、main になります。
  • <databricks-cli-installation-path> を、Databricks CLI がインストールされているローカル開発マシン上のパスに置き換えます。 たとえば、macOS の場合、これは /usr/local/bin になります。
  • <jq-installation-path> を、jq がインストールされているローカル開発マシン上のパスに置き換えます。 たとえば、macOS の場合、これは /usr/local/bin になります。
  • この例では、<job-prefix-name> をワークスペースで作成された Azure Databricks ジョブを一意に識別するための文字列に置き換えます。 たとえば、jenkins-demo になります。
  • BUNDLETARGETdev に設定されていることに注意してください。これはこの記事の後半で定義されている Databricks アセット バンドル ターゲットの名前です。 実際の実装では、これを独自のバンドル ターゲットの名前に変更します。 バンドル ターゲットの詳細については、この記事の後半で説明します。

リポジトリのルートに追加する必要がある Jenkinsfile を次に示します。

// Filename: Jenkinsfile
node {
  def GITREPOREMOTE = "https://github.com/<user-name>/<repo-name>.git"
  def GITBRANCH     = "<release-branch-name>"
  def DBCLIPATH     = "<databricks-cli-installation-path>"
  def JQPATH        = "<jq-installation-path>"
  def JOBPREFIX     = "<job-prefix-name>"
  def BUNDLETARGET  = "dev"

  stage('Checkout') {
    git branch: GITBRANCH, url: GITREPOREMOTE
  }
  stage('Validate Bundle') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET}
       """
  }
  stage('Deploy Bundle') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle deploy -t ${BUNDLETARGET}
       """
  }
  stage('Run Unit Tests') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-unit-tests
       """
  }
  stage('Run Notebook') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-dabdemo-notebook
       """
  }
  stage('Evaluate Notebook Runs') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} evaluate-notebook-runs
       """
  }
  stage('Import Test Results') {
    def DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH
    def getPath = "${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET} | ${JQPATH}/jq -r .workspace.file_path"
    def output = sh(script: getPath, returnStdout: true).trim()

    if (output) {
      DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH = "${output}"
    } else {
      error "Failed to capture output or command execution failed: ${getPath}"
    }

    sh """#!/bin/bash
          ${DBCLIPATH}/databricks workspace export-dir \
          ${DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH}/Validation/Output/test-results \
          ${WORKSPACE}/Validation/Output/test-results \
          -t ${BUNDLETARGET} \
          --overwrite
       """
  }
  stage('Publish Test Results') {
    junit allowEmptyResults: true, testResults: '**/test-results/*.xml', skipPublishingChecks: true
  }
}

この記事の残りの部分では、この Jenkins パイプラインの各ステージと、そのステージで Jenkins が実行するための成果物とコマンドを設定する方法について説明します。

サードパーティのリポジトリから最新の成果物をプルする

この Jenkins パイプラインの最初のステージ (Checkout ステージ) は、次のように定義されています。

stage('Checkout') {
  git branch: GITBRANCH, url: GITREPOREMOTE
}

このステージでは、Jenkins がローカル開発マシンで使用する作業ディレクトリに、サードパーティの Git リポジトリからの最新の成果物があることを確認します。 通常、Jenkins はこの作業ディレクトリを <your-user-home-directory>/.jenkins/workspace/<pipeline-name> に設定します。 これにより、同じローカル開発マシン上で、Jenkins がサード パーティの Git リポジトリから使用する成果物とは別に、開発中の成果物の独自のコピーを保持できます。

Databricks アセット バンドルを検証する

この Jenkins パイプラインの 2 番目のステージ (Validate Bundle ステージ) は、次のように定義されます。

stage('Validate Bundle') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET}
     """
}

このステージでは、成果物をテストおよび実行するためのワークフローを定義する Databricks アセット バンドルが構文的に正しいことを確認します。 Databricks アセット バンドル (単にバンドルとも呼ばれる) では、すべてのデータ、分析および ML プロジェクトを、ソース ファイルのコレクションとして表現できます。 「Databricks アセット バンドルとは」をご覧ください。

この記事のバンドルを定義するには、ローカル コンピューター上の複製されたリポジトリのルートに databricks.yml という名前を付けたファイルを作成します。 この例の databricks.yml ファイルでは、次のプレースホルダーを置き換えます。

  • <bundle-name> をバンドルの一意なプログラム名に置き換えます。 たとえば、jenkins-demo になります。
  • この例では、<job-prefix-name> をワークスペースで作成された Azure Databricks ジョブを一意に識別するための文字列に置き換えます。 たとえば、jenkins-demo になります。 Jenkinsfile 内の JOBPREFIX 値と一致する必要があります。
  • <spark-version-id> をジョブ クラスターの Databricks Runtime バージョン ID に置き換えます。例: 13.3.x-scala2.12
  • <cluster-node-type-id> をジョブ クラスターのノード タイプ ID に置き換えます。例: Standard_DS3_v2
  • targets マッピングの dev は、Jenkinsfile 内の BUNDLETARGET と同じであることに注意してください。 バンドル ターゲットは、ホストと関連するデプロイ動作を指定します。

この例を正しく動作させるには、リポジトリのルートに追加する必要がある databricks.yml ファイルを次に示します。

# Filename: databricks.yml
bundle:
  name: <bundle-name>

variables:
  job_prefix:
    description: A unifying prefix for this bundle's job and task names.
    default: <job-prefix-name>
  spark_version:
    description: The cluster's Spark version ID.
    default: <spark-version-id>
  node_type_id:
    description: The cluster's node type ID.
    default: <cluster-node-type-id>

artifacts:
  dabdemo-wheel:
    type: whl
    path: ./Libraries/python/dabdemo

resources:
  jobs:
    run-unit-tests:
      name: ${var.job_prefix}-run-unit-tests
      tasks:
        - task_key: ${var.job_prefix}-run-unit-tests-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          notebook_task:
            notebook_path: ./run_unit_tests.py
            source: WORKSPACE
          libraries:
            - pypi:
                package: pytest
    run-dabdemo-notebook:
      name: ${var.job_prefix}-run-dabdemo-notebook
      tasks:
        - task_key: ${var.job_prefix}-run-dabdemo-notebook-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            data_security_mode: SINGLE_USER
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          notebook_task:
            notebook_path: ./dabdemo_notebook.py
            source: WORKSPACE
          libraries:
            - whl: "/Workspace${workspace.root_path}/files/Libraries/python/dabdemo/dist/dabdemo-0.0.1-py3-none-any.whl"
    evaluate-notebook-runs:
      name: ${var.job_prefix}-evaluate-notebook-runs
      tasks:
        - task_key: ${var.job_prefix}-evaluate-notebook-runs-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          spark_python_task:
            python_file: ./evaluate_notebook_runs.py
            source: WORKSPACE
          libraries:
            - pypi:
                package: unittest-xml-reporting

targets:
  dev:
    mode: development

databricks.yml ファイルの詳細については、「Databricks アセット バンドルの構成」を参照してください。

ワークスペースにバンドルをデプロイする

Deploy Bundle というタイトルの Jenkins パイプラインの 3 番目のステージは、次のように定義されています。

stage('Deploy Bundle') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle deploy -t ${BUNDLETARGET}
     """
}

このステージでは、次の 2 つの処理が行われます。

  1. databricks.yml ファイル内の artifact マッピングが whl に設定されているため、指定した場所にある setup.py ファイルを使用して Python ホイール ファイルをビルドするよう Databricks CLI に指示します。
  2. ローカル開発マシン上に Python ホイール ファイルがビルドされると、Databricks CLI は、ビルドされた Python ホイール ファイルを指定された Python ファイルとノートブックと共に Azure Databricks ワークスペースにデプロイします。 既定では、Databricks アセット バンドルは Python ホイール ファイルとその他のファイルを /Workspace/Users/<your-username>/.bundle/<bundle-name>/<target-name> にデプロイします。

databricks.yml ファイルで指定した Python ホイール ファイルをビルドできるようにするには、ローカル コンピューター上の複製したリポジトリのルートに次のフォルダーとファイルを作成します。

ノートブックが実行される Python ホイール ファイルのロジックと単体テストを定義するには、addcol.py および test_addcol.py という名前の 2 つのファイルを作成し、リポジトリの Libraries フォルダー内の python/dabdemo/dabdemo という名前のフォルダー構造に追加します (簡潔にするために、省略記号はリポジトリ内の省略されたフォルダーを示します)。

├── ...
├── Libraries
│    └── python
│          └── dabdemo
│                └── dabdemo
│                      ├── addcol.py
│                      └── test_addcol.py
├── ...

addcol.py ファイルには、Python ホイール ファイルに組み込まれた後、Azure Databricks クラスターにインストールされる可能性があるライブラリ関数が含まれています。 これは、リテラルで入力された新しい列を Apache Spark DataFrame に追加する単純な関数です。

# Filename: addcol.py
import pyspark.sql.functions as F

def with_status(df):
  return df.withColumn("status", F.lit("checked"))

test_addcol.py ファイルには、addcol.py で定義されている with_status 関数にモック DataFrame オブジェクトを渡すテストが含まれています。 結果は、想定される値を含む DataFrame オブジェクトと比較されます。 値が一致する場合 (この例では一致)、テストは成功します:

# Filename: test_addcol.py
import pytest
from pyspark.sql import SparkSession
from dabdemo.addcol import *

class TestAppendCol(object):

  def test_with_status(self):
    spark = SparkSession.builder.getOrCreate()

    source_data = [
      ("paula", "white", "paula.white@example.com"),
      ("john", "baer", "john.baer@example.com")
    ]

    source_df = spark.createDataFrame(
      source_data,
      ["first_name", "last_name", "email"]
    )

    actual_df = with_status(source_df)

    expected_data = [
      ("paula", "white", "paula.white@example.com", "checked"),
      ("john", "baer", "john.baer@example.com", "checked")
    ]
    expected_df = spark.createDataFrame(
      expected_data,
      ["first_name", "last_name", "email", "status"]
    )

    assert(expected_df.collect() == actual_df.collect())

Databricks CLI でこのライブラリ コードを Python ホイール ファイルに正しくパッケージ化するには、__init__.py__main__.py という名前の 2 つのファイルを、前の 2 つのファイルと同じフォルダーに作成します。 また、python/dabdemo フォルダーに setup.py という名前のファイルを作成し、次のように視覚化します (簡潔にするために省略記号は省略されたフォルダーを示します):

├── ...
├── Libraries
│    └── python
│          └── dabdemo
│                ├── dabdemo
│                │    ├── __init__.py
│                │    ├── __main__.py
│                │    ├── addcol.py
│                │    └── test_addcol.py
│                └── setup.py
├── ...

__init__.py ファイルには、ライブラリのバージョン番号と作成者が含まれています。 <my-author-name> をお客様の名前に置き換えます:

# Filename: __init__.py
__version__ = '0.0.1'
__author__ = '<my-author-name>'

import sys, os

sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))

__main__.py ファイルには、ライブラリのエントリ ポイントが含まれています:

# Filename: __main__.py
import sys, os

sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))

from addcol import *

def main():
  pass

if __name__ == "__main__":
  main()

setup.py ファイルには、ライブラリを Python ホイール ファイルにビルドするための追加の設定が含まれています。 <my-url><my-author-name>@<my-organization> および <my-package-description> を意味のある値に置き換えます:

# Filename: setup.py
from setuptools import setup, find_packages

import dabdemo

setup(
  name = "dabdemo",
  version = dabdemo.__version__,
  author = dabdemo.__author__,
  url = "https://<my-url>",
  author_email = "<my-author-name>@<my-organization>",
  description = "<my-package-description>",
  packages = find_packages(include = ["dabdemo"]),
  entry_points={"group_1": "run=dabdemo.__main__:main"},
  install_requires = ["setuptools"]
)

Python ホイールのコンポーネント ロジックをテストする

Run Unit Tests ステージは、この Jenkins パイプラインの 4 番目のステージで、pytest を使用してライブラリのロジックをテストして、ビルドどおりに動作することを確認します。 このステージは次のように定義されます。

stage('Run Unit Tests') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-unit-tests
     """
}

このステージでは、Databricks CLI を使用してノートブック ジョブを実行します。 このジョブでは、run-unit-test.py というファイル名で Python ノートブックを実行します。 このノートブックは、ライブラリのロジックに対して pytest が実行されます。

この例の単体テストを実行するには、ローカル コンピューター上の複製されたリポジトリのルートに次のコンテンツを含む run_unit_tests.py という名前の Python ノートブック ファイルを追加します。

# Databricks notebook source

# COMMAND ----------

# MAGIC %sh
# MAGIC
# MAGIC mkdir -p "/Workspace${WORKSPACEBUNDLEPATH}/Validation/reports/junit/test-reports"

# COMMAND ----------

# Prepare to run pytest.
import sys, pytest, os

# Skip writing pyc files on a readonly filesystem.
sys.dont_write_bytecode = True

# Run pytest.
retcode = pytest.main(["--junit-xml", f"/Workspace{os.getenv('WORKSPACEBUNDLEPATH')}/Validation/reports/junit/test-reports/TEST-libout.xml",
                      f"/Workspace{os.getenv('WORKSPACEBUNDLEPATH')}/files/Libraries/python/dabdemo/dabdemo/"])

# Fail the cell execution if there are any test failures.
assert retcode == 0, "The pytest invocation failed. See the log for details."

ビルドされた Python ホイールを使用する

Run Notebook というタイトルの Jenkins パイプラインの 5 番目のステージでは、次のように、ビルドされた Python ホイール ファイルでロジックを呼び出す Python ノートブックが実行されます。

stage('Run Notebook') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-dabdemo-notebook
     """
  }

このステージでは、Databricks CLI を実行し、ワークスペースにノートブック ジョブを実行するように指示します。 このノートブックは、DataFrame オブジェクトを作成し、それをライブラリの with_status 関数に渡し、結果を出力して、ジョブの実行結果を報告します。 ローカル開発マシン上の複製されたリポジトリのルートに、次のコンテンツを含む dabdaddemo_notebook.py という名前の Python ノートブック ファイルを追加して、ノートブックを作成します。

# Databricks notebook source

# COMMAND ----------

# Restart Python after installing the wheel.
dbutils.library.restartPython()

# COMMAND ----------

from dabdemo.addcol import with_status

df = (spark.createDataFrame(
  schema = ["first_name", "last_name", "email"],
  data = [
    ("paula", "white", "paula.white@example.com"),
    ("john", "baer", "john.baer@example.com")
  ]
))

new_df = with_status(df)

display(new_df)

# Expected output:
#
# +------------+-----------+-------------------------+---------+
# │first_name │last_name │email                   │status  |
# +============+===========+=========================+=========+
# │paula      │white     │paula.white@example.com │checked |
# +------------+-----------+-------------------------+---------+
# │john       │baer      │john.baer@example.com   │checked |
# +------------+-----------+-------------------------+---------+

ノートブック ジョブの実行結果を評価する

Evaluate Notebook Runs ステージは、この Jenkins パイプラインの 6 番目のステージで、前のノートブック ジョブの実行結果を評価します。 このステージは次のように定義されます。

stage('Evaluate Notebook Runs') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} evaluate-notebook-runs
     """
  }

このステージでは、Databricks CLI を実行し、ワークスペースに Python ファイル ジョブを実行するように指示します。 この Python ファイルは、ノートブック ジョブの実行の失敗と成功の条件を決定し、この失敗または成功の結果を報告します。 ローカル開発マシンの複製されたリポジトリのルートに、次のコンテンツを含む evaluate_notebook_runs.py という名前のファイルを作成します。

import unittest
import xmlrunner
import json
import glob
import os

class TestJobOutput(unittest.TestCase):

  test_output_path = f"/Workspace${os.getenv('WORKSPACEBUNDLEPATH')}/Validation/Output"

  def test_performance(self):
    path = self.test_output_path
    statuses = []

    for filename in glob.glob(os.path.join(path, '*.json')):
      print('Evaluating: ' + filename)

      with open(filename) as f:
        data = json.load(f)

        duration = data['tasks'][0]['execution_duration']

        if duration > 100000:
            status = 'FAILED'
        else:
            status = 'SUCCESS'

        statuses.append(status)
        f.close()

    self.assertFalse('FAILED' in statuses)

  def test_job_run(self):
    path = self.test_output_path
    statuses = []

    for filename in glob.glob(os.path.join(path, '*.json')):
      print('Evaluating: ' + filename)

      with open(filename) as f:
        data = json.load(f)
        status = data['state']['result_state']
        statuses.append(status)
        f.close()

    self.assertFalse('FAILED' in statuses)

if __name__ == '__main__':
  unittest.main(
    testRunner = xmlrunner.XMLTestRunner(
      output = f"/Workspace${os.getenv('WORKSPACEBUNDLEPATH')}/Validation/Output/test-results",
    ),
    failfast   = False,
    buffer     = False,
    catchbreak = False,
    exit       = False
  )

テスト結果のインポートと報告

Import Test Results というタイトルの Jenkins パイプラインの 7 番目のステージでは、Databricks CLI を使用して、ワークスペースからローカル開発マシンにテスト結果を送信します。 Publish Test Results というタイトルの 8 番目と最後のステージでは、junit Jenkins プラグインを使用してテスト結果を Jenkins に公開します。 これによって、テスト結果の状態に関するレポートとダッシュボードを視覚化できます。 これらのステージは次のように定義されます。

stage('Import Test Results') {
  def DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH
  def getPath = "${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET} | ${JQPATH}/jq -r .workspace.file_path"
  def output = sh(script: getPath, returnStdout: true).trim()

  if (output) {
    DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH = "${output}"
  } else {
    error "Failed to capture output or command execution failed: ${getPath}"
  }

  sh """#!/bin/bash
        ${DBCLIPATH}/databricks workspace export-dir \
        ${DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH}/Validation/Output/test-results \
        ${WORKSPACE}/Validation/Output/test-results \
        --overwrite
     """
}
stage('Publish Test Results') {
  junit allowEmptyResults: true, testResults: '**/test-results/*.xml', skipPublishingChecks: true
}

Jenkins テストの結果

すべてのコード変更をサードパーティのリポジトリにプッシュする

これにより、ローカル開発マシン上の複製されたリポジトリのコンテンツをサードパーティのリポジトリにプッシュする必要があります。 プッシュする前に、まず、複製されたリポジトリ内の .gitignore ファイルに次のエントリを追加する必要があります。内部 Databricks アセット バンドルの作業ファイル、検証レポート、Python ビルド ファイル、Python キャッシュをサードパーティのリポジトリにプッシュしないようにする必要があります。 通常、古い可能性のある検証レポートと Python ホイール ビルドを使用するのではなく、Azure Databricks ワークスペースで新しい検証レポートと最新の Python ホイール ビルドを再生成する必要があります。

.databricks/
.vscode/
Libraries/python/dabdemo/build/
Libraries/python/dabdemo/__pycache__/
Libraries/python/dabdemo/dabdemo.egg-info/
Validation/

Jenkins パイプラインを実行する

これで、Jenkins パイプラインを手動で実行する準備ができました。 これを行うには、Jenkins ダッシュボードから次の手順を実行します。

  1. Jenkins パイプラインの名前をクリックします。
  2. サイドバーで、[今すぐビルド] をクリックします。
  3. 結果を表示するには、最新のパイプライン実行 (例: #1) をクリックし、[コンソール出力] をクリックします。

この時点で、CI/CD パイプラインでは統合とデプロイのサイクルが完了しています。 このプロセスを自動化することで、効率的で一貫性のある反復可能なプロセスによって、コードを確実にテストおよびデプロイできました。 リポジトリのプル要求など、特定のイベントが発生するたびに Jenkins を実行するようにサードパーティの Git プロバイダーに指示するには、サードパーティの Git プロバイダーのドキュメントを参照してください。