CI/CD مع Jenkins على Azure Databricks

إشعار

تتناول هذه المقالة Jenkins، التي لم يتم توفيرها أو دعمها من قبل Databricks. للاتصال بالموفر، راجع تعليمات Jenkins.

هناك العديد من أدوات CI/CD التي يمكنك استخدامها لإدارة وتشغيل مسارات CI/CD. توضح هذه المقالة كيفية استخدام خادم التنفيذ التلقائي Jenkins. CI/CD هو نمط تصميم، لذلك يجب نقل الخطوات والمراحل الموضحة في هذه المقالة مع بعض التغييرات على لغة تعريف المسار في كل أداة. علاوة على ذلك، يقوم الكثير من التعليمات البرمجية في هذا المثال بتشغيل تعليمة Python البرمجية القياسية، والتي يمكنك استدعاؤها في أدوات أخرى. للحصول على نظرة عامة على CI/CD على Azure Databricks، راجع ما هو CI/CD على Azure Databricks؟.

للحصول على معلومات حول استخدام Azure DevOps مع Azure Databricks بدلا من ذلك، راجع التكامل والتسليم المستمرين على Azure Databricks باستخدام Azure DevOps.

سير عمل تطوير CI/CD

يقترح Databricks سير العمل التالي لتطوير CI/CD مع Jenkins:

  1. إنشاء مستودع، أو استخدام مستودع موجود، مع موفر Git التابع لجهة خارجية.
  2. قم بتوصيل جهاز التطوير المحلي الخاص بك بنفس مستودع الجهات الخارجية. للحصول على الإرشادات، راجع وثائق موفر Git التابع لجهة خارجية.
  3. اسحب أي بيانات اصطناعية محدثة موجودة (مثل دفاتر الملاحظات وملفات التعليمات البرمجية والبرامج النصية للبناء) من مستودع الجهات الخارجية إلى جهاز التطوير المحلي.
  4. كما هو مطلوب، قم بإنشاء وتحديث واختبار البيانات الاصطناعية على جهاز التطوير المحلي الخاص بك. ثم ادفع أي بيانات اصطناعية جديدة ومتغيرة من جهاز التطوير المحلي الخاص بك إلى مستودع الجهات الخارجية. للحصول على إرشادات، راجع مستند موفر Git التابع لجهة خارجية.
  5. كرر الخطوين 3 و4 حسب الحاجة.
  6. استخدم Jenkins بشكل دوري كنهج متكامل لسحب البيانات الاصطناعية تلقائيا من مستودع الجهات الخارجية إلى جهاز التطوير المحلي أو مساحة عمل Azure Databricks؛ إنشاء التعليمات البرمجية واختبارها وتشغيلها على جهاز التطوير المحلي أو مساحة عمل Azure Databricks؛ والإبلاغ عن نتائج الاختبار والتشغيل. بينما يمكنك تشغيل Jenkins يدويا، في عمليات التنفيذ في العالم الحقيقي، يمكنك توجيه موفر Git التابع لجهة خارجية لتشغيل Jenkins في كل مرة يحدث فيها حدث معين، مثل طلب سحب المستودع.

تستخدم بقية هذه المقالة مثالا على مشروع لوصف طريقة واحدة لاستخدام Jenkins لتنفيذ سير عمل تطوير CI/CD السابق.

للحصول على معلومات حول استخدام Azure DevOps بدلا من Jenkins، راجع التكامل والتسليم المستمر على Azure Databricks باستخدام Azure DevOps.

إعداد جهاز التطوير المحلي

يستخدم مثال هذه المقالة Jenkins لإرشاد Databricks CLI وDatabricks Asset Bundles للقيام بما يلي:

  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 وبدء تشغيله، إذا لم تكن قد فعلت ذلك بالفعل. راجع تثبيت Jenkins ل Linux أو macOS أو Windows.

  3. تثبيت jq. يستخدم jq هذا المثال لتحليل بعض إخراج الأوامر بتنسيق JSON.

  4. استخدم pip لتثبيت أدوات بناء عجلة Python باستخدام الأمر التالي (قد تتطلب منك بعض الأنظمة استخدام pip3 بدلا من pip):

    pip install --upgrade wheel
    

