Cloud Functions を使用した DAG のトリガー

このページでは、Cloud Functions を使用して、イベントベースの DAG トリガーを実現する方法について説明します。

Airflow は定期的に DAG を実行するように設計されていますが、イベントに反応して DAG をトリガーできます。これを実現する方法の 1 つは、Cloud Functions を使用して、指定したイベントが発生したときに Cloud Composer DAG をトリガーすることです。たとえば、Cloud Storage バケット内のオブジェクトが変更されたときや、Pub/Sub トピックにメッセージが送信されたときに、DAG をトリガーする関数を作成できます。

このガイドの例では、Cloud Storage バケットで変更が生じるたびに DAG を実行します。バケット内のオブジェクトを変更すると、関数がトリガーされます。この関数は、Cloud Composer 環境の Airflow REST API にリクエストを送信します。Airflow はこのリクエストを処理し、DAG を実行します。DAG は、変更に関する情報を出力します。

始める前に

プロジェクトでAPI を有効にする

Cloud Composer and Cloud Functions API を有効にします。

API を有効にする

Airflow ウェブサーバー REST API を有効にする

Airflow 1.10.11 以降のバージョンでは API 認証機能がデフォルトで無効になっています。 Airflow ウェブサーバーは、すべてのリクエストを拒否します。リクエストを使用して DAG をトリガーするため、この機能を有効にします。

API 認証機能を有効にするには、次の Airflow 構成オプションをオーバーライドします。

セクション キー
api auth_backend airflow.api.auth.backend.default デフォルト値は airflow.api.auth.backend.deny_all です。

Airflow ウェブサーバーの URL を取得する

お使いの関数が Airflow ウェブサーバー エンドポイントにリクエストを行うため、Airflow ウェブサーバーの URL を取得します。

コンソール

Airflow ウェブサーバーの URL を取得するには、

  1. [環境] ページを開きます。

    [環境] ページを開く

  2. 環境の名前をクリックします。
  3. [環境の構成] で Airflow ウェブ UI の項目をご覧ください。

gcloud

Airflow ウェブサーバーの URL を取得するには、次のコマンドを実行します。

gcloud composer environments describe ENVIRONMENT_NAME \
    --location LOCATION \
    --format='value(config.airflowUri)'

以下のように置き換えます。

  • ENVIRONMENT_NAME は、環境の名前に置き換えます。
  • LOCATION は、環境が配置される Compute Engine のリージョンに置き換えます。

IAM プロキシの client_id を取得する

Airflow REST API エンドポイントにリクエストを送信するには、Airflow ウェブサーバーを保護する IAM プロキシのクライアント ID が必要です。

Cloud Composer は、この情報を直接提供しません。代わりに、認証されていないリクエストを Airflow ウェブサーバーに送信し、リダイレクト URL からクライアント ID を取得します。

curl -v AIRFLOW_URL 2>&1 >/dev/null | grep "location:"

AIRFLOW_URL は、Airflow ウェブ インターフェースの URL に置き換えます。

出力で、apps.googleusercontent.com で終わる client_id 文字列を検索します。次に例を示します。

location: https://accounts.google.com/o/oauth2/v2/auth?
client_id=836436932391-16q2c5f5dcsfnel77va9bvf4j280t35c.apps.googleusercontent.com&response_type= ...

Cloud Storage バケットを作成する

この例では、Cloud Storage バケットの変更に応じて DAG をトリガーするため、この例で使用する新しいバケットを作成します。

Cloud Functions から DAG をトリガーする

DAG を環境にアップロードする

DAG をお使いの環境にアップロードします。次の例の DAG は、受信した DAG 実行構成を出力します。この DAG は、このガイドで作成する関数からトリガーします。

from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.utils.dates import days_ago

with DAG(
    dag_id='composer_sample_trigger_response_dag',
    start_date=days_ago(1),
    schedule_interval=None) as dag:

    # Print the received dag_run configuration.
    # The DAG run configuration contains information about the
    # Cloud Storage object change.
    t1 = BashOperator(
        task_id='print_gcs_info',
        bash_command='echo Triggered from GCF: {{ dag_run.conf }}',
        dag=dag)

    t1

DAG をトリガーする Cloud Functions の関数をデプロイする

次の構成パラメータとコンテンツを使用して Python Cloud Function をデプロイします。

Cloud 関数構成パラメータを指定する

  • トリガー。この例では、バケット内に新しいオブジェクトが作成されたとき、または既存のオブジェクトが上書きされたときに機能するトリガーを選択します。

    • トリガーのタイプ。Cloud Storage。

    • イベントのタイプファイナライズ / 作成

    • バケット。この関数をトリガーするバケットを選択してください。

    • 失敗時に再試行する。この例では、このオプションを無効にすることをおすすめします。本番環境で独自の関数を使用する場合は、このオプションを有効にして一時的なエラーを処理できるようにします。

  • ランタイム サービス アカウント。ユーザーの好みに応じて、次のいずれかのオプションを使用します。

    • [Compute Engine のデフォルトのサービス アカウント] を選択します。デフォルトの IAM 権限を使用することで、このアカウントでは Cloud Composer 環境にアクセスする関数を実行できます。

    • Composer ユーザーの役割を持つカスタム サービス アカウントを作成し、この関数のランタイム サービス アカウントとして指定します。このオプションは、最小権限の原則に従っています。

  • ランタイムとエントリ ポイント。この例のコードを追加する場合は、Python 3.7 ランタイムを選択し、エントリ ポイントとして trigger_dag を指定します。

