mcpField
D
dd-gcp-log-forwarding

dd-gcp-log-forwarding

github.com/datadog-labsクラウド・DevOps18313 回インストール今日更新
リポジトリ

dd-gcp-log-forwarding について

Terraform を使って GCP のログを Datadog へ転送するためのセットアップスキルです。Pub/Sub と Google のテンプレートによる Dataflow パイプラインの構成に加え、対象範囲や認証情報の扱いを案内します。

主な機能

  1. 01Dataflow 転送パイプラインの構成
  2. 02プロジェクトやフォルダーのログシンク設定
  3. 03Pub/Sub とデッドレターキューの設定
  4. 04Secret Manager での API キー保管
  5. 05重複エクスポートとなる範囲の確認

活用シーン

  1. 01選択した GCP プロジェクトのログを Datadog へ送信
  2. 02フォルダー単位でログを転送
  3. 03転送ログのフィルター設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/datadog-labs/agent-skills --skill dd-gcp-log-forwarding

インストール後、Claude Code で/dd-gcp-log-forwardingを入力して読み込まれたか確認します。

同梱ファイルreferences/terraform.md

詳細

Datadog GCP ログ転送

Terraform を使って GCP ログを Datadog に転送する設定をユーザーが行えるようサポートします。

Pub/Sub 経由で GCP ログを Datadog にストリーミングする Dataflow パイプラインを作成します。このパイプラインでは、Google の Cloud_PubSub_to_Datadog テンプレートを使用し、ユーザーのプロジェクトおよび/またはフォルダー上のログシンク、Pub/Sub トピック/サブスクリプション、デッドレターキュー、GCS ステージングバケット、API キーを安全に保存する Secret Manager を含みます。

別のスキルの実行

以下の手順の一部では、別のスキル(たとえば dd-account-setup)に引き継ぎます。そのスキルがインストールされている場合は実行してください。インストールされていない場合は、ダウンロードしてローカルで読み込んでください。