الخطوة 2: إنشاء مسار Jenkins

في هذه الخطوة، يمكنك استخدام Jenkins لإنشاء مسار Jenkins لمثال هذه المقالة. يوفر Jenkins بعض أنواع المشاريع المختلفة لإنشاء مسارات CI/CD. توفر مسارات Jenkins واجهة لتحديد المراحل في مسار Jenkins باستخدام التعليمات البرمجية Groovy لاستدعاء وتكوين المكونات الإضافية Jenkins.

أنواع مشاريع Jenkins

لإنشاء مسار Jenkins في Jenkins:

  1. بعد بدء تشغيل Jenkins، من لوحة معلومات Jenkins، انقر فوق عنصر جديد.
  2. بالنسبة إلى إدخال اسم عنصر، اكتب اسما ل Jenkins Pipeline، على سبيل المثال jenkins-demo.
  3. انقر فوق أيقونة نوع مشروع البنية الأساسية لبرنامج ربط العمليات التجارية.
  4. وانقر فوق موافق. تظهر صفحة تكوين البنية الأساسية لبرنامج ربط العمليات التجارية Jenkins.
  5. في منطقة Pipeline ، في القائمة المنسدلة Defintion ، حدد Pipeline script من SCM.
  6. في القائمة المنسدلة SCM ، حدد Git.
  7. بالنسبة إلى عنوان URL للمستودع، اكتب عنوان URL إلى المستودع الذي يستضيفه موفر Git للجزء الثالث.
  8. بالنسبة لمحدد الفرع، اكتب */<branch-name>، حيث <branch-name> هو اسم الفرع في المستودع الذي تريد استخدامه، على سبيل المثال */main.
  9. بالنسبة لمسار البرنامج النصي، اكتب Jenkinsfile، إذا لم يتم تعيينه بالفعل. يمكنك إنشاء Jenkinsfile لاحقا في هذه المقالة.
  10. قم بإلغاء تحديد المربع المسمى السداد مع الخروج الخفيف، إذا كان محددا بالفعل.
  11. انقر فوق حفظ.

الخطوة 3: إضافة متغيرات البيئة العمومية إلى Jenkins

في هذه الخطوة، يمكنك إضافة ثلاثة متغيرات بيئة عمومية إلى Jenkins. يقوم Jenkins بتمرير متغيرات البيئة هذه إلى Databricks CLI. يحتاج Databricks CLI إلى قيم متغيرات البيئة هذه للمصادقة مع مساحة عمل Azure Databricks. يستخدم هذا المثال مصادقة OAuth من جهاز إلى جهاز (M2M) لكيان الخدمة (على الرغم من توفر أنواع مصادقة أخرى أيضا). لإعداد مصادقة OAuth M2M لمساحة عمل Azure Databricks، راجع مصادقة الوصول إلى Azure Databricks باستخدام كيان خدمة باستخدام OAuth (OAuth M2M).

متغيرات البيئة العمومية الثلاثة لهذا المثال هي:

  • DATABRICKS_HOST، قم بتعيين إلى عنوان URL لمساحة عمل Azure Databricks، بدءا من https://. راجع أسماء مثيلات مساحة العمل وعناوين URL والمعرفات.
  • DATABRICKS_CLIENT_ID، قم بتعيين إلى معرف عميل كيان الخدمة، والذي يعرف أيضا باسم معرف التطبيق الخاص به.
  • DATABRICKS_CLIENT_SECRET، قم بتعيين إلى سر Azure Databricks OAuth الخاص بكيان الخدمة.

