다음을 통해 공유


Azure Databricks에서 Jenkins를 사용하는 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. 타사 리포지토리에서 로컬 개발 컴퓨터로 기존의 업데이트된 아티팩트(예: Notebook, 코드 파일, 빌드 스크립트)를 끌어옵니다.
  4. 원하는 대로 로컬 개발 컴퓨터에서 아티팩트를 만들고 업데이트 및 테스트합니다. 그런 다음 로컬 개발 컴퓨터의 새 아티팩트와 변경된 아티팩트가 타사 리포지토리로 푸시됩니다. 지침은 타사 Git 공급자의 설명서를 참조하세요.
  5. 필요에 따라 3단계와 4단계를 반복합니다.
  6. 정기적으로 Jenkins를 통합된 접근 방법으로 사용하여 타사 리포지토리의 아티팩트를 로컬 개발 컴퓨터 또는 Azure Databricks 작업 영역으로 자동으로 끌어옵니다. 로컬 개발 컴퓨터 또는 Azure Databricks 작업 영역에서 코드를 빌드, 테스트 및 실행합니다. 테스트 및 실행 결과를 보고합니다. Jenkins를 수동으로 실행할 수 있지만 실제 구현에서는 리포지토리 끌어오기 요청과 같은 특정 이벤트가 발생할 때마다 타사 Git 공급자가 Jenkins를 실행하도록 지시합니다.

이 문서의 나머지 내용은 예제 프로젝트를 사용하여 Jenkins로 이전 CI/CD 개발 워크플로를 구현하는 한 가지 방법을 설명합니다.

Jenkins 대신 Azure DevOps를 사용하는 방법에 대한 자세한 내용은 Azure DevOps를 사용하여 Azure Databricks에서 연속 통합 및 제공을 참조하세요.

로컬 개발 컴퓨터 설정

이 문서의 예제에서는 Jenkins를 사용하여 Databricks CLIDatabricks 자산 번들에 다음을 수행하도록 지시합니다.

  1. 로컬 개발 컴퓨터에서 Python 휠 파일을 빌드합니다.
  2. 로컬 개발 컴퓨터에서 Azure Databricks 작업 영역으로 추가 Python 파일 및 Python Notebook과 함께 빌드된 Python 휠 파일을 배포합니다.
  3. 해당 작업 영역에서 업로드된 Python 휠 파일 및 Notebook을 테스트하고 실행합니다.

Azure Databricks 작업 영역에서 이 예제의 빌드 및 업로드 단계를 수행하게 지시하도록 로컬 개발 컴퓨터를 설정하려면 로컬 개발 컴퓨터에서 다음을 수행합니다.

1단계: 필수 도구 설치

이 단계에서는 로컬 개발 컴퓨터에 Databricks CLI, Jenkins, jq, Python 휠 빌드 도구를 설치합니다. 이 예제를 실행하려면 이러한 도구가 필요합니다.

  1. 아직 설치하지 않은 경우 Databricks CLI 버전 0.205 이상을 설치합니다. Jenkins는 Databricks CLI를 사용하여 이 예제의 테스트를 통과하고 작업 영역에서 지침을 실행합니다. Databricks CLI 설치 또는 업데이트를 참조하세요.

  2. 아직 설치하지 않은 경우 Jenkins를 설치하고 시작합니다. Linux, macOS 또는 WindowsJenkins 설치를 참조하세요.

  3. jq를 설치합니다. 이 예제에서는 jq를 사용하여 일부 JSON 형식 명령 출력을 구문 분석합니다.

  4. pip를 사용하여 다음 명령으로 Python 휠 빌드 도구를 설치합니다(일부 시스템에서는 pip대신 pip3를 사용해야 할 수 있음).

    pip install --upgrade wheel
    

2단계: Jenkins 파이프라인 만들기

이 단계에서는 Jenkins를 사용하여 이 문서의 예제에 대한 Jenkins 파이프라인을 만듭니다. Jenkins는 CI/CD 파이프라인을 만들기 위해 몇 가지 다른 프로젝트 유형을 제공합니다. Jenkins Pipelines는 Jenkins 플러그 인을 호출하고 구성하기 위해 Groovy 코드를 사용하여 Jenkins 파이프라인에서 단계를 정의하는 인터페이스를 제공합니다.

