从 GitHub 测试、同步和部署您的 DAG

Cloud Composer 1 | Cloud Composer 2 | Cloud Composer 3

本指南介绍如何创建 CI/CD 流水线,以便从 GitHub 代码库测试、同步 DAG 并将其部署到 Cloud Composer 环境。

如果您只想同步来自其他服务的数据,请参阅从其他服务转移数据

CI/CD 流水线概览

显示流程步骤的架构图。提交前审核和 PR 审核位于 GitHub 部分,DAG 同步和手动 DAG 验证位于 Google Cloud 部分。
图 1. 显示流程步骤的架构图(点击可放大)

用于测试、同步和部署 DAG 的 CI/CD 流水线包含以下步骤:

  1. 您对 DAG 进行更改,并将该更改推送到代码库中的开发分支。

  2. 您针对代码库的主分支打开一个拉取请求。

  3. Cloud Build 会运行单元测试以检查您的 DAG 是否有效。

  4. 您的拉取请求获得批准并合并到代码库的主分支中。

  5. Cloud Build 会将您的 Cloud Composer 开发环境与这些新更改同步。

  6. 您可以验证 DAG 在开发环境中是否按预期运行。

  7. 如果您的 DAG 按预期运行,请将该 DAG 上传到您的 Cloud Composer 生产环境。

目标

准备工作

  • 本指南假定您使用的是两个相同的 Cloud Composer 环境:一个开发环境和一个生产环境。

    在本指南中,您仅为开发环境配置 CI/CD 流水线。请确保您使用的环境不是生产环境。

  • 本指南假定您已将 DAG 及其测试存储在 GitHub 代码库中。

    示例 CI/CD 流水线演示了示例代码库的内容。DAG 和测试存储在 dags/ 目录中,而需求文件、限制条件文件和 Cloud Build 配置文件存储在顶层。DAG 同步实用程序及其要求位于 utils 目录下。

创建提交前检查作业和单元测试

第一个 Cloud Build 作业会运行提交前检查,用于为您的 DAG 执行单元测试。

添加单元测试

为您的 DAG 编写单元测试(如果您尚未这样做)。将这些测试与代码库中的 DAG 一起保存,每个都带有 _test 后缀。例如,example_dag.py 中 DAG 的测试文件为 example_dag_test.py。这些是在您的代码库中作为提交前检查运行的测试。

为提交前检查创建 Cloud Build YAML 配置

在您的代码库中,创建一个名为 test-dags.cloudbuild.yaml 的 YAML 文件,该文件将 Cloud Build 作业配置为提交前检查。其中包含三个步骤:

  1. 安装 DAG 所需的依赖项。
  2. 安装单元测试所需的依赖项。
  3. 执行 DAG 测试。

steps:
  # install dependencies
  - name: python:3.8-slim
    entrypoint: pip
    args: ["install", "-r", "requirements.txt", "-c", "constraints.txt", "--user"]

  - name: python:3.8-slim
    entrypoint: pip
    args: ["install", "-r", "requirements-test.txt", "--user"]

  # run in python 3.8 which is latest version in Cloud Composer
  - name: python:3.8-slim
    entrypoint: python3.8
    args: ["-m", "pytest", "-s", "dags/"]

为提交前检查创建 Cloud Build 触发器

按照在 GitHub 中构建代码库指南,创建一个具有以下配置的基于 GitHub 应用的触发器:

  • 名称test-dags

  • 事件:拉取请求

  • Source - 代码库:选择您的代码库

  • 来源 - 基本分支:^main$(如果需要,请将 main 更改为代码库基本分支的名称)

  • 来源 - 评论控制:非必要操作

  • 构建配置 - Cloud Build 配置文件:/test-dags.cloudbuild.yaml(构建文件的路径)

创建 DAG 同步作业并添加 DAG 实用程序脚本

接下来,配置一个运行 DAG 实用程序脚本的 Cloud Build 作业。在 DAG 合并到代码库中的主分支后,此作业中的实用程序脚本会将其与 Cloud Composer 环境同步。