要件を追加する

requirements.txt ファイルで依存関係を指定します。

requests_toolbelt==0.9.1
google-auth==1.28.1

関数コードを追加する

次のコードを main.py ファイルに入れて、次のように置き換えます。

  • client_id 変数の値を、前の手順で取得した client_id 値に置き換えます。
  • webserver_id 変数の値を、前の手順で取得した Airflow ウェブ インターフェース URL に置き換えます。

from google.auth.transport.requests import Request
from google.oauth2 import id_token
import requests

IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'

def trigger_dag(data, context=None):
    """Makes a POST request to the Composer DAG Trigger API

    When called via Google Cloud Functions (GCF),
    data and context are Background function parameters.

    For more info, refer to
    https://cloud.google.com/functions/docs/writing/background#functions_background_parameters-python

    To call this function from a Python script, omit the ``context`` argument
    and pass in a non-null value for the ``data`` argument.
    """

    # Fill in with your Composer info here
    # Navigate to your webserver's login page and get this from the URL
    # Or use the script found at
    # https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/composer/rest/get_client_id.py
    client_id = 'YOUR-CLIENT-ID'
    # This should be part of your webserver's URL:
    # {tenant-project-id}.appspot.com
    webserver_id = 'YOUR-TENANT-PROJECT'
    # The name of the DAG you wish to trigger
    dag_name = 'composer_sample_trigger_response_dag'
    webserver_url = (
        'https://'
        + webserver_id
        + '.appspot.com/api/experimental/dags/'
        + dag_name
        + '/dag_runs'
    )
    # Make a POST request to IAP which then Triggers the DAG
    make_iap_request(
        webserver_url, client_id, method='POST', json={"conf": data, "replace_microseconds": 'false'})

# This code is copied from
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/iap/make_iap_request.py
# START COPIED IAP CODE
def make_iap_request(url, client_id, method='GET', **kwargs):
    """Makes a request to an application protected by Identity-Aware Proxy.
    Args:
      url: The Identity-Aware Proxy-protected URL to fetch.
      client_id: The client ID used by Identity-Aware Proxy.
      method: The request method to use
              ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
      **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 by default.
    Returns:
      The page body, or raises an exception if the page couldn't be retrieved.
    """
    # Set the default timeout, if missing
    if 'timeout' not in kwargs:
        kwargs['timeout'] = 90

    # Obtain an OpenID Connect (OIDC) token from metadata server or using service
    # account.
    google_open_id_connect_token = id_token.fetch_id_token(Request(), client_id)

    # Fetch the Identity-Aware Proxy-protected URL, including an
    # Authorization header containing "Bearer " followed by a
    # Google-issued OpenID Connect token for the service account.
    resp = requests.request(
        method, url,
        headers={'Authorization': 'Bearer {}'.format(
            google_open_id_connect_token)}, **kwargs)
    if resp.status_code == 403:
        raise Exception('Service account does not have permission to '
                        'access the IAP-protected application.')
    elif resp.status_code != 200:
        raise Exception(
            'Bad response from application: {!r} / {!r} / {!r}'.format(
                resp.status_code, resp.headers, resp.text))
    else:
        return resp.text
# END COPIED IAP CODE

関数をテストする

関数と DAG が意図したとおりに機能することを確認するには、

  1. 関数がデプロイされるまで待機します。
  2. Cloud Storage バケットにファイルをアップロードします。別の方法として、Google Cloud Console でテスト関数アクションを選択して、関数を手動でトリガーすることもできます。
  3. Airflow ウェブ インターフェースの DAG ページを確認します。この DAG は、アクティブであるか、すでに完了済みの DAG が実行中である必要があります。
  4. Airflow ウェブ インターフェースで、この実行のタスクログを確認します。print_gcs_info タスクにより、関数から受信したデータがログに出力されます。
[2021-04-04 18:25:44,778] {bash_operator.py:154} INFO - Output:
[2021-04-04 18:25:44,781] {bash_operator.py:158} INFO - Triggered from GCF:
    {bucket: example-storage-for-gcf-triggers, contentType: text/plain,
    crc32c: dldNmg==, etag: COW+26Sb5e8CEAE=, generation: 1617560727904101,
    ... }
[2021-04-04 18:25:44,781] {bash_operator.py:162} INFO - Command exited with
    return code 0

次のステップ