Jenkins 프로젝트 유형

Jenkins에서 Jenkins 파이프라인을 만들려면 다음을 수행합니다.

  1. Jenkins를 시작한 후 Jenkins 대시보드에서 새 항목을 클릭합니다.
  2. 항목 이름 입력의 경우 Jenkins 파이프라인의 이름을 입력합니다(예: jenkins-demo).
  3. 파이프라인 프로젝트 유형 아이콘을 클릭합니다.
  4. 확인을 클릭합니다. Jenkins 파이프라인의 구성 페이지가 나타납니다.
  5. 파이프라인 영역의 정의 드롭다운 목록에서 SCM의 파이프라인 스크립트를 선택합니다.
  6. SCM 드롭다운 목록에서 Git을 선택합니다.
  7. 리포지토리 URL의 경우 타사 Git 공급자가 호스트하는 리포지토리의 URL을 입력합니다.
  8. 분기 지정자의 경우 */<branch-name>을 입력합니다. <branch-name>은 사용하려는 리포지토리의 분기 이름입니다(예: */main).
  9. 스크립트 경로는 아직 설정되지 않은 경우 Jenkinsfile을 입력합니다. 이 문서의 뒷부분에서 Jenkinsfile을 만듭니다.
  10. 이미 선택된 경우 경량 체크 아웃이라는 상자의 선택을 취소합니다.
  11. 저장을 클릭합니다.

3단계: Jenkins에 전역 환경 변수 추가

이 단계에서는 Jenkins에 세 개의 전역 환경 변수를 추가합니다. Jenkins는 이러한 환경 변수를 Databricks CLI에 전달합니다. Databricks CLI는 Azure Databricks 작업 영역에서 인증하기 위해 이러한 환경 변수에 대한 값이 필요합니다. 이 예제에서는 서비스 주체에 OAuth M2M(machine-to-machine) 인증을 사용합니다(다른 인증 유형도 사용할 수 있음). Azure Databricks 작업 영역에 대한 OAuth M2M 인증을 설정하려면 OAuth(OAuth M2M)를 사용하여 서비스 주체로 Azure Databricks에 액세스 인증을 참조하세요.