لتعيين متغيرات البيئة العمومية في Jenkins، من لوحة معلومات Jenkins:

  1. في الشريط الجانبي، انقر فوق إدارة Jenkins.
  2. في قسم تكوين النظام، انقر فوق النظام.
  3. في قسم الخصائص العمومية، حدد مربع متغيرات البيئة المتجانبة.
  4. انقر فوق إضافة ثم أدخل الاسم والقيمة لمتغير البيئة. كرر هذا الإجراء لكل متغير بيئة إضافي.
  5. عند الانتهاء من إضافة متغيرات البيئة، انقر فوق حفظ للعودة إلى لوحة معلومات Jenkins.

تصميم خط أنابيب Jenkins

يوفر Jenkins بعض أنواع المشاريع المختلفة لإنشاء مسارات CI/CD. ينفذ هذا المثال مسار Jenkins. توفر مسارات Jenkins واجهة لتحديد المراحل في مسار Jenkins باستخدام التعليمات البرمجية Groovy لاستدعاء وتكوين المكونات الإضافية Jenkins.

يمكنك كتابة تعريف Jenkins Pipeline في ملف نصي يسمى Jenkinsfile، والذي بدوره يتم إيداعه في مستودع التحكم بالمصادر للمشروع. لمزيد من المعلومات، راجع Jenkins Pipeline. فيما يلي مسار Jenkins لمثال هذه المقالة. في هذا المثال Jenkinsfile، استبدل العناصر النائبة التالية:

  • استبدل <user-name> و <repo-name> باسم المستخدم واسم المستودع الخاص بك المستضاف من قبل موفر Git للجزء الثالث. تستخدم هذه المقالة عنوان URL GitHub كمثال.
  • استبدل <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هذا .
  • لاحظ أنه BUNDLETARGET تم تعيين إلى dev، وهو اسم هدف مجموعة أصول 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، والمعروفة ببساطة باسم الحزم، من الممكن التعبير عن البيانات الكاملة والتحليلات ومشاريع التعلم الآلي كمجموعة من الملفات المصدر. راجع ما هي حزم أصول Databricks؟.

لتعريف حزمة هذه المقالة، أنشئ ملفا باسم databricks.yml في جذر المستودع المستنسخ على جهازك المحلي. في ملف المثال databricks.yml هذا، استبدل العناصر النائبة التالية:

  • استبدل <bundle-name> باسم برمجي فريد للحزمة. على سبيل المثال، قد يكون jenkins-demoهذا .
  • استبدل <job-prefix-name> ببعض السلسلة للمساعدة في تحديد مهام Azure Databricks التي تم إنشاؤها في مساحة العمل الخاصة بك لهذا المثال بشكل فريد. على سبيل المثال، قد يكون jenkins-demoهذا . يجب أن تتطابق مع القيمة في JOBPREFIX Jenkinsfile الخاص بك.
  • استبدل <spark-version-id> بمعرف إصدار Databricks Runtime لمجموعات الوظائف، على سبيل المثال 13.3.x-scala2.12.
  • استبدل <cluster-node-type-id> بمعرف نوع العقدة لمجموعات الوظائف، على سبيل المثال Standard_DS3_v2.
  • لاحظ أن dev في targets التعيين هو نفسه في BUNDLETARGET Jenkinsfile الخاص بك. يحدد هدف المجموعة المضيف وسلوكيات التوزيع ذات الصلة.

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. artifact نظرا لتعيين التعيين في databricks.yml الملف إلى whl، يوجه هذا Databricks CLI لإنشاء ملف عجلة Python باستخدام setup.py الملف في الموقع المحدد.
  2. بعد إنشاء ملف عجلة Python على جهاز التطوير المحلي الخاص بك، ينشر Databricks CLI ملف عجلة Python المضمن جنبا إلى جنب مع ملفات Python ودفاتر الملاحظات المحددة إلى مساحة عمل Azure Databricks. بشكل افتراضي، تقوم Databricks Asset Bundles بنشر ملف عجلة Python والملفات الأخرى إلى /Workspace/Users/<your-username>/.bundle/<bundle-name>/<target-name>.

