Managed Airflow(Gen 3) | Managed Airflow(Gen 2) | Managed Airflow(レガシー Gen 1)
このページでは、Cloud Run 関数を使用して、イベントに応答して Managed Service for Apache Airflow DAG をトリガーする方法について説明します。
Apache Airflow では定期的なスケジュールで DAG が実行されるように設計されていますが、イベントに応答して DAG をトリガーすることもできます。これを行う方法の一つとして、 Cloud Run Functions を使用して、 指定されたイベントの発生時にマネージド Airflow DAG をトリガーする方法があります。
次の機能もご利用いただけます。
このガイドの例では、イベントに応答して DAG をトリガーする関数を示します。
- Cloud Run Functions で関数のトリガーを構成します。
- 関数がトリガーされると、Managed Airflow 環境の Airflow REST API を介して DAG をトリガーするリクエストが送信されます。リクエストには、イベントの識別子とタイプ、イベントのペイロードが含まれます。
- Airflow はこのリクエストを処理し、リクエストで指定された DAG を実行します。 DAG は、関数から渡されたデータを出力します。
始める前に
このセクションでは、準備の手順について説明します。
環境のネットワーク構成を確認する
このソリューションは、プライベート IP と VPC Service Controls の構成では機能しません。これらの構成では、Cloud Run Functions から Airflow ウェブサーバーへの接続を構成できないためです。
マネージド Airflow(Gen 2)では、 別のアプローチ( Cloud Run Functions と Pub/Sub メッセージを使用して DAG をトリガーする)を使用できます。
プロジェクトで API を有効にする
コンソール
マネージド Airflow API と Cloud Run Functions API を有効にします。
API を有効にするために必要なロール
API を有効にするには、serviceusage.services.enable 権限が必要です。プロジェクトを作成した場合は、オーナーロール(roles/owner)を介してこの権限が付与されている可能性があります。それ以外の場合は、Service Usage 管理者ロール(roles/serviceusage.serviceUsageAdmin)を介してこの権限を取得できます。ロールを付与する方法をご覧ください。
gcloud
マネージド Airflow API と Cloud Run Functions API を有効にします。
API を有効にするために必要なロール
API を有効にするには、serviceusage.services.enable 権限が必要です。プロジェクトを作成した場合は、オーナーロール(roles/owner)を介してこの権限が付与されている可能性があります。それ以外の場合は、Service Usage 管理者ロール(roles/serviceusage.serviceUsageAdmin)を介してこの権限を取得できます。ロールを付与する方法をご覧ください。
gcloud services enable cloudfunctions.googleapis.comcomposer.googleapis.com
Airflow REST API を有効にする
Airflow 2 の場合、安定版の REST API はデフォルトですでに有効になっています。環境で安定した API が無効になっている場合は、 安定版の REST API を有効にします。
ウェブサーバーのネットワーク アクセス制御を使用して Airflow REST API への API 呼び出しを許可する
Cloud Run Functions は、IPv4 または IPv6 アドレス を介して Airflow REST API にアクセスできます。
呼び出し元の IP 範囲がわからない場合は、ウェブサーバーのアクセス制御 のデフォルトの構成オプション All IP addresses have access (default) を使用して、Cloud Run Functions が誤ってブロックされないようにします。ウェブサーバーのネットワーク アクセスは後でいつでも
構成できます。
Airflow ウェブサーバーの URL を取得する
この例では、Airflow ウェブサーバー エンドポイントに REST API リクエストを送信します。Cloud Functions 関数のコードで Airflow ウェブサーバーの URL を使用します。
コンソール
コンソール Google Cloud で、[Environments] ページに移動します。
環境の名前をクリックします。
[環境の詳細] ページで [環境の構成] タブに移動します。
[Airflow ウェブ UI] 項目に Airflow ウェブサーバーの URL が表示されます。
gcloud
次のコマンドを実行します。
gcloud composer environments describe ENVIRONMENT_NAME \
--location LOCATION \
--format='value(config.airflowUri)'
以下のように置き換えます。
ENVIRONMENT_NAMEを環境の名前に置き換えます。LOCATIONは、環境が配置されているリージョン。
DAG をお使いの環境にアップロードする
DAG をお使いの環境にアップロードする次の DAG の例では、受信した DAG 実行構成を出力します。このガイドの後半で作成する関数から、この DAG をトリガーします。
import datetime
import airflow
from airflow.operators.bash_operator import BashOperator
with airflow.DAG(
'composer_sample_trigger_response_dag',
start_date=datetime.datetime(2026, 1, 1),
# Not scheduled, trigger only
schedule=None) as dag:
# Print the dag_run's configuration, which includes information about the
# Cloud Storage object change.
print_gcs_info = BashOperator(
task_id='print_gcs_info', bash_command='echo {{ dag_run.conf }}}}')
DAG をトリガーする関数をデプロイする
Cloud Run Functions または Cloud Run でサポートされている言語を使用して、関数をデプロイできます。このチュートリアルでは、 Python と Java で実装された Cloud Functions の関数について説明します。
関数の構成パラメータを指定する
トリガー: 関数の Eventarc トリガーまたは複数のトリガーを選択します。
トリガーの作成の詳細については、 Eventarc でトリガーを作成するをご覧ください。 たとえば、Eventarc を使用して Cloud Storage から関数を トリガーできます。
サービス アカウント: トリガーに指定するサービス アカウントには、Managed Airflow 環境で DAG をトリガーするのに十分な権限が必要です。
最小権限の原則に従い、Composer ユーザー (
composer.user)ロールのみを付与することをおすすめします。権限の構成の詳細については、 Cloud Run ターゲットの ロールと権限をご覧ください。関数のエントリ ポイント:
(Python)この例のコードを追加する際は、Python 3.10 以降のランタイムを選択し、エントリ ポイントとして
trigger_dag_with_gcfを指定します。(Java)この例のコードを追加する際は、Java 17 ランタイムを選択し、エントリ ポイントとして
functions.TriggerDagExampleを指定します。
要件を追加する
Python
requirements.txt ファイルで依存関係を指定します。
google-auth>=2.38.0
requests>=2.34.2
functions-framework==3.*
Java
pom.xml の dependencies セクションに次の依存関係を追加します。
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-docs</artifactId>
<version>v1-rev20250917-2.0.0</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-credentials</artifactId>
<version>1.49.0</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.49.0</version>
</dependency>
関数コードを追加する
Python
main.py ファイルに次のコードを配置します。
web_server_url変数の値を、前の手順で取得した Airflow ウェブサーバーのアドレスに置き換えます。別の DAG をトリガーする場合は、
dag_id変数の値を置き換えます。
from __future__ import annotations
from typing import Any
from datetime import datetime, timezone
import google.auth
from google.auth.transport.requests import AuthorizedSession
import requests
import functions_framework
# Following Google Cloud best practices, these credentials should be
# constructed at start-up time and used throughout
# https://cloud.google.com/apis/docs/client-libraries-best-practices
AUTH_SCOPE = "https://www.googleapis.com/auth/cloud-platform"
CREDENTIALS, _ = google.auth.default(scopes=[AUTH_SCOPE])
def make_managed_airflow_web_server_request(
url: str, method: str = "GET", **kwargs: Any
) -> google.auth.transport.Response:
"""
Make a request to environment's web server.
Args:
url: The URL to fetch.
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.
"""
authed_session = AuthorizedSession(CREDENTIALS)
# Set the default timeout, if missing
if "timeout" not in kwargs:
kwargs["timeout"] = 90
return authed_session.request(method, url, **kwargs)
def trigger_dag_request(web_server_url: str, airflow_version: str, dag_id: str, data: dict, logical_date: str) -> str:
"""
Make a request to trigger a dag using the Airflow REST API.
https://airflow.apache.org/docs/apache-airflow/stable/stable-rest-api-ref.html
Args:
web_server_url: The URL of the Airflow web server.
airflow_version: Major version of Airflow. Determines the API endpoint.
dag_id: The DAG ID.
data: Additional configuration parameters for the DAG run (json).
logical_date: Data interval for which to run the DAG.
"""
if airflow_version == "2":
endpoint = f"api/v1/dags/{dag_id}/dagRuns"
elif airflow_version == "3":
endpoint = f"api/v2/dags/{dag_id}/dagRuns"
else:
raise ValueError(
f"Invalid Airflow version: {airflow_version}. Expected: 2 or 3.")
request_url = f"{web_server_url}/{endpoint}"
json_data = {
"conf": data,
"logical_date": logical_date,
}
response = make_managed_airflow_web_server_request(
request_url, method="POST", json=json_data
)
if response.status_code == 403:
raise requests.HTTPError(
"You do not have a permission to perform this operation. "
"Check Airflow RBAC roles for your account."
f"{response.headers} / {response.text}"
)
elif response.status_code != 200:
response.raise_for_status()
else:
return response.text
@functions_framework.cloud_event
def trigger_dag_with_gcf(cloud_event: CloudEvent) -> None:
"""
Entry point for the Cloud Function. Triggers a DAG and passes event data.
"""
# cloud_event.data contains the resource payload (e.g., storage object
# details or pub/sub body)
event_data = {
"id": cloud_event["id"],
"subject": cloud_event["subject"],
"type": cloud_event["type"],
"data": cloud_event.data
}
# TODO(developer): replace with your values
# Replace web_server_url with the Airflow web server address. To obtain this
# URL, run the following command for your environment:
# gcloud composer environments describe example-environment \
# --location=your-composer-region \
# --format="value(config.airflowUri)"
web_server_url = (
"https://example-airflow-ui-url-dot-us-central1.composer.googleusercontent.com"
)
# TODO(developer): If your environment uses Airflow 3, replace with "3"
airflow_major_version = "2"
# Replace with the ID of the DAG that you want to run.
dag_id = "composer_sample_trigger_response_dag"
# The data interval for which to run the DAG
# Format example: "2026-07-15T15:00:00Z"
now = datetime.now(timezone.utc)
logical_date = now.strftime("%Y-%m-%dT%H:%M:%SZ")
trigger_dag_request(web_server_url, airflow_major_version, dag_id, event_data, logical_date)
Java
TriggerDagExample.java ファイルに次のコードを配置します(このファイルを src/main/java/gcfv2/ ディレクトリに配置します)。
webServerUrl変数の値を、前の手順で取得した Airflow ウェブサーバーのアドレスに置き換えます。別の DAG をトリガーする場合は、
dagName変数の値を置き換えます。
package gcfv2;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.gson.GsonFactory;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.gson.Gson;
import io.cloudevents.CloudEvent;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.logging.Logger;
import java.util.HashMap;
import java.util.Map;
/**
* Function that triggers an Airflow DAG in response to an event ad passes data.
*/
public class TriggerDagExample implements CloudEventsFunction {
private static final Logger logger = Logger.getLogger(TriggerDagExample.class.getName());
@Override
public void accept(CloudEvent event) throws Exception{
// TODO(developer): replace with your values
// Replace webServerUrl with the Airflow web server address. To obtain this
// URL, run the following command for your environment:
// gcloud composer environments describe example-environment \
// --location=your-composer-region \
// --format="value(config.airflowUri)"
String webServerUrl = "https://example-airflow-ui-url-dot-us-central1.composer.googleusercontent.com";
// TODO(developer): If your environment uses Airflow 3, replace with "3"
String majorAirflowVersion = "2";
String apiVersion = switch (majorAirflowVersion) {
case "2" -> "v1";
case "3" -> "v2";
default -> throw new IllegalArgumentException("Invalid Airflow version: " + majorAirflowVersion);
};
String dagName = "composer_sample_trigger_response_dag";
String url = String.format("%s/api/%s/dags/%s/dagRuns", webServerUrl, apiVersion, dagName);
logger.info(String.format("Triggering DAG %s as a result of an event on the object %s.",
dagName, event.getSubject()));
logger.info(String.format("Triggering DAG through the following URL: %s", url));
GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
HttpCredentialsAdapter credentialsAdapter = new HttpCredentialsAdapter(googleCredentials);
HttpRequestFactory requestFactory =
new NetHttpTransport().createRequestFactory(credentialsAdapter);
Map<String, Object> conf = new HashMap<>();
conf.put("id", event.getId());
conf.put("subject", event.getSubject());
conf.put("type", event.getType());
if (event.getData() != null) {
String dataJson = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
Gson gson = new Gson();
Map<String, Object> dataMap = gson.fromJson(dataJson, Map.class);
conf.put("data", dataMap);
}
String currentUtcTime = Instant.now().toString();
Map<String, Object> json = new HashMap<>();
json.put("conf", conf);
json.put("logical_date", currentUtcTime);
HttpContent content = new JsonHttpContent(new GsonFactory(), json);
HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(url), content);
request.getHeaders().setContentType("application/json");
HttpResponse response = null;
try {
response = request.execute();
int statusCode = response.getStatusCode();
logger.info("Response code: " + statusCode);
logger.info(response.parseAsString());
} catch (HttpResponseException e) {
logger.info("Received HTTP exception");
logger.info(e.getLocalizedMessage());
logger.info("- 400 error: wrong arguments passed to Airflow API");
logger.info("- 401 error: check if service account has Composer User role");
logger.info("- 403 error: check Airflow RBAC roles assigned to service account");
logger.info("- 404 error: check Web Server URL");
} catch (Exception e) {
logger.info("Received exception");
logger.info(e.getLocalizedMessage());
} finally {
// Safely close and release the HTTP connection pool resource
if (response != null) {
try {
response.disconnect();
} catch (Exception e) {
logger.warning("Failed to disconnect response: " + e.getMessage());
}
}
}
}
}
関数をテストする
関数と DAG が意図したとおりに機能することを確認するには、
- 関数がデプロイされるまで待ちます。
- 指定されたトリガーに従って関数をトリガーします。関数を手動でトリガーすることもできます。 Google Cloud コンソールで [**関数をテストする**] アクションを選択します。
- Airflow ウェブ インターフェースの DAG ページを確認します。 . DAG には、有効な、またはすでに完了した DAG 実行が 1 つ必要です。
- Airflow UI で、この実行のタスクログを確認します。
print_gcs_infoタスクが、関数から受信したデータをログに出力することがわかります。
関数をテストするコマンドの例:
curl -X POST "https://service-id.region.run.app" \
-H "Authorization: bearer $(gcloud auth print-identity-token)" \
-X POST \
-H "Content-Type: application/json" \
-H "ce-id: 1234567890" \
-H "ce-specversion: 1.0" \
-H "ce-type: google.cloud.storage.object.v1.finalized" \
-H "ce-source: //storage.googleapis.com/projects/_/buckets/example-bucket" \
-d '{
"name": "example-file.csv",
"bucket": "example-bucket"
}'
出力例:
[2026-07-14, 15:10:12 UTC] {subprocess.py:88} INFO - Running command: ['/usr/bin/bash', '-c', "echo {'data': {'name': 'example-file.csv', 'bucket': 'example-bucket'}, 'id': '1234567890', 'type': 'google.cloud.storage.object.v1.finalized'}"]
[2026-07-14, 15:10:12 UTC] {subprocess.py:99} INFO - Output:
[2026-07-14, 15:10:12 UTC] {subprocess.py:106} INFO - {data: {name: example-file.csv, bucket: my-bucket}, id: 1234567890, type: google.cloud.storage.object.v1.finalized}
[2026-07-14, 15:10:12 UTC] {subprocess.py:110} INFO - Command exited with return code 0
[2026-07-15, 10:06:32 UTC] {subprocess.py:88} INFO - Running command: ['/usr/bin/bash', '-c', "echo {'id': '1234567890', 'subject': 'objects/example-file.csv', 'type': 'google.cloud.storage.object.v1.finalized', 'data': {'name': 'example-file.csv', 'bucket': 'example-bucket'}}"]
[2026-07-15, 10:06:32 UTC] {subprocess.py:99} INFO - Output:
[2026-07-15, 10:06:32 UTC] {subprocess.py:106} INFO - {id: 1234567890, subject: objects/example-file.csv, type: google.cloud.storage.object.v1.finalized, data: {name: example-file.csv, bucket: example-bucket}}
[2026-07-15, 10:06:32 UTC] {subprocess.py:110} INFO - Command exited with return code 0
トラブルシューティング:
- 関数が
NullPointerException: Null dataエラーで失敗し、スタック トレースがBackgroundFunctionExecutor.parseLegacyEvent関数を指している場合、関数が受信したイベントに標準のCloudEventメタデータ ヘッダーがないことを意味します。関数は、レガシー バックグラウンド イベントを送信していると想定して、そのイベントからdataフィールドを解析しようとして失敗します。 たとえば、関数をテストするときに任意のイベント ペイロードを送信すると、この問題が発生する可能性があります。 - 関数が
500 Internal Server Error: The server encountered an internal error and was unable to complete your request.で失敗した場合は、airflow_major_version変数の値を再確認してください。この変数は Airflow REST API エンドポイントを決定します。これは Airflow 2 と Airflow 3 で異なります。