이 예제의 세 가지 전역 환경 변수는 다음과 같습니다.

  • DATABRICKS_HOST, https://에서 시작하여 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 Pipelines는 Jenkins 플러그 인을 호출하고 구성하기 위해 Groovy 코드를 사용하여 Jenkins 파이프라인에서 단계를 정의하는 인터페이스를 제공합니다.

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 파이프라인의 두 번째 단계인 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 값과 일치해야 합니다.
  • 예를 들어 작업 클러스터에 대한 Databricks Runtime 버전 ID(예: <spark-version-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 자산 번들 구성을 참조하세요.

작업 영역에 번들 배포

Jenkins 파이프라인의 세 번째 단계인 Deploy Bundle은 다음과 같이 정의됩니다.

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

이 단계에서는 다음 두 가지 작업을 수행합니다.

  1. databricks.yml 파일의 artifact 매핑이 whl로 설정되므로 Databricks CLI가 지정된 위치에 있는 setup.py 파일을 사용하여 Python 휠 파일을 빌드하도록 지시합니다.
  2. Python 휠 파일이 로컬 개발 컴퓨터에 빌드된 후 Databricks CLI는 지정된 Python 파일 및 Notebook과 함께 빌드된 Python 휠 파일을 Azure Databricks 작업 영역에 배포합니다. 기본적으로 Databricks 자산 번들은 Python 휠 파일 및 기타 파일을 /Workspace/Users/<your-username>/.bundle/<bundle-name>/<target-name>에 배포합니다.

databricks.yml 파일에 지정된 대로 Python 휠 파일을 빌드하려면 로컬 컴퓨터의 복제된 리포지토리 루트에 다음 폴더와 파일을 만듭니다.

Notebook이 실행할 Python 휠 파일에 대한 논리 및 단위 테스트를 정의하려면 이름이 addcol.pytest_addcol.py인 두 개의 파일을 만들고, 리포지토리의 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 파일에는 with_status에 정의된 addcol.py 함수에 모의 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인 두 개의 파일을 만듭니다. 또한 다음과 같이 시각화된 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 휠의 구성 요소 논리 테스트

이 Jenkins 파이프라인의 네 번째 단계인 Run Unit Tests 단계에서는 pytest를 사용하여 라이브러리의 논리를 테스트하고 빌드된 대로 작동하는지 확인합니다. 이 단계는 다음과 같이 정의됩니다.

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

이 단계에서는 Databricks CLI를 사용하여 Notebook 작업을 실행합니다. 이 작업은 run-unit-test.py의 파일 이름으로 Python Notebook을 실행합니다. 이 Notebook은 라이브러리의 논리에 대해 pytest를 실행합니다.

이 예제의 단위 테스트를 실행하려면 다음 내용이 포함된 이름이 run_unit_tests.py인 Python Notebook 파일을 로컬 컴퓨터의 복제된 리포지토리 루트에 추가합니다.

# 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 휠 사용

이 Jenkins 파이프라인의 다섯 번째 단계인 Run Notebook에서는 다음과 같이 빌드된 Python 휠 파일에서 논리를 호출하는 Python Notebook을 실행합니다.

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

이 단계에서는 Databricks CLI를 실행하여 작업 영역에 Notebook 작업을 실행하도록 지시합니다. 이 Notebook은 DataFrame 개체를 만들고, 라이브러리의 with_status 함수에 전달하고, 결과를 인쇄하고, 작업의 실행 결과를 보고합니다. 로컬 개발 컴퓨터의 복제된 리포지토리 루트에 다음 내용이 포함된 이름이 dabdaddemo_notebook.py인 Python Notebook 파일을 추가하여 Notebook을 만듭니다.

# 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 |
# +------------+-----------+-------------------------+---------+

Notebook 작업 실행 결과 평가

이 Jenkins 파이프라인의 여섯 번째 단계인 Evaluate Notebook Runs 단계는 이전 Notebook 작업 실행의 결과를 평가합니다. 이 단계는 다음과 같이 정의됩니다.

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

이 단계에서는 Databricks CLI를 실행하여 작업 영역에 Python 파일 작업을 실행하도록 지시합니다. 이 Python 파일은 Notebook 작업 실행에 대한 실패 및 성공 조건을 결정하고 이 실패 또는 성공 결과를 보고합니다. 로컬 개발 컴퓨터의 복제된 리포지토리 루트에 다음 내용이 포함된 이름이 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
  )

테스트 결과 가져오기 및 보고

이 Jenkins 파이프라인의 일곱 번째 단계인 Import Test Results는 Databricks CLI를 사용하여 작업 영역에서 로컬 개발 컴퓨터으로 테스트 결과를 보냅니다. 여덟 번째이자 마지막 단계인 Publish Test Resultsjunit 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 테스트 결과

타사 리포지토리에 모든 코드 변경 내용 푸시

이제 로컬 개발 컴퓨터에서 복제된 리포지토리의 콘텐츠를 타사 리포지토리로 푸시해야 합니다. 푸시하기 전에 내부 Databricks 자산 번들 작업 파일, 유효성 검사 보고서, Python 빌드 파일, Python 캐시를 타사 리포지토리에 푸시하지 않아야 하므로 복제된 리포지토리의 .gitignore 파일에 다음 항목을 추가해야 합니다. 일반적으로 잠재적으로 오래된 유효성 검사 보고서 및 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 파이프라인은 통합 및 배포 주기를 완료했습니다. 이 프로세스를 자동화하면 효율적이고 일관되며 반복 가능한 프로세스를 통해 코드를 테스트하고 배포할 수 있습니다. 리포지토리 끌어오기 요청과 같은 특정 이벤트가 발생할 때마다 타사 Git 공급자가 Jenkins를 실행하도록 지시하려면 타사 Git 공급자의 설명서를 참조하세요.