لتمكين إنشاء ملف عجلة Python كما هو محدد في databricks.yml الملف، قم بإنشاء المجلدات والملفات التالية في جذر المستودع المستنسخ على جهازك المحلي.

لتعريف المنطق واختبارات الوحدة لملف عجلة Python الذي سيتم تشغيل دفتر الملاحظات عليه، أنشئ ملفين باسم addcol.py و test_addcol.py، وأضفهما إلى بنية مجلد باسم python/dabdemo/dabdemo داخل مجلد المستودع Libraries الخاص بك، وتم تصورهما على النحو التالي (تشير علامات الحذف إلى المجلدات المحذفة في المستودع، للإيجاز):

├── ...
├── 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 يحتوي الملف على اختبارات لتمرير كائن DataFrame وهمية with_status إلى الوظيفة، المعرفة في addcol.py. ثم تتم مقارنة النتيجة بعنصر 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 نفس المجلد مثل الملفين السابقين. أيضا، أنشئ ملفا باسم setup.py في python/dabdemo المجلد، تم تصوره كما يلي (تشير علامات الحذف إلى مجلدات محذفة، للإيجاز):

├── ...
├── 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 تستخدم pytest المرحلة، وهي المرحلة الرابعة من مسار Jenkins، لاختبار منطق المكتبة للتأكد من أنها تعمل كما تم إنشاؤها. يتم تعريف هذه المرحلة على النحو التالي:

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

تستخدم هذه المرحلة Databricks CLI لتشغيل مهمة دفتر ملاحظات. تقوم هذه المهمة بتشغيل دفتر ملاحظات Python باسم run-unit-test.pyملف . يعمل pytest دفتر الملاحظات هذا مقابل منطق المكتبة.

لتشغيل اختبارات الوحدة لهذا المثال، أضف ملف دفتر ملاحظات Python المسمى run_unit_tests.py بالمحتويات التالية إلى جذر المستودع المستنسخ على جهازك المحلي:

# 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 المضمن، كما يلي:

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

تقوم هذه المرحلة بتشغيل Databricks CLI، والذي بدوره يوجه مساحة العمل لتشغيل مهمة دفتر ملاحظات. يقوم دفتر الملاحظات هذا بإنشاء كائن DataFrame، وتمريره إلى دالة المكتبة with_status ، وطباعة النتيجة، والإبلاغ عن نتائج تشغيل المهمة. أنشئ دفتر الملاحظات عن طريق إضافة ملف دفتر ملاحظات Python باسم dabdaddemo_notebook.py بالمحتويات التالية في جذر المستودع المستنسخ على جهاز التطوير المحلي:

# 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 هذا، تقيم نتائج تشغيل مهمة دفتر الملاحظات السابقة. يتم تعريف هذه المرحلة على النحو التالي:

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
  )

استيراد نتائج اختبار وإعداد تقرير بها

تستخدم المرحلة السابعة في مسار Jenkins، بعنوان Import Test Results، Databricks CLI لإرسال نتائج الاختبار من مساحة العمل الخاصة بك إلى جهاز التطوير المحلي الخاص بك. المرحلة الثامنة والأخيرة، بعنوان Publish Test Results، تنشر نتائج الاختبار إلى Jenkins باستخدام junit المكون الإضافي 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 دورة التكامل والنشر. من خلال أتمتة هذه العملية، يمكنك التأكد من اختبار التعليمات البرمجية ونشرها بواسطة عملية فعالة ومتسقة وقابلة للتكرار. لإرشاد موفر Git التابع لجهة خارجية لتشغيل Jenkins في كل مرة يحدث فيها حدث معين، مثل طلب سحب المستودع، راجع وثائق موفر Git التابع لجهة خارجية.