添加 DAG 实用程序脚本

将 DAG 实用程序脚本添加到您的代码库。此实用程序脚本会将代码库的 dags/ 目录中的所有 DAG 文件复制到临时目录中,并忽略所有非 DAG Python 文件。然后,该脚本使用 Cloud Storage 客户端库将该临时目录中的所有文件上传到 Cloud Composer 环境存储桶中的 dags/ 目录。

from __future__ import annotations

import argparse
import glob
import os
from shutil import copytree, ignore_patterns
import tempfile

# Imports the Google Cloud client library
from google.cloud import storage


def _create_dags_list(dags_directory: str) -> tuple[str, list[str]]:
    temp_dir = tempfile.mkdtemp()

    # ignore non-DAG Python files
    files_to_ignore = ignore_patterns("__init__.py", "*_test.py")

    # Copy everything but the ignored files to a temp directory
    copytree(dags_directory, f"{temp_dir}/", ignore=files_to_ignore, dirs_exist_ok=True)

    # The only Python files left in our temp directory are DAG files
    # so we can exclude all non Python files
    dags = glob.glob(f"{temp_dir}/*.py")
    return (temp_dir, dags)


def upload_dags_to_composer(
    dags_directory: str, bucket_name: str, name_replacement: str = "dags/"
) -> None:
    """
    Given a directory, this function moves all DAG files from that directory
    to a temporary directory, then uploads all contents of the temporary directory
    to a given cloud storage bucket
    Args:
        dags_directory (str): a fully qualified path to a directory that contains a "dags/" subdirectory
        bucket_name (str): the GCS bucket of the Cloud Composer environment to upload DAGs to
        name_replacement (str, optional): the name of the "dags/" subdirectory that will be used when constructing the temporary directory path name Defaults to "dags/".
    """
    temp_dir, dags = _create_dags_list(dags_directory)

    if len(dags) > 0:
        # Note - the GCS client library does not currently support batch requests on uploads
        # if you have a large number of files, consider using
        # the Python subprocess module to run gsutil -m cp -r on your dags
        # See https://cloud.google.com/storage/docs/gsutil/commands/cp for more info
        storage_client = storage.Client()
        bucket = storage_client.bucket(bucket_name)

        for dag in dags:
            # Remove path to temp dir
            dag = dag.replace(f"{temp_dir}/", name_replacement)

            try:
                # Upload to your bucket
                blob = bucket.blob(dag)
                blob.upload_from_filename(dag)
                print(f"File {dag} uploaded to {bucket_name}/{dag}.")
            except FileNotFoundError:
                current_directory = os.listdir()
                print(
                    f"{name_replacement} directory not found in {current_directory}, you may need to override the default value of name_replacement to point to a relative directory"
                )
                raise

    else:
        print("No DAGs to upload.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument(
        "--dags_directory",
        help="Relative path to the source directory containing your DAGs",
    )
    parser.add_argument(
        "--dags_bucket",
        help="Name of the DAGs bucket of your Composer environment without the gs:// prefix",
    )

    args = parser.parse_args()

    upload_dags_to_composer(args.dags_directory, args.dags_bucket)

创建 Cloud Build YAML 配置以同步 DAG

在您的代码库中,创建一个名为 add-dags-to-composer.cloudbuild.yaml 的 YAML 文件,该文件将 Cloud Build 作业配置为同步 DAG。其中包含两个步骤:

  1. 安装 DAG 实用程序脚本所需的依赖项。

  2. 运行实用程序脚本,将代码库中的 DAG 与 Cloud Composer 环境同步。

steps:
  # install dependencies
  - name: python
    entrypoint: pip
    args: ["install", "-r", "utils/requirements.txt", "--user"]

  # run
  - name: python
    entrypoint: python
    args: ["utils/add_dags_to_composer.py", "--dags_directory=${_DAGS_DIRECTORY}", "--dags_bucket=${_DAGS_BUCKET}"]