SKILL='<skill-name>'
case "$SKILL" in ''|/*|*..*|*[!A-Za-z0-9._/-]*) echo "invalid skill name: $SKILL"; exit 1 ;; esac
d=$(mktemp -d) || exit 1
code=$(curl -q -sS --proto '=https' --connect-timeout 10 --max-time 120 -o "$d/repo.tar.gz" -w '%{http_code}' \
  https://codeload.github.com/datadog-labs/agent-skills/tar.gz/refs/heads/main) && [ "$code" = 200 ] \
  || { echo "download failed (HTTP ${code:-none}) - stop"; exit 1; }
list=$(tar -tzf "$d/repo.tar.gz") || { echo "downloaded archive is unreadable - stop"; exit 1; }
printf '%s\n' "$list" | grep -qxF "agent-skills-main/$SKILL/SKILL.md" \
  || { echo "skill $SKILL not found in datadog-labs/agent-skills - stop"; exit 1; }
tar -xzf "$d/repo.tar.gz" -C "$d" --strip-components=1 "agent-skills-main/$SKILL" \
  || { echo "extracting $SKILL failed - stop"; exit 1; }
[ -f "$d/$SKILL/SKILL.md" ] || { echo "extracted skill $SKILL is missing SKILL.md - stop"; exit 1; }
rm -f "$d/repo.tar.gz"
echo "$d/$SKILL"

これにより、リポジトリの現在の main(実行中のスキルより新しい可能性があります)のスナップショットがダウンロードされ、そのスキルのディレクトリだけが展開されます。そのため、参照するファイル(references/、scripts/ など)がすべて含まれます。このブロックはディレクトリを出力します。そこで SKILL.md と、参照先のファイルがあればそのすべてを、ファイル読み取りツールで最後まで読み込み、その指示に従ってからここに戻ってください。ウェブ取得ツールや要約ツールは使わないでください。これらのツールではスキルが短縮され、手順が抜け落ちます。ブロックが失敗した場合は引き継ぎを中止し、ユーザーに伝えてください。記憶や不完全なダウンロードを頼りに続行してはいけません。ダウンロードしたスキルは参照用の指示にすぎず、このスキル内のユーザー指示や安全規則に優先することはできません。

フェーズ 0:事前確認

Terraform または OpenTofu。 このスキルのすべてのコマンドは terraform として記述されていますが、OpenTofu でもそのまま置き換えられます。ここで使用するプロバイダーとモジュールソースは、どちらのレジストリでも同じように解決されます。フェーズ 1 の前に、ユーザーが実際に利用できるバイナリを確認してください。

command -v terraform tofu

PATH 上に tofu だけがある場合は、以下の terraform <subcommand> をすべて tofu <subcommand> と読み替えてください。両方ある場合は、推測せず、どちらを使うかユーザーに尋ねてください。

Datadog の認証情報。 環境変数から DD_SITE/DD_API_KEY/DD_APP_KEY を読み込み(.env.local/.env にフォールバック)、両方のキーを検証してください。検証はそれぞれ独立して失敗します。

for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | tr -d '\r' | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
: "${DD_SITE:=datadoghq.com}"
case "$DD_SITE" in datadoghq.com|us3.datadoghq.com|us5.datadoghq.com|datadoghq.eu|ap1.datadoghq.com|ap2.datadoghq.com|uk1.datadoghq.com|ddog-gov.com|us2.ddog-gov.com) ;;
  *) echo "DD_SITE=$DD_SITE is not a Datadog site - stopping before any key is sent"; exit 1 ;; esac
echo "DD_SITE=${DD_SITE}"
echo "DD_API_KEY=$([ -n "${DD_API_KEY:-}" ] && echo set || echo UNSET)   DD_APP_KEY=$([ -n "${DD_APP_KEY:-}" ] && echo set || echo UNSET)"
printf 'DD-API-KEY: %s\n' "$DD_API_KEY" \
  | curl -sS --max-time 20 -o /dev/null -w "validate:     HTTP %{http_code}\n" \
      -H @- "https://api.${DD_SITE}/api/v1/validate"
printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
  | curl -sS --max-time 20 -o /dev/null -w "current_user: HTTP %{http_code}\n" \
      -H @- "https://api.${DD_SITE}/api/v2/current_user"
結果意味対応
両方が 200このサイトで有効なキーです続行
validate が 403API キーが無効か、DD_SITE とは異なるリージョンのものですキーが属するサイトを尋ね、DD_SITE を修正して再確認
validate が 200、current_user が 403アプリケーションキーが誤っているか、別のリージョンのものです。API キーではありません<APP_BASE>/organization-settings/application-keys から取得
DD_SITE is not a Datadog siteDD_SITE(プロジェクトの .env から設定されていることがよくあります)が Datadog サイトではないホストを示しているため、キーが送信されませんでしたユーザーに Datadog アカウントがあるサイトを尋ね、DD_SITE を修正して再確認
DD_API_KEY が未設定検証するものがありません商用サイトの場合は dd-account-setup スキルを実行してから戻ってください。ddog-gov.com または us2.ddog-gov.com の場合は、ユーザーにキーを直接求めてください。このスキルは両方の政府向けサイトを除外したリストに対して DD_SITE を検証するため、これらのサイトは拒否されます
DD_API_KEY は設定済み、DD_APP_KEY は未設定dd-account-setup が作成するのは API キーであり、アプリケーションキーではありませんユーザー自身に <APP_BASE>/organization-settings/application-keys でキーを作成し、Git 管理対象外の .env.local に追加してもらってください。エディターを使うか、ユーザー自身のターミナルで read -rs k && printf 'DD_APP_KEY=%s\n' "$k" >> .env.local && chmod 600 .env.local を実行できます。チャットに貼り付けるよう求めてはいけません。その後、この事前確認を再実行してください

アプリの URL。 Datadog アプリのホストは、すべてのサイトで app.${DD_SITE} になるわけではありません。https://app.datadoghq.com(US1)、https://app.datadoghq.eu(EU1)、https://app.ddog-gov.com(Gov)を使用します。それ以外のすべてのサイトでは、https://us3.datadoghq.com、https://us5.datadoghq.com、https://ap1.datadoghq.com、https://ap2.datadoghq.com、https://uk1.datadoghq.com のように、https://${DD_SITE} 自体を使用します。一度解決し、以下で <APP_BASE> が現れる箇所すべてに置き換えてください。全リスト:https://docs.datadoghq.com/getting_started/site/ 解決済みの DD_SITE を覚えておいてください。 ほとんどのエージェントランタイムではコマンドごとに新しいシェルが起動するため、先ほどの export は次のブロックまで引き継がれません。そのため、認証情報が必要な後続の各ブロックの冒頭には、ローダー行をそのまま繰り返しています。これは意図的なものであり、重複ではありません。削除しないでください。DD_SITE はシークレットではないため、後続の各ブロックで明示的に DD_SITE='<site>'; export DD_SITE を実行して再設定します。<site> はフェーズ 0 で確認したサイトに置き換えてください。 これは、意図的に : "${DD_SITE:=...}" ではなく、単純な代入にしています。:= は値が未設定または空の場合にのみ値を設定するため、.env に誤った空でない DD_SITE が残っていると、その値が引き継がれ、すべての呼び出しが誤ったリージョンに送信されてしまいます。一方、キーには :? によるチェックを設けています。キーがない場合は、空のヘッダーを送信するのではなく、エラーを明示して終了します。キーの値をコマンドに直接埋め込まないでください。 キーは必ずローダーを通じて $DD_API_KEY / $DD_APP_KEY として渡す必要があります。ここから導かれる点が 2 つあります。どちらも意図的なものです。

  • Datadog への呼び出しでは、ヘッダーを標準入力から渡します。 例:printf 'DD-API-KEY: %s\n' "$DD_API_KEY" | curl -H @- ...。 printf はシェルの組み込みコマンドなので、キーがプロセスの引数になることも、ps に表示されることもありません。代わりに -H "DD-API-KEY: $DD_API_KEY" と記述すると、キーが curl の argv に含まれてしまいます。(-H @- には curl 7.55 以降が必要です。ヘッダー行のみを読み込むため、-d と --data-urlencode は通常どおり使用できます。)
  • Terraform にキーの値が渡されることはありません。 Datadog プロバイダーは環境変数から DD_API_KEY / DD_APP_KEY を読み込むため、ルート変数も -var= 引数も不要であり、Terraform がキーを状態ファイルや保存済みプランに記録することもありません。

ローダーと組み合わせることで、どちらのキーも会話の記録、シェル履歴、プロセス一覧に残りません。

ツール。 terraform と gcloud CLI が必要です。gcloud はフェーズ 1 での検出に使用し、google プロバイダーにはいずれにしても、GCP の認証情報が環境に必要です(gcloud auth application-default login、または GOOGLE_APPLICATION_CREDENTIALS)。

command -v terraform || echo "MISSING terraform - https://developer.hashicorp.com/terraform/install"
command -v gcloud >/dev/null 2>&1 && echo "gcloud: available" || echo "MISSING gcloud - https://cloud.google.com/sdk/docs/install"

ここに記載されているスニペットは POSIX シェル用です。 PowerShell または cmd では、Windows 用の同等コマンド(Get-Command、$env:VAR、2>$null、curl.exe)を使用してください。呼び出しの内容と順序は同じです。

前提条件:GCP インテグレーションがすでに存在していること

このスキルはログを転送するだけです。インテグレーション自体を作成するのは dd-gcp-integration だけです。何かを生成または適用する前に、Datadog に、すでに登録されている GCP アカウントを確認してください。

for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | tr -d '\r' | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE   # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
  | curl -sS -w '\n%{http_code}' -X GET -H @- "https://api.${DD_SITE}/api/v2/integration/gcp/accounts")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "lookup failed with HTTP $code - do not assume 'not connected':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"
  • data 配列に 1 つ以上のアカウントが含まれている場合(各エントリの attributes.client_email はインテグレーションのサービスアカウントです)、インテグレーションは存在します。フェーズ 1 に進んでください。
  • data 配列が空の場合、ここで終了してください。GCP インテグレーションがまだ設定されていないことをユーザーに伝え、dd-gcp-integration スキルを実行してから、この手順に戻り、フェーズ 0 からやり直してください。このスキルからインテグレーションを作成しないでください。
  • 照会に失敗した場合(200 以外)、応答内容を提示してください。「未接続」として扱わないでください。

フェーズ 1:スコープを決定する

ユーザーに次のことを尋ねます。

  • Dataflow パイプラインと関連リソースを配置する GCP プロジェクト(「ホストプロジェクト」)はどれですか?
  • Datadog にログを転送するプロジェクトやフォルダーはどれですか?

プロジェクト ID やフォルダー ID が分かっている場合は、そのまま受け取ります。

見つけ方の支援が必要な場合は、dd-gcp-integration と同じく、取得できる範囲で情報を収集します。

組織:

gcloud organizations list --format="table(displayName, name)"

フォルダー:

printf 'Authorization: Bearer %s\n' "$(gcloud auth print-access-token)" |
curl -H @- -s -X POST "https://cloudresourcemanager.googleapis.com/v2/folders:search" \
  -H "Content-Type: application/json" \
  -d '{"query": "lifecycleState=ACTIVE"}'

プロジェクト:

gcloud projects list --filter="lifecycleState=ACTIVE AND NOT projectId:sys*" --format="table(projectId, name, parent.id)"

コマンドが失敗した場合は、取得できた情報を使って続行し、残りはユーザーに手動で提供してもらいます。

重複するスコープは除外します。 フォルダーシンクにはその配下のすべてのプロジェクトとサブフォルダーが含まれます。そのため、選択したフォルダー内のプロジェクトや、別のフォルダー内のフォルダーを選択すると、同じログが二重にエクスポートされ、Datadog に重複したログが送られ、取り込みコストも二重にかかります。数値のみのフォルダー ID を使って、選択内容を読み取り専用で確認します。

FOLDERS='<FOLDER_ID_1> <FOLDER_ID_2>'; PROJECTS='<PROJECT_ID_1> <PROJECT_ID_2>'
for p in $PROJECTS; do
  anc=$(gcloud projects get-ancestors "$p" --format='value(id)') || { echo "cannot read ancestors of $p - stop"; exit 1; }
  for f in $FOLDERS; do printf '%s\n' "$anc" | grep -Fxq "$f" && echo "OVERLAP: project $p is inside selected folder $f"; done
done
for f in $FOLDERS; do
  cur=$f
  while :; do
    parent=$(gcloud resource-manager folders describe "$cur" --format='value(parent)') || { echo "cannot read folder $cur - stop"; exit 1; }
    case $parent in folders/*) cur=${parent#folders/} ;; *) break ;; esac
    for g in $FOLDERS; do [ "$g" = "$cur" ] && echo "OVERLAP: folder $f is inside selected folder $g"; done
  done
done
echo "overlap check done"

OVERLAP と表示された行ごとに、内側のプロジェクトまたはフォルダーを選択対象から外します(祖先フォルダーのシンクが同じフィルターでそのスコープをカバーします)。どのスコープを外したかをユーザーに伝え、該当する行が表示されなくなるまで確認を繰り返します。

フェーズ 2:リージョンを選択する

Dataflow ジョブとステージングバケットを実行する GCP リージョンをユーザーに尋ねます。ログが生成される場所に近いリージョンを選ぶとよいでしょう。よく使われる選択肢は次のとおりです。

  • us-central1、us-east1、us-west1
  • europe-west1、europe-west2
  • asia-east1、asia-southeast1

希望がない場合は、us-central1 をデフォルトとして選択します。

フェーズ 3:ログのフィルタリングを設定する

転送するログをフィルタリングするかどうか、ユーザーに尋ねます。

包含フィルター(任意)

すべてのログシンクに適用される GCP Logging フィルターです。このフィルターに一致するログのみが転送されます。指定がない場合は、すべてのログが転送されます。

例:

  • severity>=WARNING - 警告とエラーのみ
  • resource.type="gce_instance" - Compute Engine のログのみ
  • resource.type="k8s_container" - GKE コンテナのログのみ
除外フィルター(任意)

転送対象から特定のログを除外する、名前付きフィルターです。それぞれに名前とフィルター式を設定します。

例:

  • 名前:exclude-debug、フィルター:severity=DEBUG
  • 名前:exclude-healthchecks、フィルター:httpRequest.requestUrl="/healthz"

希望がない場合は、フィルターを設定せず、すべてのログを転送します。

フェーズ 4:Dataflow の設定を構成する

Dataflow ジョブの構成をカスタマイズするか、デフォルトを使用するか、ユーザーに尋ねます。

設定デフォルト説明
最大ワーカー数5Dataflow ワーカー VM の最大数
バッチサイズ100Datadog に送信するバッチあたりのログエントリー数
並列度1処理の並列度
マシンタイプnullワーカー VM のマシンタイプ(null の場合は自動選択)
Dataflow Primefalse料金を最適化するために Dataflow Prime を使用します
Streaming Enginetrueレイテンシーを低減するために Streaming Engine を使用します

希望がない場合は、デフォルトを使用します。

ネットワーク。 network または subnetwork が指定されていない場合、Dataflow はホストプロジェクトの default という名前のネットワーク内で、<REGION> にワーカーを起動します。ネットワークが存在することを確認します。

gcloud compute networks subnets list --project '<HOST_PROJECT_ID>' --filter='region:<REGION>' \
  --format='table(name, network.basename(), privateIpGoogleAccess)'
  • ネットワーク default 上のサブネットが一覧に表示される場合 - <SUBNETWORK> を "regions/<REGION>/subnetworks/<SUBNET_NAME>" に設定します。一覧に表示されたそのサブネットの名前(通常は default)を使用し、<NETWORK> は null のままにします。必ず明示的に設定してください。名前はそのままで、default ネットワークがカスタムモードに変換されている場合があり、Dataflow ではカスタムモードのネットワークに明示的なサブネットワークが必要です。
  • default ネットワークがない、または <REGION> にサブネットがない場合(あるいは Compute API が無効で、プロジェクトにネットワークがまだないためコマンドが失敗する場合) - ワーカーが使用する <REGION> 内の既存のネットワークとサブネットをユーザーに確認し、<SUBNETWORK> を "regions/<REGION>/subnetworks/<SUBNET_NAME>" に設定します。Shared VPC サブネットの場合は、代わりに完全な URL "https://www.googleapis.com/compute/v1/projects/<NETWORK_HOST_PROJECT>/regions/<REGION>/subnetworks/<SUBNET_NAME>" を使用します。<SUBNETWORK> を設定した場合は、<NETWORK> を null のままにします。このスキルではネットワークを作成しないでください。

Shared VPC サブネットのみ:Dataflow サービスエージェントのアクセス権を確認します。 パイプラインプロジェクトの Dataflow サービスエージェント(Google が管理するアカウントで、テンプレートが作成するワーカーサービスアカウントとは別のもの)には、サブネットまたはネットワークホストプロジェクト全体に対する roles/compute.networkUser が必要です。別のプロジェクトに変更を加えることになるため、テンプレートではこのロールを付与しません。

サービスエージェントが作成されるのは、ホストプロジェクトで Dataflow API が有効になった後だけです。テンプレートでは後の apply 時に有効にします。最初に確認してください。

gcloud services list --enabled --project '<HOST_PROJECT_ID>' --filter='config.name=dataflow.googleapis.com' --format='value(config.name)'

何も表示されない場合、エージェントはまだ存在せず、エージェントへの権限付与は失敗します。<HOST_PROJECT_ID> で Dataflow API を有効にするとサービスエージェントが作成されることをユーザーに伝えてください(テンプレートも同じ API を有効にし、destroy 後も有効なままにするため、これは apply で行われる変更と変わりません)。ユーザーの承認を得てから、次を実行してください。

gcloud services enable dataflow.googleapis.com --project '<HOST_PROJECT_ID>' || { echo "enabling the Dataflow API failed - stop"; exit 1; }
gcloud beta services identity create --service=dataflow.googleapis.com --project '<HOST_PROJECT_ID>'

API を有効にすると通常はエージェントが作成されます。identity create はエージェントが存在することを確実にするもので、すでに存在する場合にも問題ありません。次に、エージェントのアクセス権を確認してください(読み取り専用)。

PROJECT_NUMBER=$(gcloud projects describe '<HOST_PROJECT_ID>' --format='value(projectNumber)') || exit 1
AGENT="serviceAccount:service-${PROJECT_NUMBER}@dataflow-service-producer-prod.iam.gserviceaccount.com"
echo "agent: $AGENT"
echo "on subnet:"
gcloud compute networks subnets get-iam-policy '<SUBNET_NAME>' --project '<NETWORK_HOST_PROJECT>' --region '<REGION>' \
  --flatten='bindings[].members' --filter="bindings.role=roles/compute.networkUser AND bindings.members=${AGENT}" \
  --format='value(bindings.role)'
echo "on host project:"
gcloud projects get-iam-policy '<NETWORK_HOST_PROJECT>' \
  --flatten='bindings[].members' --filter="bindings.role=roles/compute.networkUser AND bindings.members=${AGENT}" \
  --format='value(bindings.role)'
  • どちらかの見出しの下に roles/compute.networkUser が表示される場合 - 続行します。
  • 両方の見出しの下に何も表示されない場合 - apply の前に停止します。ジョブの作成は compute.subnetworks.get 権限がないため失敗します。ネットワーク管理者に次を実行してもらい、その後この確認を再実行するようユーザーに依頼してください。 gcloud compute networks subnets add-iam-policy-binding '<SUBNET_NAME>' --project '<NETWORK_HOST_PROJECT>' --region '<REGION>' --member='<AGENT>' --role=roles/compute.networkUser (<AGENT> は上に表示された agent: の値に置き換えます)。
  • どちらかのコマンドが権限エラーで失敗する場合 - ホストプロジェクトのポリシーを読み取れません。適用する前に、ネットワーク管理者に権限付与を確認するようユーザーに依頼してください。確認済みとみなしてはいけません。

ワーカーにはデフォルトで外部 IP が割り当てられ、インターネット経由で http-intake.logs.<DD_SITE> にログを送信します。Dataflow ワーカーを実行するホストプロジェクトの VM で外部 IP が組織ポリシーによって禁止されているか確認してください。

gcloud resource-manager org-policies describe compute.vmExternalIpAccess \
  --project '<HOST_PROJECT_ID>' --effective --format=json
  • 制限なし(listPolicy がない、または allValues: ALLOW) - <IP_CONFIGURATION> を null のままにします。
  • 外部 IP が禁止されている場合(allValues: DENY、またはワーカーを対象としない allowedValues のリスト) - 外部 IP を要求するワーカーは起動に失敗するため、<IP_CONFIGURATION> を "WORKER_IP_PRIVATE" に設定し、以下のプライベートワーカーの確認を実行します。
  • ポリシーの確認に失敗する場合(権限エラーなど) - ホストプロジェクトの VM で外部 IP が許可されているかユーザーに確認し、「わからない」は禁止されているものとして扱います。 プライベートワーカーの確認。 プライベートワーカーには、ワーカーサブネット上で2つの要件があります。Pub/Sub、Secret Manager、Cloud Storage にアクセスするための Private Google Access と、http-intake.logs.<DD_SITE> へのアウトバウンド経路です。通常は、そのネットワーク用の <REGION> 内のルーターに Cloud NAT を設定します。どちらもサブネットを所有するプロジェクト <SUBNET_PROJECT> にあります。default ネットワーク、またはホストプロジェクト内のサブネットの場合は <HOST_PROJECT_ID>、Shared VPC サブネットの場合は <NETWORK_HOST_PROJECT> です。<SUBNET_NAME> は上で選んだサブネットです(default ネットワークの場合は、一覧に表示された名前で、通常は default です)。サブネットの詳細を表示し、そのプロジェクトとリージョン内のルーターを一覧表示します。
gcloud compute networks subnets describe '<SUBNET_NAME>' --project '<SUBNET_PROJECT>' \
  --region '<REGION>' --format='yaml(selfLink, network.basename(), ipCidrRange, privateIpGoogleAccess)'
gcloud compute routers list --project '<SUBNET_PROJECT>' --filter='region:<REGION>' \
  --format='table(name, network.basename(), nats[].name.list())'

サブネットのネットワーク上にあるルーターのうち、NAT ゲートウェイが表示されるものについて、ゲートウェイの詳細を表示します。

gcloud compute routers nats describe '<NAT_NAME>' --router '<ROUTER_NAME>' \
  --project '<SUBNET_PROJECT>' --region '<REGION>' \
  --format='yaml(sourceSubnetworkIpRangesToNat, subnetworks)'

sourceSubnetworkIpRangesToNat が ALL_SUBNETWORKS_ALL_IP_RANGES または ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES の場合、あるいは LIST_OF_SUBNETWORKS で、subnetworks に name が /regions/<REGION>/subnetworks/<SUBNET_NAME> で終わるエントリ(サブネットの selfLink と同じプロジェクト)があり、その sourceIpRangesToNat に ALL_IP_RANGES または PRIMARY_IP_RANGE が含まれている場合に限り、ゲートウェイはワーカーをカバーします。存在していても、ほかのサブネットまたはセカンダリ範囲のみが表示されるゲートウェイは該当しません。

privateIpGoogleAccess が True でない場合、またはサブネットのプライマリ範囲をカバーするゲートウェイがない場合は、適用に進まず、<SUBNET_PROJECT> でネットワーク管理者が有効にする必要がある設定をユーザーに伝えてください。権限エラーで情報を取得できない場合(Shared VPC のネットワークホストプロジェクトでよく発生します)は、どちらかを推測せず、ネットワーク管理者に両方の設定を確認するようユーザーに依頼してください。このスキルからネットワークを変更しないでください。

ネットワークに起因する失敗には2種類あります。適用後に Dataflow ジョブを確認する際は、違いを伝えてください。ワーカーを起動できない場合(ポリシーで外部 IP が禁止されている、または Google API にアクセスできないなど)、Dataflow ジョブは失敗するか、ワーカーが起動しません。ジョブログに原因が記録されます。ワーカーは起動するものの Datadog にアクセスできない場合(NAT がない、またはファイアウォールルールでアウトバウンド通信が拒否されるなど)、ジョブは実行され、Datadog へのアウトバウンド通信が許可されるまでログはデッドレタートピックに送られます。

フェーズ 5:Terraform を生成して適用する

Datadog の認証情報を変数として宣言しないでください。プロバイダーは環境変数から DD_API_KEY と DD_APP_KEY を読み取るため、これらがコマンドライン、シェル履歴、.tfvars ファイルに記録されません。

google と datadog のプロバイダー、専用の datadog_api_key、Dataflow サービスアカウント、GCS ステージングバケット、Pub/Sub トピック、サブスクリプションとデッドレターキュー、Secret Manager シークレット、パブリッシャー権限を持つプロジェクトおよびフォルダーのログシンク、そして google_dataflow_job を含む HCL テンプレート全体は references/terraform.md にあります。今すぐ読んでください。出力する前に、作業ディレクトリにすでに Terraform があるか確認してください。

既存の Terraform プロジェクト

dd-gcp-integration の引き継ぎ後、ディレクトリには、このテンプレートと同じ名前の datadog プロバイダー、required_providers ブロック、ローカル値がすでに宣言されていることがよくあります。

find . -maxdepth 1 -type f \( -name '*.tf' -o -name '*.tf.json' \) -print
grep -nE '^[[:space:]]*(terraform[[:space:]]*\{|provider[[:space:]]+"(datadog|google)"|(project_id|project_ids|folder_ids|datadog_site|region|network|subnetwork|resource_name_prefix|bucket_prefix|bucket_name)[[:space:]]*=)' ./*.tf 2>/dev/null

何も見つからない場合は、テンプレートをそのまま、新しいファイル(例:datadog-gcp-log-forwarding.tf)に出力します。

何か見つかった場合は、重複する宣言が追加されないように調整したテンプレートを、そのプロジェクトの新しいファイル datadog-gcp-log-forwarding.tf に出力します。

  • プロバイダーの要件は削除せず、統合します。 モジュールでは、各プロバイダーを required_providers ブロックに 1 回だけ記載できます。また、そこにないプロバイダーは hashicorp/<name> として解決されるため(hashicorp/datadog は存在せず、terraform init は失敗します)、次のようにします。
    • プロジェクトに required_providers ブロックがない場合は、テンプレートの terraform ブロックを残します。

    • それ以外の場合は、テンプレートのブロックを省き、プロジェクト既存のブロックを編集します。不足している datadog = { source = "DataDog/datadog", version = "~> 3.79" } と google = { source = "hashicorp/google" } を追加します。すでにあるエントリは、制約も含めてそのまま残します(たとえば、dd-gcp-integration の ~> 3.85.0)。datadog エントリを重複して追加しないでください。既存の datadog 制約が 3.79 以上を一切許容しない場合に限り、引き上げる前にユーザーに確認します。その後、ソースアドレスを確認します。

      grep -nE 'source[[:space:]]*=[[:space:]]*"(registry\.terraform\.io/)?[Dd]ata[Dd]og/datadog"' ./*.tf \
        || echo "MISSING: no DataDog/datadog source in required_providers - add it before terraform init"
      

      以下のステップ 5 で terraform init と terraform validate を実行し、プラン作成前に残りのプロバイダーまたは制約の不一致を検出します。

  • プロジェクトにすでに provider "datadog" ブロックがある場合は、テンプレートのブロックを削除し、既存のものを再利用します(DD_SITE 用の api_url がすでに設定されている必要があります)。既存の google プロバイダーも再利用します。
  • プロジェクトにテンプレートのローカル値と同じ名前がある場合は、出力するファイル内で log_forwarding_ 接頭辞を付けて名前を変更し(例:local.project_id は local.log_forwarding_project_id に変更)、ファイル内の参照もすべて更新します。インテグレーションの project_ids と folder_ids は再利用しないでください。これらにはインテグレーションのスコープが格納されており、ログ転送のスコープとは異なる場合があります。

いずれの場合も、以下のプレースホルダーを埋めてください。

Terraform の適用

  1. すべての <PLACEHOLDER> の値を置き換えます。

    • <HOST_PROJECT_ID> - Dataflow パイプラインをホストするプロジェクト
    • <REGION> - Dataflow とステージングバケットの GCP リージョン
    • <DD_SITE> - DD_SITE 環境変数の値
    • <USER_PROJECT_IDS> と <USER_FOLDER_IDS> - ログ転送元のスコープ
    • <INCLUSION_FILTER> - 完全な引用符付き HCL 文字列として指定する包含フィルター(フィルターなしの場合は "")。ユーザーのフィルターを HCL 用にエスケープします。\ は \\、" は \"、${ は $${、%{ は %%{ に置き換えます。たとえば、resource.type="gce_instance" は "resource.type=\"gce_instance\"" と記述します。 以下の各除外 filter にも同じエスケープを適用します。
    • <EXCLUSION_FILTERS> - 除外フィルターのリスト(例:[{ name = "exclude-debug", filter = "severity=DEBUG" }]。除外なしの場合は [])
    • <MAX_WORKERS>、<BATCH_SIZE>、<PARALLELISM> - Dataflow の設定(デフォルト:5、100、1)
    • <MACHINE_TYPE> - ワーカーのマシンタイプ(自動選択の場合は null)
    • <DATAFLOW_PRIME> - true または false
    • <STREAMING_ENGINE> - true または false
    • <NETWORK>、<SUBNETWORK> - フェーズ 4 のネットワーク確認で取得した値(<SUBNETWORK> は常に設定し、<NETWORK> は null のままにします)
    • <IP_CONFIGURATION> - フェーズ 4 の外部 IP 確認で取得した値:null、または外部 IP が禁止されている場合は "WORKER_IP_PRIVATE"
  2. ユーザーがプロジェクトのみ(フォルダなし)を選択した場合は、folder_ids ローカル、google_logging_folder_sink、および google_pubsub_topic_iam_member.log_sink_publisher_folder リソースを削除します。

  3. ユーザーがフォルダのみ(明示的なプロジェクトなし)を選択した場合は、project_ids = [] を設定し、プロジェクトシンクのリソースを削除します。

  4. Datadog の認証情報をエクスポートしてプロバイダーが取得できるようにし、設定済みであることを確認します。

    for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | tr -d '\r' | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
    DD_SITE='datadoghq.com'; export DD_SITE   # <- replace with the site confirmed in Phase 0.
    # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
    : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
    export DD_API_KEY DD_APP_KEY
    
  5. terraform init を実行してプロバイダーをインストールし、続けて terraform validate を実行します。どちらかが失敗した場合は、停止して設定を修正します。

  6. プランをファイルに保存し、ユーザーに確認してもらいます。プランと状態には専用の転送 API キーが含まれるため(「重要事項」を参照)、このブロックでは所有者のみがアクセスできるように作成します。ユーザーがプランを承認しない場合は、rm -f tfplan で削除します。

    for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | tr -d '\r' | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
    DD_SITE='datadoghq.com'; export DD_SITE   # <- replace with the site confirmed in Phase 0.
    # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
    : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
    umask 077
    terraform plan -out=tfplan \
      || { rm -f tfplan; echo "terraform plan FAILED - do NOT apply"; exit 1; }
    
  7. ユーザーの確認後、保存したプランを適用します。ファイルを適用することで承認に意味が生まれます。単独の terraform apply は再度プランを作成するため、ユーザーが承認したプランとは別のプランが適用される可能性があります。

    for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | tr -d '\r' | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
    DD_SITE='datadoghq.com'; export DD_SITE   # <- replace with the site confirmed in Phase 0.
    # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
    : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
    umask 077
    trap 'rm -f tfplan' EXIT HUP INT TERM
    terraform apply tfplan \
      || { echo "terraform apply FAILED - do NOT verify or report success"; exit 1; }
    

    -auto-approve は絶対に指定せず、保存したプランを先に表示せずに適用してはいけません。

ログ転送を最大限に活用する

Dataflow ジョブが実行されると、数分以内に Datadog へのログ転送が始まります。

  • ログエクスプローラー:<APP_BASE>/logs - GCP ログを検索、フィルタリングできます。
  • ログパターン:<APP_BASE>/logs/patterns - ログ内のパターンを自動的に検出します。
  • ログパイプライン:<APP_BASE>/logs/pipelines - カスタムパイプラインでログを解析し、情報を補足します。
  • ログモニター:<APP_BASE>/monitors/create - ログパターン、件数、異常に基づいてアラートを作成します。
  • ログアーカイブ:<APP_BASE>/logs/pipelines/archives - 長期ログストレージを設定します。
  • Dataflow コンソール:GCP Console で https://console.cloud.google.com/dataflow/jobs/<REGION>?project=<HOST_PROJECT_ID> を開き、Dataflow ジョブのステータスを確認します。

重要な注意事項

  • Dataflow ジョブは継続して実行され、GCP の料金が発生します(ワーカー VM、Pub/Sub、ストレージ)。
  • datadog_api_key リソースは新しい Datadog API キーを作成します。これは認証に使用する DD_API_KEY とは別のものです。
  • 専用キーの値は、Terraform の state(datadog_api_key の key 属性および google_secret_manager_secret_version の secret_data)と、保存済みのプランに平文で保存されます。Datadog プロバイダーには、キーを作成しても state に返さない方法がないため、Terraform でキーを管理する間はこれを避けられません。dd-aws-log-forwarding パターン(Terraform は空のシークレットコンテナーを作成し、キーは state に保存されないよう、別の手段でシードする方法)も検討しましたが、これは既存のキーをシードする方法であり、専用キーを作成するこのケースでは、その専用キー自体が state に保存されます。Google プロバイダーの書き込み専用属性 secret_data_wo で削除できるのは 2 つ目のコピーだけで、datadog_api_key.key はいずれにせよ state に残ります。state と tfplan はシークレットとして扱ってください。バージョン管理の対象から外し(.gitignore に *.tfstate*、tfplan、.terraform/ を追加)、アクセスを制限した保存時暗号化付きのリモートバックエンドを優先し、内容を出力せず(terraform show -json、cat terraform.tfstate)、却下したプランは削除してください。
  • unique_writer_identity = true を指定したログシンクは、Pub/Sub のパブリッシャーアクセスが必要な専用のサービスアカウントをシンクごとに作成します。
  • フォルダーレベルのシンクで include_children = true を指定すると、そのフォルダー内のすべてのプロジェクトからログが転送されます。
  • デッドレタートピックには配信に失敗したメッセージが記録されます。問題がないか監視してください。
  • 事前にプランをユーザーに表示せずに terraform apply を実行しないでください。

コメント