创建 Cloud Build 触发器

按照在 GitHub 中构建代码库指南,创建一个具有以下配置的基于 GitHub 应用的触发器:

  • 名称add-dags-to-composer

  • 事件:推送到分支

  • Source - 代码库:选择您的代码库

  • 来源 - 基本分支:^main$(如果需要,请将 main 更改为代码库基本分支的名称)

  • 来源 - 包含的文件过滤条件 (glob):dags/**

  • 构建配置 - Cloud Build 配置文件:/add-dags-to-composer.cloudbuild.yaml(构建文件的路径)

在高级配置中,添加两个替代变量

  • _DAGS_DIRECTORY - DAG 在您的代码库中所在的目录。如果您使用的是本指南中的示例代码库,则它为 dags/

  • _DAGS_BUCKET - 开发 Cloud Composer 环境中的 dags/ 目录所在的 Cloud Storage 存储桶。省略 gs:// 前缀。例如:us-central1-example-env-1234ab56-bucket

测试 CI/CD 流水线

在本部分中,请按照使用新创建的 Cloud Build 触发器的 DAG 开发流程进行操作。

运行预提交作业

为主分支创建拉取请求以测试 build。在页面上找到您的提交前检查。点击详细信息并选择查看有关 Google Cloud Build 的更多详情,以在 Google Cloud 控制台中查看您的构建日志。

一个名为 test-dags 的 GitHub 检查的屏幕截图,其中有一个红色箭头指向括号中的项目名称
图 2.GitHub 上的 Cloud Build 提交前检查状态的屏幕截图(点击可放大)

如果提交前检查未通过,请参阅解决构建失败问题

验证您的 DAG 在 Cloud Composer 开发环境中是否正常运行

拉取请求获得批准后,将其合并到主分支。使用 Google Cloud 控制台查看构建结果。如果您有多个 Cloud Build 触发器,可以根据触发器名称 add-dags-to-composer 过滤构建。

Cloud Build 同步作业成功后,同步的 DAG 会显示在您的 Cloud Composer 开发环境中。在这里,您可以验证 DAG 是否按预期运行。

将 DAG 添加到您的生产环境

DAG 按预期运行后,手动将其添加到生产环境。为此,请将 DAG 文件上传到生产 Cloud Composer 环境存储桶中的 dags/ 目录。

如果您的 DAG 同步作业失败,或者 DAG 在开发 Cloud Composer 环境中未按预期运行,请参阅解决构建失败问题

解决构建失败的问题

本部分介绍了如何解决常见的构建失败场景。

如果预提交检查失败,该怎么办?

在拉取请求中,点击详细信息,然后选择查看有关 Google Cloud Build 的更多详细信息,以在 Google Cloud 控制台中查看您的构建日志。这些日志可帮助您调试 DAG 的问题。解决问题后,提交修复程序并推送到您的分支。提交前检查会再次运行,并且您可以继续将日志用作调试工具进行迭代。

如果我的 DAG 同步作业失败,该怎么办?

使用 Google Cloud 控制台查看构建结果。如果您有多个 Cloud Build 触发器,可以根据触发器名称 add-dags-to-composer 过滤构建。检查构建作业的日志并解决错误。如果您在解决错误时需要其他帮助,请使用支持渠道

如果我的 DAG 在 Cloud Composer 环境中无法正常运行,该怎么办?

如果您的 DAG 在 Cloud Composer 开发环境中未按预期运行,请勿手动将该 DAG 提升到 Cloud Composer 生产环境,请改为采用以下方法之一:

  • 还原拉取请求,其中包含破坏 DAG 的更改,以立即将其恢复到更改之前的状态(这也会还原该拉取请求中的所有其他文件)。
  • 创建新的拉取请求,以手动还原对损坏的 DAG 的更改。
  • 创建新的拉取请求以修复 DAG 中的错误。

执行上述任一步骤都会触发新的提交前检查,并在合并后触发 DAG 同步作业。

后续步骤