From a597b12feae89c73f5907b0ff830f06efcff665f Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:39 +0900 Subject: [PATCH 001/123] docs(gcp): remove lab URL links from Cymbal direct prompt design guide --- Cymbal-direct-agent-platform-prompt-design-guide.html | 4 ---- Cymbal-direct-agent-platform-prompt-design-guide.md | 3 --- 2 files changed, 7 deletions(-) diff --git a/Cymbal-direct-agent-platform-prompt-design-guide.html b/Cymbal-direct-agent-platform-prompt-design-guide.html index 1cb881f3b..165d2a98f 100644 --- a/Cymbal-direct-agent-platform-prompt-design-guide.html +++ b/Cymbal-direct-agent-platform-prompt-design-guide.html @@ -545,10 +545,6 @@

Cymbal Direct チャレンジラボ攻略ガイド

各対応方法には根拠となる Google Cloud 公式ドキュメントの URL を併記しています。

- diff --git a/Cymbal-direct-agent-platform-prompt-design-guide.md b/Cymbal-direct-agent-platform-prompt-design-guide.md index 98a98a279..86069076c 100644 --- a/Cymbal-direct-agent-platform-prompt-design-guide.md +++ b/Cymbal-direct-agent-platform-prompt-design-guide.md @@ -2,9 +2,6 @@ **Agent Platform(旧 Vertex AI)でのプロンプト設計ベストプラクティス** -対象ラボ: *Prompt Design in Agent Platform* コースの Challenge Lab -(`https://www.skills.google/paths/118/course_templates/976/labs/594527`) - --- ## 目次 From 00a7bc9257e7c1a12119b9f831639f60891168e4 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:40 +0900 Subject: [PATCH 002/123] docs(gcp): add BigQuery AppsScript, COVID-19, and data sharing challenge lab guides --- ...uery-appsscript-connectedsheets-guide.html | 1477 ++++++++++++ Bigquery-appsscript-connectedsheets-guide.md | 339 +++ Bigquery-covid19-challenge-lab-guide.html | 2065 +++++++++++++++++ Bigquery-covid19-challenge-lab-guide.md | 602 +++++ ...uery-data-sharing-challenge-lab-guide.html | 1595 +++++++++++++ Bigquery-data-sharing-challenge-lab-guide.md | 260 +++ 6 files changed, 6338 insertions(+) create mode 100644 Bigquery-appsscript-connectedsheets-guide.html create mode 100644 Bigquery-appsscript-connectedsheets-guide.md create mode 100644 Bigquery-covid19-challenge-lab-guide.html create mode 100644 Bigquery-covid19-challenge-lab-guide.md create mode 100644 Bigquery-data-sharing-challenge-lab-guide.html create mode 100644 Bigquery-data-sharing-challenge-lab-guide.md diff --git a/Bigquery-appsscript-connectedsheets-guide.html b/Bigquery-appsscript-connectedsheets-guide.html new file mode 100644 index 000000000..6595d04f6 --- /dev/null +++ b/Bigquery-appsscript-connectedsheets-guide.html @@ -0,0 +1,1477 @@ + + + + + + BigQuery × Apps Script × Connected Sheets 実践ガイド + + + + +
+ + +
+
+

Google Cloud Skills Boost チャレンジラボ攻略

+

BigQuery × Apps Script × Connected Sheets 実践ガイド

+

+ 初学者でも「なぜそうするのか」まで理解しながら4つのタスクを完了できるよう、公式ドキュメントの根拠付きでベストプラクティスを解説します。 +

+
+ 対象ラボ: + skills.google 元ページ + 対象読者: BigQuery・Apps + Script・Sheets連携の初学者 +
+
+ +
+

このラボの全体像

+

+ このラボは「BigQuery Public Datasets」を題材に、Apps Script(自動化・プログラム連携)とConnected Sheets(ノーコードでのデータ接続)という2つの異なるアプローチでBigQueryのデータをGoogle + Sheets上で扱う体験を通じて学ぶ構成になっています。 +

+ +
+
+

+ 4タスクの全体アーキテクチャ(クリックで拡大されません。スクロールしてご覧ください) +

+
+ +

+ この2系統を並べて経験することで、「プログラムで自動化すべき処理」と「ビジネスユーザーが自分で分析すべき処理」を使い分ける感覚を養うのがこのラボの狙いです。 +

+
+ +
+

ラボを始める前の準備

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目ベストプラクティスと理由
ブラウザウィンドウ + シークレット(プライベート)ウィンドウを使用する。個人アカウントのセッションとラボ用の一時アカウントが同じブラウザ内で混在すると、意図せず個人のGoogle + Cloudアカウントに課金される事故につながるため +
アカウント + ラボが払い出す一時的な学習用アカウントのみを使用する。個人アカウントでリソースを作成すると、ラボ終了後も課金が継続するリスクがある +
タイマー + ラボは一時停止できないため、着手前にタスク1〜4を一通り読み、必要な作業時間を見積もっておく +
プロジェクトID + 各タスクで払い出される一時プロジェクトのIDをメモしておく。Apps + Scriptのコード内で + PROJECT_ID として明示的に使用するため +
+
+
+ +
+

タスク1: Apps ScriptからBigQueryを呼び出しSheetsへ書き込む

+ +

手順の流れ

+
    +
  1. + script.google.com + で新しいApps Scriptプロジェクトを作成し、任意の名前を付ける +
  2. +
  3. + エディタの「サービス」から + BigQuery API + をアドバンストサービスとして追加する(この時点でCloud + Platformプロジェクト側のBigQuery APIも有効化される)[2][3] +
  4. +
  5. + コードファイルを + bq-sheets.gs + にリネームし、クエリ実行用のスクリプトを実装する +
  6. +
  7. PROJECT_ID に払い出されたプロジェクトIDを設定する
  8. +
  9. runQuery() を実行し、OAuth認可フローを承認する
  10. +
  11. + 実行ログに出力される新規スプレッドシートのURLを開き、Shakespeare作品群の頻出単語トップ10が書き込まれていることを確認する +
  12. +
+ +

処理フローの可視化

+

+ runQuery() + がやっていることを分解すると、次のようなシーケンスになります。BigQueryのクエリジョブは非同期で実行されるため、「ジョブを投げる」→「完了をポーリングする」→「結果を取得する」という3段階の設計になっている点が最大のポイントです[5][6]。 +

+ +
+
+

runQuery() の処理シーケンス

+
+ +

コードの重要ポイント

+

+ サンプルコード(Apache License + 2.0で提供されている公式サンプル[3][17])の中でも、特に押さえておくべき箇所は次の2つです。 +

+ +

(1) 指数バックオフによるジョブ完了待機

+
var sleepTimeMs = 500;
+while (!queryResults.jobComplete) {
+  Utilities.sleep(sleepTimeMs);
+  sleepTimeMs *= 2;
+  queryResults = BigQuery.Jobs.getQueryResults(PROJECT_ID, jobId);
+}
+

+ これはGoogle Cloudが横断的に推奨している「指数バックオフ(Exponential + Backoff)」パターンそのものです。一定間隔で即座にリトライするのではなく、待機時間を倍々に伸ばしながら再試行することで、サーバー側への負荷集中や同時多発的なリトライの衝突を防ぎます[8][9]。 +

+ +

(2) ページトークンによるページネーション

+
while (queryResults.pageToken) {
+  queryResults = BigQuery.Jobs.getQueryResults(PROJECT_ID, jobId, {
+    pageToken: queryResults.pageToken
+  });
+  rows = rows.concat(queryResults.rows);
+}
+

+ BigQueryの結果セットは1回のレスポンスに収まらないことがあるため、pageToken + が返ってくる限りループで取得し続ける必要があります。件数が少ないサンプルクエリでは意識しにくい処理ですが、本番データに対して同じコードを流用する際に必須になる実装です。 +

+ +
+
+

指数バックオフのロジック

+
+ +

ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目推奨する対応出典
アドバンストサービスの有効化 + Apps Scriptエディタ側とCloud Console側の両方でBigQuery + APIが有効になっていることを確認する + [2][4]
SQL方言 + サンプルコードは + [project:dataset.table] + 形式のレガシーSQLを使用しているが、新規に書くクエリは標準SQL(GoogleSQL)でバッククォート記法(`project.dataset.table`)に統一する。標準SQLはウィンドウ関数やDDL/DMLなど機能面でも優位 + [7]
ジョブのポーリング + 固定間隔リトライではなく指数バックオフを用いる。上限(max + backoff)を設けて無限に待ち続けないようにする + [8][9]
ページネーション + pageToken + の有無をチェックするループを省略しない + [5][6]
プロジェクトIDの管理 + ハードコードで動かす場合も、コードの先頭で未設定チェックを入れる。恒久的な運用に載せる際はスクリプトプロパティ(Properties + Service)などに切り出す + [3]
権限 + クエリの実行のみが目的であれば「BigQuery Job + User」+対象データセットの「BigQuery Data + Viewer」という最小権限の組み合わせを意識する + [10]
+
+ +

よくあるエラーと対処

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
エラー・症状主な原因対処
+ Exception: Service BigQuery API has not been enabled + for your Apps + + アドバンストサービスの追加とCloud + Platform側のAPI有効化のタイミングがずれている + + エディタの「サービス」からBigQuery + APIを一度削除し、再度追加し直す +
+ jobComplete が + true にならずタイムアウトする + + クエリが重い、または + sleepTimeMs + の上限を設けずに無限ループになっている + + 最大待機時間・最大リトライ回数を設け、超えたらエラーとしてログ出力する設計にする[8][9] +
実行時に権限エラー(403) + 実行アカウントに対象プロジェクトへのBigQueryアクセス権が不足 + + 「BigQuery Job + User」ロールがプロジェクトに付与されているか確認する[10] +
+
+
+ +
+

+ タスク2: BigQueryデータセットをGoogle Sheetsに接続する(Connected Sheets) +

+ +

手順の流れ

+
    +
  1. Google Sheetsのホーム画面から新しい空白のスプレッドシートを作成する
  2. +
  3. + メニューの「データ」→「データコネクタ」→「BigQueryに接続」を選択する +
  4. +
  5. + 課金が有効なプロジェクトを選び、「公開データセット」から + chicago_taxi_trips を検索する +
  6. +
  7. taxi_trips テーブルを選択し「接続」をクリックする
  8. +
  9. + 接続後に表示されるプレビューシート上で、ピボットテーブルや数式を使って分析を行う +
  10. +
+ +

アーキテクチャ

+

+ Connected + Sheetsは、Sheets上のデータをBigQueryにコピーするのではなく、必要な範囲だけをその都度BigQueryへ問い合わせるアーキテクチャです。プレビューには先頭500行のみが表示されますが、ピボットテーブルや数式、グラフは接続先の全データに対して実行されます[11]。 +

+ +
+
+

Connected Sheets のデータフロー

+
+ +

数式によるデータ分析の考え方

+

+ taxi_trips テーブルには + company(配車会社名)、tips(チップ額)、fare(運賃)などの列が含まれています[18]。3つの設問は、いずれも列単位の集計関数で解けるように設計されています。 +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
分析したいこと考え方使う関数の例
タクシー会社の数 + company 列に含まれるユニークな値の数を数える + + COUNTA と + UNIQUE の組み合わせ、またはピボットテーブルで + company を行にドラッグし件数を確認 +
チップがあった配車の割合 + tips 列が0より大きい行数を、全体の行数で割る + + COUNTIF(tips範囲, ">0") / COUNTA(tips範囲) +
運賃が0より大きい配車の総数fare 列が0より大きい行だけを数えるCOUNTIF(fare範囲, ">0")
+
+ +
+ 注意: + 実際のセル参照や列位置は接続後のプレビューシートの構成に依存するため、上表は「どの関数を組み合わせるか」という考え方のガイドとして使ってください。ピボットテーブルの「値」に集計方法(合計・カウント・個別カウント)を指定するだけでも同等の答えが得られます。 +
+ +

ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目推奨する対応出典
プレビュー行数の理解 + 画面に表示されるのは先頭500行だが、数式・ピボット・グラフは全データに対して実行される点を理解した上で分析する + [11]
集計方法の選択 + 単純な合計・件数はピボットテーブル、条件付きの計算は数式(COUNTIF等)と使い分ける + [11][12]
データの更新 + 元データが変わりうる分析では、手動更新のほかにスケジュール更新(定期リフレッシュ)の設定も検討する + [11][12]
SQLを書かずに分析 + Connected + SheetsはSQLの知識がなくても大規模データにアクセスできる点が価値。まずは使い慣れたSheetsの関数・ピボットで試すのが推奨アプローチ + [13][12]
権限 + Connected Sheetsは + bigquery.readonly + スコープでBigQueryにアクセスする。読み取り専用であることを意識し、接続先データセットへの最小権限(Data + Viewer)で運用する + [16][10]
+
+
+ +
+

タスク3: Google Chartsで可視化する

+ +

手順の流れ

+
    +
  1. Connected Sheets接続済みのシート上で「挿入」→「グラフ」を選択する
  2. +
  3. + 支払い方法(payment_type)の内訳を + 円グラフ で可視化する +
  4. +
  5. + モバイル決済(mobile)の売上推移を + 折れ線グラフ で可視化する +
  6. +
  7. + 2015年にピークを迎えた後の推移だけを見たい場合は、グラフの期間フィルタや軸の範囲を絞り込む +
  8. +
+ +

グラフタイプの選び方

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
分析の目的適したグラフ理由
ある時点における内訳(構成比)を見たい円グラフ + 支払い方法ごとの割合など、全体に対する構成比の把握に向く +
時間の経過に伴う変化・トレンドを見たい折れ線グラフ売上や件数の推移、ピークの検出、期間比較に向く
特定期間だけを深掘りしたい折れ線グラフ+軸の範囲指定 + ピーク(2015年)以降のみに絞ることで、その後の減少・回復傾向を読み取りやすくする +
+
+ +

+ Google + Sheets上でグラフを作成した後は、元データが更新された場合に「グラフを更新」ボタンでBigQuery側の最新データを反映できます[15]。ダッシュボードのように毎週参照するグラフであれば、この更新導線をチームに共有しておくと運用がスムーズです。 +

+ +

ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目推奨する対応出典
グラフの選定 + 「構成比を見たいのか」「推移を見たいのか」を先に決めてからグラフ種別を選ぶ + [15]
更新性 + Connected + Sheets上のグラフは「更新」操作でBigQueryの最新データを反映できることをチームに周知する + [15]
期間の絞り込み + ピーク検出後は、軸範囲やフィルタで期間を絞った別ビューを作ると変化が読み取りやすい + [15]
+
+
+ +
+

タスク4: Apps Scriptで新規ワークシートを作成する

+ +

手順の流れ

+
    +
  1. Google Sheetsを開き、新しい空白のスプレッドシートを作成する
  2. +
  3. 左上のセルA1(1行目・A列)をクリックする
  4. +
  5. 76 9th Ave, New York という住所文字列を入力する
  6. +
+ +

なぜここでApps Scriptの組み込みサービスが登場するのか

+

+ タスク1では「BigQuery + API」というアドバンストサービス(明示的な有効化が必要)を扱いましたが、タスク4のような「新しいシートを作る」「セルに値を入れる」といった操作は + SpreadsheetApp + という組み込みサービスだけで完結します。両者の違いを理解しておくと、今後どちらを使うべきか迷わなくなります[2]。 +

+ +
+
+

組み込みサービス(例: SpreadsheetApp)

+

+ 有効化不要。Sheets・Docs・Gmailなど主要Workspaceサービスの操作(シート作成、値の書き込み、書式設定)に使う。認可は自動的なOAuthフローで完結する。 +

+
+
+

アドバンストサービス(例: BigQuery)

+

+ Apps Script側・Cloud + Console側の両方で明示的な有効化が必要。BigQueryなど外部Google Cloud + APIへの薄いラッパーとして、大規模データへのクエリや外部システム連携に使う。 +

+
+
+ +

ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + +
項目推奨する対応出典
サービスの使い分け + Workspace内で完結する操作は組み込みサービス、外部Google + CloudのAPIを叩く操作はアドバンストサービス、という判断軸を持つ + [2]
セル入力の自動化 + 手動入力で済むタスクでも、繰り返し発生する住所入力などは + sheet.getRange("A1").setValue(address) + のようにコード化しておくと再現性が高まる + [3]
+
+
+ +
+

全体のベストプラクティスまとめ

+

4つのタスクを横断して意識すべき観点を、テーマ別に整理します。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
テーマベストプラクティス出典
セキュリティ(最小権限の原則) + クエリ実行には「BigQuery Job + User」、データ閲覧には対象データセットの「BigQuery Data + Viewer」というように、コンピュート権限とデータ権限を分けて最小限だけ付与する + [10]
信頼性 + 非同期ジョブに対しては指数バックオフでポーリングし、結果はページトークンを使い切るまで取得する + [8][9][5]
保守性 + 新規に書くクエリはレガシーSQLではなく標準SQL(GoogleSQL)に統一する + [7]
コスト意識 + 大規模データセットに対するクエリはスキャン量が課金に直結するため、必要な列だけを + SELECT し、LIMIT を活用する + [6]
ノーコード活用 + プログラムを書かずに済む定型的な分析(会社数の集計、割合の算出など)はConnected + Sheets、繰り返し実行・自動化したい処理はApps + Scriptという住み分けを意識する + [11][3]
+
+
+ +
+

トラブルシューティング早見表

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
タスク症状主な原因対処
タスク1BigQuery APIが有効化されていないというエラーアドバンストサービスの追加処理が不完全サービスを一度削除し再追加する
タスク1クエリ結果が0件、または想定と異なる集計になる + レガシーSQLとGoogleSQLで + , や識別子の扱いが異なる + クエリ全体をどちらか一方の方言に統一する[7]
タスク2「データコネクタ」メニューが表示されない組織のポリシーやエディション、事前設定の不足 + 公式ヘルプの「開始する前に」の項目を確認する[13] +
タスク2数式の結果が想定と合わないプレビューの500行だけを見て検算してしまっている + 数式・ピボットは全データに対して実行される前提で結果を確認する[11] +
タスク3グラフが最新のBigQueryデータを反映していない手動更新が行われていないグラフ下部の「更新」を実行する[15]
+
+
+ +
+

参考文献

+
+
+ +
+ 本ガイドは Google Cloud Skills Boost + の公開ラボ手順を基に、公式ドキュメントを参照しながら作成した学習補助資料です。実際の画面や仕様は更新される場合があるため、最新情報は各参考文献のリンク先を確認してください。 +
+
+
+ + + + + + diff --git a/Bigquery-appsscript-connectedsheets-guide.md b/Bigquery-appsscript-connectedsheets-guide.md new file mode 100644 index 000000000..136e988ac --- /dev/null +++ b/Bigquery-appsscript-connectedsheets-guide.md @@ -0,0 +1,339 @@ +# BigQuery × Apps Script × Connected Sheets 実践ガイド +### 〜 Google Cloud Skills Boost チャレンジラボ攻略のためのベストプラクティス解説 〜 + +> 対象ラボ: [Google Cloud Skills Boost(元ラボページ)](https://www.skills.google/course_templates/737/labs/607137) [1] +> 対象読者: BigQuery・Apps Script・Google Sheetsの連携を初めて行うインフラ/アプリケーションエンジニア +> 本ガイドのゴール: 4つのタスクを「なぜそうするのか」まで理解した上で、公式ドキュメントに基づいたベストプラクティスで完了できるようになること + +--- + +## 目次 + +1. [このラボの全体像](#このラボの全体像) +2. [ラボを始める前の準備](#ラボを始める前の準備) +3. [タスク1: Apps ScriptからBigQueryを呼び出しSheetsへ書き込む](#タスク1-apps-scriptからbigqueryを呼び出しsheetsへ書き込む) +4. [タスク2: BigQueryデータセットをGoogle Sheetsに接続する(Connected Sheets)](#タスク2-bigqueryデータセットをgoogle-sheetsに接続するconnected-sheets) +5. [タスク3: Google Chartsで可視化する](#タスク3-google-chartsで可視化する) +6. [タスク4: Apps Scriptで新規ワークシートを作成する](#タスク4-apps-scriptで新規ワークシートを作成する) +7. [全体のベストプラクティスまとめ](#全体のベストプラクティスまとめ) +8. [トラブルシューティング早見表](#トラブルシューティング早見表) +9. [参考文献](#参考文献) + +--- + +## このラボの全体像 + +このラボは「BigQuery Public Datasets」を題材に、**Apps Script(自動化・プログラム連携)**と**Connected Sheets(ノーコードでのデータ接続)**という2つの異なるアプローチでBigQueryのデータをGoogle Sheets上で扱う体験を通じて学ぶ構成になっています。 + +```mermaid +flowchart TB + subgraph T1["タスク1: Apps Script 連携"] + direction TB + A1["BigQuery Public Dataset
samples.shakespeare"] --> A2["Apps Script
BigQuery Advanced Service"] + A2 --> A3["新規スプレッドシート
(クエリ結果を書き込み)"] + end + + subgraph T2["タスク2: Connected Sheets 接続"] + direction TB + B1["BigQuery Public Dataset
chicago_taxi_trips"] --> B2["Google Sheets
Data Connectors"] + B2 --> B3["数式でのデータ分析
(会社数 / チップ率 / 件数)"] + end + + subgraph T3["タスク3: Google Charts 可視化"] + direction TB + C1["円グラフ
支払い方法の内訳"] + C2["折れ線グラフ
モバイル決済の推移"] + end + + subgraph T4["タスク4: 新規シート作成"] + direction TB + D1["Apps Script
SpreadsheetApp"] --> D2["セルA1に住所を入力"] + end + + B3 --> C1 + B3 --> C2 +``` + +この2系統を並べて経験することで、「プログラムで自動化すべき処理」と「ビジネスユーザーが自分で分析すべき処理」を使い分ける感覚を養うのがこのラボの狙いです。 + +--- + +## ラボを始める前の準備 + +| 項目 | ベストプラクティスと理由 | +|---|---| +| ブラウザウィンドウ | シークレット(プライベート)ウィンドウを使用する。個人アカウントのセッションとラボ用の一時アカウントが同じブラウザ内で混在すると、意図せず個人のGoogle Cloudアカウントに課金される事故につながるため | +| アカウント | ラボが払い出す一時的な学習用アカウントのみを使用する。個人アカウントでリソースを作成すると、ラボ終了後も課金が継続するリスクがある | +| タイマー | ラボは一時停止できないため、着手前にタスク1〜4を一通り読み、必要な作業時間を見積もっておく | +| プロジェクトID | 各タスクで払い出される一時プロジェクトのIDをメモしておく。Apps Scriptのコード内で`PROJECT_ID`として明示的に使用するため | + +--- + +## タスク1: Apps ScriptからBigQueryを呼び出しSheetsへ書き込む + +### 手順の流れ + +1. [script.google.com](https://script.google.com) で新しいApps Scriptプロジェクトを作成し、任意の名前を付ける +2. エディタの「サービス」から **BigQuery API** をアドバンストサービスとして追加する(この時点でCloud Platformプロジェクト側のBigQuery APIも有効化される)[2][3] +3. コードファイルを `bq-sheets.gs` にリネームし、クエリ実行用のスクリプトを実装する +4. `PROJECT_ID` に払い出されたプロジェクトIDを設定する +5. `runQuery()` を実行し、OAuth認可フローを承認する +6. 実行ログに出力される新規スプレッドシートのURLを開き、Shakespeare作品群の頻出単語トップ10が書き込まれていることを確認する + +### 処理フローの可視化 + +`runQuery()` がやっていることを分解すると、次のようなシーケンスになります。BigQueryのクエリジョブは**非同期**で実行されるため、「ジョブを投げる」→「完了をポーリングする」→「結果を取得する」という3段階の設計になっている点が最大のポイントです[5][6]。 + +```mermaid +sequenceDiagram + participant Dev as 開発者 + participant AS as Apps Script + participant BQ as BigQuery API + participant Sheet as Google Sheets + + Dev->>AS: runQuery() を実行 + AS->>BQ: Jobs.query(request, PROJECT_ID) + BQ-->>AS: jobReference.jobId を返却 + + loop ジョブ完了まで指数バックオフでポーリング + AS->>AS: jobComplete が true か確認 + AS->>AS: Utilities.sleep(sleepTimeMs) + AS->>AS: sleepTimeMs を2倍にする + AS->>BQ: Jobs.getQueryResults(jobId) + BQ-->>AS: 最新のジョブ状態を返却 + end + + loop pageToken が存在する間 + AS->>BQ: Jobs.getQueryResults(jobId, pageToken) + BQ-->>AS: 追加の rows を返却 + end + + AS->>Sheet: SpreadsheetApp.create(QUERY_NAME) + AS->>Sheet: appendRow(headers) / setValues(data) + Sheet-->>Dev: 新規スプレッドシートのURL +``` + +### コードの重要ポイント + +サンプルコード(Apache License 2.0で提供されている公式サンプル [3][17])の中でも、特に押さえておくべき箇所は次の2つです。 + +**(1) 指数バックオフによるジョブ完了待機** + +```javascript +var sleepTimeMs = 500; +while (!queryResults.jobComplete) { + Utilities.sleep(sleepTimeMs); + sleepTimeMs *= 2; + queryResults = BigQuery.Jobs.getQueryResults(PROJECT_ID, jobId); +} +``` + +これはGoogle Cloudが横断的に推奨している「指数バックオフ(Exponential Backoff)」パターンそのものです。一定間隔で即座にリトライするのではなく、待機時間を倍々に伸ばしながら再試行することで、サーバー側への負荷集中や同時多発的なリトライの衝突(thundering herd)を防ぎます[8][9]。 + +**(2) ページトークンによるページネーション** + +```javascript +while (queryResults.pageToken) { + queryResults = BigQuery.Jobs.getQueryResults(PROJECT_ID, jobId, { + pageToken: queryResults.pageToken + }); + rows = rows.concat(queryResults.rows); +} +``` + +BigQueryの結果セットは1回のレスポンスに収まらないことがあるため、`pageToken` が返ってくる限りループで取得し続ける必要があります。件数が少ないサンプルクエリでは意識しにくい処理ですが、本番データに対して同じコードを流用する際に必須になる実装です。 + +### 指数バックオフのロジック図 + +```mermaid +flowchart TD + Start(["クエリジョブを送信"]) --> Check{"jobComplete?"} + Check -->|"true"| Done["結果を取得してSheetsへ書き込み"] + Check -->|"false"| Sleep["Utilities.sleep(sleepTimeMs)"] + Sleep --> Double["sleepTimeMs = sleepTimeMs * 2"] + Double --> Poll["Jobs.getQueryResults() を再実行"] + Poll --> Check +``` + +### ベストプラクティス一覧 + +| 項目 | 推奨する対応 | 出典 | +|---|---|---| +| アドバンストサービスの有効化 | Apps Scriptエディタ側とCloud Console側の両方でBigQuery APIが有効になっていることを確認する | [2][4] | +| SQL方言 | サンプルコードは `[project:dataset.table]` 形式のレガシーSQLを使用しているが、新規に書くクエリは標準SQL(GoogleSQL)でバッククォート記法(`` `project.dataset.table` ``)に統一する。標準SQLはウィンドウ関数やDDL/DMLなど機能面でも優位 | [7] | +| ジョブのポーリング | 固定間隔リトライではなく指数バックオフを用いる。上限(max backoff)を設けて無限に待ち続けないようにする | [8][9] | +| ページネーション | `pageToken` の有無をチェックするループを省略しない | [5][6] | +| プロジェクトIDの管理 | ハードコードで動かす場合も、コードの先頭で `if (!PROJECT_ID) throw Error(...)` のような未設定チェックを入れる。恒久的な運用に載せる際はスクリプトプロパティ(Properties Service)などに切り出す | [3] | +| 権限 | クエリの実行のみが目的であれば、プロジェクト全体に強い権限を持たせず「BigQuery Job User」+対象データセットの「BigQuery Data Viewer」という最小権限の組み合わせを意識する | [10] | + +### よくあるエラーと対処 + +| エラー・症状 | 主な原因 | 対処 | +|---|---|---| +| `Exception: Service BigQuery API has not been enabled for your Apps` | アドバンストサービスの追加とCloud Platform側のAPI有効化のタイミングがずれている | エディタの「サービス」からBigQuery APIを一度削除し、再度追加し直す(ラボ本文にも明記されている既知の回避策) | +| `jobComplete` が `true` にならずタイムアウトする | クエリが重い、または `sleepTimeMs` の上限を設けずに無限ループになっている | 最大待機時間・最大リトライ回数を設け、超えたらエラーとしてログ出力する設計にする[8][9] | +| 実行時に権限エラー(403) | 実行アカウントに対象プロジェクトへのBigQueryアクセス権が不足 | 「BigQuery Job User」ロールがプロジェクトに付与されているか確認する[10] | + +--- + +## タスク2: BigQueryデータセットをGoogle Sheetsに接続する(Connected Sheets) + +### 手順の流れ + +1. Google Sheetsのホーム画面から新しい空白のスプレッドシートを作成する +2. メニューの「データ」→「データコネクタ」→「BigQueryに接続」を選択する +3. 課金が有効なプロジェクトを選び、「公開データセット」から `chicago_taxi_trips` を検索する +4. `taxi_trips` テーブルを選択し「接続」をクリックする +5. 接続後に表示されるプレビューシート上で、ピボットテーブルや数式を使って分析を行う + +### アーキテクチャ + +Connected Sheetsは、Sheets上のデータをBigQueryにコピーするのではなく、**必要な範囲だけをその都度BigQueryへ問い合わせる**アーキテクチャです。プレビューには先頭500行のみが表示されますが、ピボットテーブルや数式、グラフは接続先の全データに対して実行されます[11]。 + +```mermaid +flowchart LR + subgraph GCP["Google Cloud"] + BQ["BigQuery
chicago_taxi_trips.taxi_trips"] + end + + subgraph GS["Google Sheets"] + DS["DATA_SOURCE シート
(プレビューは先頭500行)"] + PIVOT["ピボットテーブル"] + FORMULA["数式
(COUNTIF / SUM など)"] + CHART["Google Charts"] + end + + BQ -- "データコネクタ
OAuthスコープ: bigquery.readonly" --> DS + DS --> PIVOT + DS --> FORMULA + PIVOT --> CHART + FORMULA --> CHART +``` + +### 数式によるデータ分析の考え方 + +`taxi_trips` テーブルには `company`(配車会社名)、`tips`(チップ額)、`fare`(運賃)などの列が含まれています[18]。3つの設問は、いずれも列単位の集計関数で解けるように設計されています。 + +| 分析したいこと | 考え方 | 使う関数の例 | +|---|---|---| +| タクシー会社の数 | `company` 列に含まれるユニークな値の数を数える | `COUNTA` と `UNIQUE` の組み合わせ、またはピボットテーブルで `company` を行にドラッグし件数を確認 | +| チップがあった配車の割合 | `tips` 列が0より大きい行数を、全体の行数で割る | `COUNTIF(tips範囲, ">0") / COUNTA(tips範囲)` | +| 運賃が0より大きい配車の総数 | `fare` 列が0より大きい行だけを数える | `COUNTIF(fare範囲, ">0")` | + +> ⚠️ 実際のセル参照や列位置は接続後のプレビューシートの構成に依存するため、上表は「どの関数を組み合わせるか」という考え方のガイドとして使ってください。ピボットテーブルの「値」に集計方法(合計・カウント・個別カウント)を指定するだけでも同等の答えが得られます。 + +### ベストプラクティス一覧 + +| 項目 | 推奨する対応 | 出典 | +|---|---|---| +| プレビュー行数の理解 | 画面に表示されるのは先頭500行だが、数式・ピボット・グラフは全データに対して実行される点を理解した上で分析する | [11] | +| 集計方法の選択 | 単純な合計・件数はピボットテーブル、条件付きの計算は数式(`COUNTIF`等)と使い分ける | [11][12] | +| データの更新 | 元データが変わりうる分析では、手動更新のほかにスケジュール更新(定期リフレッシュ)の設定も検討する | [11][12] | +| SQLを書かずに分析 | Connected SheetsはSQLの知識がなくても大規模データにアクセスできる点が価値。まずは使い慣れたSheetsの関数・ピボットで試すのが推奨アプローチ | [13][12] | +| 権限 | Connected Sheetsは `bigquery.readonly` スコープでBigQueryにアクセスする。読み取り専用であることを意識し、接続先データセットへの最小権限(Data Viewer)で運用する | [16][10] | + +--- + +## タスク3: Google Chartsで可視化する + +### 手順の流れ + +1. Connected Sheets接続済みのシート上で「挿入」→「グラフ」を選択する +2. 支払い方法(`payment_type`)の内訳を **円グラフ** で可視化する +3. モバイル決済(mobile)の売上推移を **折れ線グラフ** で可視化する +4. 2015年にピークを迎えた後の推移だけを見たい場合は、グラフの期間フィルタや軸の範囲を絞り込む + +### グラフタイプの選び方 + +| 分析の目的 | 適したグラフ | 理由 | +|---|---|---| +| ある時点における内訳(構成比)を見たい | 円グラフ | 支払い方法ごとの割合など、全体に対する構成比の把握に向く | +| 時間の経過に伴う変化・トレンドを見たい | 折れ線グラフ | 売上や件数の推移、ピークの検出、期間比較に向く | +| 特定期間だけを深掘りしたい | 折れ線グラフ+軸の範囲指定 | ピーク(2015年)以降のみに絞ることで、その後の減少・回復傾向を読み取りやすくする | + +Google Sheets上でグラフを作成した後は、元データが更新された場合に「グラフを更新」ボタンでBigQuery側の最新データを反映できます[15]。ダッシュボードのように毎週参照するグラフであれば、この更新導線をチームに共有しておくと運用がスムーズです。 + +### ベストプラクティス一覧 + +| 項目 | 推奨する対応 | 出典 | +|---|---|---| +| グラフの選定 | 「構成比を見たいのか」「推移を見たいのか」を先に決めてからグラフ種別を選ぶ | [15] | +| 更新性 | Connected Sheets上のグラフは「更新」操作でBigQueryの最新データを反映できることをチームに周知する | [15] | +| 期間の絞り込み | ピーク検出後は、軸範囲やフィルタで期間を絞った別ビューを作ると変化が読み取りやすい | [15] | + +--- + +## タスク4: Apps Scriptで新規ワークシートを作成する + +### 手順の流れ + +1. Google Sheetsを開き、新しい空白のスプレッドシートを作成する +2. 左上のセルA1(1行目・A列)をクリックする +3. `76 9th Ave, New York` という住所文字列を入力する + +### なぜここでApps Scriptの組み込みサービスが登場するのか + +タスク1では「BigQuery API」というアドバンストサービス(明示的な有効化が必要)を扱いましたが、タスク4のような「新しいシートを作る」「セルに値を入れる」といった操作は `SpreadsheetApp` という**組み込みサービス**だけで完結します。両者の違いを理解しておくと、今後どちらを使うべきか迷わなくなります[2]。 + +| 観点 | 組み込みサービス(例: `SpreadsheetApp`) | アドバンストサービス(例: `BigQuery`) | +|---|---|---| +| 有効化 | 不要(最初から利用可能) | 明示的な有効化が必要(Apps Script側+Cloud Console側) | +| 対象 | Google Workspaceの主要サービス(Sheets, Docs, Gmail等) | BigQueryなど外部APIへの薄いラッパー | +| 認可 | 自動的なOAuth認可フロー | 同様に自動化されているが、対応するCloud APIの有効化が前提 | +| 典型的な用途 | シート作成、値の書き込み、書式設定 | 大規模データへのクエリ、外部システム連携 | + +### ベストプラクティス一覧 + +| 項目 | 推奨する対応 | 出典 | +|---|---|---| +| サービスの使い分け | Workspace内で完結する操作は組み込みサービス、外部Google CloudのAPIを叩く操作はアドバンストサービス、という判断軸を持つ | [2] | +| セル入力の自動化 | 手動入力で済むタスクでも、繰り返し発生する住所入力などはこの後 `sheet.getRange("A1").setValue(address)` のようにコード化しておくと再現性が高まる | [3] | + +--- + +## 全体のベストプラクティスまとめ + +4つのタスクを横断して意識すべき観点を、テーマ別に整理します。 + +| テーマ | ベストプラクティス | 出典 | +|---|---|---| +| セキュリティ(最小権限の原則) | クエリ実行には「BigQuery Job User」、データ閲覧には対象データセットの「BigQuery Data Viewer」というように、コンピュート権限とデータ権限を分けて最小限だけ付与する | [10] | +| 信頼性 | 非同期ジョブに対しては指数バックオフでポーリングし、結果はページトークンを使い切るまで取得する | [8][9][5] | +| 保守性 | 新規に書くクエリはレガシーSQLではなく標準SQL(GoogleSQL)に統一する | [7] | +| コスト意識 | 大規模データセットに対するクエリはスキャン量が課金に直結するため、必要な列だけを `SELECT` し、`LIMIT` を活用する | [6] | +| ノーコード活用 | プログラムを書かずに済む定型的な分析(会社数の集計、割合の算出など)はConnected Sheets、繰り返し実行・自動化したい処理はApps Scriptという住み分けを意識する | [11][3] | + +--- + +## トラブルシューティング早見表 + +| タスク | 症状 | 主な原因 | 対処 | +|---|---|---|---| +| タスク1 | BigQuery APIが有効化されていないというエラー | アドバンストサービスの追加処理が不完全 | サービスを一度削除し再追加する | +| タスク1 | クエリ結果が0件、または想定と異なる集計になる | レガシーSQLとGoogleSQLで `,` や識別子の扱いが異なる | クエリ全体をどちらか一方の方言に統一する[7] | +| タスク2 | 「データコネクタ」メニューが表示されない | 組織のポリシーやエディション、事前設定の不足 | 公式ヘルプの「開始する前に」の項目を確認する[13] | +| タスク2 | 数式の結果が想定と合わない | プレビューの500行だけを見て検算してしまっている | 数式・ピボットは全データに対して実行される前提で結果を確認する[11] | +| タスク3 | グラフが最新のBigQueryデータを反映していない | 手動更新が行われていない | グラフ下部の「更新」を実行する[15] | + +--- + +## 参考文献 + +1. Google Cloud Skills Boost 元ラボページ — https://www.skills.google/course_templates/737/labs/607137 +2. Advanced Google services(Apps Script アドバンストサービスの有効化) — https://developers.google.com/apps-script/guides/services/advanced +3. BigQuery Service(Apps Script BigQueryアドバンストサービス リファレンス) — https://developers.google.com/apps-script/advanced/bigquery +4. Manage BigQuery API dependencies(BigQuery APIの依存関係管理) — https://docs.cloud.google.com/bigquery/docs/service-dependencies +5. Running jobs programmatically(BigQueryジョブのプログラムからの実行) — https://docs.cloud.google.com/bigquery/docs/running-jobs +6. Run a query(`jobs.query` / `jobs.insert` の使い分け) — https://docs.cloud.google.com/bigquery/docs/running-queries +7. Migrating to GoogleSQL(レガシーSQLから標準SQLへの移行) — https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/migrating-from-legacy-sql +8. Retry strategy(Cloud Storageにおける指数バックオフの解説) — https://docs.cloud.google.com/storage/docs/retry-strategy +9. Exponential backoff(Memorystore for Redis:バックオフアルゴリズムの定義) — https://docs.cloud.google.com/memorystore/docs/redis/exponential-backoff +10. Troubleshoot IAM permissions in BigQuery(最小権限の原則の適用方法) — https://docs.cloud.google.com/bigquery/docs/troubleshoot-access-control +11. Using Connected Sheets(BigQuery公式ドキュメント) — https://docs.cloud.google.com/bigquery/docs/connected-sheets +12. Using Connected Sheets to analyze BigQuery data(Google Cloud Blog) — https://cloud.google.com/blog/products/data-analytics/using-connected-sheets-to-analyze-bigquery-data +13. Get started with BigQuery data in Google Sheets(Google Docsエディタ ヘルプ) — https://support.google.com/docs/answer/9702507?hl=en +14. Use Connected Sheets(Apps Script) — https://developers.google.com/apps-script/guides/sheets/connected-sheets +15. Analyze & refresh BigQuery data in Google Sheets using Connected Sheets(グラフの更新方法) — https://support.google.com/docs/answer/9703214?hl=en +16. Connected Sheets(Google Sheets API ガイド、Shakespeareデータセットの例) — https://developers.google.com/workspace/sheets/api/guides/connected-sheets +17. Turn your big data into insights using Google Sheets and Slides(Codelab、サンプルコードの出典) — https://codelabs.developers.google.com/codelabs/bigquery-sheets-slides/ +18. Chicago Taxi Trips(データセットの詳細ページ、列定義) — https://cloud.google.com/bigquery/public-data/chicago-taxi diff --git a/Bigquery-covid19-challenge-lab-guide.html b/Bigquery-covid19-challenge-lab-guide.html new file mode 100644 index 000000000..c808a6c41 --- /dev/null +++ b/Bigquery-covid19-challenge-lab-guide.html @@ -0,0 +1,2065 @@ + + + + + + BigQueryで学ぶCOVID-19データ分析 | Challenge Lab 完全攻略ガイド + + + + + + + +
+ + +
+
+
+
+ 初学者向けステップバイステップ解説 +
+

+ BigQueryで学ぶ
COVID-19データ分析
チャレンジラボ + 完全攻略ガイド +

+

+ 公開データセット + bigquery-public-data.covid19_open_data.covid19_open_data + に対する10個のSQLタスクを、集計・ウィンドウ関数・CTEの実務的な考え方から解説します。 +

+
+ GoogleSQL + BigQuery + CTE / WINDOW + Looker Studio +
+ 対象ラボ: Derive Insights from + BigQuery Data +
+
+
10 Tasks Overview
+
+ 01世界の確定症例数の合計 +
+
+
+ 02被害が大きい地域の特定 +
+
+
+ 03ホットスポットの一覧化 +
+
+
+ 04致死率の計算 +
+
+
+ 05しきい値を超えた日の特定 +
+
+
+ 06壊れたクエリの修正(CTE) +
+
+
+ 07倍加速度の算出 +
+
+
+ 08回復率ランキング +
+
+
+ 09壊れたクエリの修正(CDGR) +
+
+
+ 10Looker Studioレポート +
+
+
+ +
+ +
+

+ このラボは値がランダム化されます。 + Date・Death Count・Confirmed Cases・Month・Limit Value + は受講者ごとに異なる数値で出題されます。本ガイドのコード中の + <Date> + のような表記は、自分のラボ画面に表示された実際の値に置き換えてください。 +

+
+
+ + +
+
STEP 0
+

ラボ全体の流れをつかむ

+

+ 個別のSQLに入る前に、まず全体の流れを図で押さえます。BigQueryコンソールを開いてからLooker + Studioでレポートを完成させるまでの一連の流れです。 +

+
+ 図を読み込み中です… +
+

+ 「Check my + progressで採点する」は自動採点システムによる判定です。不合格の場合は該当タスクのSQLを見直して再実行します。 +

+
+ + +
+
STEP 1
+

データセットの構造を理解する(最重要の前提知識)

+

+ 10個のタスクすべてに共通して関わる構造上の注意点です。ここを理解しないままクエリを書くと、一見正しく動くのに集計結果が水増しされる、という事故が起きやすくなります。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
カラム名意味補足
dateその行のデータの対象日DATE型
country_code国コード(例: US)ISO準拠のコード
country_name国名(例: United States of America)表記ゆれに注意
subregion1_name州・省などの地域名国レベルの行ではNULL
cumulative_confirmedその日までの累積確定症例数「新規」ではなく「累積」
cumulative_deceasedその日までの累積死亡者数同上
cumulative_recoveredその日までの累積回復者数同上
+ +
+ +
+

+ なぜ単純にSUM(cumulative_confirmed)だけでは危険なのか。このテーブルは「国レベルの行」「州レベルの行」「郡レベルの行」を同じ1つのフラットなテーブルの中に混在させて格納しています。行を親子関係でネストしているのではなく、粒度の異なる行が並列に並んでいる、という点がポイントです。 +

+
+
+ +
+ 図を読み込み中です… +
+

+ 矢印は「親から子」のリレーションではなく、同じ日付・同じ国に対して粒度違いの行が複数存在することを表しています。 +

+ +

+ 例えばアメリカの場合、country_name = "United States of America"という条件だけで絞り込むと、国全体の1行・50州分の行・数千の郡の行がすべて同時にヒットします。この挙動はデータセット提供元の公式リポジトリでも明記されており、subregion1_codeがNULLであれば国レベル、値が入っていれば州レベルの集計であるとされています(GoogleCloudPlatform/covid-19-open-data README + )。 +

+ +
+ +
+

+ 実務での回避策:地域別に集計したいときは、必ずsubregion1_name IS NOT NULL(州レベルだけ)やsubregion1_name IS NULL AND subregion2_name IS NULL(国レベルだけ)のように、対象の粒度を明示的にWHERE句で絞り込みます。ただしタスク1のように「日付だけで単純にSUMする」ことが公式の想定解になっているタスクもあり、これは採点システムの期待値がその単純な合計に合わせて作られているためです。 +

+
+
+ +
+ +
+ このセクションで登場した用語 +
+
累積値
+
+ ある時点までの合計。前日までの値に当日分を足し込んだ値で、「その日単体の新規件数」ではない +
+
集計レベル
+
+ データがどの地理的粒度(国・州・郡)で集計されているかを表す区分 +
+
+
+
+
+ + +
+
STEP 2
+

全タスクに共通するベストプラクティス

+

+ 個別タスクに入る前に、10個のタスクを通して繰り返し使うSQLパターンをまとめます。 +

+ +

GoogleSQLダイアレクトを使う

+

+ BigQueryのクエリエディタは既定でGoogleSQL(旧称: Standard + SQL)です。古い記事の[project:dataset.table]という角カッコ表記(Legacy + SQL)ではなく、本ガイドはすべて`project.dataset.table`形式のGoogleSQLで統一しています(GoogleSQLの名称について )。 +

+ +

集計後の値で絞り込みたいときはHAVINGを使う

+
+ +
+

+ なぜ単純なWHERE 集計結果 > 100では解けないのか。WHERE句はグループ化(GROUP BY)が行われる前の生の行に対して評価されます。SUM(...)のような集計結果はグループ化が終わった後に初めて存在する値なので、WHEREの中では参照できません。 +

+
+
+ + + + + + + + + + + + + + + + + + + + +
方法書き方向いている場面
HAVINGを使うGROUP BY 列 HAVING 集計結果 > 100集計とその後の絞り込みだけで完結する場合
サブクエリ / CTEで包む内側で集計し外側のWHEREで絞り込む絞り込んだ後にさらに計算を続ける場合
+ +

前日比較にはウィンドウ関数(LAG)を使う

+
+ +
+

+ なぜ自己結合では非効率なのか。日付テーブルを自分自身とJOINして「1日前のレコード」を探す書き方もできますが、自己結合は出力行数が膨らみやすくパフォーマンスの問題を起こしやすいとBigQueryの公式ドキュメントでも指摘されています。同じ目的はLAGなどのウィンドウ関数で書き直すことが推奨されます(BigQueryクエリプランの解説 + )。 +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
関数やること使うべき場面
LAG(値) OVER (ORDER BY 日付)1つ前の行の値を取得する前日比・前月比などの差分計算(タスク6・7)
LEAD(値) OVER (ORDER BY 日付)1つ後の行の値を取得する未来方向の値を同じ行に並べる(タスク9)
ROW_NUMBER() OVER (...)重複のない連番を振る同率を区別して上位N件だけ取りたいとき
RANK() / DENSE_RANK() OVER (...)同率に同じ順位を振るランキングで同率を同じ順位に見せたいとき
+

+ LAGやLEADはいずれもOVER句とセットでなければ使えません。書き忘れるとエラーになる点は、後述するタスク9のデバッグで重要になります(ナビゲーション関数リファレンス )。 +

+ +

割り算にはSAFE_DIVIDEを検討する

+

+ 致死率・回復率・増加率のような割り算では、分母が0になるとエラーで処理全体が止まります。SAFE_DIVIDE(分子, 分母)は分母が0のときエラーではなくNULLを返してくれます(数学関数リファレンス )。本ガイドでは割り算を行うすべてのタスクでこの関数を使います。 +

+ +
+ +
+ このセクションで登場した用語 +
+
ウィンドウ関数
+
+ 行を1行に集約する集計関数とは違い、各行を保ったまま前後の値などを計算できる関数 +
+
OVER句
+
ウィンドウ関数がどの範囲・並び順で計算するかを指定する句
+
SAFE_DIVIDE
+
+ ゼロ除算が起きてもエラーにせずNULLを返す安全な割り算関数 +
+
+
+
+
+ + +
+
+ 01 +

全世界の確定症例数の合計

+
+
+ 指定した日付における、全世界のcumulative_confirmedを1行に集計するクエリです。 +
+ +
+
task1.sql
+
SELECT
+  SUM(cumulative_confirmed) AS total_cases_worldwide
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  date = "<Date>"
+  -- date は "YYYY-MM-DD" 形式の文字列リテラルとして渡す
+
+ +

+ 処理の流れ: + WHERE date = "<Date>"で対象日の行だけに絞り込み、全行のcumulative_confirmedをSUMで合計します。 +

+ +
+ +
+

+ 実務コラム:Step + 1で説明した通り、このWHERE句には地域粒度の絞り込みが入っていないため、国・州・郡レベルの行がすべて合算されます。ラボの採点はこの単純な合計を期待値としているため、このまま提出して問題ありません。自社のダッシュボードなど実務でこのデータセットを使う場合は、subregion1_name IS NULL AND subregion2_name IS NULLを加えて国レベルの行だけに絞り込むほうが安全です。 +

+
+
+
+ + +
+
+ 02 +

被害が大きい地域を特定する

+
+
+ アメリカ国内で、指定した死亡者数を超えた州がいくつあるかを数えるクエリです。 +
+ +
+
task2.sql
+
SELECT
+  COUNT(*) AS count_of_states
+FROM (
+  SELECT
+    subregion1_name AS state,
+    SUM(cumulative_deceased) AS death_count
+  FROM
+    `bigquery-public-data.covid19_open_data.covid19_open_data`
+  WHERE
+    country_name = "United States of America"
+    AND date = "<Date>"
+    AND subregion1_name IS NOT NULL  -- 国全体の1行を除外する
+  GROUP BY
+    subregion1_name
+)
+WHERE
+  death_count > <Death Count>
+
+ +

処理の流れ:

+
    +
  1. + 内側のサブクエリで州ごとにcumulative_deceasedを集計しdeath_countを作る +
  2. +
  3. + subregion1_name IS NOT NULLで国レベルの合計行を弾く。忘れると「51件目の州」として国全体の行が混ざり件数がずれる +
  4. +
  5. + 外側のWHERE death_count > <Death Count>でしきい値を超えた州だけを残す +
  6. +
  7. COUNT(*)で残った州の件数を数える
  8. +
+ +
+ +
+

+ country_code = "US"ではなくcountry_name = "United States of America"を条件に使っています。どちらのカラムで絞り込んでいるかは常に意識しておくとよい習慣です。 +

+
+
+
+ + +
+
+ 03 +

ホットスポットを特定する

+
+
+ アメリカ国内で、指定した確定症例数を超えた州を、症例数が多い順に一覧表示するクエリです。 +
+ +
+
task3.sql
+
SELECT
+  subregion1_name AS state,
+  SUM(cumulative_confirmed) AS total_confirmed_cases
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  country_code = "US"
+  AND date = "<Date>"
+  AND subregion1_name IS NOT NULL
+GROUP BY
+  subregion1_name
+HAVING
+  total_confirmed_cases > <Confirmed Cases>
+ORDER BY
+  total_confirmed_cases DESC
+
+ +
+ +
+

+ タスク2ではサブクエリ、タスク3ではHAVINGを使いました。どちらも「集計後の値で絞り込む」という同じ目的のための書き方の違いです。今回はこの後さらに計算を続けないので、HAVINGのほうが行数の少ないシンプルな書き方になります。 +

+
+
+
+ + +
+
+ 04 +

致死率(Case-Fatality Ratio)を計算する

+
+
+ イタリアの指定した月について、(累積死亡者数 ÷ + 累積確定症例数)× 100 を計算するクエリです。 +
+ +
+
task4.sql
+
SELECT
+  SUM(cumulative_confirmed) AS total_confirmed_cases,
+  SUM(cumulative_deceased) AS total_deaths,
+  SAFE_DIVIDE(SUM(cumulative_deceased), SUM(cumulative_confirmed)) * 100 AS case_fatality_ratio
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  country_name = "Italy"
+  AND date BETWEEN "<Monthの初日, 例: 2020-04-01>" AND "<Monthの末日, 例: 2020-04-30>"
+
+ +
+ +
+

+ 月末日を手で数える手間を減らしたい場合:閏年の2月など月末日を間違えやすいケースがあります。EXTRACT(YEAR FROM date)とEXTRACT(MONTH FROM date)で絞り込めば、月末日を意識せずに書けます。 +

+
+
+ +
+
+ task4_alt.sql(EXTRACTを使う代替案) +
+
SELECT
+  SUM(cumulative_confirmed) AS total_confirmed_cases,
+  SUM(cumulative_deceased) AS total_deaths,
+  SAFE_DIVIDE(SUM(cumulative_deceased), SUM(cumulative_confirmed)) * 100 AS case_fatality_ratio
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  country_name = "Italy"
+  AND EXTRACT(YEAR FROM date) = <対象年, 例: 2020>
+  AND EXTRACT(MONTH FROM date) = <対象月の数字, 例: 4>
+
+ +
+ +
+ このセクションで登場した用語 +
+
EXTRACT
+
日付や時刻の値から年・月・日などの一部だけを取り出す関数
+
+
+
+
+ + +
+
+ 05 +

しきい値を超えた特定の日を探す

+
+
+ イタリアの累積死亡者数が、指定したしきい値を初めて超えた日付を1件だけ返すクエリです。 +
+ +
+
task5.sql
+
SELECT
+  date
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  country_name = "Italy"
+  AND cumulative_deceased > <Death Count>
+ORDER BY
+  date ASC
+LIMIT 1
+
+ +
+ +
+

+ なぜLIMIT 1だけで安全に「最初の日」が取れるのか。cumulative_deceasedは累積値なので、通常は日付が進むにつれて単調に増加(または横ばい)します。しきい値を超えた日付を昇順に並べて先頭を取れば、それが最初に超えた日になります。ただし実データでは、報告方法の見直しなどにより過去の値が下方修正され、累積値が一時的に前日を下回るケースもゼロではありません。 +

+
+
+ +
+ +
+ このセクションで登場した用語 +
+
単調増加
+
+ 値が時間とともに減ることなく、増える・または変わらない、を繰り返す性質 +
+
+
+
+
+ + +
+
+ 06 +

新規症例数がゼロだった日を数える(壊れたクエリの修正)

+
+
+ インドで、前日から確定症例数が増えなかった日が何日あったかを数えます。ただし、お題として渡されたクエリはそのままでは実行できません。 +
+ +
+ +
+

+ 壊れたクエリのどこが問題か。お題のSQLはindia_previous_day_comparisonというCTEを作るところまでしか書かれておらず、そのCTEを実際に読み出す外側のSELECT文がありません。CTEは定義しただけでは何も返さないため、このままではクエリが完結しません。加えてdate between '' and ''も空文字列のままなので、実際の日付に差し替える必要があります。 +

+
+
+ +
+
+ task6_fixed.sql +
+
WITH india_cases_by_date AS (
+  SELECT
+    date,
+    SUM(cumulative_confirmed) AS cases
+  FROM
+    `bigquery-public-data.covid19_open_data.covid19_open_data`
+  WHERE
+    country_name = "India"
+    AND date BETWEEN "<Start date>" AND "<Close date>"
+  GROUP BY
+    date
+  ORDER BY
+    date ASC
+)
+
+, india_previous_day_comparison AS (
+  SELECT
+    date,
+    cases,
+    LAG(cases) OVER (ORDER BY date) AS previous_day,
+    cases - LAG(cases) OVER (ORDER BY date) AS net_new_cases
+  FROM
+    india_cases_by_date
+)
+
+SELECT
+  COUNT(date) AS days_with_zero_net_new_cases
+FROM
+  india_previous_day_comparison
+WHERE
+  net_new_cases = 0
+
+ +

+ このクエリは「CTEを段階的につないでいく」という、この後のタスク7・9でも繰り返し使うパターンの基本形です。下の図はそのパイプラインの流れを一般化したものです。 +

+
+ 図を読み込み中です… +
+ +
+ +
+

+ LAGは先頭の行(対象期間の一番古い日)では「1つ前の行」が存在しないためNULLを返します。NULL - 数値の計算結果もNULLになるため、net_new_cases = 0の判定には引っかからず、エラーにもならずに自然に除外されます。 +

+
+
+ +
+ +
+ このセクションで登場した用語 +
+
CTE(共通テーブル式)
+
+ WITH 名前 AS (...)の形でクエリの中に一時的な名前付きテーブルを定義する仕組み。複雑な計算を段階に分けて書けるため可読性が上がる +
+
+
+
+
+ + +
+
+ 07 +

倍加速度(Doubling Rate)を調べる

+
+
+ アメリカで、指定した期間中に前日比で一定パーセント以上増えた日を一覧にします。タスク6のCTEパイプラインに、増加率の計算を1段追加します。 +
+ +
+
task7.sql
+
WITH us_cases_by_date AS (
+  SELECT
+    date,
+    SUM(cumulative_confirmed) AS cases
+  FROM
+    `bigquery-public-data.covid19_open_data.covid19_open_data`
+  WHERE
+    country_name = "United States of America"
+    AND date BETWEEN "2020-03-22" AND "2020-04-20"
+  GROUP BY
+    date
+  ORDER BY
+    date ASC
+)
+
+, us_previous_day_comparison AS (
+  SELECT
+    date,
+    cases,
+    LAG(cases) OVER (ORDER BY date) AS previous_day,
+    SAFE_DIVIDE(
+      cases - LAG(cases) OVER (ORDER BY date),
+      LAG(cases) OVER (ORDER BY date)
+    ) * 100 AS percentage_increase
+  FROM
+    us_cases_by_date
+)
+
+SELECT
+  date AS Date,
+  cases AS Confirmed_Cases_On_Day,
+  previous_day AS Confirmed_Cases_Previous_Day,
+  percentage_increase AS Percentage_Increase_In_Cases
+FROM
+  us_previous_day_comparison
+WHERE
+  percentage_increase > <Limit Value>
+
+ +

+ 処理の流れは前掲の「CTEパイプライン」の図とほぼ同じです。違いは2段目のCTEで、引き算だけでなく割り算してパーセントに換算する計算を加えている点です。 +

+ +
+ +
+

+ ここでSAFE_DIVIDEを使う理由:パンデミック初期の日付を対象期間に含めると、前日の累積症例数が実際に0件というケースがあり得ます。通常の/演算子だとゼロ除算でクエリがエラーになりますが、SAFE_DIVIDEならエラーにならず、その行の値がNULLになるだけで処理が続行されます。 +

+
+
+
+ + +
+
+ 08 +

回復率(Recovery Rate)ランキングを作る

+
+
+ 指定した日付時点で、確定症例数が5万件を超える国だけを対象に、回復率が高い順に上位いくつかを表示します。 +
+ +
+
task8.sql
+
WITH cases_by_country AS (
+  SELECT
+    country_name AS country,
+    SUM(cumulative_confirmed) AS confirmed_cases,
+    SUM(cumulative_recovered) AS recovered_cases
+  FROM
+    `bigquery-public-data.covid19_open_data.covid19_open_data`
+  WHERE
+    date = "2020-05-10"
+  GROUP BY
+    country_name
+)
+
+SELECT
+  country,
+  recovered_cases,
+  confirmed_cases,
+  SAFE_DIVIDE(recovered_cases, confirmed_cases) * 100 AS recovery_rate
+FROM
+  cases_by_country
+WHERE
+  confirmed_cases > 50000
+ORDER BY
+  recovery_rate DESC
+LIMIT <Limit Value>
+
+ +
+ +
+

+ ORDER BYの前にWHERE confirmed_cases > 50000を適用することで、症例数が少ないのに回復率だけ100%に近いような小規模な国がランキング上位に紛れ込むのを防いでいます。この順序(先に絞り込み、後で並べ替え)は、集計を伴うランキングクエリで繰り返し使えるパターンです。 +

+
+
+
+ + +
+
+ 09 +

CDGR(累積日次成長率)を計算する(壊れたクエリの修正)

+
+
+ フランスで最初の症例が報告された日から指定した日までの、1日あたりの複利的な増加率(CDGR)を計算します。お題のクエリには3か所の不具合があります。 +
+ +
+
CDGRの定義
+
CDGR = (最終日の症例数 / 初日の症例数) ^ (1 / 経過日数) - 1
+
+ +

+ 下の図は、お題のクエリに含まれる3つの不具合と、その修正内容を順番に示したものです。 +

+
+ 図を読み込み中です… +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
不具合内容修正
1. 構文エラーLEAD(total_cases)にOVER句がないLEAD(total_cases) OVER (ORDER BY date)に修正
2. 未入力の値date IN ('2020-01-24', '')の2番目が空文字最終日の日付リテラルを補完
3. 関数の選び間違いSQRTは引数を1つしか取らないべき乗計算には2引数のPOWER(底, 指数)を使う
+ +

+ 不具合1は、Step + 2で説明した「ウィンドウ関数は必ずOVER句とセットで書く」というルール違反です(ナビゲーション関数リファレンス )。不具合3は、平方根と累乗という別の演算を混同したものです(数学関数リファレンス )。 +

+ +
+
+ task9_fixed.sql +
+
WITH france_cases AS (
+  SELECT
+    date,
+    SUM(cumulative_confirmed) AS total_cases
+  FROM
+    `bigquery-public-data.covid19_open_data.covid19_open_data`
+  WHERE
+    country_name = "France"
+    AND date IN ("2020-01-24", "<最終日, 例: 2020-05-10>")
+  GROUP BY
+    date
+  ORDER BY
+    date
+)
+
+, summary AS (
+  SELECT
+    total_cases AS first_day_cases,
+    LEAD(total_cases) OVER (ORDER BY date) AS last_day_cases,
+    DATE_DIFF(LEAD(date) OVER (ORDER BY date), date, DAY) AS days_diff
+  FROM
+    france_cases
+  LIMIT 1
+)
+
+SELECT
+  first_day_cases,
+  last_day_cases,
+  days_diff,
+  POWER(SAFE_DIVIDE(last_day_cases, first_day_cases), SAFE_DIVIDE(1, days_diff)) - 1 AS cdgr
+FROM
+  summary
+
+ +

処理の流れ:

+
    +
  1. france_casesCTEで、初日と最終日、2行だけを取り出す
  2. +
  3. + summaryCTEで、LEADを使って「1行目に初日、2行目に最終日」という2行を1行にまとめ、DATE_DIFFで経過日数も同じ行に並べる +
  4. +
  5. LIMIT 1で、まとめ終わった1行だけを残す
  6. +
  7. 最後のSELECTでPOWERを使ってCDGRを計算する
  8. +
+ +
+ +
+ このセクションで登場した用語 +
+
DATE_DIFF
+
+ 2つの日付の間の日数を返す関数(日付関数リファレンス) +
+
POWER(底, 指数)
+
底を指数乗した値を返す関数
+
+
+
+
+ + +
+
+ 10 +

Looker Studioでレポートを作成する

+
+
+ BigQueryのカスタムクエリをLooker Studio(旧Data + Studio)に接続し、アメリカの確定症例数と死亡者数の時系列グラフを作ります。 +
+ +
+
+ task10.sql +
+
SELECT
+  date,
+  SUM(cumulative_confirmed) AS country_cases,
+  SUM(cumulative_deceased) AS country_deaths
+FROM
+  `bigquery-public-data.covid19_open_data.covid19_open_data`
+WHERE
+  country_name = "United States of America"
+  AND date BETWEEN "<Date Rangeの開始日>" AND "<Date Rangeの終了日>"
+GROUP BY
+  date
+ORDER BY
+  date
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
手順操作内容
1 + BigQueryのクエリエディタで上記クエリを実行し、正しく結果が返ることを確認する +
2 + 結果画面から「Explore Data」→「Explore with Looker + Studio」(手順書では「Explore with Data Studio」)を選ぶ +
3Looker StudioにBigQueryへのアクセスを許可(承認)する
4 + 初回ログイン時に失敗する場合は「空のレポート」を作成して利用規約に同意したうえで、BigQuery側から改めて接続し直す +
5 + レポート編集画面で「グラフを追加」から「時系列グラフ」を選ぶ +
6 + 指標(Metric)にcountry_casesとcountry_deathsの両方を追加する +
7「保存」をクリックして変更を確定する
+ +
+ +
+

+ ラボの指示では「BigQueryのExplore with Data + Studioオプションは使わないこと」となっている場合があります。必ず自分のラボの手順書の指示を優先してください(Looker StudioとBigQueryの接続 公式ドキュメント + )。 +

+
+
+
+ + +
+
SUMMARY
+

提出前チェックリスト

+
    +
  • + クエリ中のすべての<プレースホルダー>を、自分のラボ画面に表示された実際の値に置き換えたか +
  • +
  • + country_nameとcountry_codeのどちらで絞り込むべきタスクか、混同していないか +
  • +
  • + 州・郡レベルの集計をするタスクでsubregion1_name IS NOT NULLの絞り込みを入れたか +
  • +
  • + 集計後の値を条件にするときはWHEREではなくHAVINGかサブクエリ/CTEを使っているか +
  • +
  • + LAG / + LEADにOVER (ORDER BY ...)を付け忘れていないか +
  • +
  • + 割り算を含むタスクで、ゼロ除算対策(SAFE_DIVIDE)を検討したか +
  • +
  • + タスク10では、手順書の指示とこのガイドの説明のどちらを優先すべきか確認したか +
  • +
+
+ + +
+
SOURCES
+

参考文献・出典

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目用途URL
ラボ本体(Google Cloud Skills Boost)このガイドが解説する課題ラボ本体 + skills.google/course_templates/623/labs/629091 +
covid-19-open-data 公式リポジトリ集計レベル・subregionカラムの意味に関する一次情報 + github.com/GoogleCloudPlatform/covid-19-open-data +
BigQuery標準SQL: 関数一覧GoogleSQLの名称に関する一次情報 + docs.cloud.google.com/.../functions-all +
BigQuery標準SQL: ウィンドウ関数の呼び出し方OVER句・ウィンドウ関数全般の構文リファレンス + cloud.google.com/.../analytic-function-concepts +
BigQuery標準SQL: ナビゲーション関数LAG / LEAD関数の引数・挙動 + cloud.google.com/.../navigation_functions +
BigQuery標準SQL: 日付関数DATE_DIFFの構文と算出ロジック + cloud.google.com/.../date_functions +
BigQuery標準SQL: 数学関数POWER / SAFE_DIVIDEなどの一次情報 + cloud.google.com/.../mathematical_functions +
BigQueryクエリプランと実行タイムライン自己結合よりウィンドウ関数を推奨する根拠 + docs.cloud.google.com/.../query-plan-explanation +
BigQuery関数のベストプラクティスエラー処理関数の利用指針 + docs.cloud.google.com/.../best-practices-performance-functions +
Looker StudioとBigQueryの接続カスタムクエリでの接続手順(公式) + cloud.google.com/looker/docs/studio/connect-to-google-bigquery +
+
+ +
+

+ 本ガイドはGoogle Cloud Skills Boostの公開ラボ「Derive Insights from BigQuery + Data」の学習補助として作成された非公式解説です。ラボ本体の手順・採点基準は予告なく変更される場合があるため、最新の指示は必ずラボ画面を優先してください。 +

+
+
+
+ + + + + diff --git a/Bigquery-covid19-challenge-lab-guide.md b/Bigquery-covid19-challenge-lab-guide.md new file mode 100644 index 000000000..87b48be11 --- /dev/null +++ b/Bigquery-covid19-challenge-lab-guide.md @@ -0,0 +1,602 @@ +# BigQueryで学ぶCOVID-19データ分析チャレンジラボ 完全攻略ガイド + +> 対象ラボ: [Derive Insights from BigQuery Data: Challenge Lab](https://www.skills.google/course_templates/623/labs/629091)(Google Cloud Skills Boost) +> 対象テーブル: `bigquery-public-data.covid19_open_data.covid19_open_data` + +## この記事について + +💡 このガイドを一言で言うと:「BigQueryの公開データセットに対して10個のSQLタスクを解きながら、集計・ウィンドウ関数・CTE(共通テーブル式)の実務的な使い方を身につけるための解説書」です。 + +このラボは **Date(日付)**、**Death Count(死亡者数のしきい値)**、**Confirmed Cases(確定症例数のしきい値)**、**Month(対象月)**、**Limit Value(パーセンテージや件数のしきい値)** といった値を、受講者ごとにランダムな数値へ差し替えて出題します。そのため本ガイドのSQLコード中では、これらを `` のような山カッコ付きプレースホルダーで表記しています。実際に提出するときは、自分のラボ画面に表示されている具体的な数値・日付に置き換えてください。 + +--- + +## Step 0. ラボ全体の流れをつかむ + +この章では、10個のタスクがラボ全体の中でどう位置づけられているかを説明します。個別のSQLに入る前に、まず全体の流れを図で押さえておくと、各タスクの目的を見失いにくくなります。 + +下の図は、BigQueryコンソールを開いてからLooker Studioでレポートを完成させるまでの一連の流れを表しています。上から下へ読み進めてください。 + +```mermaid +flowchart TD + A["BigQueryコンソールを開く"] --> B["covid19_open_data パブリックデータセットを追加する"] + B --> C["タスク1〜9のSQLクエリを順番に実行する"] + C --> D{"Check my progressで採点する"} + D -- "不合格" --> C + D -- "合格" --> E["Looker StudioでBigQueryコネクタからカスタムクエリを接続する"] + E --> F["確定症例数と死亡者数の時系列グラフを作成する"] + F --> G["ラボ完了"] +``` + +各ノードの意味: +- 「Check my progressで採点する」(ひし形):ラボの自動採点システムが、あなたのクエリ結果を期待値と突き合わせて判定します。不合格の場合は該当タスクのSQLを見直して再実行します。 +- 「Looker Studio」:旧称は「Data Studio」で、ラボの手順書にはこの旧称で書かれています。現行の正式名称は Looker Studio です([公式ドキュメント](https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery))。 + +📖 このセクションで登場した用語 +- Check my progress:Qwiklabs / Google Cloud Skills Boostが提供する自動採点ボタン。実行結果を裏側の期待値と比較して合否を返す + +--- + +## Step 1. データセットの構造を理解する(最重要の前提知識) + +この章では、10個のタスクすべてに共通して関わる `covid19_open_data` テーブルの構造上の注意点を説明します。ここを理解しないままクエリを書くと、一見正しく動くのに集計結果が水増しされる、という事故が起きやすくなります。 + +### 主なカラム + +| カラム名 | 意味 | 補足 | +|---|---|---| +| `date` | その行のデータの対象日 | DATE型 | +| `country_code` | 国コード(例: `US`) | ISO準拠のコード | +| `country_name` | 国名(例: `United States of America`) | 表記ゆれに注意(後述) | +| `subregion1_name` | 州・省などの地域名 | 国レベルの行では `NULL` | +| `cumulative_confirmed` | その日までの累積確定症例数 | 「新規」ではなく「累積」 | +| `cumulative_deceased` | その日までの累積死亡者数 | 同上 | +| `cumulative_recovered` | その日までの累積回復者数 | 同上 | + +⚠️ なぜ単純に `SUM(cumulative_confirmed)` だけでは危険なのか:このテーブルは「国レベルの行」「州レベルの行」「郡レベルの行」を **同じ1つのフラットなテーブルの中に混在させて** 格納しています。行を親子関係でネストしているのではなく、粒度の異なる行が並列に並んでいる、という点がポイントです。下の図は、その粒度の違いを模式的に表したものです。 + +```mermaid +flowchart TD + A["国レベルの行: country_name のみで特定でき subregion1_name は NULL"] --> B["州 / 準州レベルの行: subregion1_name に値が入り subregion2_name は NULL"] + B --> C["郡 / 市レベルの行: subregion1_name と subregion2_name の両方に値が入る"] +``` + +各ノードの意味: +- 矢印は「親から子」というリレーションではなく、同じ `date` ・同じ `country_name` に対して **粒度違いの行が複数存在する** ことを表しています。 +- 例えばアメリカの場合、`country_name = "United States of America"` という条件だけで絞り込むと、国全体の1行・50州分の行・数千の郡の行がすべて同時にヒットします。ここで単純に `SUM(cumulative_confirmed)` を取ると、症例数が何倍にも水増しされます。 + +この挙動は、データセット提供元の公式リポジトリでも明記されています。`subregion1_code` が `NULL` であれば国レベルの集計、値が入っていれば州レベルの集計であるとされ、集計レベルの判定には `aggregation_level` を使う方法もあると案内されています([GoogleCloudPlatform/covid-19-open-data README](https://github.com/GoogleCloudPlatform/covid-19-open-data))。 + +✅ 実務での回避策:地域別に集計したいときは、必ず `subregion1_name IS NOT NULL`(州レベルだけを見る)や `subregion1_name IS NULL AND subregion2_name IS NULL`(国レベルだけを見る)のように、対象の粒度を明示的にWHERE句で絞り込みます。 + +⚠️ ただし1点注意:本ラボのタスク1(世界全体の確定症例数)のように「日付だけで単純に `SUM` する」ことが公式の想定解になっているタスクもあります。これは採点システムの期待値がその単純な合計に合わせて作られているためです。本ガイドでは、ラボへの提出クエリはラボの想定解パターンに沿えつつ、実務で同じデータセットを使う際に注意すべき点は都度コラムとして補足します。 + +📖 このセクションで登場した用語 +- 累積値(cumulative):ある時点までの合計。前日までの値に当日分を足し込んだ値であり、「その日単体の新規件数」ではない +- 集計レベル(aggregation level):データがどの地理的粒度(国・州・郡)で集計されているかを表す区分 + +--- + +## Step 2. 全タスクに共通するベストプラクティス + +この章では、個別タスクに入る前に、10個のタスクを通して繰り返し使うSQLパターンをまとめて説明します。 + +### 2-1. GoogleSQLダイアレクトを使う + +BigQueryのクエリエディタは既定でGoogleSQL(旧称: Standard SQL)ダイアレクトです。古い記事では `[project:dataset.table]` のような角カッコ表記のLegacy SQLが使われていることがありますが、本ガイドはすべて `` `project.dataset.table` `` 形式のGoogleSQLで統一しています。GoogleSQLという名称は、以前のGoogle Standard SQLの新しい呼び方です([BigQuery関数リファレンス](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/functions-all))。 + +### 2-2. 「集計後の値」で絞り込みたいときはHAVINGを使う + +💡 一言で言うと:「`SUM` や `COUNT` で作った集計結果を条件に使いたいときは、`WHERE` ではなく `HAVING`、もしくはサブクエリ・CTEで包む」というパターンです。 + +⚠️ なぜ単純な `WHERE 集計結果 > 100` では解けないのか:`WHERE` 句はグループ化(`GROUP BY`)が行われる **前** の生の行に対して評価されます。`SUM(cumulative_deceased)` のような集計結果はグループ化が終わった **後** に初めて存在する値なので、`WHERE` の中では参照できません。これは後述するタスク2・タスク3で実際に使うテクニックです。 + +回避策は2つあります。 + +| 方法 | 書き方 | 向いている場面 | +|---|---|---| +| `HAVING` を使う | `GROUP BY 列 HAVING 集計結果 > 100` | 集計とその後の絞り込みだけで完結する場合 | +| サブクエリ / CTEで包む | 内側で集計し、外側の `SELECT ... FROM (...) WHERE ...` で絞り込む | 絞り込んだ後にさらに `JOIN` や別の計算を続ける場合 | + +### 2-3. 前日比較にはウィンドウ関数(LAG)を使う + +💡 一言で言うと:「ある行から、1つ前の日付の値を同じ行に並べて計算したいときに使うのが `LAG` 関数」です。 + +⚠️ なぜ自己結合(自分自身とのJOIN)では非効率なのか:日付テーブルを自分自身と `JOIN` して「1日前のレコード」を探す書き方もできますが、自己結合は出力行数が膨らみやすく、パフォーマンスの問題を起こしやすいとBigQueryの公式ドキュメントでも指摘されています。同じ目的は `LAG` などのウィンドウ(分析)関数で書き直すことが推奨されています([BigQueryクエリプランの解説](https://docs.cloud.google.com/bigquery/docs/query-plan-explanation))。 + +ウィンドウ関数にはいくつか種類があり、目的によって使い分けます。 + +| 関数 | やること | 使うべき場面 | +|---|---|---| +| `LAG(値) OVER (ORDER BY 日付)` | 1つ前の行の値を取得する | 前日比・前月比などの差分計算(本ラボのタスク6・7) | +| `LEAD(値) OVER (ORDER BY 日付)` | 1つ後の行の値を取得する | 未来方向の値を同じ行に並べたいとき(本ラボのタスク9) | +| `ROW_NUMBER() OVER (...)` | 重複のない連番を振る | 同率を区別して上位N件だけ取りたいとき | +| `RANK() / DENSE_RANK() OVER (...)` | 同率に同じ順位を振る | ランキング表示で同率を同じ順位として見せたいとき | + +`LAG` や `LEAD` はいずれも「ウィンドウ関数」の一種で、`OVER` 句とセットでなければ使えません。`OVER` 句を書き忘れるとエラーになる、という点は後述するタスク9のデバッグで重要になります([ナビゲーション関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions)、[ウィンドウ関数の呼び出し方](https://cloud.google.com/bigquery/docs/reference/standard-sql/analytic-function-concepts))。 + +### 2-4. 割り算にはSAFE_DIVIDEを検討する + +致死率・回復率・増加率のように「割り算」を扱うタスクでは、分母が0になるとエラーで処理全体が止まってしまうことがあります。BigQueryには `SAFE_DIVIDE(分子, 分母)` という関数があり、分母が0のときにエラーではなく `NULL` を返してくれます([数学関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions))。本ガイドでは、割り算を行うタスクで積極的にこの関数を使います。 + +📖 このセクションで登場した用語 +- ウィンドウ関数(分析関数):行をグループごとにまとめて1行に集約する集計関数とは違い、各行を保ったまま「その行の前後の値」などを計算できる関数 +- `OVER`句:ウィンドウ関数がどの範囲・どの並び順で計算するかを指定する句 +- `SAFE_DIVIDE`:ゼロ除算が起きてもエラーにせず `NULL` を返す安全な割り算関数 + +--- + +## Task 1. 全世界の確定症例数の合計 + +💡 一言で言うと:「指定した日付における、全世界の `cumulative_confirmed` を1行に集計するクエリ」です。 + +```sql +SELECT + SUM(cumulative_confirmed) AS total_cases_worldwide +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + date = "" + -- date は "YYYY-MM-DD" 形式の文字列リテラルとして渡す +``` + +処理の流れ: +- `WHERE date = ""` で対象日の行だけに絞り込む +- 絞り込んだ全行の `cumulative_confirmed` を `SUM` で合計し、`total_cases_worldwide` という名前で返す + +⚠️ 実務コラム:Step 1で説明した通り、この `WHERE` 句には地域粒度の絞り込みが入っていないため、国レベル・州レベル・郡レベルの行がすべて合算されます。ラボの採点はこの単純な合計を期待値としているため、このままのクエリで提出して問題ありません。ただし、自社のダッシュボードなど実務でこのデータセットを使う場合は、`subregion1_name IS NULL AND subregion2_name IS NULL` を加えて国レベルの行だけに絞り込むほうが安全です。 + +📖 このセクションで登場した用語 +- (新出用語なし。Step 1・Step 2の用語を参照) + +--- + +## Task 2. 被害が大きい地域を特定する + +💡 一言で言うと:「アメリカ国内で、指定した死亡者数を超えた州がいくつあるかを数えるクエリ」です。 + +```sql +SELECT + COUNT(*) AS count_of_states +FROM ( + SELECT + subregion1_name AS state, + SUM(cumulative_deceased) AS death_count + FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` + WHERE + country_name = "United States of America" + AND date = "" + AND subregion1_name IS NOT NULL -- 国全体の1行(州レベルではない行)を除外する + GROUP BY + subregion1_name +) +WHERE + death_count > +``` + +処理の流れ: +1. 内側のサブクエリで、州ごとに `cumulative_deceased` を集計し `death_count` を作る +2. `subregion1_name IS NOT NULL` によって、国レベルの合計行(州の情報を持たない1行)を弾く。これを忘れると「51件目の州」として国全体の行が混ざり、件数がずれる +3. 外側の `WHERE death_count > ` で、しきい値を超えた州だけを残す +4. `COUNT(*)` で、残った州の件数を数える + +⚠️ なぜ内側と外側でクエリを分けているのか:Step 2-2で説明した通り、`death_count` は集計後にしか存在しない値なので、`WHERE death_count > ` を集計と同じ階層に書くことはできません。サブクエリで一段階「確定させてから」外側でさらに絞り込む、という順番が重要です。 + +なお `country_code = "US"` ではなく `country_name = "United States of America"` を条件に使っている点にも注意してください。データセットによっては同じ国を指す表記が複数存在することがあるため、どちらのカラムで絞り込んでいるかは常に意識しておくとよい習慣です。 + +📖 このセクションで登場した用語 +- (新出用語なし) + +--- + +## Task 3. ホットスポットを特定する + +💡 一言で言うと:「アメリカ国内で、指定した確定症例数を超えた州を、症例数が多い順に一覧表示するクエリ」です。 + +```sql +SELECT + subregion1_name AS state, + SUM(cumulative_confirmed) AS total_confirmed_cases +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + country_code = "US" + AND date = "" + AND subregion1_name IS NOT NULL +GROUP BY + subregion1_name +HAVING + total_confirmed_cases > +ORDER BY + total_confirmed_cases DESC +``` + +処理の流れ: +1. `WHERE` で対象日・対象国・州レベルの行だけに絞り込む +2. `GROUP BY subregion1_name` で州ごとに集計する +3. `HAVING total_confirmed_cases > ` で、Step 2-2のパターンどおり「集計後の値」をしきい値で絞り込む +4. `ORDER BY total_confirmed_cases DESC` で症例数が多い順に並べ替える + +💡 補足:タスク2ではサブクエリ、タスク3では `HAVING` を使いました。どちらも「集計後の値で絞り込む」という同じ目的のための書き方の違いです。今回はこの後さらに別の計算を続けるわけではないので、`HAVING` のほうが行数の少ないシンプルな書き方になります。 + +📖 このセクションで登場した用語 +- (新出用語なし) + +--- + +## Task 4. 致死率(Case-Fatality Ratio)を計算する + +💡 一言で言うと:「イタリアの指定した月について、(累積死亡者数 ÷ 累積確定症例数)× 100 を計算するクエリ」です。 + +```sql +SELECT + SUM(cumulative_confirmed) AS total_confirmed_cases, + SUM(cumulative_deceased) AS total_deaths, + SAFE_DIVIDE(SUM(cumulative_deceased), SUM(cumulative_confirmed)) * 100 AS case_fatality_ratio +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + country_name = "Italy" + AND date BETWEEN "" AND "" +``` + +処理の流れ: +- `date BETWEEN 初日 AND 末日` で対象月の行だけに絞り込む +- `SUM` でその月の(=月末時点の)累積確定症例数・累積死亡者数をそのまま取得する +- `SAFE_DIVIDE` で割り算し、100倍してパーセント表記にする + +⚠️ 月末日を手で数える手間を減らしたい場合:閏年の2月など、月末日を間違えやすいケースがあります。次のように `EXTRACT` を使うと、月末日を意識せずに書けます。 + +```sql +SELECT + SUM(cumulative_confirmed) AS total_confirmed_cases, + SUM(cumulative_deceased) AS total_deaths, + SAFE_DIVIDE(SUM(cumulative_deceased), SUM(cumulative_confirmed)) * 100 AS case_fatality_ratio +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + country_name = "Italy" + AND EXTRACT(YEAR FROM date) = <対象年, 例: 2020> + AND EXTRACT(MONTH FROM date) = <対象月の数字, 例: 4> +``` + +📖 このセクションで登場した用語 +- `EXTRACT`:日付や時刻の値から年・月・日などの一部だけを取り出す関数 + +--- + +## Task 5. しきい値を超えた特定の日を探す + +💡 一言で言うと:「イタリアの累積死亡者数が、指定したしきい値を初めて超えた日付を1件だけ返すクエリ」です。 + +```sql +SELECT + date +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + country_name = "Italy" + AND cumulative_deceased > +ORDER BY + date ASC +LIMIT 1 +``` + +処理の流れ: +- `cumulative_deceased > ` で、しきい値を超えている日だけに絞り込む +- `ORDER BY date ASC` で古い日付順に並べ替える +- `LIMIT 1` で先頭の1件、つまり「最初に超えた日」だけを取り出す + +⚠️ なぜ `LIMIT 1` だけで安全に「最初の日」が取れるのか:`cumulative_deceased` は累積値なので、通常は日付が進むにつれて単調に増加(または横ばい)します。そのため、しきい値を超えた日付を昇順に並べて先頭を取れば、それが最初に超えた日になります。ただし実データでは、報告方法の見直しなどにより過去の値が下方修正され、累積値が一時的に前日を下回るケースもゼロではありません。その場合は「しきい値を初めて超えた日」と「現在しきい値を超えている最も古い日」が一致しない可能性がある、という点は覚えておくとよいでしょう。 + +📖 このセクションで登場した用語 +- 単調増加:値が時間とともに減ることなく、増える・または変わらない、を繰り返す性質 + +--- + +## Task 6. 新規症例数がゼロだった日を数える(壊れたクエリの修正) + +💡 一言で言うと:「インドで、前日から確定症例数が増えなかった日が何日あったかを数えるクエリ」です。ただし、お題として渡されたクエリはそのままでは実行できません。まずどこが壊れているかを見ていきます。 + +⚠️ 壊れたクエリのどこが問題か:お題のSQLは `india_previous_day_comparison` というCTE(`WITH`句で定義する名前付きの一時テーブル)を作るところまでしか書かれておらず、そのCTEを実際に読み出す外側の `SELECT` 文がありません。CTEは「定義しただけ」では何も返さないため、このままではクエリ全体が完結せず、スクリプトの終わりが来ていないというエラーになります。加えて、`date between '' and ''` の部分も空文字列のままなので、対象期間の日付を実際の値に差し替える必要があります。 + +```sql +WITH india_cases_by_date AS ( + SELECT + date, + SUM(cumulative_confirmed) AS cases + FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` + WHERE + country_name = "India" + AND date BETWEEN "" AND "" + GROUP BY + date + ORDER BY + date ASC +) + +, india_previous_day_comparison AS ( + SELECT + date, + cases, + LAG(cases) OVER (ORDER BY date) AS previous_day, + cases - LAG(cases) OVER (ORDER BY date) AS net_new_cases + FROM + india_cases_by_date +) + +SELECT + COUNT(date) AS days_with_zero_net_new_cases +FROM + india_previous_day_comparison +WHERE + net_new_cases = 0 +``` + +このクエリは「CTEを段階的につないでいく」という、この後のタスク7・9でも繰り返し使うパターンの基本形です。下の図は、そのパイプラインの流れを一般化したものです。 + +```mermaid +flowchart LR + A["CTE1: SUMで日付ごとの合計値を集計する"] --> B["CTE2: LAG関数で1行前の日付の値を同じ行に並べる"] + B --> C["同じ行の中で当日値と前日値を引き算する"] + C --> D["外側のSELECTでWHERE句により条件を満たす行だけを抽出する"] +``` + +各ノードの意味: +- 「CTE1」:`india_cases_by_date` にあたる、日付ごとの単純な集計 +- 「CTE2」:`india_previous_day_comparison` にあたる、前日値を並べる工程 +- 「外側のSELECT」:最終的に欲しい件数や一覧だけを取り出す工程 + +💡 補足:`LAG` は先頭の行(対象期間の一番古い日)では「1つ前の行」が存在しないため `NULL` を返します。`NULL - 数値` の計算結果も `NULL` になるため、`net_new_cases = 0` の判定には引っかからず、エラーにもならずに自然に除外されます。BigQueryではNULLを含む演算はエラーではなく「不明(NULL)」として扱われる、という挙動を覚えておくと、次のタスク7のデバッグでも役立ちます。 + +📖 このセクションで登場した用語 +- CTE(共通テーブル式):`WITH 名前 AS (...)` の形で、クエリの中に一時的な名前付きテーブルを定義する仕組み。複雑な計算を段階に分けて書けるため可読性が上がる + +--- + +## Task 7. 倍加速度(Doubling Rate)を調べる + +💡 一言で言うと:「アメリカで、指定した期間中に前日比で一定パーセント以上増えた日を一覧にするクエリ」です。タスク6のCTEパイプラインに、増加率の計算を1段追加します。 + +```sql +WITH us_cases_by_date AS ( + SELECT + date, + SUM(cumulative_confirmed) AS cases + FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` + WHERE + country_name = "United States of America" + AND date BETWEEN "2020-03-22" AND "2020-04-20" + GROUP BY + date + ORDER BY + date ASC +) + +, us_previous_day_comparison AS ( + SELECT + date, + cases, + LAG(cases) OVER (ORDER BY date) AS previous_day, + SAFE_DIVIDE( + cases - LAG(cases) OVER (ORDER BY date), + LAG(cases) OVER (ORDER BY date) + ) * 100 AS percentage_increase + FROM + us_cases_by_date +) + +SELECT + date AS Date, + cases AS Confirmed_Cases_On_Day, + previous_day AS Confirmed_Cases_Previous_Day, + percentage_increase AS Percentage_Increase_In_Cases +FROM + us_previous_day_comparison +WHERE + percentage_increase > +``` + +処理の流れは、前掲の「CTEパイプライン」の図とほぼ同じです。違いは、2段目のCTEで「引き算」だけでなく「割り算してパーセントに換算する」計算を加えている点です。 + +⚠️ ここで `SAFE_DIVIDE` を使う理由:パンデミック初期の日付を対象期間に含めると、前日の累積症例数が実際に0件というケースがあり得ます。通常の `/` 演算子でゼロ除算が起きるとクエリはエラーで止まってしまいますが、`SAFE_DIVIDE` を使えばエラーにならず、その行の `percentage_increase` が `NULL` になるだけで処理が続行されます([数学関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions))。 + +📖 このセクションで登場した用語 +- (新出用語なし。Step 2-4を参照) + +--- + +## Task 8. 回復率(Recovery Rate)ランキングを作る + +💡 一言で言うと:「指定した日付時点で、確定症例数が5万件を超える国だけを対象に、回復率が高い順に上位いくつかを表示するクエリ」です。 + +```sql +WITH cases_by_country AS ( + SELECT + country_name AS country, + SUM(cumulative_confirmed) AS confirmed_cases, + SUM(cumulative_recovered) AS recovered_cases + FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` + WHERE + date = "2020-05-10" + GROUP BY + country_name +) + +SELECT + country, + recovered_cases, + confirmed_cases, + SAFE_DIVIDE(recovered_cases, confirmed_cases) * 100 AS recovery_rate +FROM + cases_by_country +WHERE + confirmed_cases > 50000 +ORDER BY + recovery_rate DESC +LIMIT +``` + +処理の流れ: +1. CTEで国ごとに確定症例数・回復者数を集計する +2. 外側の `WHERE confirmed_cases > 50000` で、感染規模がある程度大きい国だけに絞り込む(Step 2-2と同じ「集計後の値で絞り込む」パターン) +3. `recovery_rate` を計算し、降順に並べ替えて `LIMIT` で件数を絞る + +💡 補足:`ORDER BY` と `LIMIT` を組み合わせる際は、`WHERE confirmed_cases > 50000` を先に適用してから並べ替えることで、「症例数が少ないのに回復率だけ100%に近い」ような小規模な国がランキング上位に紛れ込むのを防いでいます。この順序(先に絞り込み、後で並べ替え)は、集計を伴うランキングクエリで繰り返し使えるパターンです。 + +📖 このセクションで登場した用語 +- (新出用語なし) + +--- + +## Task 9. CDGR(累積日次成長率)を計算する(壊れたクエリの修正) + +💡 一言で言うと:「フランスで最初の症例が報告された日から指定した日までの、1日あたりの複利的な増加率(CDGR)を計算するクエリ」です。お題のクエリには3か所の不具合があります。順番に見ていきましょう。 + +CDGRの定義(ラボの説明を式で整理したもの): + +```text +CDGR = (最終日の症例数 / 初日の症例数) ^ (1 / 経過日数) - 1 +``` + +これは「1日あたり何倍ずつ増えていれば、初日から最終日までの増加を説明できるか」を表す指標で、指数(べき乗)計算が必要になります。 + +下の図は、お題のクエリに含まれる3つの不具合と、その修正内容を順番に示したものです。 + +```mermaid +flowchart TD + A["元のクエリを実行する"] --> B{"不具合1: LEADにOVER句がない"} + B --> C["LEAD(total_cases) OVER (ORDER BY date) に修正する"] + C --> D{"不具合2: date INの2番目の値が空文字"} + D --> E["最終日の日付リテラルを補完する"] + E --> F{"不具合3: SQRTは引数を1つしか取らない"} + F --> G["べき乗計算にはPOWER(底, 指数)を使うよう修正する"] + G --> H["修正後のクエリでCDGRを計算する"] +``` + +各不具合の解説: + +- **不具合1(構文エラー)**:`LEAD(total_cases)` はウィンドウ関数ですが、`OVER` 句が付いていません。Step 2-3で説明した通り、ウィンドウ関数は必ず `OVER` 句とセットで書く必要があるため、このままではエラーになります([ナビゲーション関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions))。`LEAD(total_cases) OVER (ORDER BY date)` のように修正します。 +- **不具合2(未入力の値)**:`date IN ('2020-01-24', '')` の2番目が空文字列のままです。CDGRを計算したい最終日(例では2020年5月10日)の日付リテラルに置き換えます。 +- **不具合3(関数の選び間違い)**:最後の `SELECT` で `SQRT((last_day_cases/first_day_cases),(1/days_diff))-1` という書き方をしていますが、`SQRT`(平方根)は引数を1つしか取らない関数です。ここで本当にやりたいのは「累乗(べき乗)」の計算なので、2つの引数(底と指数)を取る `POWER(底, 指数)` 関数に置き換える必要があります([数学関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions))。 + +修正後のクエリ: + +```sql +WITH france_cases AS ( + SELECT + date, + SUM(cumulative_confirmed) AS total_cases + FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` + WHERE + country_name = "France" + AND date IN ("2020-01-24", "<最終日, 例: 2020-05-10>") + GROUP BY + date + ORDER BY + date +) + +, summary AS ( + SELECT + total_cases AS first_day_cases, + LEAD(total_cases) OVER (ORDER BY date) AS last_day_cases, + DATE_DIFF(LEAD(date) OVER (ORDER BY date), date, DAY) AS days_diff + FROM + france_cases + LIMIT 1 +) + +SELECT + first_day_cases, + last_day_cases, + days_diff, + POWER(SAFE_DIVIDE(last_day_cases, first_day_cases), SAFE_DIVIDE(1, days_diff)) - 1 AS cdgr +FROM + summary +``` + +処理の流れ: +1. `france_cases` CTEで、初日と最終日、2行だけを取り出す +2. `summary` CTEで、`LEAD` を使って「1行目に初日、2行目に最終日」という2行を1行にまとめ、`DATE_DIFF` で経過日数も同じ行に並べる +3. `LIMIT 1` で、まとめ終わった1行だけを残す +4. 最後の `SELECT` で `POWER` を使ってCDGRを計算する + +📖 このセクションで登場した用語 +- `DATE_DIFF(日付A, 日付B, DAY)`:日付Aと日付Bの間の日数を返す関数([日付関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions)) +- `POWER(底, 指数)`:底を指数乗した値を返す関数 + +--- + +## Task 10. Looker Studioでレポートを作成する + +💡 一言で言うと:「BigQueryのカスタムクエリをLooker Studio(旧Data Studio)に接続し、アメリカの確定症例数と死亡者数の時系列グラフを作るタスク」です。 + +まず、レポートの元になるクエリを用意します。 + +```sql +SELECT + date, + SUM(cumulative_confirmed) AS country_cases, + SUM(cumulative_deceased) AS country_deaths +FROM + `bigquery-public-data.covid19_open_data.covid19_open_data` +WHERE + country_name = "United States of America" + AND date BETWEEN "" AND "" +GROUP BY + date +ORDER BY + date +``` + +接続からグラフ作成までの手順は次のとおりです([Looker StudioとBigQueryの接続 公式ドキュメント](https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery))。 + +| 手順 | 操作内容 | +|---|---| +| 1 | BigQueryのクエリエディタで上記クエリを実行し、正しく結果が返ることを確認する | +| 2 | 結果画面から「Explore Data」→「Explore with Looker Studio」(ラボの手順書では「Explore with Data Studio」)を選ぶ | +| 3 | Looker StudioにBigQueryへのアクセスを許可(承認)する | +| 4 | 初回ログイン時にレポート作成に失敗する場合は、「空のレポート」を作成して利用規約に同意したうえで、BigQueryの画面から改めて接続し直す | +| 5 | レポート編集画面で「グラフを追加」から「時系列グラフ」を選ぶ | +| 6 | 指標(Metric)に `country_cases` と `country_deaths` の両方を追加する | +| 7 | 「保存」をクリックして変更を確定する | + +⚠️ 注意:ラボの指示では「BigQueryのExplore with Data Studioオプションは使わないこと」となっている場合があります。必ず自分のラボの手順書の指示(BigQueryコネクタを選び、Custom Queryにこのクエリを貼り付けて「Add」→「Add to report」する方法)を優先してください。手順書とこのガイドの操作方法が食い違う場合は、手順書側を正としてください。 + +📖 このセクションで登場した用語 +- (新出用語なし) + +--- + +## まとめ:提出前チェックリスト + +- [ ] クエリ中のすべての `<プレースホルダー>` を、自分のラボ画面に表示された実際の値に置き換えたか +- [ ] `country_name` と `country_code` のどちらで絞り込むべきタスクか、混同していないか +- [ ] 州・郡レベルの集計をするタスクで `subregion1_name IS NOT NULL` の絞り込みを入れたか +- [ ] 集計後の値(`SUM` や `COUNT` の結果)を条件にするときは `WHERE` ではなく `HAVING` かサブクエリ/CTEを使っているか +- [ ] `LAG` / `LEAD` に `OVER (ORDER BY ...)` を付け忘れていないか +- [ ] 割り算を含むタスクで、ゼロ除算対策(`SAFE_DIVIDE`)を検討したか +- [ ] タスク10では、手順書の指示(Data Studio連携の方法)とこのガイドの説明のどちらを優先すべきか確認したか + +--- + +## 参考文献・出典 + +| 項目 | 用途 | URL | +|---|---|---| +| ラボ本体(Google Cloud Skills Boost) | このガイドが解説する課題ラボ本体 | https://www.skills.google/course_templates/623/labs/629091 | +| covid-19-open-data 公式リポジトリ(GoogleCloudPlatform) | テーブルの集計レベル・`subregion1_code`/`subregion2_code`の意味に関する一次情報 | https://github.com/GoogleCloudPlatform/covid-19-open-data | +| BigQuery標準SQL: 関数一覧(GoogleSQLの名称について) | GoogleSQLがGoogle Standard SQLの新名称であることの一次情報 | https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/functions-all | +| BigQuery標準SQL: ウィンドウ関数の呼び出し方 | `OVER`句・ウィンドウ関数全般の構文リファレンス | https://cloud.google.com/bigquery/docs/reference/standard-sql/analytic-function-concepts | +| BigQuery標準SQL: ナビゲーション関数 | `LAG` / `LEAD`関数の引数・挙動の一次情報 | https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions | +| BigQuery標準SQL: 日付関数 | `DATE_DIFF`の構文と算出ロジック | https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions | +| BigQuery標準SQL: 数学関数 | `POWER` / `SAFE_DIVIDE`など数値計算関数の一次情報 | https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions | +| BigQueryクエリプランと実行タイムラインの解説 | 自己結合よりウィンドウ関数を推奨するパフォーマンス指針の根拠 | https://docs.cloud.google.com/bigquery/docs/query-plan-explanation | +| BigQuery関数のベストプラクティス | 近似集計関数やエラー処理関数の利用指針 | https://docs.cloud.google.com/bigquery/docs/best-practices-performance-functions | +| Looker StudioとBigQueryの接続(公式ドキュメント) | カスタムクエリでBigQueryに接続する公式手順 | https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery | diff --git a/Bigquery-data-sharing-challenge-lab-guide.html b/Bigquery-data-sharing-challenge-lab-guide.html new file mode 100644 index 000000000..a6c15ed2d --- /dev/null +++ b/Bigquery-data-sharing-challenge-lab-guide.html @@ -0,0 +1,1595 @@ + + + + + + BigQueryによるデータ共有チャレンジラボ 徹底解説ガイド + + + + + +
+ + +
+
+
+ Google Cloud Challenge Lab 解説 +
+

BigQueryによるデータ共有チャレンジラボ 徹底解説ガイド

+

+ 対象ラボ: Share Data Using Google Data Cloud: Challenge Lab ― + https://www.skills.google/course_templates/657/labs/591412
+ 初学者でも迷わず完了できるよう、各手順の「なぜそうするのか」という根拠に公式ドキュメントのURLを添えてステップバイステップで解説します。 +

+
+ +
+

1. このラボの全体像

+

このラボでは、あなたは2つの役割を1人で演じます。

+ + + + + + + + + + + + + + + + + + + + + +
役割立場このラボでやること
+ データ共有パートナー + データ提供者 + 公開データセット(郵便番号ごとの地理情報)を Authorized View + として顧客に公開する +
+ 顧客 + データ利用者兼提供者 + パートナーのビューを使って自社データを補完し、集計結果を再びパートナーに + Authorized View として公開する +
+ +

+ つまり「パートナー → 顧客」「顧客 → + パートナー」の双方向データ共有を、BigQueryの Authorized + View という仕組みだけで実現するのがこのラボの核心です。 +

+ +

1.1 全体アーキテクチャ

+
+
+ データフロー / アーキテクチャ図 +
+
+
+ +
+ +
+
読み方のポイント
+

+ Authorized View + は「元データそのもの」ではなく「クエリ結果への窓口」を共有する仕組みです。相手は元テーブル(customer_info + や + zip_codes)には直接アクセスできず、あくまで公開されたビューの結果しか見えません。これが + Authorized View の最大の利点です。 +

+

+ 出典: + Authorized views | BigQuery | Google Cloud +

+

+ ビューを「作成」しただけでは相手はまだ何も見られません。「ビューの承認(Authorize)」と「ユーザーへのIAMロール付与」という2段階の許可が必要です。この2段階を混同するのがこのラボで最もつまずきやすいポイントです(詳しくは3章・5章で解説します)。 +

+
+
+ +

1.2 作業フローの全体像(誰が何をするか)

+
+
+ Task 1 から Task 4 までの流れ +
+
+
+
+ +
+

2. 事前準備の注意点

+
    +
  • + 演習用アカウントは + Incognito(シークレット)ウィンドウで使い、個人のGoogleアカウントと混在させないこと。これはラボの標準的な注意事項ですが、IAM設定の切り替えミスを防ぐという意味でも実務上重要です。 +
  • +
  • + Task 1・Task 4はパートナープロジェクトのコンソール、Task 2・Task + 3は顧客プロジェクトのコンソールで作業します。今どちらの役割としてログインしているかを常に意識してください。作業ミスの多くは「ログインしているプロジェクトの取り違え」から発生します。 +
  • +
+
+ +
+
+ パートナープロジェクトで作業 +
+

3. Task 1: パートナー承認済みビュー(Partner authorized view)の作成

+ +

3.1 手順

+
    +
  1. + パートナープロジェクトのBigQueryコンソールで + demo_dataset を開く(なければ作成する)。 +
  2. +
  3. + 以下のクエリでビューを作成し、demo_dataset + 内に指定された名前で保存する。 +
    SELECT
    + *
    +FROM
    + `bigquery-public-data.geo_us_boundaries.zip_codes`;
    +
  4. +
  5. 作成したビューを承認(Authorize)する。
  6. +
  7. + 顧客ユーザー(Customer username)に、そのビューへの + BigQuery Data Viewer ロールを付与する。 +
  8. +
+ +

3.2 なぜこの順序なのか

+
+ +
+

+ BigQueryの公開データセットは「データセットは共有されているがプロジェクトは共有されていない」という特殊な構造です。そのためクエリを実行するには自分のプロジェクトを課金プロジェクトとして指定する必要があります。この特性上、公開データを直接顧客に渡すのではなく、いったん自分のプロジェクトのビューとして再公開する、というこのラボの設計は理にかなっています。 +

+

+ 出典: + Connect to Google BigQuery | Looker Studio(「公開データセットはデータセットのみが共有されプロジェクトは共有されない」という仕様について記載) +

+

+ ビューの「作成」と「承認」が分離されているのは、BigQueryのセキュリティモデルの根幹です。Authorized + View + は「ビュー自身にソースデータへのアクセス権を持たせる」ことで、閲覧者本人に元データへの権限を渡さずに結果だけを渡す仕組みです。ビューを承認するという操作は、まさに「このビューにはソースデータへのアクセスを許可する」という宣言にあたります。 +

+

+ 出典: + Create an authorized view | BigQuery +

+
+
+ +

3.3 詰まりやすいポイント

+
+ +
+
    +
  • + 同じソースデータセットに対して複数の Authorized View + を作る予定がある場合は、ビュー単位ではなくデータセット単位で承認する「Authorized Dataset」を使うと管理が楽になります。今回のラボはビューが1つなので個別承認で十分ですが、実務でスケールする際はこちらを検討してください。 + +
  • +
  • + IAMロール付与は「ビューの承認」とは別操作です。承認だけして権限付与を忘れると、顧客はビューの存在自体を認識できずクエリはPermission + Deniedになります。 + +
  • +
+
+
+
+ +
+
顧客プロジェクトで作業
+

4. Task 2: 顧客データテーブルの更新

+ +

4.1 手順

+

顧客プロジェクトのコンソールに切り替え、次のクエリを実行します。

+
UPDATE
+ `Customer A Project ID.customer_dataset.customer_info` cust
+SET
+cust.county=vw.county
+FROM
+`Partner Project ID.demo_dataset.Partner authorized view` vw
+WHERE
+vw.zip_code=cust.postal_code;
+

+ 実行後、This statement modified 14 rows in customer_info. + のようなメッセージが表示されれば成功です。 +

+ +

4.2 なぜこの書き方をするのか

+
+ +
+

+ BigQueryのDML UPDATE 文は + FROM + 句で別テーブル(ここでは他プロジェクトのAuthorized + View)と結合し、条件に合致した行だけを更新できます。1行ずつUPDATEを繰り返すのではなく、このように条件付きの一括更新にすることが公式に推奨されているベストプラクティスです。 +

+

+ 出典: + Data manipulation language (DML) statements in GoogleSQL、Transform data with DML | BigQuery +

+

+ WHERE 句で結合条件(vw.zip_code = cust.postal_code)を1件のソース行に一意に絞れない場合、UPDATE/MERGE must match at most one source row for each target + row + というランタイムエラーになります。ソース側(ビュー)にzip_codeの重複がないか事前に確認しておくと安全です。 +

+

+ 出典: + Data manipulation language (DML) statements in GoogleSQL +

+
+
+ +

4.3 詰まりやすいポイント

+
+ +
+
    +
  • + postal_code と + zip_code + のデータ型が一致しているかを確認してください(片方がSTRING、もう片方がINT64だと結合条件が一致せず更新件数が0になります)。 +
  • +
  • + Task + 1でCustomerユーザーへの権限付与が漏れていると、このUPDATE文は他プロジェクトのビューを参照できずエラーになります。エラーが出たらまずTask + 1のIAM設定に戻って確認するのが早道です。 +
  • +
+
+
+
+ +
+
顧客プロジェクトで作業
+

5. Task 3: 顧客承認済みビュー(Customer authorized view)の作成

+ +

5.1 手順

+
    +
  1. + 顧客プロジェクトの + customer_dataset + に、以下のクエリでビューを作成する。 +
    SELECT
    +  county,
    +COUNT(1) AS Count
    +FROM
    + `Customer A Project ID.customer_dataset.customer_info` cust
    +GROUP BY
    + county
    +HAVING county is not null
    +
  2. +
  3. ビューを承認する。
  4. +
  5. + パートナーユーザー(Partner username)に + BigQuery Data Viewer ロールを付与する。 +
  6. +
+ +

5.2 なぜこの設計が良いのか

+
+ +
+

+ このビューは生の + customer_info + テーブルを丸ごと見せるのではなく、county + ごとの件数という集計済みの粒度だけを公開しています。これはAuthorized + Viewの典型的な使い方で、「相手に必要な情報の粒度だけを渡し、個々の顧客レコードのような機微な情報は渡さない」というデータ最小化の原則にも合致します。 +

+

+ 出典: + Create an authorized view | BigQuery(「列やフィールドを絞り込んで結果を返せる」という記載) +

+

+ 権限付与についても、Task + 1と同じく「必要な相手に、必要な粒度のデータだけを、最小権限で」というIAMの最小権限の原則(Principle + of Least Privilege)に沿っています。 +

+

+ 出典: + Use IAM securely | Google Cloud +

+
+
+ +

5.3 詰まりやすいポイント

+
+ +
+
    +
  • + HAVING county is not null + を忘れると、county + が補完されなかった顧客(Task 2のUPDATEで一致しなかった行)がnullの集計行として混入し、Task 4のグラフが歪みます。 +
  • +
  • + Data Viewerロールだけではクエリを実行できないケースに注意してください。roles/bigquery.dataViewer + にはデータを読む権限は含まれますが、ジョブを実行する権限(bigquery.jobs.create)は含まれていません。ラボ環境では通常プロジェクトの基本ロールで担保されていますが、実務で同じ構成を組む場合は + roles/bigquery.jobUser + を別途プロジェクトレベルで付与する必要があります。 + +
  • +
+
+
+
+ +
+
+ パートナープロジェクトで作業 +
+

6. Task 4: Looker Studio(旧 Data Studio)での可視化

+ +

6.1 用語について

+

+ ラボの手順書には「Data Studio」と記載されていますが、このプロダクトは + Looker Studio + に名称変更されています。操作画面や手順自体は同一ですので、「Data + Studio」という表記が出てきたら「Looker Studio」と読み替えてください。 +

+ +

6.2 手順

+
    +
  1. + Looker Studio(lookerstudio.google.com)を開き、空のレポート(Blank Report)を作成する。 +
  2. +
  3. BigQueryコネクタを選択し、Googleアカウントを認証する。
  4. +
  5. + 「My Projects」から顧客プロジェクトへ移動し、Customer authorized view + を選択してレポートに追加する。 +
  6. +
  7. レポート名を指定された名前に設定する。
  8. +
  9. 縦棒グラフ(Vertical Bar Chart)を挿入する。
  10. +
  11. + county をディメンションに、Count + を内訳ディメンション(Breakdown Dimension)およびメトリクスに設定する。 +
  12. +
+ +

6.3 なぜこの手順なのか

+
+ +
+

+ Looker + StudioからBigQueryに接続する際、テーブルではなくあらかじめ集計・整形されたビューに接続するのは公式にも推奨されているパターンです。ダッシュボード側で毎回重い集計クエリを走らせるより、ビュー側で事前集計しておく方が表示速度とコストの両面で有利です。今回のCustomer + authorized + viewは、まさにこの「ビュー経由で接続する」パターンの実例になっています。 +

+

+ 出典: + Connect to Google BigQuery | Looker Studio +

+
+
+ +

6.4 詰まりやすいポイント

+
+ +
+
    +
  • + 「My + Projects」に顧客プロジェクトが表示されない場合、認証しているGoogleアカウントにCustomer + authorized viewへのData Viewerロールが付与されているか(Task + 3の最後の手順)を再確認してください。 +
  • +
  • + 縦棒グラフのフィールド設定で「ディメンション」と「内訳ディメンション」を混同しやすいので注意してください。ディメンションはX軸の分類(county)、内訳ディメンションは色分けの基準、メトリクスは棒の高さ(Count)を決めます。 +
  • +
+
+
+
+ +
+

7. よくあるエラーと対処法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状主な原因対処方法
ビューは作成できるが、相手がクエリすると権限エラーになる + 「ビューの承認」と「相手ユーザーへのIAM付与」のどちらか、または両方が未実施 + + 承認とIAM付与は別工程。両方が完了しているかIAMポリシーの画面で確認する +
Task 2のUPDATE文の更新件数が0件になる + zip_code と + postal_code + の型不一致、または結合条件に一致する行が存在しない + + 両カラムの型を確認し、必要ならCASTする。件数がおかしい場合はSELECTで結合結果を先に確認する +
+ UPDATE/MERGE must match at most one source row + というエラー + ソース側(ビュー)の結合キーに重複がある + GROUP BY や + DISTINCT + でソース側の重複を排除してから結合する +
Looker StudioでCustomer authorized viewが選択肢に出てこない + 認証アカウントにData + Viewerロールが付与されていない、または別プロジェクトを見ている + + Task 3のIAM付与を再確認し、「My + Projects」で正しいプロジェクトを選び直す +
Data Viewerロールを付与したのにクエリ実行時にエラーになる + Data Viewerロールにはジョブ実行権限(bigquery.jobs.create)が含まれない + + プロジェクトレベルで + roles/bigquery.jobUser + を追加で付与する +
+
+ +
+

8. ベストプラクティスまとめ

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
観点ベストプラクティス出典
データ共有の粒度 + 元テーブルを直接公開せず、必要な列・集計結果だけをビューとして公開する + + Create an authorized view +
権限設計 + 常に最小権限(Data + Viewerなど必要最小限のロール)を、必要な相手にのみ付与する + + Use IAM securely +
複数ビューの承認管理 + 同一データセットに対するビューが増えたらAuthorized + Datasetへの切り替えを検討する + + Authorized datasets +
大量データの更新 + UPDATEは1行ずつでなく条件付きの一括更新にする。頻繁に更新する場合はクラスタリングも検討する + + Transform data with DML +
BIツールとの接続 + 生テーブルではなく、事前集計済みのビュー経由で接続しダッシュボードの速度とコストを最適化する + + Connect to Google BigQuery | Looker Studio +
組織を越えたスケール + 個別のAuthorized + Viewの手動運用が煩雑になったら、カタログ化・モニタリング機能を持つ + BigQuery sharing(旧 Analytics Hub)への移行を検討する + + Introduction to BigQuery sharing +
+ +

補足: このラボの先にある選択肢(BigQuery sharing / 旧Analytics Hub)

+
+ +
+

+ このラボで使ったAuthorized + Viewは、少数のプロジェクト間でのシンプルな共有には最適です。一方、共有先が増えたり、組織をまたいだデータ交換を継続的に運用する必要がある場合は、BigQuery sharing(旧称 Analytics Hub)という上位の仕組みがあります。これはデータをコピーせずに「リンクされた読み取り専用データセット」として提供し、サブスクライバーの利用状況もモニタリングできる、より運用性の高い共有基盤です。今回学んだAuthorized + Viewの考え方(元データへの直接アクセスを渡さず、結果だけを共有する)は、BigQuery + sharingでも土台として使われています。 +

+

+ 出典: + Introduction to BigQuery sharing | BigQuery +

+
+
+
+ +
+

9. 参考文献 / ソース一覧

+ + + +
+
+
+ + + + + + + diff --git a/Bigquery-data-sharing-challenge-lab-guide.md b/Bigquery-data-sharing-challenge-lab-guide.md new file mode 100644 index 000000000..84fc4ac4d --- /dev/null +++ b/Bigquery-data-sharing-challenge-lab-guide.md @@ -0,0 +1,260 @@ +# BigQueryによるデータ共有チャレンジラボ 徹底解説ガイド + +> 対象ラボ: *Share Data Using Google Data Cloud: Challenge Lab* +> https://www.skills.google/course_templates/657/labs/591412 + +このガイドは、Google Cloud のチャレンジラボ「BigQueryデータセットをプロジェクト間で共有し、双方向データ交換とLooker Studio(旧 Data Studio)での可視化を行う」課題を、初学者でも迷わず完了できるようにステップバイステップで解説したものです。各手順の「なぜそうするのか」という根拠には、必ず公式ドキュメントなど一次情報のURLを添えています。 + +--- + +## 1. このラボの全体像 + +このラボでは、あなたは2つの役割を1人で演じます。 + +| 役割 | 立場 | このラボでやること | +|---|---|---| +| データ共有パートナー (Data Sharing Partner) | データ提供者 | 公開データセット(郵便番号ごとの地理情報)を Authorized View として顧客に公開する | +| 顧客 (Customer) | データ利用者兼提供者 | パートナーのビューを使って自社データを補完し、集計結果を再びパートナーに Authorized View として公開する | + +つまり「パートナー → 顧客」「顧客 → パートナー」の**双方向データ共有**を、BigQueryの Authorized View という仕組みだけで実現するのがこのラボの核心です。 + +### 1.1 全体アーキテクチャ + +```mermaid +flowchart TB + subgraph PARTNER["データ共有パートナー プロジェクト"] + PUB["bigquery-public-data.geo_us_boundaries.zip_codes
公開データセット(郵便番号別の地理データ)"] + DS1["demo_dataset"] + PV["Partner authorized view
郵便番号 to 郡 のマッピング"] + PUB -->|"SELECT * で参照するビューを作成"| PV + DS1 -.格納.-> PV + end + + subgraph CUSTOMER["顧客プロジェクト"] + CT["customer_dataset.customer_info
顧客テーブル(postal_code列を保持)"] + DS2["customer_dataset"] + CV["Customer authorized view
郡ごとの顧客数集計"] + CT -->|"county列をUPDATEで補完"| CT + CT -->|"GROUP BY county で集計するビューを作成"| CV + DS2 -.格納.-> CV + end + + LS["Looker Studio
旧称 Data Studio"] + + PV -->|"Customerユーザーに
BigQuery Data Viewerロールを付与"| CT + CV -->|"Partnerユーザーに
BigQuery Data Viewerロールを付与"| LS +``` + +**読み方のポイント** + +- Authorized View は「元データそのもの」ではなく「クエリ結果への窓口」を共有する仕組みです。相手は元テーブル(`customer_info` や `zip_codes`)には直接アクセスできず、あくまで公開されたビューの結果しか見えません。これが Authorized View の最大の利点です。 + 出典: [Authorized views | BigQuery | Google Cloud](https://cloud.google.com/bigquery/docs/authorized-views) +- ビューを「作成」しただけでは相手はまだ何も見られません。「ビューの承認(Authorize)」と「ユーザーへのIAMロール付与」という**2段階の許可**が必要です。この2段階を混同するのがこのラボで最もつまずきやすいポイントです(詳しくは4章・6章で解説します)。 + +### 1.2 作業フローの全体像(誰が何をするか) + +```mermaid +flowchart TB + subgraph T1["Task 1 - パートナー側で作業"] + direction LR + A1["公開データセットを参照する
Partner authorized viewを作成"] --> A2["ビューを承認し
顧客ユーザーにData Viewerを付与"] + end + subgraph T2["Task 2 - 顧客側で作業"] + direction LR + B1["Partner authorized viewを参照する
UPDATE文でcounty列を補完"] + end + subgraph T3["Task 3 - 顧客側で作業"] + direction LR + C1["郡別の顧客数を集計する
Customer authorized viewを作成"] --> C2["ビューを承認し
パートナーユーザーにData Viewerを付与"] + end + subgraph T4["Task 4 - パートナー側で作業"] + direction LR + D1["Looker StudioでBigQueryに接続"] --> D2["縦棒グラフで可視化"] + end + + T1 --> T2 --> T3 --> T4 +``` + +--- + +## 2. 事前準備の注意点 + +- 演習用アカウントは Incognito(シークレット)ウィンドウで使い、個人のGoogleアカウントと混在させないこと。これはラボの標準的な注意事項ですが、IAM設定の切り替えミスを防ぐという意味でも実務上重要です。 +- Task 1・Task 4はパートナープロジェクトのコンソール、Task 2・Task 3は顧客プロジェクトのコンソールで作業します。**今どちらの役割としてログインしているか**を常に意識してください。作業ミスの多くは「ログインしているプロジェクトの取り違え」から発生します。 + +--- + +## 3. Task 1: パートナー承認済みビュー(Partner authorized view)の作成 + +### 3.1 手順 + +1. パートナープロジェクトのBigQueryコンソールで `demo_dataset` を開く(なければ作成する)。 +2. 以下のクエリでビューを作成し、`demo_dataset` 内に指定された名前で保存する。 + +```sql +SELECT + * +FROM + `bigquery-public-data.geo_us_boundaries.zip_codes`; +``` + +3. 作成したビューを**承認(Authorize)**する。 +4. 顧客ユーザー(Customer username)に、そのビューへの **BigQuery Data Viewer** ロールを付与する。 + +### 3.2 なぜこの順序なのか + +BigQueryの公開データセットは「データセットは共有されているがプロジェクトは共有されていない」という特殊な構造です。そのためクエリを実行するには自分のプロジェクトを課金プロジェクトとして指定する必要があります。この特性上、公開データを直接顧客に渡すのではなく、いったん自分のプロジェクトのビューとして再公開する、というこのラボの設計は理にかなっています。 +出典: [Connect to Google BigQuery | Looker Studio](https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery)(「公開データセットはデータセットのみが共有されプロジェクトは共有されない」という仕様について記載) + +ビューの「作成」と「承認」が分離されているのは、BigQueryのセキュリティモデルの根幹です。Authorized View は「ビュー自身にソースデータへのアクセス権を持たせる」ことで、閲覧者本人に元データへの権限を渡さずに結果だけを渡す仕組みです。ビューを承認するという操作は、まさに「このビューにはソースデータへのアクセスを許可する」という宣言にあたります。 +出典: [Create an authorized view | BigQuery](https://cloud.google.com/bigquery/docs/create-authorized-views) + +### 3.3 詰まりやすいポイント + +- 同じソースデータセットに対して複数の Authorized View を作る予定がある場合は、ビュー単位ではなく**データセット単位で承認する「Authorized Dataset」**を使うと管理が楽になります。今回のラボはビューが1つなので個別承認で十分ですが、実務でスケールする際はこちらを検討してください。 + 出典: [Authorized datasets | BigQuery](https://cloud.google.com/bigquery/docs/authorized-datasets) +- IAMロール付与は「ビューの承認」とは別操作です。承認だけして権限付与を忘れると、顧客はビューの存在自体を認識できずクエリはPermission Deniedになります。 + 出典: [Control access to resources with IAM | BigQuery](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam) + +--- + +## 4. Task 2: 顧客データテーブルの更新 + +### 4.1 手順 + +顧客プロジェクトのコンソールに切り替え、次のクエリを実行します。 + +```sql +UPDATE + `Customer A Project ID.customer_dataset.customer_info` cust +SET +cust.county=vw.county +FROM +`Partner Project ID.demo_dataset.Partner authorized view` vw +WHERE +vw.zip_code=cust.postal_code; +``` + +実行後、`14行が更新されました(This statement modified 14 rows)` のようなメッセージが表示されれば成功です。 + +### 4.2 なぜこの書き方をするのか + +BigQueryのDML `UPDATE` 文は `FROM` 句で別テーブル(ここでは他プロジェクトのAuthorized View)と結合し、条件に合致した行だけを更新できます。1行ずつUPDATEを繰り返すのではなく、このように**条件付きの一括更新**にすることが公式に推奨されているベストプラクティスです。 +出典: [Data manipulation language (DML) statements in GoogleSQL](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax)、[Transform data with DML | BigQuery](https://cloud.google.com/bigquery/docs/data-manipulation-language) + +`WHERE` 句で結合条件(`vw.zip_code = cust.postal_code`)を1件のソース行に一意に絞れない場合、`UPDATE/MERGE must match at most one source row for each target row` というランタイムエラーになります。ソース側(ビュー)にzip_codeの重複がないか事前に確認しておくと安全です。 +出典: [Data manipulation language (DML) statements in GoogleSQL](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax) + +### 4.3 詰まりやすいポイント + +- `postal_code` と `zip_code` の**データ型が一致しているか**を確認してください(片方がSTRING、もう片方がINT64だと結合条件が一致せず更新件数が0になります)。 +- Task 1でCustomerユーザーへの権限付与が漏れていると、このUPDATE文は他プロジェクトのビューを参照できずエラーになります。エラーが出たらまずTask 1のIAM設定に戻って確認するのが早道です。 + +--- + +## 5. Task 3: 顧客承認済みビュー(Customer authorized view)の作成 + +### 5.1 手順 + +1. 顧客プロジェクトの `customer_dataset` に、以下のクエリでビューを作成する。 + +```sql +SELECT + county, +COUNT(1) AS Count +FROM + `Customer A Project ID.customer_dataset.customer_info` cust +GROUP BY + county +HAVING county is not null +``` + +2. ビューを**承認**する。 +3. パートナーユーザー(Partner username)に **BigQuery Data Viewer** ロールを付与する。 + +### 5.2 なぜこの設計が良いのか + +このビューは生の `customer_info` テーブルを丸ごと見せるのではなく、`county` ごとの件数という**集計済みの粒度**だけを公開しています。これはAuthorized Viewの典型的な使い方で、「相手に必要な情報の粒度だけを渡し、個々の顧客レコードのような機微な情報は渡さない」というデータ最小化の原則にも合致します。 +出典: [Create an authorized view | BigQuery](https://cloud.google.com/bigquery/docs/create-authorized-views)(「列やフィールドを絞り込んで結果を返せる」という記載) + +権限付与についても、Task 1と同じく「必要な相手に、必要な粒度のデータだけを、最小権限で」というIAMの最小権限の原則(Principle of Least Privilege)に沿っています。 +出典: [Use IAM securely | Google Cloud](https://cloud.google.com/iam/docs/using-iam-securely) + +### 5.3 詰まりやすいポイント + +- `HAVING county is not null` を忘れると、`county` が補完されなかった顧客(Task 2のUPDATEで一致しなかった行)が `null` の集計行として混入し、Task 4のグラフが歪みます。 +- **Data Viewerロールだけではクエリを実行できないケース**に注意してください。`roles/bigquery.dataViewer` にはデータを読む権限は含まれますが、ジョブを実行する権限(`bigquery.jobs.create`)は含まれていません。ラボ環境では通常プロジェクトの基本ロールで担保されていますが、実務で同じ構成を組む場合は `roles/bigquery.jobUser` を別途プロジェクトレベルで付与する必要があります。 + 出典: [BigQuery IAM roles and permissions](https://cloud.google.com/bigquery/docs/access-control) + +--- + +## 6. Task 4: Looker Studio(旧 Data Studio)での可視化 + +### 6.1 用語について + +ラボの手順書には「Data Studio」と記載されていますが、このプロダクトは **Looker Studio** に名称変更されています。操作画面や手順自体は同一ですので、「Data Studio」という表記が出てきたら「Looker Studio」と読み替えてください。 + +### 6.2 手順 + +1. Looker Studio (`lookerstudio.google.com`) を開き、空のレポート(Blank Report)を作成する。 +2. BigQueryコネクタを選択し、Googleアカウントを認証する。 +3. 「My Projects」から顧客プロジェクトへ移動し、`Customer authorized view` を選択してレポートに追加する。 +4. レポート名を指定された名前に設定する。 +5. 縦棒グラフ(Vertical Bar Chart)を挿入する。 +6. `county` をディメンションに、`Count` を内訳ディメンション(Breakdown Dimension)およびメトリクスに設定する。 + +### 6.3 なぜこの手順なのか + +Looker StudioからBigQueryに接続する際、テーブルではなく**あらかじめ集計・整形されたビュー**に接続するのは公式にも推奨されているパターンです。ダッシュボード側で毎回重い集計クエリを走らせるより、ビュー側で事前集計しておく方が表示速度とコストの両面で有利です。今回のCustomer authorized viewは、まさにこの「ビュー経由で接続する」パターンの実例になっています。 +出典: [Connect to Google BigQuery | Looker Studio](https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery) + +### 6.4 詰まりやすいポイント + +- 「My Projects」に顧客プロジェクトが表示されない場合、認証しているGoogleアカウントにCustomer authorized viewへのData Viewerロールが付与されているか(Task 3の最後の手順)を再確認してください。 +- 縦棒グラフのフィールド設定で「ディメンション」と「内訳ディメンション」を混同しやすいので注意してください。ディメンションはX軸の分類(`county`)、内訳ディメンションは色分けの基準、メトリクスは棒の高さ(`Count`)を決めます。 + +--- + +## 7. よくあるエラーと対処法 + +| 症状 | 主な原因 | 対処方法 | +|---|---|---| +| ビューは作成できるが、相手がクエリすると権限エラーになる | 「ビューの承認」と「相手ユーザーへのIAM付与」のどちらか、または両方が未実施 | 承認とIAM付与は別工程。両方が完了しているかIAMポリシーの画面で確認する | +| Task 2のUPDATE文の更新件数が0件になる | `zip_code` と `postal_code` の型不一致、または結合条件に一致する行が存在しない | 両カラムの型を確認し、必要ならCASTする。件数がおかしい場合はSELECTで結合結果を先に確認する | +| UPDATE/MERGE must match at most one source row というエラー | ソース側(ビュー)の結合キーに重複がある | `GROUP BY` や `DISTINCT` でソース側の重複を排除してから結合する | +| Looker StudioでCustomer authorized viewが選択肢に出てこない | 認証アカウントにData Viewerロールが付与されていない、または別プロジェクトを見ている | Task 3のIAM付与を再確認し、「My Projects」で正しいプロジェクトを選び直す | +| Data Viewerロールを付与したのにクエリ実行時にエラーになる | Data Viewerロールにはジョブ実行権限(bigquery.jobs.create)が含まれない | プロジェクトレベルで roles/bigquery.jobUser を追加で付与する | + +--- + +## 8. ベストプラクティスまとめ + +| 観点 | ベストプラクティス | 出典 | +|---|---|---| +| データ共有の粒度 | 元テーブルを直接公開せず、必要な列・集計結果だけをビューとして公開する | [Create an authorized view](https://cloud.google.com/bigquery/docs/create-authorized-views) | +| 権限設計 | 常に最小権限(Data Viewerなど必要最小限のロール)を、必要な相手にのみ付与する | [Use IAM securely](https://cloud.google.com/iam/docs/using-iam-securely) | +| 複数ビューの承認管理 | 同一データセットに対するビューが増えたらAuthorized Datasetへの切り替えを検討する | [Authorized datasets](https://cloud.google.com/bigquery/docs/authorized-datasets) | +| 大量データの更新 | UPDATEは1行ずつでなく条件付きの一括更新にする。頻繁に更新する場合はクラスタリングも検討する | [Transform data with DML](https://cloud.google.com/bigquery/docs/data-manipulation-language) | +| BIツールとの接続 | 生テーブルではなく、事前集計済みのビュー経由で接続しダッシュボードの速度とコストを最適化する | [Connect to Google BigQuery \| Looker Studio](https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery) | +| 組織を越えたスケール | 個別のAuthorized Viewの手動運用が煩雑になったら、カタログ化・モニタリング機能を持つ BigQuery sharing(旧 Analytics Hub)への移行を検討する | [Introduction to BigQuery sharing](https://cloud.google.com/bigquery/docs/analytics-hub-introduction) | + +### 補足: このラボの先にある選択肢(BigQuery sharing / 旧Analytics Hub) + +このラボで使ったAuthorized Viewは、少数のプロジェクト間でのシンプルな共有には最適です。一方、共有先が増えたり、組織をまたいだデータ交換を継続的に運用する必要がある場合は、**BigQuery sharing(旧称 Analytics Hub)**という上位の仕組みがあります。これはデータをコピーせずに「リンクされた読み取り専用データセット」として提供し、サブスクライバーの利用状況もモニタリングできる、より運用性の高い共有基盤です。今回学んだAuthorized Viewの考え方(元データへの直接アクセスを渡さず、結果だけを共有する)は、BigQuery sharingでも土台として使われています。 +出典: [Introduction to BigQuery sharing | BigQuery](https://cloud.google.com/bigquery/docs/analytics-hub-introduction) + +--- + +## 9. 参考文献 / ソース一覧 + +- Authorized views(概要): https://cloud.google.com/bigquery/docs/authorized-views +- Create an authorized view(作成手順): https://cloud.google.com/bigquery/docs/create-authorized-views +- Authorized datasets: https://cloud.google.com/bigquery/docs/authorized-datasets +- Control access to resources with IAM | BigQuery: https://cloud.google.com/bigquery/docs/control-access-to-resources-iam +- BigQuery IAM roles and permissions: https://cloud.google.com/bigquery/docs/access-control +- Use IAM securely | Google Cloud: https://cloud.google.com/iam/docs/using-iam-securely +- Data manipulation language (DML) statements in GoogleSQL: https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax +- Transform data with data manipulation language (DML): https://cloud.google.com/bigquery/docs/data-manipulation-language +- Connect to Google BigQuery | Looker Studio: https://cloud.google.com/looker/docs/studio/connect-to-google-bigquery +- Introduction to BigQuery sharing(旧 Analytics Hub): https://cloud.google.com/bigquery/docs/analytics-hub-introduction +- ラボ本体: https://www.skills.google/course_templates/657/labs/591412 From d32f49151abd93d6137ecb90192bedf59fa07f61 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:42 +0900 Subject: [PATCH 003/123] docs(gcp): add network security and service account IAM best practices guides --- Gcp-pcne-s5-network-security.html | 4338 +++++++++++++++++++ Gcp-pcne-s5-network-security.md | 1143 +++++ Gcp-service-account-iam-best-practices.html | 1560 +++++++ 3 files changed, 7041 insertions(+) create mode 100644 Gcp-pcne-s5-network-security.html create mode 100644 Gcp-pcne-s5-network-security.md create mode 100644 Gcp-service-account-iam-best-practices.html diff --git a/Gcp-pcne-s5-network-security.html b/Gcp-pcne-s5-network-security.html new file mode 100644 index 000000000..a0d823dc6 --- /dev/null +++ b/Gcp-pcne-s5-network-security.html @@ -0,0 +1,4338 @@ + + + + + + PCNE S5: ネットワークセキュリティの設計と実装 + + + + + +
+ + +
+
+ PROFESSIONAL CLOUD NETWORK ENGINEER 対策ガイド +

S5: ネットワークセキュリティの設計と実装

+
+
+ 対象試験Professional Cloud Network Engineer +
+
+ 対応範囲Exam Guide Section 6(出題比率 約13%) +
+
+ レベル中級者〜上級者 +
+
+ 図解方針Mermaid + Markdown表(ASCII図解なし) +
+
+
+ 対応範囲: 公式Exam Guide + Section 6「Configuring, implementing and managing a cloud network + security solution」。Cloud Armor・Cloud NGFW / VPCファイアウォール・Cloud NAT・Secure Web + Proxy・セルフマネージドNVA / Packet + Mirroringの4タスクを、各項目の詳細説明とベストプラクティス、公式ドキュメントの出典URLとともに解説します。 +
+
+ +

この章の対象範囲(スコープ対応表)

+

公式Exam Guideの原文タスクと、本ガイドのPartの対応関係は以下の通りです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Exam Guide タスク番号原文タイトル本ガイドでの構成
6.1 Configuring Google Cloud Armor policies + エッジ/バックエンドセキュリティポリシー、WAFルール、DDoS/Adaptive + Protection、レート制限、bot管理、Threat Intelligence + Part 1
+ 6.2 Configuring and managing NGFW policies and VPC Firewall + rules + + ファイアウォール戦略、階層評価、GKE/LB対応、L7検査、移行、ルール基準、ロギング、マイクロセグメンテーション、階層(Essentials/Standard/Enterprise) + Part 2
+ 6.3 Configuring and securing internet egress traffic using + Public Cloud NAT and Secure Web Proxy + + Cloud NAT IPアドレッシング、ポート割り当て、Secure Web Proxy構成 + Part 3
+ 6.4 Configuring self-managed network virtual appliance and + Packet Mirroring + + マルチNIC + VMルーティング、ILBネクストホップ、ポリシーベースルート、アウトオブバンド統合、Packet + Mirroring + Part 4
+
+
+

+ 出典: + Professional Cloud Network Engineer Certification exam guide (PDF) +

+
+ +

+ Part 1: Google Cloud Armorポリシーの構成 +

+

+ 1.1 Cloud Armorのアーキテクチャと適用ポイント +

+

+ Cloud Armorは、Googleのグローバルネットワークのエッジ(Point of Presence, + PoP)で動作するセキュリティサービスです。リクエストがバックエンドに到達する前に、可能な限りソースに近い場所でフィルタリング・レート制限・リダイレクトを行うことで、不要なトラフィックがVPCネットワークやバックエンドリソースを消費するのを防ぎます。 +

+

+ Cloud + Armorのセキュリティポリシーには複数の種類があり、それぞれ適用対象(アタッチ先)が異なります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ポリシー種別type フラグアタッチ先主な用途
バックエンドセキュリティポリシー省略時のデフォルト + 外部ALB・内部リージョンALB・グローバル外部プロキシNLBのバックエンドサービス/バックエンドバケット + WAF、L7フィルタリング、レート制限、bot管理
エッジセキュリティポリシーCLOUD_ARMOR_EDGEバックエンドバケットやキャッシュ可能コンテンツキャッシュされたコンテンツの保護
ネットワークエッジセキュリティポリシーCLOUD_ARMOR_NETWORKリージョンの「ネットワークエッジセキュリティサービス」 + 外部パススルーNLB・プロトコルフォワーディング・パブリックIP + VMへの高度なネットワークDDoS防御 +
内部サービスセキュリティポリシー—Cloud Service MeshのエンドポイントポリシーBService Mesh内でのフェアシェアレート制限
+
+

対応するロードバランサーの種類は以下の通りです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ロードバランサー種別Cloud Armor(バックエンドポリシー)対応
グローバル外部Application Load Balancer(Classic含む)○
リージョン内部Application Load Balancer○
グローバル外部プロキシNetwork Load Balancer(TCP/SSL)○
外部パススルーNetwork Load Balancer△(ネットワークエッジセキュリティポリシー経由でDDoS防御のみ)
+
+
+flowchart LR
+    subgraph Internet["インターネット"]
+        Client["クライアント"]
+    end
+
+    subgraph Edge["Googleエッジ(PoP) — Cloud Armor評価ポイント"]
+        CA["Cloud Armor<br/>セキュリティポリシー評価<br/>許可 / 拒否 / レート制限 / リダイレクト"]
+    end
+
+    subgraph GCP["Google Cloudネットワーク内部"]
+        LB["外部ロードバランサー<br/>(Application / Proxy Network)"]
+        BE1["バックエンドサービス<br/>(MIG / NEG / サーバーレスNEG)"]
+        BE2["バックエンドバケット<br/>(Cloud Storage)"]
+    end
+
+    Client -->|"HTTPS リクエスト"| CA
+    CA -->|"ALLOW"| LB
+    CA -->|"DENY"| Dropped["リクエスト破棄<br/>(バックエンドへ到達しない)"]
+    LB --> BE1
+    LB --> BE2
+
+    style CA fill:#1a73e8,color:#fff
+    style Dropped fill:#d93025,color:#fff
+
+

出典:

+ +
+
+

+ ベストプラクティス: + バックエンドサービスを新規作成した際は、必ずCloud + Armorセキュリティポリシーのアタッチ漏れがないか確認してください。アタッチされていないバックエンドサービスはCloud + Armorの保護対象外となり、既知の攻撃パターンに対して無防備な状態になります。 +

+
+
+

+ 1.2 セキュリティポリシーの評価順序とルール構造 +

+

+ Cloud + Armorのルール評価順序は優先度(priority)の数値が小さいほど高優先度で、最も低い数値のルールから順に評価されます。マッチしたルールのアクションが即座に適用され、それ以降のルールは評価されません。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
アクション説明
allowトラフィックを許可し、バックエンドへ転送
deny(403/404/502等)指定したHTTPステータスコードでリクエストを拒否
rate_based_ban閾値を超えたクライアントを一定時間バン
throttle閾値を超えたリクエストをスロットリング(一部を許可)
redirectreCAPTCHA評価や別URLへのリダイレクト
+
+
+flowchart TD
+    Start(["受信リクエスト"]) --> P1{"優先度が最も低い<br/>数値のルールから評価"}
+    P1 -->|"条件マッチ"| Act{"ルールのアクション"}
+    P1 -->|"マッチなし"| Next["次に優先度が低い<br/>(数値が大きい)ルールを評価"]
+    Next --> P1
+    P1 -->|"全ルール未マッチ"| Default["デフォルトルール<br/>(通常は allow)"]
+
+    Act -->|"allow"| Allowed["バックエンドへ転送"]
+    Act -->|"deny"| Denied["拒否レスポンス<br/>(403等)を返却"]
+    Act -->|"throttle / rate_based_ban"| RateCheck["レート制限判定へ"]
+    Act -->|"redirect"| Redirect["reCAPTCHA評価 or<br/>指定URLへリダイレクト"]
+
+    style Denied fill:#d93025,color:#fff
+    style Allowed fill:#188038,color:#fff
+
+

+ 出典: + Create and manage security policies +

+
+
+

+ ベストプラクティス: ルールの優先度は100, 1000, + 2000のように間隔を空けて採番し、後から緊急ルールを既存ルールの間に挿入できる余地を残してください。国コード(ISO + 3166-1 + alpha-2)による地域制限を行う場合は、各コードが独立して評価される点に注意し、意図しない許可漏れがないかテストしてください。 +

+
+
+

+ 1.3 プリコンフィグドWAFルール(OWASP CRS) +

+

+ Cloud Armorのプリコンフィグド(事前構成済み)WAFルールは、OWASP ModSecurity Core + Rule + Set(CRS)をベースにした署名(シグネチャ)群です。SQLインジェクション(sqli)、クロスサイトスクリプティング(xss)、リモートファイルインクルージョン(rfi)、ローカルファイルインクルージョン(lfi)、リモートコード実行(rce)、スキャナー検出(scannerdetection)など、OWASP + Top 10に対応する攻撃カテゴリごとにルールが用意されています。 +

+

+ ルール名の形式は + <攻撃カテゴリ>-<CRSバージョン>-<バージョンフィールド> + です(例: + xss-v422-stable、sqli-v33-stable)。Googleは最新の保護のためCRS + 4.22 の使用を推奨しており、CRS 3.0系は非推奨です。 +

+

+ 各シグネチャには感度レベル(sensitivity level)0〜4が設定されており、OWASPのパラノイアレベルに対応します。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
感度レベル特性
0ルール無効(デフォルトでは何も有効化されない)
1(低)高確信度シグネチャのみ。誤検知(false positive)が最も少ない
2〜3(中)セキュリティと誤検知リスクのバランス
4(高、デフォルト)有効化時に全シグネチャを評価。誤検知リスクが最も高い
+
+
# 感度レベル1でSQLiルールをプレビューモードで作成する例
+evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})
+
+# 特定のシグネチャIDを除外(誤検知対策)
+evaluatePreconfiguredWaf('xss-v422-stable', {'opt_out_rule_ids': ['owasp-crs-v042200-id941100-xss']})
+
+

出典:

+ +
+
+

+ ベストプラクティス: + 本番環境への適用前に、必ずプレビューモード(--preview)で数週間ルールを稼働させ、誤検知の有無をログで確認してください。感度レベルは1から開始し、段階的に引き上げることで、正規トラフィックの誤ブロックを避けながらセキュリティレベルを高められます。 +

+
+
+

+ 注意: + 感度レベルを4(デフォルト)のまま本番運用に投入すると、レガシーAPIやリッチテキスト入力を許可するアプリケーションで想定以上の誤検知が発生する可能性があります。 +

+
+
+

+ 1.4 高度なネットワークDDoS防御とAdaptive Protection +

+

+ Cloud + ArmorのDDoS防御は、ネットワーク層(L3/L4)とアプリケーション層(L7)の2系統に分かれます。 +

+

+ ネットワーク層: 標準保護 vs 高度なネットワークDDoS防御 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目標準ネットワークDDoS防御高度なネットワークDDoS防御
有効化常時有効(操作不要) + Cloud Armor + Enterpriseへの加入とリージョン単位の明示的な設定が必要 +
対象 + Google + Cloud基盤の安定性維持が目的。クォータ超過トラフィックのスロットリングのみ + + 外部パススルーNLB・プロトコルフォワーディング・パブリックIP + VMへの標的型攻撃検知・緩和 +
攻撃シグネチャ検知なしあり(常時オンの volumetric attack detection)
適用単位Google Cloud全体 + リージョン単位(ネットワークエッジセキュリティサービスに関連付け) +
+
+
+flowchart TD
+    Start(["外部パススルーNLB / プロトコルフォワーディング / パブリックIP VM"]) --> Std["標準ネットワークDDoS防御<br/>(常時有効・操作不要)"]
+    Std --> Enroll{"Cloud Armor Enterpriseに<br/>加入しているか?"}
+    Enroll -->|"いいえ"| StdOnly["標準保護のみ<br/>(クォータ超過トラフィックの抑制)"]
+    Enroll -->|"はい"| CreatePolicy["type=CLOUD_ARMOR_NETWORK の<br/>セキュリティポリシーを作成"]
+    CreatePolicy --> EnableAdv["セキュリティポリシーで<br/>高度なDDoS防御を有効化"]
+    EnableAdv --> CreateService["リージョンにネットワークエッジ<br/>セキュリティサービスを作成し関連付け"]
+    CreateService --> Profiling["トラフィックプロファイリング<br/>(基準値の学習、目安24時間)"]
+    Profiling --> Advanced["常時オンの標的型<br/>攻撃検知・緩和が有効化"]
+
+    style Advanced fill:#188038,color:#fff
+    style StdOnly fill:#f9ab00,color:#000
+
+

出典:

+ +
+

+ アプリケーション層: Adaptive Protection +

+

+ Adaptive + Protectionは、機械学習によりバックエンドサービスへのトラフィックパターンの「正常な基準値(baseline)」を学習し、そこからの逸脱をL7 + DDoS攻撃(HTTPフラッド等)として検知・アラートするCloud Armor + Enterpriseの機能です。 +

+
+flowchart LR
+    A["通常トラフィックの<br/>継続的な学習"] --> B["基準値(baseline)の確立"]
+    B --> C{"トラフィックパターンが<br/>基準値から逸脱?"}
+    C -->|"いいえ"| A
+    C -->|"はい"| D["Cloud Loggingへ<br/>アラートを生成"]
+    D --> E["攻撃シグネチャ・<br/>確信度スコア(confidence score)・<br/>推奨WAFルールを算出"]
+    E --> F{"自動デプロイ<br/>(auto-deploy)が有効?"}
+    F -->|"いいえ"| G["インシデント対応者が<br/>手動でルールをレビュー・適用"]
+    F -->|"はい"| H{"確信度・負荷しきい値を<br/>超過?"}
+    H -->|"はい"| I["推奨ルールを自動デプロイ<br/>(有効期限付き)"]
+    H -->|"いいえ"| J["監視を継続"]
+
+    style D fill:#f9ab00,color:#000
+    style I fill:#d93025,color:#fff
+

Adaptive Protectionのアラートには以下の情報が含まれます。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目内容
確信度スコア(confidence score)トラフィックパターンの変化が異常である確からしさ(0〜1)
攻撃シグネチャ悪意あるHTTPヘッダー、クライアントの地理情報などの特徴
想定影響ベースライン率(impacted baseline rate) + 推奨ルールを適用した場合にブロックされる正常トラフィックの割合 +
推奨WAFルール攻撃シグネチャに一致するCloud Armorルール案
+
+
+

出典:

+ +
+
+

+ ベストプラクティス: + アラートポリシーの確信度しきい値は0.5程度の低い値から開始し、潜在的な攻撃の見逃しを避けてください。誤検知の許容範囲を確認しながら段階的に引き上げます。自動デプロイ(auto-deploy)を有効化する場合は、確信度しきい値を0.8以上、想定影響ベースライン率を0.01以下といった保守的な値に設定し、有効期限(2〜4時間程度)を必ず設定してください。まずは自動デプロイを無効にした手動レビュー運用で数週間の実績を積んでから自動化することを推奨します。 +

+
+
+

1.5 レート制限

+

+ レート制限ルールは、指定した集計キー(IPアドレス、reCAPTCHAトークン、HTTPヘッダー等)ごとにリクエスト数を集計し、しきい値超過時にthrottle(一部リクエストの間引き)またはrate_based_ban(一定時間の完全ブロック)を適用します。 +

+
+ + + + + + + + + + + + + + + + + +
アクション動作
throttle + しきい値を超えたリクエストの一部を拒否し、許可レートまで抑制 +
rate_based_ban + しきい値を超えたクライアントを、指定した期間にわたって完全にブロック +
+
+

+ カスタムエラーレスポンスを設定することで、レート制限時にエンドユーザーへ独自のエラーメッセージを返すことも可能です。 +

+
+

+ 出典: + Rate limiting overview、Configure rate limiting +

+
+
+

+ ベストプラクティス: 初期波状攻撃(initial + wave)への対処にはthrottle、それでも継続する攻撃者にはrate_based_banという2段階の防御を組み合わせてください。reCAPTCHA連携時は、アクショントークン・セッショントークン・免除Cookieの再利用によるトークン濫用を防ぐため、それぞれに対するレート制限ルールを個別に設定することが推奨されます。 +

+
+
+

1.6 Bot管理(reCAPTCHA連携)

+

+ Cloud Armorのbot管理は、reCAPTCHA + Enterpriseと統合し、高度なリスク分析によって人間のユーザーと自動化クライアントを区別します。reCAPTCHAはリクエストのリスク属性を暗号化トークンとして発行し、Cloud + Armorはこのトークンをインラインで復号します(reCAPTCHAサービスへの追加リクエストは不要)。トークンの属性に基づき、トラフィックを許可・拒否・レート制限・リダイレクトできます。 +

+

+ レート制限ルールはbot管理機能と組み合わせ可能で、しきい値超過時にreCAPTCHA評価へのリダイレクトや、免除Cookie・トークンを悪用するクライアントのバンといった制御ができます。 +

+
+

+ 出典: + Bot management overview +

+
+
+

+ ベストプラクティス: + reCAPTCHA免除Cookieやトークンを使い回すクライアント(トークン濫用)を防ぐため、アクショントークン・セッショントークン・免除Cookieそれぞれをキーとしたレート制限ルールを設定してください。クレデンシャルスタッフィングやスクレイピング、在庫買い占め攻撃などの不正取引対策として、reCAPTCHA + Enterpriseのスコアベース評価と組み合わせることで検知精度が向上します。 +

+
+
+

1.7 Google Threat Intelligence

+

+ Google Threat Intelligenceは、Cloud Armor + Enterpriseの購読者向けに、Google/Mandiantが継続的に更新する脅威データフィードに基づいてトラフィックを許可・拒否できる機能です。evaluateThreatIntelligence('FEED_NAME')というマッチ式を用いて構成します。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
カテゴリ(フィード)説明
Torエグジットノード + 匿名通信を可能にするTorネットワークの出口ポイントのIPアドレス +
既知の悪意あるIPアドレス + Webアプリケーションへの攻撃の発信元として実績のあるIPアドレス +
Bad bots悪意のあるボット由来と判定されたトラフィック
パブリッククラウドエンドポイント主要パブリッククラウドプロバイダーのIPレンジ
+
+

+ フィード内の情報は継続的に更新されるため、追加の運用作業なしに新しい脅威に対する保護が維持されます。 +

+
+

+ 出典: + Apply Google Threat Intelligence +

+
+
+

+ ベストプラクティス: + Torエグジットノードやパブリッククラウドエンドポイントの一律ブロックは、正規のプライバシー重視ユーザーや正規のクラウド間トラフィックを誤って遮断するリスクがあるため、まずはthrottleや監視目的のログ記録から開始し、業務要件に応じてdenyへ段階的に移行することを検討してください。 +

+
+
+

1.8 Part 1 ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
領域ベストプラクティス
ポリシー適用漏れ防止 + 新規バックエンドサービス作成時は必ずCloud + Armorポリシーのアタッチを確認する +
ルール優先度設計 + 優先度番号は100・1000単位で間隔を空け、緊急ルール挿入の余地を残す +
WAFルール導入 + プレビューモードで数週間検証してから本番適用。感度レベルは1から段階的に引き上げる +
DDoS防御 + 外部パススルーNLB/プロトコルフォワーディング/パブリックIP + VMを保護する場合は高度なネットワークDDoS防御への加入を検討する +
Adaptive Protection + 確信度0.5から監視を開始し、自動デプロイは保守的なしきい値(確信度0.8以上)かつ有効期限付きで運用する +
レート制限 + throttleとrate_based_banを段階的に組み合わせ、reCAPTCHAトークンの再利用も監視する +
Threat Intelligence一律ブロックの前に監視・throttleで影響範囲を確認する
監視 + Adaptive Protectionイベント・Cloud Armorログ・Security Command + CenterのCloud Armorカードを定期的にレビューする +
+
+
+

+ Part 2: Cloud NGFW / VPCファイアウォールルールの構成と管理 +

+

+ 2.1 ファイアウォール戦略とポリシー種別 +

+

+ Google + Cloudのファイアウォールは、Andromedaネットワーク仮想化スタックの一部として完全分散型・ホストベースで実装されており、各VMのネットワークインターフェースに対して直接プログラムされます。ポリシーの種類ごとに適用範囲とIAM統合の粒度が異なります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ポリシー種別適用範囲Secure Tags対応Network Tags対応課金(有料機能利用時)
階層ファイアウォールポリシー組織・フォルダ全体(複数VPC・複数プロジェクトに横断適用)○×機能に応じて課金
リージョンシステムファイアウォールポリシーGoogle管理(GKE等の自動生成ルール)——課金なし
VPCファイアウォールルール(classic)単一のVPCネットワーク×○Essentials機能のみで課金なし
グローバルネットワークファイアウォールポリシー単一VPCの全リージョン○×機能に応じて課金
リージョンネットワークファイアウォールポリシー単一VPCの特定リージョン○×機能に応じて課金
+
+

ファイアウォール戦略の設計指針:

+
    +
  1. + 組織全体で強制すべき絶対要件(既知の悪意あるIP範囲のブロック、ヘルスチェックの許可等)は階層ファイアウォールポリシーで一元管理する。 +
  2. +
  3. + VPCネットワーク単位・リージョン単位の柔軟なルールはグローバル/リージョンネットワークファイアウォールポリシーで管理する。 +
  4. +
  5. + レガシー環境やシンプルな単一VPC構成では引き続きVPCファイアウォールルールを使うことも可能だが、Googleは新規機能をすべてファイアウォールポリシー側にのみ追加する方針であり、長期的にはネットワークファイアウォールポリシーへの移行が推奨される。 +
  6. +
+
+

出典:

+ +
+
+

+ 2.2 ファイアウォールルールの評価順序 +

+

+ VPCネットワークにはネットワークファイアウォールポリシー適用順序(network + firewall policy enforcement + order)という設定があり、グローバル/リージョンネットワークファイアウォールポリシーをVPCファイアウォールルールより前に評価するか後に評価するかを制御します。 +

+
+ + + + + + + + + + + + + + + + + + + + +
適用順序デフォルト説明
AFTER_CLASSIC_FIREWALL○(デフォルト) + VPCファイアウォールルールを、グローバル/リージョンネットワークファイアウォールポリシーより先に評価 +
BEFORE_CLASSIC_FIREWALL— + グローバル/リージョンネットワークファイアウォールポリシーを、VPCファイアウォールルールより先に評価 +
+
+

+ 階層ファイアウォールポリシーとリージョンシステムファイアウォールポリシーは、適用順序の設定に関わらず常に最初に評価されます。 +

+
+flowchart TD
+    Start(["新規接続パケット到着<br/>(Ingress / Egress)"]) --> Hier["① 階層ファイアウォールポリシー<br/>(組織 → フォルダ、常に最優先)"]
+    Hier -->|"allow / deny"| Stop1["評価終了・アクション適用"]
+    Hier -->|"goto_next<br/>または未マッチ"| Sys["② リージョンシステム<br/>ファイアウォールポリシー(Google管理)"]
+    Sys -->|"allow / deny"| Stop1
+    Sys -->|"goto_next<br/>または未マッチ"| Order{"ネットワークファイアウォール<br/>ポリシー適用順序は?"}
+
+    Order -->|"AFTER_CLASSIC_FIREWALL<br/>(デフォルト)"| VPC1["③ VPCファイアウォールルール"]
+    VPC1 -->|"allow / deny"| Stop1
+    VPC1 -->|"未マッチ"| Global1["④ グローバルネットワーク<br/>ファイアウォールポリシー"]
+    Global1 -->|"allow / deny / apply_security_profile_group"| Stop1
+    Global1 -->|"goto_next<br/>または未マッチ"| Regional1["⑤ リージョンネットワーク<br/>ファイアウォールポリシー"]
+    Regional1 -->|"allow / deny"| Stop1
+    Regional1 -->|"goto_next<br/>または未マッチ"| Implied1["⑥ 暗黙のアクション<br/>(Ingress:deny / Egress:allow)"]
+
+    Order -->|"BEFORE_CLASSIC_FIREWALL"| Global2["③ グローバルネットワーク<br/>ファイアウォールポリシー"]
+    Global2 -->|"allow / deny / apply_security_profile_group"| Stop1
+    Global2 -->|"goto_next<br/>または未マッチ"| Regional2["④ リージョンネットワーク<br/>ファイアウォールポリシー"]
+    Regional2 -->|"allow / deny"| Stop1
+    Regional2 -->|"goto_next<br/>または未マッチ"| VPC2["⑤ VPCファイアウォールルール"]
+    VPC2 -->|"allow / deny"| Stop1
+    VPC2 -->|"未マッチ"| Implied2["⑥ 暗黙のアクション<br/>(Ingress:deny / Egress:allow)"]
+
+    style Stop1 fill:#188038,color:#fff
+    style Implied1 fill:#f9ab00,color:#000
+    style Implied2 fill:#f9ab00,color:#000
+

各ステップにおける評価ロジックは共通しており、次の3段階で処理されます。

+
    +
  1. ターゲットが一致しないルールを除外する。
  2. +
  3. パケットの方向(ingress/egress)が一致しないルールを除外する。
  4. +
  5. + 残ったルールを優先度の高い順(数値が小さい順)に評価し、ターゲットに適用されるルールがマッチするか、マッチするルールがなくなるまで続ける。 +
  6. +
+

+ 最終ステップの暗黙のアクション(implied + action)は方向とターゲットによって異なります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
トラフィック方向ターゲット暗黙のアクション
IngressVMインスタンスのネットワークインターフェースdeny
Ingress内部ALB/内部プロキシNLBのフォワーディングルールallow
Egress(すべて)allow
+
+

+ VPCファイアウォールルールで2つのルールが同一優先度でマッチした場合、denyルールがallowルールより優先して適用されます。 +

+
+

+ 出典: + Evaluation order for firewall policies and rules +

+
+
+

+ ベストプラクティス: + 適用順序を変更する強い理由がない限り、デフォルトのAFTER_CLASSIC_FIREWALLを維持してください。新規に大規模なファイアウォール基盤を構築する場合、レガシーなVPCファイアウォールルールへの依存を避け、ネットワークファイアウォールポリシー(グローバル/リージョナル)へ統一することで、Secure + Tagsによる一貫したIAM統制と将来の機能追加の恩恵を受けられます。 +

+
+
+

+ 2.3 階層ファイアウォールポリシーとEffective Rules +

+

+ 階層ファイアウォールポリシーは組織・フォルダに関連付けられるコンテナで、下位のポリシーやVPCファイアウォールルールへ評価を委譲するgoto_nextアクションを持つのが特徴です。組織レベルの上位ルールは、下位のフォルダ・プロジェクトのルールで上書きできません。 +

+
+flowchart TD
+    Pkt(["ターゲットVMへの新規接続パケット"]) --> Org["組織レベルの<br/>階層ファイアウォールポリシー"]
+    Org -->|"allow"| AllowOrg["許可・評価終了"]
+    Org -->|"deny"| DenyOrg["拒否・評価終了"]
+    Org -->|"apply_security_profile_group"| SPG["ファイアウォールエンドポイントへ転送<br/>(L7検査)・評価終了"]
+    Org -->|"goto_next"| F1["トップレベルフォルダの<br/>階層ファイアウォールポリシー"]
+    F1 -->|"allow / deny / apply_security_profile_group"| Term1["評価終了"]
+    F1 -->|"goto_next"| F2["...ターゲットを含む<br/>下位フォルダのポリシー"]
+    F2 -->|"allow / deny / apply_security_profile_group"| Term2["評価終了"]
+    F2 -->|"goto_next<br/>または全ポリシー評価完了"| Next["次の評価ステップ<br/>(リージョンシステムポリシーへ)"]
+
+    style AllowOrg fill:#188038,color:#fff
+    style DenyOrg fill:#d93025,color:#fff
+    style Term1 fill:#188038,color:#fff
+    style Term2 fill:#188038,color:#fff
+

+ Effective Firewall Rules(実効ファイアウォールルール)は、あるVPCネットワークやVMインターフェースに実際に適用されているルール群を可視化する機能です。階層ファイアウォールポリシー由来のルール、VPCファイアウォールルール、グローバル/リージョンネットワークファイアウォールポリシー由来のルールを、組織レベルからVPCネットワークまでの順序で一覧表示します。 +

+
# ネットワーク全体の実効ファイアウォールルールを表示
+gcloud compute networks get-effective-firewalls NETWORK_NAME
+
+

出典:

+ +
+
+

+ ベストプラクティス: + 組織レベルのポリシーは「絶対に守るべき最小限のルール」に留め、goto_nextを積極的に使って評価を下位へ委譲してください。過度に制限的な組織ポリシーは、各チームの自律的な運用を妨げる摩擦の原因になります。トラブルシューティング時は必ずEffective + Firewall + Rulesで実際の適用状況を確認し、想定と異なる階層でルールがブロックされていないか検証してください。 +

+
+
+

+ 2.4 Cloud NGFWの3つの階層(Essentials/Standard/Enterprise) +

+

+ Cloud + NGFWは3つの階層(ティア)で提供され、階層が上がるほど高度な機能と、それに応じた課金体系が適用されます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ティア主な機能課金対象トラフィック
Essentials + 標準的なネットワーク属性(IPレンジ・ポート・プロトコル)によるルール、Secure + Tags、アドレスグループ、階層/グローバル/リージョンポリシー基盤 + 課金なし(無料)
Standard + Essentialsの全機能 + + FQDNオブジェクト、ジオロケーションオブジェクト、Google Threat + Intelligence(NGFW版) + 南北トラフィック(インターネット⇔VM)のみ課金
Enterprise + Standardの全機能 + + レイヤー7検査(URLフィルタリングサービス、侵入検知防止サービス + IDPS) + 南北 + 東西トラフィック(Google Cloudリソース間)を課金
+
+
+flowchart LR
+    subgraph Essentials["Essentials(無料)"]
+        E1["Secure Tags"]
+        E2["アドレスグループ"]
+        E3["階層/グローバル/<br/>リージョンポリシー基盤"]
+    end
+    subgraph Standard["Standard(南北トラフィック課金)"]
+        S1["FQDNオブジェクト"]
+        S2["ジオロケーション<br/>オブジェクト"]
+        S3["Threat Intelligence"]
+    end
+    subgraph Enterprise["Enterprise(南北+東西課金)"]
+        En1["URLフィルタリング<br/>サービス"]
+        En2["IDPS<br/>(侵入検知防止)"]
+        En3["TLS Inspection"]
+    end
+
+    Essentials --> Standard --> Enterprise
+
+    style Essentials fill:#188038,color:#fff
+    style Standard fill:#f9ab00,color:#000
+    style Enterprise fill:#1a73e8,color:#fff
+

+ コスト最適化パターン: + 課金はルールが評価された時点(トラフィックフローが有料機能を含むルールによって評価された時点)で発生するため、Essentials機能のみを使うルールをより高い優先度(小さい数値)に配置し、大部分のトラフィックをそこで処理させることで、有料ティアの評価対象を必要最小限に絞り込めます。 +

+
+flowchart TD
+    Traffic(["受信トラフィック"]) --> R1["優先度1000(高優先度):<br/>Essentials機能のみのルール<br/>(IPアドレス・タグベース)<br/>→ 課金なし"]
+    R1 -->|"マッチ"| Done1["処理完了(無料)"]
+    R1 -->|"未マッチ"| R2["優先度2000:<br/>Standard/Enterprise機能を含むルール<br/>(特定タグの組み合わせのみ対象)<br/>→ 該当トラフィックのみ課金"]
+    R2 -->|"マッチ"| Done2["IDPS検査等を実施<br/>(該当フローのみ課金)"]
+
+    style Done1 fill:#188038,color:#fff
+    style Done2 fill:#f9ab00,color:#000
+
+

出典:

+ +
+
+

+ ベストプラクティス: + データベース層など重要度の高いワークロードにのみIDPS検査(Enterprise機能)を適用し、東西トラフィック全体を無差別に検査対象にしないでください。Essentialsルールを高優先度に配置しバルクトラフィックを無料で処理する設計は、機能面だけでなくコスト面でも重要な設計判断です。 +

+
+
+

+ 2.5 レイヤー7検査: TLS Inspection・URLフィルタリング・IDPS +

+

+ Cloud NGFW + Enterpriseのレイヤー7検査機能は、ファイアウォールエンドポイントとセキュリティプロファイルという2つの構成要素で実現されます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
構成要素役割
ファイアウォールエンドポイント + 組織レベルのゾーンリソース。1つ以上のVPCに関連付けて傍受トラフィックを検査 +
セキュリティプロファイル + url-filtering(URLフィルタリングルール定義)またはthreat-prevention(IDPS設定)のいずれかの種別を持つ検査設定 +
セキュリティプロファイルグループ + 各種別1つずつのセキュリティプロファイルを含むコンテナ。apply_security_profile_groupアクションで参照 +
TLS Inspectionポリシー + Certificate Authority + Service(CAS)を用いて暗号化トラフィックを復号し、L7検査を可能にする設定 +
+
+

+ TLS + Inspectionは、GoogleマネージドのCAS経由で短命の中間証明書を生成し、傍受したTLSトラフィックを復号 + → L7検査(URLフィルタリング・IDPS) → + 再暗号化して送信先へ転送、という流れで動作します。プロトコルバージョンはTLS + 1.0〜1.3をサポートしますが、HTTP/2・QUIC・HTTP/3・PROXYプロトコルはTLS + Inspectionと併用できません。 +

+
+sequenceDiagram
+    participant VM as "送信元VM"
+    participant FW as "ファイアウォール<br/>ポリシールール"
+    participant EP as "ファイアウォール<br/>エンドポイント"
+    participant CAS as "Certificate Authority<br/>Service(CAS)"
+    participant SPG as "セキュリティプロファイル<br/>グループ(URL Filter/IDPS)"
+    participant Dest as "宛先"
+
+    VM->>FW: "TLS/HTTP(S) トラフィック"
+    FW->>FW: "apply_security_profile_group<br/>ルールにマッチ"
+    FW->>EP: "トラフィックを転送"
+    EP->>CAS: "中間証明書を要求(TLS Inspection時)"
+    CAS-->>EP: "短命の中間証明書を発行"
+    EP->>EP: "TLSを復号し、<br/>URLフィルタリング/IDPSを実行"
+    alt "検査結果: 許可"
+        EP->>Dest: "再暗号化して転送"
+    else "検査結果: 拒否"
+        EP->>VM: "接続を切断"
+    end
+

+ URLフィルタリングは、TLS + Inspectionが無効な場合でもTLSネゴシエーション時のSNI(Server Name + Indication)を用いてドメインマッチングが可能です。ただし完全なURLパスでのフィルタリングにはTLS + Inspectionが必要です。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: + URLフィルタリングのマッチャー文字列は優先度順に評価され、SNI/ドメイン情報を持たないトラフィックの扱いは最高優先度のURLフィルタ(明示的ALLOWまたは暗黙のDENY)によって決まります。ポリシーの末尾に優先度2147483647のワイルドカード拒否ルールを配置し、意図しない許可漏れを防ぐ「暗黙のdeny-all」を明示的に設計してください。Secure + Web Proxy(Part 3参照)と組み合わせる場合は、NGFW EnterpriseとSWPの双方でTLS + Inspectionを重複させないよう、NGFW側のtls_inspectを無効化することを検討してください。 +

+
+
+

+ 2.6 ファイアウォールルールの基準(criteria) +

+

+ ファイアウォールルール(VPCファイアウォールルール・ファイアウォールポリシールール共通)の主要な構成基準は以下の通りです。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
基準説明
優先度(priority) + 0〜65535の整数。数値が小さいほど高優先度。ポリシー内で一意である必要がある +
方向(direction)ingress(受信)またはegress(送信)
プロトコル/ポートTCP/UDP/ICMP等のプロトコルと、任意でポート範囲を指定
送信元(ingressの場合) + IPレンジ、Secure Tags/Network + Tags、サービスアカウント、FQDNオブジェクト(Standard以上)、ジオロケーション(Standard以上) +
宛先(egressの場合)同上
ターゲット + ルールを適用するリソース(全インスタンス、特定のSecure + Tags/Network Tags、特定のサービスアカウント) +
アクション + allow / deny / apply_security_profile_group / + goto_next(階層ポリシーのみ) +
ロギング + ルールごとに有効/無効を設定可能(goto_nextルールはロギング不可) +
+
+

+ REST + APIで階層ファイアウォールポリシールールを直接作成する場合は方向を明示的に指定する必要がありますが、gcloud + CLIでは方向省略時のデフォルトはINGRESSです。 +

+
+

+ 出典: + Manage hierarchical firewall policies and rules +

+
+
+

+ ベストプラクティス: + ルールには必ずdescriptionフィールドで意図を記録してください。半年後に見返した際、なぜそのルールが存在するのかをチーム全員が理解できることが、大規模組織でのファイアウォール運用の生命線になります。 +

+
+
+

+ 2.7 Secure Tags と Network Tags によるマイクロセグメンテーション +

+

Google Cloudには2種類の「タグ」があり、対応するポリシー種別が異なります。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目Secure Tags(IAM-governed tags)Network Tags(従来のタグ)
管理場所Resource Managerでキー・値のペアとして管理VMインスタンス/インスタンステンプレートに直接付与する文字列
アクセス制御あり(IAMで誰がタグを作成・付与できるか統制可能)なし(単なる文字列、アクセス制御機構を持たない)
対応ポリシー + 階層ファイアウォールポリシー、グローバル/リージョンネットワークファイアウォールポリシー + VPCファイアウォールルール(classic)のみ
VPCファイアウォールルールでの利用不可可能
適用範囲組織全体で一意なキー(最大1,000個のユニークな値を参照可能)VPCネットワークごとに独立した文字列
+
+

+ Secure + Tagsは、IAMによる厳格なアクセス制御のもとで、リージョン・ネットワーク構成に関わらずワークロードに一貫したポリシーを適用できるため、大規模なマイクロセグメンテーション基盤に適しています。GKEワークロードに対してもSecure + Tagsを付与できます。 +

+
+

+ 出典: + Secure tags for firewalls +

+
+
+

+ ベストプラクティス: + 新規に大規模なマイクロセグメンテーション設計を行う場合は、アクセス制御の効かないNetwork + Tagsではなく、Secure + Tagsを起点に設計してください。「誰がタグを付与できるか」をIAMで統制できることは、多数のチームが同じVPCを共有するShared + VPC環境において特に重要な統制ポイントになります。 +

+
+
+

2.8 ファイアウォールルールロギング

+

+ ファイアウォールルールロギングは、ルールごとに有効化する任意設定で、そのルールにマッチしたトラフィックの詳細(接続情報)をCloud + Loggingへ記録します。VPCファイアウォールルールとファイアウォールポリシールールでログフォーマットが異なるため、ログ基盤側でのパース処理は両方に対応させる必要があります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ロギング種別対象主な用途
VPCファイアウォールルールロギングclassic VPCファイアウォールルールレガシー環境のトラフィック可視化
ファイアウォールポリシールールロギング + 階層/グローバル/リージョンネットワークファイアウォールポリシー + 統合的なトラフィック監査・コンプライアンス証跡
Firewall Insights全ポリシー種別 + 過度に許可的なルール・未使用ルール・シャドウルールの検出と改善提案 +
+
+
+

+ 出典: + Logging for firewall policy rules +

+
+
+

+ ベストプラクティス: + すべてのdeny/allowルールでロギングを有効化するとログ量とコストが増大するため、コンプライアンス上重要な境界(組織/フォルダレベルのdenyルール、機密ワークロードへのアクセスを許可するルール)を優先的にロギング対象とし、内部の高頻度な東西トラフィックは必要に応じてサンプリングやFirewall + Insightsによる定期レビューで補完してください。 +

+
+
+

+ 2.9 VPCファイアウォールルールからCloud NGFWポリシーへの移行 +

+

+ Googleは移行ツール(gcloud beta compute firewall-rules migrate)を提供しており、既存のVPCファイアウォールルールをグローバルネットワークファイアウォールポリシーへ自動変換できます。 +

+
+flowchart TD
+    A["既存VPCファイアウォールルールの棚卸し<br/>(優先度・依存関係を記録)"] --> B{"Network Tags や<br/>サービスアカウントに依存するルールか?"}
+    B -->|"依存なし"| C["gcloud beta compute firewall-rules migrate<br/>--source-network --target-firewall-policy"]
+    B -->|"依存あり"| D["Network TagsをSecure Tagsへ<br/>マッピングしてから移行"]
+    D --> C
+    C --> E["移行ツールが新規グローバル<br/>ネットワークファイアウォールポリシーを生成<br/>(既存ルールをポリシールールへ変換)"]
+    E --> F["ポリシーを検証<br/>(get-effective-firewalls等で比較)"]
+    F --> G["gcloud compute network-firewall-policies<br/>associations create でVPCに関連付け"]
+    G --> H{"GKE自動生成ルールが<br/>含まれるか?"}
+    H -->|"はい"| I["GKE自動生成ルール(gke-*, k8s-*)は<br/>除外パターンで移行対象から外し、<br/>個別に移行手順を実施"]
+    H -->|"いいえ"| J["旧VPCファイアウォールルールを削除"]
+    I --> J
+
+    style J fill:#188038,color:#fff
+

+ 移行によって得られる主な利点は、Secure + Tagsを用いたIAM統制、バッチ編集による一括ルール更新、FQDNオブジェクト・ジオロケーションオブジェクト・Threat + Intelligenceといった高度な属性の利用、そして複数VPCへの単一ポリシーの共有です。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: + GKEが自動生成するVPCファイアウォールルール(gke-(.+)-ipv6-all、k8s-fw-*等の正規表現にマッチするルール)は移行ツールの対象から除外し、GKEサービスIP向けのingressルールを個別に手動作成した上で、既存の自動生成allowルールを無効化する専用手順に従ってください。移行直後はすぐに旧ルールを削除せず、Effective + Firewall + Rulesで新旧ポリシーの評価結果が一致することを確認してから削除作業に進むことを推奨します。 +

+
+
+

+ 2.10 GKEおよびCloud Load BalancingでのCloud NGFWサポート +

+

Cloud NGFWはGKEワークロードとCloud Load Balancingの双方に対応しています。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ワークロード種別対応内容
GKE Podレベル + Secure + TagsをPodに付与し、ネットワークポリシーと組み合わせたマイクロセグメンテーションが可能 +
GKEノードレベル + Essentials/Standard/Enterpriseいずれの機能もノードのVMインターフェースに適用可能 +
内部ALB/内部プロキシNLB + マネージドEnvoyプロキシに対してもファイアウォールルールがingress対象として適用される +
外部ALB(グローバル/リージョン) + グローバル/リージョンネットワークファイアウォールポリシーでバックエンドを保護可能(Cloud + Armorと併用可能) +
+
+
+

+ 出典: + Firewall policies and rules +

+
+
+

+ ベストプラクティス: GKEクラスタでSecure + Tagsベースのマイクロセグメンテーションを導入する際は、GKEのネットワークポリシー(Kubernetes + NetworkPolicyリソース、Dataplane V2)とCloud + NGFWのファイアウォールポリシーが二重に競合しないよう、責任分界(Podレベルの制御はKubernetes + NetworkPolicy、ノード/クラスタ境界の制御はCloud NGFW)を明確にしてください。 +

+
+
+

2.11 Part 2 ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
領域ベストプラクティス
ポリシー戦略 + 組織全体の絶対要件は階層ポリシー、柔軟なルールはグローバル/リージョンネットワークポリシーで管理する +
評価順序 + 特段の理由がなければデフォルトのAFTER_CLASSIC_FIREWALLを維持する +
階層ポリシー + 組織レベルは最小限に留め、goto_nextで下位への委譲を積極的に活用する +
コスト最適化 + Essentials機能のルールを高優先度に配置し、有料ティアの評価対象を絞り込む +
L7検査 + Enterprise階層のIDPS/URLフィルタリングは重要ワークロードに限定して適用する +
マイクロセグメンテーション新規設計はNetwork TagsではなくSecure Tagsを起点にする
ロギング + コンプライアンス上重要な境界を優先し、全ルール一律のロギングは避ける +
移行 + GKE自動生成ルールを除外し、Effective Firewall + Rulesで新旧の一致を確認してから旧ルールを削除する +
GKE統合 + Podレベルの制御はKubernetes + NetworkPolicy、ノード/クラスタ境界はCloud + NGFWと責任分界を明確にする +
+
+
+

+ Part 3: インターネットEgressの構成と保護 — Public Cloud NATとSecure Web Proxy +

+

3.1 Cloud NATのIPアドレッシング

+

+ Public Cloud + NATは、外部IPを持たないVMやGKEノードに対してソースNAT(SNAT)を行い、インターネットへのegress接続を可能にするリージョンサービスです。NAT + IPアドレスの割り当て方式には2種類あります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
割り当て方式動作予測可能性主な用途
自動(Automatic) + VM数・必要ポート数に応じてGoogle + Cloudが静的外部IPを自動的に追加/削除。選択したネットワーク階層(Premium/Standard)のIPが割り当てられる + 不可(次に割り当てられるIPを事前に予測できない)スケーラビリティを優先する一般的なワークロード
手動(Manual)管理者が予約済み静的外部IPアドレスを明示的に指定可能 + サードパーティAPIのIP許可リスト(allowlist)登録が必要なワークロード +
+
+

+ 自動割り当てのNAT + IPは、そのIP上のポートを使用するVMが1つもなくなるまで解放されません(使用中のVMがある限りIPはアクティブなまま保持され、Cloud + NATはVMを別IPへ動的に再割り当てすることはありません。これは既存の接続を破壊しないための設計です)。 +

+
+

+ 出典: + IP addresses and ports、Quickstart: Set up and manage network address translation with Public + NAT +

+
+
+

+ ベストプラクティス: + サードパーティのAPIやパートナーシステムがIP許可リストを要求する場合は、必ず手動IP割り当てを選択し、静的予約IPを使用してください。自動割り当てのままでは、IPが変更された際に相手先での許可リスト更新が必要になり、予期しない接続断が発生するリスクがあります。 +

+
+
+

3.2 ポート割り当て(静的/動的)

+

+ Cloud NAT + IPアドレス1つあたり、TCP/UDPそれぞれ64,512個のソースポート(0〜1,023のウェルノウンポートを除く65,536個から算出)が利用可能です。ポート割り当て方式には静的と動的の2種類があります。 +

+
+ + + + + + + + + + + + + + + + + + + + +
割り当て方式動作デフォルト値
静的ポート割り当て全VMに対して固定数のポートを一律割り当て最小64ポート/VM
動的ポート割り当て + VMごとの実際の使用量に応じて異なる数のポートを動的に割り当て。初期値は最小ポート数からスタートし、必要に応じて最大値まで増加 + + 環境により最小/最大を設定(推奨値: + 最小2048、最大4096など、ワークロードにより調整) +
+
+
+flowchart TD
+    Start(["Cloud NATゲートウェイの設計"]) --> Q1{"VMごとの接続数に<br/>ばらつきが大きいか?"}
+    Q1 -->|"いいえ(均一なワークロード)"| Static["静的ポート割り当てを選択<br/>(最小ポート数を用途に応じて調整)"]
+    Q1 -->|"はい(バーストする<br/>ワークロードが存在)"| Dynamic["動的ポート割り当てを選択<br/>(最小/最大ポート数を設定)"]
+
+    Static --> IPCalc["IPアドレス数 = <br/>必要VM数 × 最小ポート数 ÷ 64,512<br/>を事前に計算"]
+    Dynamic --> Monitor["ポート使用率メトリクスを監視し、<br/>枯渇の兆候があれば最大値を引き上げ"]
+
+    IPCalc --> Manual{"IPの予測可能性が必要か?<br/>(サードパーティ許可リスト等)"}
+    Manual -->|"はい"| ManualIP["手動IPアドレス割り当てを併用"]
+    Manual -->|"いいえ"| AutoIP["自動IPアドレス割り当てを使用"]
+
+    style Static fill:#1a73e8,color:#fff
+    style Dynamic fill:#188038,color:#fff
+

+ ポート割り当て方式の変更や、静的方式でのポート数の減少は既存のNAT接続を切断する可能性があるため、変更前に「IPアドレスのドレイン(段階的な切り離し)」の検討が必要です。一方、ポート数の増加(静的・動的いずれも)は既存接続を中断しません。 +

+
+

+ 出典: + IP addresses and ports、Quickstart: Set up and manage network address translation with Public + NAT +

+
+
+

+ ベストプラクティス: + ポート枯渇によるNATエラー(allocation_status="DROPPED")をCloud + Loggingで継続的に監視してください。バーストする可能性のあるワークロードには動的ポート割り当てを採用し、固定サイズのワークロードには静的割り当てでリソースを予測可能に保つという使い分けが基本方針になります。IPアドレスを変更する際は、必ず「外部IPアドレスのドレイン」手順に従い、既存接続を保護してください。 +

+
+
+

+ 3.3 Secure Web Proxyの概要とデプロイモード +

+

+ Secure Web Proxy(Cloud + SWP)は、egressのWeb(HTTP/HTTPS)トラフィックに対して、送信元アイデンティティ(Secure + Tags・サービスアカウント・IPアドレス)、宛先(ドメイン・URL・URLリスト)、リクエスト属性(メソッド・ヘッダー)に基づく粒度の高いアクセスポリシーを適用するサービスです。 +

+

+ トラフィックの発信元として、VMインスタンス、コンテナ、サーバーレスVPCアクセスコネクタ経由のサーバーレス環境、Cloud + VPN/Cloud Interconnect経由のオンプレミスワークロードをサポートします。 +

+
+ + + + + + + + + + + + + +
デプロイモード説明
明示的プロキシルーティングモード + クライアント側でSecure Web + Proxyを明示的にプロキシサーバーとして構成。クライアントに代わって新しいTCP接続を作成し、インターネットから分離する +
+
+

+ Secure Web ProxyはCertificate Authority Service(CAS)を用いたTLS + Inspectionを統合的に提供し、暗号化されたリクエストの内容(完全なURLパス、HTTPヘッダー)まで検査できます。クライアント-プロキシ間のトンネルもTLSで保護可能で、HTTP/HTTPS + CONNECTによるクライアント起点のエンドツーエンドTLS接続もサポートします。 +

+
+sequenceDiagram
+    participant VM as "VM / コンテナ / サーバーレス"
+    participant SWP as "Secure Web Proxy<br/>(Envoyプロキシプール)"
+    participant CAS as "Certificate Authority<br/>Service"
+    participant Ext as "外部Webサイト"
+
+    VM->>SWP: "明示的プロキシ経由でHTTPS接続要求"
+    SWP->>SWP: "ポリシー評価:<br/>送信元(Tag/SA)・宛先(URL)・<br/>リクエスト属性をマッチング"
+    alt "TLS Inspection有効"
+        SWP->>CAS: "証明書を要求"
+        CAS-->>SWP: "証明書を発行"
+        SWP->>SWP: "TLSを復号し、<br/>URLパス/ヘッダーを検査"
+    end
+    alt "ポリシーで許可"
+        SWP->>Ext: "新規TCP接続を作成し転送"
+        Ext-->>SWP: "レスポンス"
+        SWP-->>VM: "レスポンスを返却"
+    else "ポリシーで拒否"
+        SWP-->>VM: "接続拒否 + Cloud Loggingへ記録"
+    end
+
+

出典:

+ +
+
+

+ ベストプラクティス: TLS + InspectionはクライアントデバイスがSecure Web + Proxyのプライベート認証局(内部CA)を信頼済みルートとして事前インストールしている、管理下のデバイス(マネージドVM等)でのみ有効に機能します。証明書ピンニングを行うアプリケーション(特定の公開鍵/CAチェーンをハードコードしたクライアント)はTLS + Inspection経由で通信できない場合があるため、事前に対象アプリケーションの互換性を確認してください。 +

+
+
+

3.4 Secure Web Proxyポリシーの構成

+

+ Secure Web Proxyのポリシーはデフォルトで全てのegress Webトラフィックを拒否し、明示的なルールで許可した通信のみを通す「ホワイトリスト方式」で動作します。 +

+
+ + + + + + + + + + + + + + + + + + + + + +
属性カテゴリ利用可能な識別子
送信元(source) + サービスアカウント、Secure Tags(Resource + Managerタグ)、IPアドレス(社内固定IPやGoogle Cloud静的IP) +
宛先(destination) + 宛先ドメイン、完全URLパス(TLS + Inspection有効時)、URLリスト、宛先ポート +
リクエスト属性 + HTTPメソッド、ヘッダー、URL(ワイルドカード・パターンで指定可能) +
+
+

+ URLリストは複数のポリシーから再利用できるモジュール化されたオブジェクトであり、中央管理者が定義したリストを、各チームが自身のポリシーから参照する運用が可能です。 +

+

+ Secure Web ProxyのegressトラフィックはPublic Cloud + NAT経由でインターネットへ出るため、固定の送信元IPアドレスが必要な場合は、Cloud + NAT側の設定を「自動(推奨)」から「手動」へ変更し、静的予約IPを割り当てます。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: Secure Web Proxyのegress + IPを固定化する場合、Cloud NAT側で動的ポート割り当てを有効化し(推奨値: + 最小2048ポート/VM、最大4096ポート/VM)、限られた静的IPプールを効率的に利用してください。VPC + Service Controlsと組み合わせることで、Cloud StorageやBigQueryなどのGoogle + Cloudサービスからのデータ持ち出し(exfiltration)防止も同時に実現できます。 +

+
+
+

3.5 Part 3 ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
領域ベストプラクティス
IPアドレッシング + サードパーティのIP許可リスト連携が必要な場合は手動IP割り当てを使用する +
ポート割り当て + 均一なワークロードは静的、バーストするワークロードは動的ポート割り当てを選択する +
監視 + allocation_status="DROPPED"ログを継続監視し、ポート枯渇を早期検知する +
IP変更 + 変更前に外部IPアドレスのドレイン手順を実施し、既存接続への影響を最小化する +
SWP TLS Inspection + 証明書ピンニングを行うアプリケーションの互換性を事前確認する +
SWPポリシー + デフォルト拒否の原則を維持し、必要な宛先のみを明示的に許可する +
SWP × Cloud NAT + 固定送信元IPが必要な場合はCloud NAT側を手動割り当て + + 動的ポート割り当てに構成する +
データ保護 + VPC Service Controlsと組み合わせてデータ持ち出しリスクを低減する +
+
+
+

+ Part 4: セルフマネージドNVAとPacket Mirroringの構成 +

+

+ 4.1 マルチNIC VMによるVPC間トラフィックのルーティングと検査 +

+

+ セルフマネージドのネットワーク仮想アプライアンス(NVA)は、複数のネットワークインターフェース(マルチNIC)を持つCompute + Engine + VMとして構成され、異なるVPCネットワーク間のトラフィックを検査・ルーティングする役割を担います。サードパーティ製のNGFWアプライアンス(FortiGate、Palo + Alto Networks + VM-Series等)や自作のルーティング/ゲートウェイソフトウェアが該当します。 +

+

+ 典型的な構成は、ハブVPCに配置したマルチNIC + NVAのインスタンスグループが、複数のスポークVPCからのトラフィックを集約・検査するハブアンドスポーク型です。 +

+
+flowchart TB
+    subgraph Hub["ハブVPC"]
+        NVA1["NVA VM #1<br/>(nic0: spoke-A側 / nic1: spoke-B側)"]
+        NVA2["NVA VM #2<br/>(nic0: spoke-A側 / nic1: spoke-B側)"]
+        ILB_A["内部パススルーNLB #A<br/>(nic0向け)"]
+        ILB_B["内部パススルーNLB #B<br/>(nic1向け)"]
+    end
+
+    subgraph SpokeA["スポークVPC A"]
+        VMA["ワークロードVM"]
+        RouteA["静的ルート:<br/>next-hop = ILB_A"]
+    end
+
+    subgraph SpokeB["スポークVPC B"]
+        VMB["ワークロードVM"]
+        RouteB["静的ルート:<br/>next-hop = ILB_B"]
+    end
+
+    VMA -->|"VPC Peering / NCC経由"| RouteA
+    RouteA --> ILB_A
+    ILB_A --> NVA1
+    ILB_A --> NVA2
+    NVA1 -->|"検査・SNAT/ルーティング"| ILB_B
+    NVA2 -->|"検査・SNAT/ルーティング"| ILB_B
+    ILB_B --> RouteB
+    RouteB --> VMB
+
+    style NVA1 fill:#1a73e8,color:#fff
+    style NVA2 fill:#1a73e8,color:#fff
+
+

+ 出典: + Internal passthrough Network Load Balancers as next hops +

+
+
+

+ ベストプラクティス: マルチNIC + NVAを自前で構築・運用する前に、Cloud NGFW Enterprise(Part 2参照)やNetwork + Security + Integration(本Partの4.4節参照)で同等の要件を満たせないか検討してください。セルフマネージドNVAはGoogle管理サービスに比べて構成・パッチ適用・スケーリングの運用負荷が高く、可能な限りマネージドサービスへの移行を優先することが長期的な運用コストの削減につながります。 +

+
+
+

+ 4.2 HA構成: 内部パススルーNLBをネクストホップにする +

+

+ 内部パススルーNetwork Load + Balancer(ILB)は、静的ルートのネクストホップとして指定できます。これにより、マルチNIC + NVAを冗長構成(インスタンスグループの複数VM)にした上で、ヘルスチェックによる自動フェイルオーバーを実現できます。 +

+
+ + + + + + + + + + + + + + + + + + + + + +
用途説明
デフォルトルートのネクストホップ + インターネットへのトラフィックを、負荷分散されたゲートウェイVM群経由でルーティング +
複数方向へのトラフィック分散 + 同一のマルチNIC + VMセットを、方向ごとに異なるILB(nic0向け・nic1向け)の背後に配置し、双方向トラフィックを処理 +
タグベースの複数ネクストホップ + Network + Tagsを使い、クライアントVMごとに異なるILBネクストホップへ振り分け(ECMPは同一優先度・同一タグの複数ルート間では非対応) +
+
+
+flowchart LR
+    Client["クライアントVM群"] --> Route["スタティックルート<br/>(0.0.0.0/0)<br/>next-hop = ILB"]
+    Route --> ILB["内部パススルーNLB<br/>(5-tupleハッシュで負荷分散)"]
+    ILB --> HC{"ヘルスチェック"}
+    HC -->|"healthy"| ActiveVM["アクティブNVA VM"]
+    HC -->|"unhealthy"| Failover["トラフィックを<br/>他の健全なVMへ自動転送"]
+
+    ActiveVM --> Backend["バックエンドVMインスタンス<br/>(インスタンスグループ)"]
+    Failover --> Backend
+
+    style ActiveVM fill:#188038,color:#fff
+    style Failover fill:#f9ab00,color:#000
+

+ ILBネクストホップの背後にあるバックエンドVMは、IP転送(IP forwarding)を有効化する必要があります。ILBがネクストホップの場合、クライアントVM側のゲストOSには特別な設定は不要です(クライアントはロードバランサーの背後にあるバックエンドを経由してパケットを送信するだけです)。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: + FortiGateなど商用NVAのHAクラスタを構成する場合、アクティブ/パッシブの判定にベンダー固有のヘルスチェックプローブレスポンダー(アクティブなクラスタメンバーのみが応答するプローブ)を使用し、Cloud + Load + Balancingのヘルスチェックと連携させてください。フェイルオーバー時の既存TCP接続の維持には、Cloud + Load + Balancingのコネクショントラッキング機能が有効に機能します。タグベースのネクストホップルートはVPC + Network + Peering経由ではエクスポート/インポートされない点に注意し、Peering先での経路設計を別途検討してください。 +

+
+
+

+ 4.3 HA マルチNIC VMルーティングのためのポリシーベースルート +

+

+ ポリシーベースルート(Policy-Based Routes, + PBR)は、パケットの宛先IPアドレスだけでなく、プロトコルや送信元IPアドレスも加味してネクストホップを選択できるルーティング機構です。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目仕様
マッチ条件宛先IP、プロトコル、送信元IPアドレス
適用対象 + 同一VPC内の全VMインスタンス/Interconnect + VLANアタッチメント/VPNトンネル、または特定のNetwork + Tagsを持つVMのみ、または特定リージョンのVLANアタッチメントのみ +
ネクストホップ + 有効な内部パススルーNLBである必要がある(同一VPC、またはVPC + Network Peering接続先のVPC) +
バックエンド要件 + ネクストホップILBの背後のVMインスタンスはIP転送を有効化する必要がある +
評価順序 + サブネットルート・スタティックルート・ダイナミックルートより先、特殊経路(special + routing paths)より後に評価される +
同一優先度の競合 + 複数のポリシーベースルートが同一優先度でマッチする場合、Google + Cloudが内部アルゴリズムで1つを選択(最も詳細なマッチが選ばれるとは限らない) +
+
+
+flowchart TD
+    Pkt(["パケット到着"]) --> Special["① 特殊経路<br/>(default internet gateway等)"]
+    Special --> PBR["② ポリシーベースルート<br/>(宛先IP + プロトコル + 送信元IPでマッチ)"]
+    PBR -->|"マッチ"| ILBNext["内部パススルーNLBへ<br/>(NVA/ファイアウォールへ挿入)"]
+    PBR -->|"未マッチ"| Subnet["③ サブネットルート"]
+    Subnet --> Static["④ スタティックルート"]
+    Static --> Dynamic["⑤ ダイナミックルート<br/>(Cloud Router BGP)"]
+
+    style ILBNext fill:#1a73e8,color:#fff
+

+ ポリシーベースルートは、通常のスタティックルート(宛先IPのみでマッチ)よりも粒度の高い制御が必要な場合、たとえば「特定のプロトコル(TCP/443のみ)や特定の送信元サブネットのトラフィックのみをNVA経由でインスペクションしたい」といったユースケースで使用します。 +

+
+

+ 出典: + Policy-based routes +

+
+
+

+ ベストプラクティス: マルチNIC + NVAをHA構成にする際は、ポリシーベースルートのネクストホップにも内部パススルーNLBを指定し、静的ルート(4.2節)と組み合わせることで、プロトコル/送信元単位の柔軟なトラフィック挿入と、ロードバランサーによる自動フェイルオーバーの両方を実現してください。同一優先度でのルート競合は選択結果が保証されないため、意図した経路制御には優先度を明示的に分離してください。 +

+
+
+

+ 4.4 アウトオブバンドのNetwork Security Integration戦略 +

+

+ Network Security Integration(NSI)のアウトオブバンド統合は、Packet + Mirroring技術を基盤としつつ、プロデューサー(検査サービス提供側)とコンシューマー(トラフィックを検査してほしい側)を分離したモデルを提供する、よりスケーラブルなアーキテクチャです。トラフィックはGeneveカプセル化によって元のパケットを保持したまま転送され、VPCネットワーク識別子が付与されるため、重複するIPアドレス範囲を持つ複数VPCが存在する環境でも正しく識別できます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
コンポーネント役割
ミラーリングデプロイグループ(プロデューサー側) + 複数ゾーンにまたがるミラーリングデプロイの集合。プロデューサーの検査サービスを表すグローバルなプロジェクトレベルリソース +
ミラーリングエンドポイントグループ(コンシューマー側) + プロデューサーのデプロイグループを参照するコンシューマー側リソース +
ミラーリングエンドポイントグループアソシエーション + エンドポイントグループを特定のVPCネットワークに関連付け、そのVPCのトラフィックを検査対象にする +
カスタムミラーリングセキュリティプロファイル + ミラーリングエンドポイントグループを参照する検査設定。セキュリティプロファイルグループに含めてファイアウォールルールのMIRRORアクションから参照 +
+
+
+flowchart LR
+    subgraph Consumer["コンシューマーVPC(検査対象)"]
+        CVM["ワークロードVM"]
+        FWPolicy["ネットワークファイアウォールポリシー<br/>(ミラーリングルール: action=MIRROR)"]
+        MEG["ミラーリングエンドポイント<br/>グループ"]
+        Assoc["エンドポイントグループ<br/>アソシエーション"]
+    end
+
+    subgraph Producer["プロデューサーVPC(検査サービス提供側)"]
+        MDG["ミラーリングデプロイグループ"]
+        MD["ミラーリングデプロイ<br/>(ゾーンごと)"]
+        ILB2["内部パススルーNLB"]
+        Collector["検査アプライアンス<br/>(サードパーティ製 等)"]
+    end
+
+    CVM -->|"トラフィック"| FWPolicy
+    FWPolicy -->|"MIRROR一致"| Assoc
+    Assoc --> MEG
+    MEG -->|"Geneveカプセル化<br/>(VPC識別子付与)"| MDG
+    MDG --> MD
+    MD --> ILB2
+    ILB2 --> Collector
+
+    style MEG fill:#1a73e8,color:#fff
+    style MDG fill:#188038,color:#fff
+

+ NSIアウトオブバンド統合は、ミラーリングコレクターのサービス化という運用モデルもサポートします。セキュリティ管理者が所有する専用プロジェクトでミラーリングデプロイグループを一元運用し、各アプリケーションチームのVPC(コンシューマー)がそれをサービスとして利用する、という責任分界が可能です。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: + 複数のアプリケーションチームが同じ検査基盤(IDS/NTAツール等)を共有する組織では、従来のPacket + Mirroring(4.5節)よりも、プロデューサー/コンシューマーモデルのNetwork + Security + Integrationを優先的に検討してください。検査アプライアンスの運用をセキュリティチームに集約しつつ、各チームのVPCからはサービスとして疎結合に利用できるため、大規模組織でのスケーラビリティと運用分離の両方を実現できます。regional + network firewall policiesはPacket + Mirroringに対応していない点にも留意してください。 +

+
+
+

+ 4.5 Packet Mirroring(セルフマネージドコレクター) +

+

+ 従来のPacket + Mirroring機能は、指定したVPC内のミラーリング対象インスタンス(mirrored + sources)のトラフィックを複製し、内部パススルーNLBの背後にあるコレクターインスタンスグループへ転送します。ペイロードとヘッダーを含む全トラフィックをエクスポートするため、サンプリングベースのVPC + Flow + Logsでは検出できない詳細な脅威分析やアプリケーションパフォーマンス分析が可能です。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
設定項目内容
ミラーリング対象(source) + サブネット、Network + Tags、インスタンス名のいずれかで指定。複数指定した場合、いずれかにマッチするインスタンスが対象 +
キャプチャ方向ingressのみ・egressのみ・両方向、を選択可能
コレクター destination + 内部パススルーNLBの背後にあるインスタンスグループ(コレクターインスタンス) +
スコープの制約 + ミラーリング対象は同一プロジェクト・同一VPCネットワーク・同一リージョン内である必要がある +
+
+
+flowchart TD
+    subgraph Sources["ミラーリング対象"]
+        S1["VM(サブネット指定)"]
+        S2["VM(Network Tags指定)"]
+    end
+
+    Policy["Packet Mirroringポリシー<br/>(同一リージョン内で定義)"] --> Sources
+    Sources -->|"ingress / egress / 両方向を複製"| ILB3["内部パススルーNLB<br/>(collector destination)"]
+    ILB3 --> Collector1["コレクターVM #1"]
+    ILB3 --> Collector2["コレクターVM #2"]
+    Collector1 --> Analysis["セキュリティ分析ソフトウェア<br/>(脅威検知・異常検知)"]
+    Collector2 --> Analysis
+
+    style ILB3 fill:#1a73e8,color:#fff
+

+ コレクターインスタンスは、ミラーリング対象からのトラフィックとGoogle + Cloudヘルスチェックシステムからのトラフィックを受信できるファイアウォールルールが必要です。また、コレクターにはインターネットトラフィックが到達しないよう、内部IPアドレスのみを割り当てることが推奨されます。 +

+

+ VPC Flow + Logsはミラーリングされたパケット自体をログに記録しませんが、コレクターインスタンスが配置されたサブネットでVPC + Flow + Logsが有効な場合、コレクター宛ての直接トラフィック(元の宛先IPがコレクターのIPと一致するフロー)はログに記録されます。 +

+
+

出典:

+ +
+
+

+ ベストプラクティス: + ミラーリング対象・コレクターともに同一プロジェクト・同一VPC・同一リージョンという制約があるため、複数リージョンにまたがる大規模環境では、リージョンごとに独立したPacket + Mirroringポリシーとコレクター基盤を設計する必要があります。組織横断的な集約検査基盤が必要な場合は、4.4節のNetwork + Security + Integration(アウトオブバンド統合)への移行を検討してください。ミラーリングはVM側で追加の帯域を消費する点も、キャパシティプランニング時に考慮してください。 +

+
+
+

4.6 Part 4 ベストプラクティス一覧

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
領域ベストプラクティス
NVA導入の判断 + セルフマネージドNVAの前に、Cloud NGFW + EnterpriseやNSIで要件を満たせないか検討する +
HA設計 + 内部パススルーNLBをネクストホップにし、ヘルスチェックによる自動フェイルオーバーを構成する +
IP転送 + ネクストホップILB背後のバックエンドVMでは必ずIP転送を有効化する +
ポリシーベースルート + プロトコル/送信元単位の細かい制御が必要な場合はPBRを、シンプルなデフォルトルート挿入には静的ルートを使い分ける +
タグベースルート + VPC Network + Peering越しにはタグ付きルートがエクスポートされない点を設計に織り込む +
検査基盤の選定 + 複数チーム共有の検査基盤はNSI(プロデューサー/コンシューマーモデル)を優先し、単純な単一VPC内検査には従来のPacket + Mirroringを使う +
スコープ制約 + Packet + Mirroringはプロジェクト/VPC/リージョンの境界を越えられないため、マルチリージョン環境ではリージョンごとに設計する +
コレクター保護 + コレクターインスタンスには内部IPのみを割り当て、インターネットからの直接到達を防ぐ +
+
+
+

設計・実装チェックリスト

+

+ 以下は、Section + 6「ネットワークセキュリティの設計と実装」に関する設計・実装レビュー用のチェックリストです。 +

+
+
+

Cloud Armor(6.1)

+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+
+

Cloud NGFW / VPCファイアウォール(6.2)

+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+
+

Cloud NAT・Secure Web Proxy(6.3)

+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+
+

セルフマネージドNVA・Packet Mirroring(6.4)

+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+

参考文献

+
+ + + + + +
+
+
+ + + + + + diff --git a/Gcp-pcne-s5-network-security.md b/Gcp-pcne-s5-network-security.md new file mode 100644 index 000000000..7be85dcdd --- /dev/null +++ b/Gcp-pcne-s5-network-security.md @@ -0,0 +1,1143 @@ +# Google Cloud Professional Cloud Network Engineer 対策ガイド S5: ネットワークセキュリティの設計と実装 + +> 対象: Google Cloud Professional Cloud Network Engineer(PCNE)認定試験 +> 対応範囲: 公式Exam Guide **Section 6「Configuring, implementing and managing a cloud network security solution」(出題比率 約13%)** +> レベル: 中級者〜上級者 +> 図解方針: ASCIIアートは使用せず、フローチャートは全てMermaid、図解・比較表は全てMarkdown表で記述 + +## この章の対象範囲(スコープ対応表) + +公式Exam Guideの原文タスクと、本ガイドのPartの対応関係は以下の通りです。 + +| Exam Guide タスク番号 | 原文タイトル | 本ガイドでの構成 | +|---|---|---| +| 6.1 Configuring Google Cloud Armor policies | エッジ/バックエンドセキュリティポリシー、WAFルール、DDoS/Adaptive Protection、レート制限、bot管理、Threat Intelligence | Part 1 | +| 6.2 Configuring and managing NGFW policies and VPC Firewall rules | ファイアウォール戦略、階層評価、GKE/LB対応、L7検査、移行、ルール基準、ロギング、マイクロセグメンテーション、階層(Essentials/Standard/Enterprise) | Part 2 | +| 6.3 Configuring and securing internet egress traffic using Public Cloud NAT and Secure Web Proxy | Cloud NAT IPアドレッシング、ポート割り当て、Secure Web Proxy構成 | Part 3 | +| 6.4 Configuring self-managed network virtual appliance and Packet Mirroring | マルチNIC VMルーティング、ILBネクストホップ、ポリシーベースルート、アウトオブバンド統合、Packet Mirroring | Part 4 | + +> **出典**: [Professional Cloud Network Engineer Certification exam guide (PDF)](https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf) + +--- + +## 目次 + +1. [Part 1: Google Cloud Armorポリシーの構成](#part-1-google-cloud-armorポリシーの構成) + - [1.1 Cloud Armorのアーキテクチャと適用ポイント](#11-cloud-armorのアーキテクチャと適用ポイント) + - [1.2 セキュリティポリシーの評価順序とルール構造](#12-セキュリティポリシーの評価順序とルール構造) + - [1.3 プリコンフィグドWAFルール(OWASP CRS)](#13-プリコンフィグドwafルールowasp-crs) + - [1.4 高度なネットワークDDoS防御とAdaptive Protection](#14-高度なネットワークddos防御とadaptive-protection) + - [1.5 レート制限](#15-レート制限) + - [1.6 Bot管理(reCAPTCHA連携)](#16-bot管理recaptcha連携) + - [1.7 Google Threat Intelligence](#17-google-threat-intelligence) + - [1.8 Part 1 ベストプラクティス一覧](#18-part-1-ベストプラクティス一覧) +2. [Part 2: Cloud NGFW / VPCファイアウォールルールの構成と管理](#part-2-cloud-ngfw--vpcファイアウォールルールの構成と管理) + - [2.1 ファイアウォール戦略とポリシー種別](#21-ファイアウォール戦略とポリシー種別) + - [2.2 ファイアウォールルールの評価順序](#22-ファイアウォールルールの評価順序) + - [2.3 階層ファイアウォールポリシーとEffective Rules](#23-階層ファイアウォールポリシーとeffective-rules) + - [2.4 Cloud NGFWの3つの階層(Essentials/Standard/Enterprise)](#24-cloud-ngfwの3つの階層essentialsstandardenterprise) + - [2.5 レイヤー7検査: TLS Inspection・URLフィルタリング・IDPS](#25-レイヤー7検査-tls-inspectionurlフィルタリングidps) + - [2.6 ファイアウォールルールの基準(criteria)](#26-ファイアウォールルールの基準criteria) + - [2.7 Secure Tags と Network Tags によるマイクロセグメンテーション](#27-secure-tags-と-network-tags-によるマイクロセグメンテーション) + - [2.8 ファイアウォールルールロギング](#28-ファイアウォールルールロギング) + - [2.9 VPCファイアウォールルールからCloud NGFWポリシーへの移行](#29-vpcファイアウォールルールからcloud-ngfwポリシーへの移行) + - [2.10 GKEおよびCloud Load BalancingでのCloud NGFWサポート](#210-gkeおよびcloud-load-balancingでのcloud-ngfwサポート) + - [2.11 Part 2 ベストプラクティス一覧](#211-part-2-ベストプラクティス一覧) +3. [Part 3: インターネットEgressの構成と保護 — Public Cloud NATとSecure Web Proxy](#part-3-インターネットegressの構成と保護--public-cloud-natとsecure-web-proxy) + - [3.1 Cloud NATのIPアドレッシング](#31-cloud-natのipアドレッシング) + - [3.2 ポート割り当て(静的/動的)](#32-ポート割り当て静的動的) + - [3.3 Secure Web Proxyの概要とデプロイモード](#33-secure-web-proxyの概要とデプロイモード) + - [3.4 Secure Web Proxyポリシーの構成](#34-secure-web-proxyポリシーの構成) + - [3.5 Part 3 ベストプラクティス一覧](#35-part-3-ベストプラクティス一覧) +4. [Part 4: セルフマネージドNVAとPacket Mirroringの構成](#part-4-セルフマネージドnvaとpacket-mirroringの構成) + - [4.1 マルチNIC VMによるVPC間トラフィックのルーティングと検査](#41-マルチnic-vmによるvpc間トラフィックのルーティングと検査) + - [4.2 HA構成: 内部パススルーNLBをネクストホップにする](#42-ha構成-内部パススルーnlbをネクストホップにする) + - [4.3 HA マルチNIC VMルーティングのためのポリシーベースルート](#43-ha-マルチnic-vmルーティングのためのポリシーベースルート) + - [4.4 アウトオブバンドのNetwork Security Integration戦略](#44-アウトオブバンドのnetwork-security-integration戦略) + - [4.5 Packet Mirroring(セルフマネージドコレクター)](#45-packet-mirroringセルフマネージドコレクター) + - [4.6 Part 4 ベストプラクティス一覧](#46-part-4-ベストプラクティス一覧) +5. [設計・実装チェックリスト](#設計実装チェックリスト) +6. [参考文献](#参考文献) + +--- + +## Part 1: Google Cloud Armorポリシーの構成 + +### 1.1 Cloud Armorのアーキテクチャと適用ポイント + +Cloud Armorは、Googleのグローバルネットワークのエッジ(Point of Presence, PoP)で動作するセキュリティサービスです。リクエストがバックエンドに到達する前に、可能な限りソースに近い場所でフィルタリング・レート制限・リダイレクトを行うことで、不要なトラフィックがVPCネットワークやバックエンドリソースを消費するのを防ぎます。 + +Cloud Armorのセキュリティポリシーには複数の種類があり、それぞれ適用対象(アタッチ先)が異なります。 + +| ポリシー種別 | type フラグ | アタッチ先 | 主な用途 | +|---|---|---|---| +| バックエンドセキュリティポリシー | 省略時のデフォルト | 外部ALB・内部リージョンALB・グローバル外部プロキシNLBのバックエンドサービス/バックエンドバケット | WAF、L7フィルタリング、レート制限、bot管理 | +| エッジセキュリティポリシー | `CLOUD_ARMOR_EDGE` | バックエンドバケットやキャッシュ可能コンテンツ | キャッシュされたコンテンツの保護 | +| ネットワークエッジセキュリティポリシー | `CLOUD_ARMOR_NETWORK` | リージョンの「ネットワークエッジセキュリティサービス」 | 外部パススルーNLB・プロトコルフォワーディング・パブリックIP VMへの高度なネットワークDDoS防御 | +| 内部サービスセキュリティポリシー | — | Cloud Service MeshのエンドポイントポリシーB | Service Mesh内でのフェアシェアレート制限 | + +対応するロードバランサーの種類は以下の通りです。 + +| ロードバランサー種別 | Cloud Armor(バックエンドポリシー)対応 | +|---|---| +| グローバル外部Application Load Balancer(Classic含む) | ○ | +| リージョン内部Application Load Balancer | ○ | +| グローバル外部プロキシNetwork Load Balancer(TCP/SSL) | ○ | +| 外部パススルーNetwork Load Balancer | △(ネットワークエッジセキュリティポリシー経由でDDoS防御のみ) | + +```mermaid +flowchart LR + subgraph Internet["インターネット"] + Client["クライアント"] + end + + subgraph Edge["Googleエッジ(PoP) — Cloud Armor評価ポイント"] + CA["Cloud Armor\nセキュリティポリシー評価\n許可 / 拒否 / レート制限 / リダイレクト"] + end + + subgraph GCP["Google Cloudネットワーク内部"] + LB["外部ロードバランサー\n(Application / Proxy Network)"] + BE1["バックエンドサービス\n(MIG / NEG / サーバーレスNEG)"] + BE2["バックエンドバケット\n(Cloud Storage)"] + end + + Client -->|"HTTPS リクエスト"| CA + CA -->|"ALLOW"| LB + CA -->|"DENY"| Dropped["リクエスト破棄\n(バックエンドへ到達しない)"] + LB --> BE1 + LB --> BE2 + + style CA fill:#1a73e8,color:#fff + style Dropped fill:#d93025,color:#fff +``` + +> **出典**: +> - [Cloud Armor overview](https://docs.cloud.google.com/armor/docs/cloud-armor-overview) +> - [Security policy overview](https://docs.cloud.google.com/armor/docs/security-policy-overview) +> - [Use cases for security policies](https://docs.cloud.google.com/armor/docs/common-use-cases) + +> **ベストプラクティス**: バックエンドサービスを新規作成した際は、必ずCloud Armorセキュリティポリシーのアタッチ漏れがないか確認してください。アタッチされていないバックエンドサービスはCloud Armorの保護対象外となり、既知の攻撃パターンに対して無防備な状態になります。 + +--- + +### 1.2 セキュリティポリシーの評価順序とルール構造 + +Cloud Armorのルール評価順序は**優先度(priority)の数値が小さいほど高優先度**で、最も低い数値のルールから順に評価されます。マッチしたルールのアクションが即座に適用され、それ以降のルールは評価されません。 + +| アクション | 説明 | +|---|---| +| `allow` | トラフィックを許可し、バックエンドへ転送 | +| `deny(403/404/502等)` | 指定したHTTPステータスコードでリクエストを拒否 | +| `rate_based_ban` | 閾値を超えたクライアントを一定時間バン | +| `throttle` | 閾値を超えたリクエストをスロットリング(一部を許可) | +| `redirect` | reCAPTCHA評価や別URLへのリダイレクト | + +```mermaid +flowchart TD + Start(["受信リクエスト"]) --> P1{"優先度が最も低い\n数値のルールから評価"} + P1 -->|"条件マッチ"| Act{"ルールのアクション"} + P1 -->|"マッチなし"| Next["次に優先度が低い\n(数値が大きい)ルールを評価"] + Next --> P1 + P1 -->|"全ルール未マッチ"| Default["デフォルトルール\n(通常は allow)"] + + Act -->|"allow"| Allowed["バックエンドへ転送"] + Act -->|"deny"| Denied["拒否レスポンス\n(403等)を返却"] + Act -->|"throttle / rate_based_ban"| RateCheck["レート制限判定へ"] + Act -->|"redirect"| Redirect["reCAPTCHA評価 or\n指定URLへリダイレクト"] + + style Denied fill:#d93025,color:#fff + style Allowed fill:#188038,color:#fff +``` + +> **出典**: [Create and manage security policies](https://docs.cloud.google.com/armor/docs/configure-security-policies) + +> **ベストプラクティス**: ルールの優先度は100, 1000, 2000のように間隔を空けて採番し、後から緊急ルールを既存ルールの間に挿入できる余地を残してください。国コード(ISO 3166-1 alpha-2)による地域制限を行う場合は、各コードが独立して評価される点に注意し、意図しない許可漏れがないかテストしてください。 + +--- + +### 1.3 プリコンフィグドWAFルール(OWASP CRS) + +Cloud Armorのプリコンフィグド(事前構成済み)WAFルールは、OWASP ModSecurity Core Rule Set(CRS)をベースにした署名(シグネチャ)群です。SQLインジェクション(sqli)、クロスサイトスクリプティング(xss)、リモートファイルインクルージョン(rfi)、ローカルファイルインクルージョン(lfi)、リモートコード実行(rce)、スキャナー検出(scannerdetection)など、OWASP Top 10に対応する攻撃カテゴリごとにルールが用意されています。 + +ルール名の形式は `<攻撃カテゴリ>--<バージョンフィールド>` です(例: `xss-v422-stable`、`sqli-v33-stable`)。Googleは最新の保護のためCRS **4.22** の使用を推奨しており、CRS 3.0系は非推奨です。 + +各シグネチャには**感度レベル(sensitivity level)0〜4**が設定されており、OWASPのパラノイアレベルに対応します。 + +| 感度レベル | 特性 | +|---|---| +| 0 | ルール無効(デフォルトでは何も有効化されない) | +| 1(低) | 高確信度シグネチャのみ。誤検知(false positive)が最も少ない | +| 2〜3(中) | セキュリティと誤検知リスクのバランス | +| 4(高、デフォルト) | 有効化時に全シグネチャを評価。誤検知リスクが最も高い | + +``` +# 感度レベル1でSQLiルールをプレビューモードで作成する例 +evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1}) + +# 特定のシグネチャIDを除外(誤検知対策) +evaluatePreconfiguredWaf('xss-v422-stable', {'opt_out_rule_ids': ['owasp-crs-v042200-id941100-xss']}) +``` + +> **出典**: +> - [Preconfigured WAF rules overview](https://docs.cloud.google.com/armor/docs/waf-rules) +> - [Tune Cloud Armor preconfigured WAF rules](https://docs.cloud.google.com/armor/docs/rule-tuning) +> - [Configure custom rules language attributes](https://docs.cloud.google.com/armor/docs/rules-language-reference) + +> **ベストプラクティス**: 本番環境への適用前に、必ず**プレビューモード**(`--preview`)で数週間ルールを稼働させ、誤検知の有無をログで確認してください。感度レベルは1から開始し、段階的に引き上げることで、正規トラフィックの誤ブロックを避けながらセキュリティレベルを高められます。 + +> **注意**: 感度レベルを4(デフォルト)のまま本番運用に投入すると、レガシーAPIやリッチテキスト入力を許可するアプリケーションで想定以上の誤検知が発生する可能性があります。 + +--- + +### 1.4 高度なネットワークDDoS防御とAdaptive Protection + +Cloud ArmorのDDoS防御は、**ネットワーク層**(L3/L4)と**アプリケーション層**(L7)の2系統に分かれます。 + +#### ネットワーク層: 標準保護 vs 高度なネットワークDDoS防御 + +| 項目 | 標準ネットワークDDoS防御 | 高度なネットワークDDoS防御 | +|---|---|---| +| 有効化 | 常時有効(操作不要) | Cloud Armor Enterpriseへの加入とリージョン単位の明示的な設定が必要 | +| 対象 | Google Cloud基盤の安定性維持が目的。クォータ超過トラフィックのスロットリングのみ | 外部パススルーNLB・プロトコルフォワーディング・パブリックIP VMへの標的型攻撃検知・緩和 | +| 攻撃シグネチャ検知 | なし | あり(常時オンの volumetric attack detection) | +| 適用単位 | Google Cloud全体 | リージョン単位(ネットワークエッジセキュリティサービスに関連付け) | + +```mermaid +flowchart TD + Start(["外部パススルーNLB / プロトコルフォワーディング / パブリックIP VM"]) --> Std["標準ネットワークDDoS防御\n(常時有効・操作不要)"] + Std --> Enroll{"Cloud Armor Enterpriseに\n加入しているか?"} + Enroll -->|"いいえ"| StdOnly["標準保護のみ\n(クォータ超過トラフィックの抑制)"] + Enroll -->|"はい"| CreatePolicy["type=CLOUD_ARMOR_NETWORK の\nセキュリティポリシーを作成"] + CreatePolicy --> EnableAdv["セキュリティポリシーで\n高度なDDoS防御を有効化"] + EnableAdv --> CreateService["リージョンにネットワークエッジ\nセキュリティサービスを作成し関連付け"] + CreateService --> Profiling["トラフィックプロファイリング\n(基準値の学習、目安24時間)"] + Profiling --> Advanced["常時オンの標的型\n攻撃検知・緩和が有効化"] + + style Advanced fill:#188038,color:#fff + style StdOnly fill:#f9ab00,color:#000 +``` + +> **出典**: +> - [Configure advanced network DDoS protection](https://docs.cloud.google.com/armor/docs/advanced-network-ddos) +> - [Configure network edge security policies](https://docs.cloud.google.com/armor/docs/network-edge-policies) + +#### アプリケーション層: Adaptive Protection + +Adaptive Protectionは、機械学習によりバックエンドサービスへのトラフィックパターンの「正常な基準値(baseline)」を学習し、そこからの逸脱をL7 DDoS攻撃(HTTPフラッド等)として検知・アラートするCloud Armor Enterpriseの機能です。 + +```mermaid +flowchart LR + A["通常トラフィックの\n継続的な学習"] --> B["基準値(baseline)の確立"] + B --> C{"トラフィックパターンが\n基準値から逸脱?"} + C -->|"いいえ"| A + C -->|"はい"| D["Cloud Loggingへ\nアラートを生成"] + D --> E["攻撃シグネチャ・\n確信度スコア(confidence score)・\n推奨WAFルールを算出"] + E --> F{"自動デプロイ\n(auto-deploy)が有効?"} + F -->|"いいえ"| G["インシデント対応者が\n手動でルールをレビュー・適用"] + F -->|"はい"| H{"確信度・負荷しきい値を\n超過?"} + H -->|"はい"| I["推奨ルールを自動デプロイ\n(有効期限付き)"] + H -->|"いいえ"| J["監視を継続"] + + style D fill:#f9ab00,color:#000 + style I fill:#d93025,color:#fff +``` + +Adaptive Protectionのアラートには以下の情報が含まれます。 + +| 項目 | 内容 | +|---|---| +| 確信度スコア(confidence score) | トラフィックパターンの変化が異常である確からしさ(0〜1) | +| 攻撃シグネチャ | 悪意あるHTTPヘッダー、クライアントの地理情報などの特徴 | +| 想定影響ベースライン率(impacted baseline rate) | 推奨ルールを適用した場合にブロックされる正常トラフィックの割合 | +| 推奨WAFルール | 攻撃シグネチャに一致するCloud Armorルール案 | + +> **出典**: +> - [Adaptive Protection overview](https://docs.cloud.google.com/armor/docs/adaptive-protection-overview) +> - [Adaptive Protection use cases](https://docs.cloud.google.com/armor/docs/adaptive-protection-use-cases) +> - [Automatically deploy Adaptive Protection suggested rules](https://docs.cloud.google.com/armor/docs/adaptive-protection-auto-deploy) + +> **ベストプラクティス**: アラートポリシーの確信度しきい値は**0.5程度の低い値から開始**し、潜在的な攻撃の見逃しを避けてください。誤検知の許容範囲を確認しながら段階的に引き上げます。自動デプロイ(auto-deploy)を有効化する場合は、確信度しきい値を0.8以上、想定影響ベースライン率を0.01以下といった保守的な値に設定し、有効期限(2〜4時間程度)を必ず設定してください。まずは自動デプロイを無効にした手動レビュー運用で数週間の実績を積んでから自動化することを推奨します。 + +--- + +### 1.5 レート制限 + +レート制限ルールは、指定した集計キー(IPアドレス、reCAPTCHAトークン、HTTPヘッダー等)ごとにリクエスト数を集計し、しきい値超過時に`throttle`(一部リクエストの間引き)または`rate_based_ban`(一定時間の完全ブロック)を適用します。 + +| アクション | 動作 | +|---|---| +| `throttle` | しきい値を超えたリクエストの一部を拒否し、許可レートまで抑制 | +| `rate_based_ban` | しきい値を超えたクライアントを、指定した期間にわたって完全にブロック | + +カスタムエラーレスポンスを設定することで、レート制限時にエンドユーザーへ独自のエラーメッセージを返すことも可能です。 + +> **出典**: [Rate limiting overview](https://docs.cloud.google.com/armor/docs/rate-limiting-overview)、[Configure rate limiting](https://docs.cloud.google.com/armor/docs/configure-rate-limiting) + +> **ベストプラクティス**: 初期波状攻撃(initial wave)への対処には`throttle`、それでも継続する攻撃者には`rate_based_ban`という2段階の防御を組み合わせてください。reCAPTCHA連携時は、アクショントークン・セッショントークン・免除Cookieの再利用によるトークン濫用を防ぐため、それぞれに対するレート制限ルールを個別に設定することが推奨されます。 + +--- + +### 1.6 Bot管理(reCAPTCHA連携) + +Cloud Armorのbot管理は、reCAPTCHA Enterpriseと統合し、高度なリスク分析によって人間のユーザーと自動化クライアントを区別します。reCAPTCHAはリクエストのリスク属性を暗号化トークンとして発行し、Cloud Armorはこのトークンをインラインで復号します(reCAPTCHAサービスへの追加リクエストは不要)。トークンの属性に基づき、トラフィックを許可・拒否・レート制限・リダイレクトできます。 + +レート制限ルールはbot管理機能と組み合わせ可能で、しきい値超過時にreCAPTCHA評価へのリダイレクトや、免除Cookie・トークンを悪用するクライアントのバンといった制御ができます。 + +> **出典**: [Bot management overview](https://docs.cloud.google.com/armor/docs/bot-management) + +> **ベストプラクティス**: reCAPTCHA免除Cookieやトークンを使い回すクライアント(トークン濫用)を防ぐため、アクショントークン・セッショントークン・免除Cookieそれぞれをキーとしたレート制限ルールを設定してください。クレデンシャルスタッフィングやスクレイピング、在庫買い占め攻撃などの不正取引対策として、reCAPTCHA Enterpriseのスコアベース評価と組み合わせることで検知精度が向上します。 + +--- + +### 1.7 Google Threat Intelligence + +Google Threat Intelligenceは、Cloud Armor Enterpriseの購読者向けに、Google/Mandiantが継続的に更新する脅威データフィードに基づいてトラフィックを許可・拒否できる機能です。`evaluateThreatIntelligence('FEED_NAME')`というマッチ式を用いて構成します。 + +| カテゴリ(フィード) | 説明 | +|---|---| +| Torエグジットノード | 匿名通信を可能にするTorネットワークの出口ポイントのIPアドレス | +| 既知の悪意あるIPアドレス | Webアプリケーションへの攻撃の発信元として実績のあるIPアドレス | +| Bad bots | 悪意のあるボット由来と判定されたトラフィック | +| パブリッククラウドエンドポイント | 主要パブリッククラウドプロバイダーのIPレンジ | + +フィード内の情報は継続的に更新されるため、追加の運用作業なしに新しい脅威に対する保護が維持されます。 + +> **出典**: [Apply Google Threat Intelligence](https://docs.cloud.google.com/armor/docs/threat-intelligence) + +> **ベストプラクティス**: Torエグジットノードやパブリッククラウドエンドポイントの一律ブロックは、正規のプライバシー重視ユーザーや正規のクラウド間トラフィックを誤って遮断するリスクがあるため、まずは`throttle`や監視目的のログ記録から開始し、業務要件に応じて`deny`へ段階的に移行することを検討してください。 + +--- + +### 1.8 Part 1 ベストプラクティス一覧 + +| 領域 | ベストプラクティス | +|---|---| +| ポリシー適用漏れ防止 | 新規バックエンドサービス作成時は必ずCloud Armorポリシーのアタッチを確認する | +| ルール優先度設計 | 優先度番号は100・1000単位で間隔を空け、緊急ルール挿入の余地を残す | +| WAFルール導入 | プレビューモードで数週間検証してから本番適用。感度レベルは1から段階的に引き上げる | +| DDoS防御 | 外部パススルーNLB/プロトコルフォワーディング/パブリックIP VMを保護する場合は高度なネットワークDDoS防御への加入を検討する | +| Adaptive Protection | 確信度0.5から監視を開始し、自動デプロイは保守的なしきい値(確信度0.8以上)かつ有効期限付きで運用する | +| レート制限 | throttleとrate_based_banを段階的に組み合わせ、reCAPTCHAトークンの再利用も監視する | +| Threat Intelligence | 一律ブロックの前に監視・throttleで影響範囲を確認する | +| 監視 | Adaptive Protectionイベント・Cloud Armorログ・Security Command CenterのCloud Armorカードを定期的にレビューする | + +--- + +## Part 2: Cloud NGFW / VPCファイアウォールルールの構成と管理 + +### 2.1 ファイアウォール戦略とポリシー種別 + +Google Cloudのファイアウォールは、Andromedaネットワーク仮想化スタックの一部として**完全分散型・ホストベース**で実装されており、各VMのネットワークインターフェースに対して直接プログラムされます。ポリシーの種類ごとに適用範囲とIAM統合の粒度が異なります。 + +| ポリシー種別 | 適用範囲 | Secure Tags対応 | Network Tags対応 | 課金(有料機能利用時) | +|---|---|---|---|---| +| 階層ファイアウォールポリシー | 組織・フォルダ全体(複数VPC・複数プロジェクトに横断適用) | ○ | × | 機能に応じて課金 | +| リージョンシステムファイアウォールポリシー | Google管理(GKE等の自動生成ルール) | — | — | 課金なし | +| VPCファイアウォールルール(classic) | 単一のVPCネットワーク | × | ○ | Essentials機能のみで課金なし | +| グローバルネットワークファイアウォールポリシー | 単一VPCの全リージョン | ○ | × | 機能に応じて課金 | +| リージョンネットワークファイアウォールポリシー | 単一VPCの特定リージョン | ○ | × | 機能に応じて課金 | + +**ファイアウォール戦略の設計指針**: + +1. **組織全体で強制すべき絶対要件**(既知の悪意あるIP範囲のブロック、ヘルスチェックの許可等)は階層ファイアウォールポリシーで一元管理する。 +2. **VPCネットワーク単位・リージョン単位の柔軟なルール**はグローバル/リージョンネットワークファイアウォールポリシーで管理する。 +3. **レガシー環境やシンプルな単一VPC構成**では引き続きVPCファイアウォールルールを使うことも可能だが、Googleは新規機能をすべてファイアウォールポリシー側にのみ追加する方針であり、長期的にはネットワークファイアウォールポリシーへの移行が推奨される。 + +> **出典**: +> - [Firewall policies and rules](https://docs.cloud.google.com/firewall/docs/firewall-policies-overview) +> - [Cloud NGFW overview](https://docs.cloud.google.com/firewall/docs/about-firewalls) + +--- + +### 2.2 ファイアウォールルールの評価順序 + +VPCネットワークには**ネットワークファイアウォールポリシー適用順序**(network firewall policy enforcement order)という設定があり、グローバル/リージョンネットワークファイアウォールポリシーをVPCファイアウォールルールより前に評価するか後に評価するかを制御します。 + +| 適用順序 | デフォルト | 説明 | +|---|---|---| +| `AFTER_CLASSIC_FIREWALL` | ○(デフォルト) | VPCファイアウォールルールを、グローバル/リージョンネットワークファイアウォールポリシーより先に評価 | +| `BEFORE_CLASSIC_FIREWALL` | — | グローバル/リージョンネットワークファイアウォールポリシーを、VPCファイアウォールルールより先に評価 | + +階層ファイアウォールポリシーとリージョンシステムファイアウォールポリシーは、適用順序の設定に関わらず**常に最初に評価**されます。 + +```mermaid +flowchart TD + Start(["新規接続パケット到着\n(Ingress / Egress)"]) --> Hier["① 階層ファイアウォールポリシー\n(組織 → フォルダ、常に最優先)"] + Hier -->|"allow / deny"| Stop1["評価終了・アクション適用"] + Hier -->|"goto_next\nまたは未マッチ"| Sys["② リージョンシステム\nファイアウォールポリシー(Google管理)"] + Sys -->|"allow / deny"| Stop1 + Sys -->|"goto_next\nまたは未マッチ"| Order{"ネットワークファイアウォール\nポリシー適用順序は?"} + + Order -->|"AFTER_CLASSIC_FIREWALL\n(デフォルト)"| VPC1["③ VPCファイアウォールルール"] + VPC1 -->|"allow / deny"| Stop1 + VPC1 -->|"未マッチ"| Global1["④ グローバルネットワーク\nファイアウォールポリシー"] + Global1 -->|"allow / deny / apply_security_profile_group"| Stop1 + Global1 -->|"goto_next\nまたは未マッチ"| Regional1["⑤ リージョンネットワーク\nファイアウォールポリシー"] + Regional1 -->|"allow / deny"| Stop1 + Regional1 -->|"goto_next\nまたは未マッチ"| Implied1["⑥ 暗黙のアクション\n(Ingress:deny / Egress:allow)"] + + Order -->|"BEFORE_CLASSIC_FIREWALL"| Global2["③ グローバルネットワーク\nファイアウォールポリシー"] + Global2 -->|"allow / deny / apply_security_profile_group"| Stop1 + Global2 -->|"goto_next\nまたは未マッチ"| Regional2["④ リージョンネットワーク\nファイアウォールポリシー"] + Regional2 -->|"allow / deny"| Stop1 + Regional2 -->|"goto_next\nまたは未マッチ"| VPC2["⑤ VPCファイアウォールルール"] + VPC2 -->|"allow / deny"| Stop1 + VPC2 -->|"未マッチ"| Implied2["⑥ 暗黙のアクション\n(Ingress:deny / Egress:allow)"] + + style Stop1 fill:#188038,color:#fff + style Implied1 fill:#f9ab00,color:#000 + style Implied2 fill:#f9ab00,color:#000 +``` + +各ステップにおける評価ロジックは共通しており、次の3段階で処理されます。 + +1. ターゲットが一致しないルールを除外する。 +2. パケットの方向(ingress/egress)が一致しないルールを除外する。 +3. 残ったルールを優先度の高い順(数値が小さい順)に評価し、ターゲットに適用されるルールがマッチするか、マッチするルールがなくなるまで続ける。 + +最終ステップの**暗黙のアクション**(implied action)は方向とターゲットによって異なります。 + +| トラフィック方向 | ターゲット | 暗黙のアクション | +|---|---|---| +| Ingress | VMインスタンスのネットワークインターフェース | `deny` | +| Ingress | 内部ALB/内部プロキシNLBのフォワーディングルール | `allow` | +| Egress | (すべて) | `allow` | + +VPCファイアウォールルールで2つのルールが同一優先度でマッチした場合、`deny`ルールが`allow`ルールより優先して適用されます。 + +> **出典**: [Evaluation order for firewall policies and rules](https://docs.cloud.google.com/firewall/docs/firewall-policies-rule-eval-order) + +> **ベストプラクティス**: 適用順序を変更する強い理由がない限り、デフォルトの`AFTER_CLASSIC_FIREWALL`を維持してください。新規に大規模なファイアウォール基盤を構築する場合、レガシーなVPCファイアウォールルールへの依存を避け、ネットワークファイアウォールポリシー(グローバル/リージョナル)へ統一することで、Secure Tagsによる一貫したIAM統制と将来の機能追加の恩恵を受けられます。 + +--- + +### 2.3 階層ファイアウォールポリシーとEffective Rules + +階層ファイアウォールポリシーは組織・フォルダに関連付けられるコンテナで、下位のポリシーやVPCファイアウォールルールへ評価を委譲する`goto_next`アクションを持つのが特徴です。組織レベルの上位ルールは、下位のフォルダ・プロジェクトのルールで上書きできません。 + +```mermaid +flowchart TD + Pkt(["ターゲットVMへの新規接続パケット"]) --> Org["組織レベルの\n階層ファイアウォールポリシー"] + Org -->|"allow"| AllowOrg["許可・評価終了"] + Org -->|"deny"| DenyOrg["拒否・評価終了"] + Org -->|"apply_security_profile_group"| SPG["ファイアウォールエンドポイントへ転送\n(L7検査)・評価終了"] + Org -->|"goto_next"| F1["トップレベルフォルダの\n階層ファイアウォールポリシー"] + F1 -->|"allow / deny / apply_security_profile_group"| Term1["評価終了"] + F1 -->|"goto_next"| F2["...ターゲットを含む\n下位フォルダのポリシー"] + F2 -->|"allow / deny / apply_security_profile_group"| Term2["評価終了"] + F2 -->|"goto_next\nまたは全ポリシー評価完了"| Next["次の評価ステップ\n(リージョンシステムポリシーへ)"] + + style AllowOrg fill:#188038,color:#fff + style DenyOrg fill:#d93025,color:#fff + style Term1 fill:#188038,color:#fff + style Term2 fill:#188038,color:#fff +``` + +**Effective Firewall Rules**(実効ファイアウォールルール)は、あるVPCネットワークやVMインターフェースに実際に適用されているルール群を可視化する機能です。階層ファイアウォールポリシー由来のルール、VPCファイアウォールルール、グローバル/リージョンネットワークファイアウォールポリシー由来のルールを、組織レベルからVPCネットワークまでの順序で一覧表示します。 + +```bash +# ネットワーク全体の実効ファイアウォールルールを表示 +gcloud compute networks get-effective-firewalls NETWORK_NAME +``` + +> **出典**: +> - [Hierarchical firewall policies](https://docs.cloud.google.com/firewall/docs/firewall-policies) +> - [Manage hierarchical firewall policies and rules](https://docs.cloud.google.com/firewall/docs/manage-hierarchical-firewall-policies) +> - [Create hierarchical firewall policies and rules](https://docs.cloud.google.com/firewall/docs/using-firewall-policies) + +> **ベストプラクティス**: 組織レベルのポリシーは「絶対に守るべき最小限のルール」に留め、`goto_next`を積極的に使って評価を下位へ委譲してください。過度に制限的な組織ポリシーは、各チームの自律的な運用を妨げる摩擦の原因になります。トラブルシューティング時は必ずEffective Firewall Rulesで実際の適用状況を確認し、想定と異なる階層でルールがブロックされていないか検証してください。 + +--- + +### 2.4 Cloud NGFWの3つの階層(Essentials/Standard/Enterprise) + +Cloud NGFWは3つの階層(ティア)で提供され、階層が上がるほど高度な機能と、それに応じた課金体系が適用されます。 + +| ティア | 主な機能 | 課金対象トラフィック | +|---|---|---| +| **Essentials** | 標準的なネットワーク属性(IPレンジ・ポート・プロトコル)によるルール、Secure Tags、アドレスグループ、階層/グローバル/リージョンポリシー基盤 | 課金なし(無料) | +| **Standard** | Essentialsの全機能 + FQDNオブジェクト、ジオロケーションオブジェクト、Google Threat Intelligence(NGFW版) | 南北トラフィック(インターネット⇔VM)のみ課金 | +| **Enterprise** | Standardの全機能 + レイヤー7検査(URLフィルタリングサービス、侵入検知防止サービス IDPS) | 南北 + 東西トラフィック(Google Cloudリソース間)を課金 | + +```mermaid +flowchart LR + subgraph Essentials["Essentials(無料)"] + E1["Secure Tags"] + E2["アドレスグループ"] + E3["階層/グローバル/\nリージョンポリシー基盤"] + end + subgraph Standard["Standard(南北トラフィック課金)"] + S1["FQDNオブジェクト"] + S2["ジオロケーション\nオブジェクト"] + S3["Threat Intelligence"] + end + subgraph Enterprise["Enterprise(南北+東西課金)"] + En1["URLフィルタリング\nサービス"] + En2["IDPS\n(侵入検知防止)"] + En3["TLS Inspection"] + end + + Essentials --> Standard --> Enterprise + + style Essentials fill:#188038,color:#fff + style Standard fill:#f9ab00,color:#000 + style Enterprise fill:#1a73e8,color:#fff +``` + +**コスト最適化パターン**: 課金はルールが評価された時点(トラフィックフローが有料機能を含むルールによって評価された時点)で発生するため、Essentials機能のみを使うルールを**より高い優先度**(小さい数値)に配置し、大部分のトラフィックをそこで処理させることで、有料ティアの評価対象を必要最小限に絞り込めます。 + +```mermaid +flowchart TD + Traffic(["受信トラフィック"]) --> R1["優先度1000(高優先度):\nEssentials機能のみのルール\n(IPアドレス・タグベース)\n→ 課金なし"] + R1 -->|"マッチ"| Done1["処理完了(無料)"] + R1 -->|"未マッチ"| R2["優先度2000:\nStandard/Enterprise機能を含むルール\n(特定タグの組み合わせのみ対象)\n→ 該当トラフィックのみ課金"] + R2 -->|"マッチ"| Done2["IDPS検査等を実施\n(該当フローのみ課金)"] + + style Done1 fill:#188038,color:#fff + style Done2 fill:#f9ab00,color:#000 +``` + +> **出典**: +> - [Cloud NGFW tiers](https://docs.cloud.google.com/firewall/docs/ngfw_tiers) +> - [Cloud Next Generation Firewall pricing](https://cloud.google.com/firewall/pricing) +> - [Key terms](https://docs.cloud.google.com/firewall/docs/key-terms) + +> **ベストプラクティス**: データベース層など重要度の高いワークロードにのみIDPS検査(Enterprise機能)を適用し、東西トラフィック全体を無差別に検査対象にしないでください。Essentialsルールを高優先度に配置しバルクトラフィックを無料で処理する設計は、機能面だけでなくコスト面でも重要な設計判断です。 + +--- + +### 2.5 レイヤー7検査: TLS Inspection・URLフィルタリング・IDPS + +Cloud NGFW Enterpriseのレイヤー7検査機能は、**ファイアウォールエンドポイント**と**セキュリティプロファイル**という2つの構成要素で実現されます。 + +| 構成要素 | 役割 | +|---|---| +| ファイアウォールエンドポイント | 組織レベルのゾーンリソース。1つ以上のVPCに関連付けて傍受トラフィックを検査 | +| セキュリティプロファイル | `url-filtering`(URLフィルタリングルール定義)または`threat-prevention`(IDPS設定)のいずれかの種別を持つ検査設定 | +| セキュリティプロファイルグループ | 各種別1つずつのセキュリティプロファイルを含むコンテナ。`apply_security_profile_group`アクションで参照 | +| TLS Inspectionポリシー | Certificate Authority Service(CAS)を用いて暗号化トラフィックを復号し、L7検査を可能にする設定 | + +TLS Inspectionは、GoogleマネージドのCAS経由で短命の中間証明書を生成し、傍受したTLSトラフィックを復号 → L7検査(URLフィルタリング・IDPS) → 再暗号化して送信先へ転送、という流れで動作します。プロトコルバージョンはTLS 1.0〜1.3をサポートしますが、HTTP/2・QUIC・HTTP/3・PROXYプロトコルはTLS Inspectionと併用できません。 + +```mermaid +sequenceDiagram + participant VM as "送信元VM" + participant FW as "ファイアウォール\nポリシールール" + participant EP as "ファイアウォール\nエンドポイント" + participant CAS as "Certificate Authority\nService(CAS)" + participant SPG as "セキュリティプロファイル\nグループ(URL Filter/IDPS)" + participant Dest as "宛先" + + VM->>FW: "TLS/HTTP(S) トラフィック" + FW->>FW: "apply_security_profile_group\nルールにマッチ" + FW->>EP: "トラフィックを転送" + EP->>CAS: "中間証明書を要求(TLS Inspection時)" + CAS-->>EP: "短命の中間証明書を発行" + EP->>EP: "TLSを復号し、\nURLフィルタリング/IDPSを実行" + alt "検査結果: 許可" + EP->>Dest: "再暗号化して転送" + else "検査結果: 拒否" + EP->>VM: "接続を切断" + end +``` + +URLフィルタリングは、TLS Inspectionが無効な場合でもTLSネゴシエーション時のSNI(Server Name Indication)を用いてドメインマッチングが可能です。ただし完全なURLパスでのフィルタリングにはTLS Inspectionが必要です。 + +> **出典**: +> - [Application layer inspection overview](https://docs.cloud.google.com/firewall/docs/about-app-layer-inspection) +> - [URL filtering service overview](https://docs.cloud.google.com/firewall/docs/about-url-filtering) +> - [TLS inspection overview](https://docs.cloud.google.com/firewall/docs/about-tls-inspection) +> - [Create and manage URL filtering security profiles](https://docs.cloud.google.com/firewall/docs/configure-urlf-security-profiles) + +> **ベストプラクティス**: URLフィルタリングのマッチャー文字列は優先度順に評価され、SNI/ドメイン情報を持たないトラフィックの扱いは最高優先度のURLフィルタ(明示的ALLOWまたは暗黙のDENY)によって決まります。ポリシーの末尾に優先度`2147483647`のワイルドカード拒否ルールを配置し、意図しない許可漏れを防ぐ「暗黙のdeny-all」を明示的に設計してください。Secure Web Proxy(Part 3参照)と組み合わせる場合は、NGFW EnterpriseとSWPの双方でTLS Inspectionを重複させないよう、NGFW側の`tls_inspect`を無効化することを検討してください。 + +--- + +### 2.6 ファイアウォールルールの基準(criteria) + +ファイアウォールルール(VPCファイアウォールルール・ファイアウォールポリシールール共通)の主要な構成基準は以下の通りです。 + +| 基準 | 説明 | +|---|---| +| 優先度(priority) | 0〜65535の整数。数値が小さいほど高優先度。ポリシー内で一意である必要がある | +| 方向(direction) | ingress(受信)またはegress(送信) | +| プロトコル/ポート | TCP/UDP/ICMP等のプロトコルと、任意でポート範囲を指定 | +| 送信元(ingressの場合) | IPレンジ、Secure Tags/Network Tags、サービスアカウント、FQDNオブジェクト(Standard以上)、ジオロケーション(Standard以上) | +| 宛先(egressの場合) | 同上 | +| ターゲット | ルールを適用するリソース(全インスタンス、特定のSecure Tags/Network Tags、特定のサービスアカウント) | +| アクション | allow / deny / apply_security_profile_group / goto_next(階層ポリシーのみ) | +| ロギング | ルールごとに有効/無効を設定可能(`goto_next`ルールはロギング不可) | + +REST APIで階層ファイアウォールポリシールールを直接作成する場合は方向を明示的に指定する必要がありますが、gcloud CLIでは方向省略時のデフォルトは`INGRESS`です。 + +> **出典**: [Manage hierarchical firewall policies and rules](https://docs.cloud.google.com/firewall/docs/manage-hierarchical-firewall-policies) + +> **ベストプラクティス**: ルールには必ず`description`フィールドで意図を記録してください。半年後に見返した際、なぜそのルールが存在するのかをチーム全員が理解できることが、大規模組織でのファイアウォール運用の生命線になります。 + +--- + +### 2.7 Secure Tags と Network Tags によるマイクロセグメンテーション + +Google Cloudには2種類の「タグ」があり、対応するポリシー種別が異なります。 + +| 項目 | Secure Tags(IAM-governed tags) | Network Tags(従来のタグ) | +|---|---|---| +| 管理場所 | Resource Managerでキー・値のペアとして管理 | VMインスタンス/インスタンステンプレートに直接付与する文字列 | +| アクセス制御 | あり(IAMで誰がタグを作成・付与できるか統制可能) | なし(単なる文字列、アクセス制御機構を持たない) | +| 対応ポリシー | 階層ファイアウォールポリシー、グローバル/リージョンネットワークファイアウォールポリシー | VPCファイアウォールルール(classic)のみ | +| VPCファイアウォールルールでの利用 | 不可 | 可能 | +| 適用範囲 | 組織全体で一意なキー(最大1,000個のユニークな値を参照可能) | VPCネットワークごとに独立した文字列 | + +Secure Tagsは、IAMによる厳格なアクセス制御のもとで、リージョン・ネットワーク構成に関わらずワークロードに一貫したポリシーを適用できるため、大規模なマイクロセグメンテーション基盤に適しています。GKEワークロードに対してもSecure Tagsを付与できます。 + +> **出典**: [Secure tags for firewalls](https://docs.cloud.google.com/firewall/docs/tags-firewalls-overview) + +> **ベストプラクティス**: 新規に大規模なマイクロセグメンテーション設計を行う場合は、アクセス制御の効かないNetwork Tagsではなく、Secure Tagsを起点に設計してください。「誰がタグを付与できるか」をIAMで統制できることは、多数のチームが同じVPCを共有するShared VPC環境において特に重要な統制ポイントになります。 + +--- + +### 2.8 ファイアウォールルールロギング + +ファイアウォールルールロギングは、ルールごとに有効化する任意設定で、そのルールにマッチしたトラフィックの詳細(接続情報)をCloud Loggingへ記録します。VPCファイアウォールルールとファイアウォールポリシールールでログフォーマットが異なるため、ログ基盤側でのパース処理は両方に対応させる必要があります。 + +| ロギング種別 | 対象 | 主な用途 | +|---|---|---| +| VPCファイアウォールルールロギング | classic VPCファイアウォールルール | レガシー環境のトラフィック可視化 | +| ファイアウォールポリシールールロギング | 階層/グローバル/リージョンネットワークファイアウォールポリシー | 統合的なトラフィック監査・コンプライアンス証跡 | +| Firewall Insights | 全ポリシー種別 | 過度に許可的なルール・未使用ルール・シャドウルールの検出と改善提案 | + +> **出典**: [Logging for firewall policy rules](https://docs.cloud.google.com/firewall/docs/firewall-policy-rules-logging-overview) + +> **ベストプラクティス**: すべてのdeny/allowルールでロギングを有効化するとログ量とコストが増大するため、コンプライアンス上重要な境界(組織/フォルダレベルのdenyルール、機密ワークロードへのアクセスを許可するルール)を優先的にロギング対象とし、内部の高頻度な東西トラフィックは必要に応じてサンプリングやFirewall Insightsによる定期レビューで補完してください。 + +--- + +### 2.9 VPCファイアウォールルールからCloud NGFWポリシーへの移行 + +Googleは移行ツール(`gcloud beta compute firewall-rules migrate`)を提供しており、既存のVPCファイアウォールルールをグローバルネットワークファイアウォールポリシーへ自動変換できます。 + +```mermaid +flowchart TD + A["既存VPCファイアウォールルールの棚卸し\n(優先度・依存関係を記録)"] --> B{"Network Tags や\nサービスアカウントに依存するルールか?"} + B -->|"依存なし"| C["gcloud beta compute firewall-rules migrate\n--source-network --target-firewall-policy"] + B -->|"依存あり"| D["Network TagsをSecure Tagsへ\nマッピングしてから移行"] + D --> C + C --> E["移行ツールが新規グローバル\nネットワークファイアウォールポリシーを生成\n(既存ルールをポリシールールへ変換)"] + E --> F["ポリシーを検証\n(get-effective-firewalls等で比較)"] + F --> G["gcloud compute network-firewall-policies\nassociations create でVPCに関連付け"] + G --> H{"GKE自動生成ルールが\n含まれるか?"} + H -->|"はい"| I["GKE自動生成ルール(gke-*, k8s-*)は\n除外パターンで移行対象から外し、\n個別に移行手順を実施"] + H -->|"いいえ"| J["旧VPCファイアウォールルールを削除"] + I --> J + + style J fill:#188038,color:#fff +``` + +移行によって得られる主な利点は、Secure Tagsを用いたIAM統制、バッチ編集による一括ルール更新、FQDNオブジェクト・ジオロケーションオブジェクト・Threat Intelligenceといった高度な属性の利用、そして複数VPCへの単一ポリシーの共有です。 + +> **出典**: +> - [VPC firewall rules migration overview](https://cloud.google.com/firewall/docs/migrate-vpc-firewall-rules-overview) +> - [Migrate VPC firewall rules that don't use network tags and service accounts](https://cloud.google.com/firewall/docs/migrate-firewall-rules-no-dependencies) +> - [From VPC firewall rules to Cloud NGFW network firewall policies](https://cloud.google.com/blog/products/networking/from-vpc-firewall-rules-to-cloud-ngfw-network-firewall-policies) + +> **ベストプラクティス**: GKEが自動生成するVPCファイアウォールルール(`gke-(.+)-ipv6-all`、`k8s-fw-*`等の正規表現にマッチするルール)は移行ツールの対象から除外し、GKEサービスIP向けのingressルールを個別に手動作成した上で、既存の自動生成allowルールを無効化する専用手順に従ってください。移行直後はすぐに旧ルールを削除せず、Effective Firewall Rulesで新旧ポリシーの評価結果が一致することを確認してから削除作業に進むことを推奨します。 + +--- + +### 2.10 GKEおよびCloud Load BalancingでのCloud NGFWサポート + +Cloud NGFWはGKEワークロードとCloud Load Balancingの双方に対応しています。 + +| ワークロード種別 | 対応内容 | +|---|---| +| GKE Podレベル | Secure TagsをPodに付与し、ネットワークポリシーと組み合わせたマイクロセグメンテーションが可能 | +| GKEノードレベル | Essentials/Standard/Enterpriseいずれの機能もノードのVMインターフェースに適用可能 | +| 内部ALB/内部プロキシNLB | マネージドEnvoyプロキシに対してもファイアウォールルールがingress対象として適用される | +| 外部ALB(グローバル/リージョン) | グローバル/リージョンネットワークファイアウォールポリシーでバックエンドを保護可能(Cloud Armorと併用可能) | + +> **出典**: [Firewall policies and rules](https://docs.cloud.google.com/firewall/docs/firewall-policies-overview) + +> **ベストプラクティス**: GKEクラスタでSecure Tagsベースのマイクロセグメンテーションを導入する際は、GKEのネットワークポリシー(Kubernetes NetworkPolicyリソース、Dataplane V2)とCloud NGFWのファイアウォールポリシーが二重に競合しないよう、責任分界(Podレベルの制御はKubernetes NetworkPolicy、ノード/クラスタ境界の制御はCloud NGFW)を明確にしてください。 + +--- + +### 2.11 Part 2 ベストプラクティス一覧 + +| 領域 | ベストプラクティス | +|---|---| +| ポリシー戦略 | 組織全体の絶対要件は階層ポリシー、柔軟なルールはグローバル/リージョンネットワークポリシーで管理する | +| 評価順序 | 特段の理由がなければデフォルトの`AFTER_CLASSIC_FIREWALL`を維持する | +| 階層ポリシー | 組織レベルは最小限に留め、`goto_next`で下位への委譲を積極的に活用する | +| コスト最適化 | Essentials機能のルールを高優先度に配置し、有料ティアの評価対象を絞り込む | +| L7検査 | Enterprise階層のIDPS/URLフィルタリングは重要ワークロードに限定して適用する | +| マイクロセグメンテーション | 新規設計はNetwork TagsではなくSecure Tagsを起点にする | +| ロギング | コンプライアンス上重要な境界を優先し、全ルール一律のロギングは避ける | +| 移行 | GKE自動生成ルールを除外し、Effective Firewall Rulesで新旧の一致を確認してから旧ルールを削除する | +| GKE統合 | Podレベルの制御はKubernetes NetworkPolicy、ノード/クラスタ境界はCloud NGFWと責任分界を明確にする | + +--- + +## Part 3: インターネットEgressの構成と保護 — Public Cloud NATとSecure Web Proxy + +### 3.1 Cloud NATのIPアドレッシング + +Public Cloud NATは、外部IPを持たないVMやGKEノードに対してソースNAT(SNAT)を行い、インターネットへのegress接続を可能にするリージョンサービスです。NAT IPアドレスの割り当て方式には2種類あります。 + +| 割り当て方式 | 動作 | 予測可能性 | 主な用途 | +|---|---|---|---| +| 自動(Automatic) | VM数・必要ポート数に応じてGoogle Cloudが静的外部IPを自動的に追加/削除。選択したネットワーク階層(Premium/Standard)のIPが割り当てられる | 不可(次に割り当てられるIPを事前に予測できない) | スケーラビリティを優先する一般的なワークロード | +| 手動(Manual) | 管理者が予約済み静的外部IPアドレスを明示的に指定 | 可能 | サードパーティAPIのIP許可リスト(allowlist)登録が必要なワークロード | + +自動割り当てのNAT IPは、そのIP上のポートを使用するVMが1つもなくなるまで解放されません(使用中のVMがある限りIPはアクティブなまま保持され、Cloud NATはVMを別IPへ動的に再割り当てすることはありません。これは既存の接続を破壊しないための設計です)。 + +> **出典**: [IP addresses and ports](https://docs.cloud.google.com/nat/docs/ports-and-addresses)、[Quickstart: Set up and manage network address translation with Public NAT](https://docs.cloud.google.com/nat/docs/set-up-manage-network-address-translation) + +> **ベストプラクティス**: サードパーティのAPIやパートナーシステムがIP許可リストを要求する場合は、必ず手動IP割り当てを選択し、静的予約IPを使用してください。自動割り当てのままでは、IPが変更された際に相手先での許可リスト更新が必要になり、予期しない接続断が発生するリスクがあります。 + +--- + +### 3.2 ポート割り当て(静的/動的) + +Cloud NAT IPアドレス1つあたり、TCP/UDPそれぞれ64,512個のソースポート(0〜1,023のウェルノウンポートを除く65,536個から算出)が利用可能です。ポート割り当て方式には静的と動的の2種類があります。 + +| 割り当て方式 | 動作 | デフォルト値 | +|---|---|---| +| 静的ポート割り当て | 全VMに対して固定数のポートを一律割り当て | 最小64ポート/VM | +| 動的ポート割り当て | VMごとの実際の使用量に応じて異なる数のポートを動的に割り当て。初期値は最小ポート数からスタートし、必要に応じて最大値まで増加 | 環境により最小/最大を設定(推奨値: 最小2048、最大4096など、ワークロードにより調整) | + +```mermaid +flowchart TD + Start(["Cloud NATゲートウェイの設計"]) --> Q1{"VMごとの接続数に\nばらつきが大きいか?"} + Q1 -->|"いいえ(均一なワークロード)"| Static["静的ポート割り当てを選択\n(最小ポート数を用途に応じて調整)"] + Q1 -->|"はい(バーストする\nワークロードが存在)"| Dynamic["動的ポート割り当てを選択\n(最小/最大ポート数を設定)"] + + Static --> IPCalc["IPアドレス数 = \n必要VM数 × 最小ポート数 ÷ 64,512\nを事前に計算"] + Dynamic --> Monitor["ポート使用率メトリクスを監視し、\n枯渇の兆候があれば最大値を引き上げ"] + + IPCalc --> Manual{"IPの予測可能性が必要か?\n(サードパーティ許可リスト等)"} + Manual -->|"はい"| ManualIP["手動IPアドレス割り当てを併用"] + Manual -->|"いいえ"| AutoIP["自動IPアドレス割り当てを使用"] + + style Static fill:#1a73e8,color:#fff + style Dynamic fill:#188038,color:#fff +``` + +ポート割り当て方式の変更や、静的方式でのポート数の**減少**は既存のNAT接続を切断する可能性があるため、変更前に「IPアドレスのドレイン(段階的な切り離し)」の検討が必要です。一方、ポート数の**増加**(静的・動的いずれも)は既存接続を中断しません。 + +> **出典**: [IP addresses and ports](https://docs.cloud.google.com/nat/docs/ports-and-addresses)、[Quickstart: Set up and manage network address translation with Public NAT](https://docs.cloud.google.com/nat/docs/set-up-manage-network-address-translation) + +> **ベストプラクティス**: ポート枯渇によるNATエラー(`allocation_status="DROPPED"`)をCloud Loggingで継続的に監視してください。バーストする可能性のあるワークロードには動的ポート割り当てを採用し、固定サイズのワークロードには静的割り当てでリソースを予測可能に保つという使い分けが基本方針になります。IPアドレスを変更する際は、必ず「外部IPアドレスのドレイン」手順に従い、既存接続を保護してください。 + +--- + +### 3.3 Secure Web Proxyの概要とデプロイモード + +Secure Web Proxy(Cloud SWP)は、egressのWeb(HTTP/HTTPS)トラフィックに対して、送信元アイデンティティ(Secure Tags・サービスアカウント・IPアドレス)、宛先(ドメイン・URL・URLリスト)、リクエスト属性(メソッド・ヘッダー)に基づく粒度の高いアクセスポリシーを適用するサービスです。 + +トラフィックの発信元として、VMインスタンス、コンテナ、サーバーレスVPCアクセスコネクタ経由のサーバーレス環境、Cloud VPN/Cloud Interconnect経由のオンプレミスワークロードをサポートします。 + +| デプロイモード | 説明 | +|---|---| +| 明示的プロキシルーティングモード | クライアント側でSecure Web Proxyを明示的にプロキシサーバーとして構成。クライアントに代わって新しいTCP接続を作成し、インターネットから分離する | + +Secure Web ProxyはCertificate Authority Service(CAS)を用いたTLS Inspectionを統合的に提供し、暗号化されたリクエストの内容(完全なURLパス、HTTPヘッダー)まで検査できます。クライアント-プロキシ間のトンネルもTLSで保護可能で、HTTP/HTTPS CONNECTによるクライアント起点のエンドツーエンドTLS接続もサポートします。 + +```mermaid +sequenceDiagram + participant VM as "VM / コンテナ / サーバーレス" + participant SWP as "Secure Web Proxy\n(Envoyプロキシプール)" + participant CAS as "Certificate Authority\nService" + participant Ext as "外部Webサイト" + + VM->>SWP: "明示的プロキシ経由でHTTPS接続要求" + SWP->>SWP: "ポリシー評価:\n送信元(Tag/SA)・宛先(URL)・\nリクエスト属性をマッチング" + alt "TLS Inspection有効" + SWP->>CAS: "証明書を要求" + CAS-->>SWP: "証明書を発行" + SWP->>SWP: "TLSを復号し、\nURLパス/ヘッダーを検査" + end + alt "ポリシーで許可" + SWP->>Ext: "新規TCP接続を作成し転送" + Ext-->>SWP: "レスポンス" + SWP-->>VM: "レスポンスを返却" + else "ポリシーで拒否" + SWP-->>VM: "接続拒否 + Cloud Loggingへ記録" + end +``` + +> **出典**: +> - [Secure Web Proxy overview](https://docs.cloud.google.com/secure-web-proxy/docs/overview) +> - [TLS inspection overview | Secure Web Proxy](https://docs.cloud.google.com/secure-web-proxy/docs/tls-inspection-overview) +> - [Secure Web Proxy (SWP)](https://cloud.google.com/security/products/secure-web-proxy) + +> **ベストプラクティス**: TLS InspectionはクライアントデバイスがSecure Web Proxyのプライベート認証局(内部CA)を信頼済みルートとして事前インストールしている、管理下のデバイス(マネージドVM等)でのみ有効に機能します。証明書ピンニングを行うアプリケーション(特定の公開鍵/CAチェーンをハードコードしたクライアント)はTLS Inspection経由で通信できない場合があるため、事前に対象アプリケーションの互換性を確認してください。 + +--- + +### 3.4 Secure Web Proxyポリシーの構成 + +Secure Web Proxyのポリシーは**デフォルトで全てのegress Webトラフィックを拒否**し、明示的なルールで許可した通信のみを通す「ホワイトリスト方式」で動作します。 + +| 属性カテゴリ | 利用可能な識別子 | +|---|---| +| 送信元(source) | サービスアカウント、Secure Tags(Resource Managerタグ)、IPアドレス(社内固定IPやGoogle Cloud静的IP) | +| 宛先(destination) | 宛先ドメイン、完全URLパス(TLS Inspection有効時)、URLリスト、宛先ポート | +| リクエスト属性 | HTTPメソッド、ヘッダー、URL(ワイルドカード・パターンで指定可能) | + +URLリストは複数のポリシーから再利用できるモジュール化されたオブジェクトであり、中央管理者が定義したリストを、各チームが自身のポリシーから参照する運用が可能です。 + +Secure Web ProxyのegressトラフィックはPublic Cloud NAT経由でインターネットへ出るため、固定の送信元IPアドレスが必要な場合は、Cloud NAT側の設定を「自動(推奨)」から「手動」へ変更し、静的予約IPを割り当てます。 + +> **出典**: +> - [Secure Web Proxy policies overview](https://docs.cloud.google.com/secure-web-proxy/docs/policies-overview) +> - [Assign static IP addresses for outbound traffic](https://docs.cloud.google.com/secure-web-proxy/docs/assign-static-ip-addresses-for-egress-traffic) + +> **ベストプラクティス**: Secure Web Proxyのegress IPを固定化する場合、Cloud NAT側で動的ポート割り当てを有効化し(推奨値: 最小2048ポート/VM、最大4096ポート/VM)、限られた静的IPプールを効率的に利用してください。VPC Service Controlsと組み合わせることで、Cloud StorageやBigQueryなどのGoogle Cloudサービスからのデータ持ち出し(exfiltration)防止も同時に実現できます。 + +--- + +### 3.5 Part 3 ベストプラクティス一覧 + +| 領域 | ベストプラクティス | +|---|---| +| IPアドレッシング | サードパーティのIP許可リスト連携が必要な場合は手動IP割り当てを使用する | +| ポート割り当て | 均一なワークロードは静的、バーストするワークロードは動的ポート割り当てを選択する | +| 監視 | `allocation_status="DROPPED"`ログを継続監視し、ポート枯渇を早期検知する | +| IP変更 | 変更前に外部IPアドレスのドレイン手順を実施し、既存接続への影響を最小化する | +| SWP TLS Inspection | 証明書ピンニングを行うアプリケーションの互換性を事前確認する | +| SWPポリシー | デフォルト拒否の原則を維持し、必要な宛先のみを明示的に許可する | +| SWP × Cloud NAT | 固定送信元IPが必要な場合はCloud NAT側を手動割り当て + 動的ポート割り当てに構成する | +| データ保護 | VPC Service Controlsと組み合わせてデータ持ち出しリスクを低減する | + +--- + +## Part 4: セルフマネージドNVAとPacket Mirroringの構成 + +### 4.1 マルチNIC VMによるVPC間トラフィックのルーティングと検査 + +セルフマネージドのネットワーク仮想アプライアンス(NVA)は、複数のネットワークインターフェース(マルチNIC)を持つCompute Engine VMとして構成され、異なるVPCネットワーク間のトラフィックを検査・ルーティングする役割を担います。サードパーティ製のNGFWアプライアンス(FortiGate、Palo Alto Networks VM-Series等)や自作のルーティング/ゲートウェイソフトウェアが該当します。 + +典型的な構成は、ハブVPCに配置したマルチNIC NVAのインスタンスグループが、複数のスポークVPCからのトラフィックを集約・検査するハブアンドスポーク型です。 + +```mermaid +flowchart TB + subgraph Hub["ハブVPC"] + NVA1["NVA VM #1\n(nic0: spoke-A側 / nic1: spoke-B側)"] + NVA2["NVA VM #2\n(nic0: spoke-A側 / nic1: spoke-B側)"] + ILB_A["内部パススルーNLB #A\n(nic0向け)"] + ILB_B["内部パススルーNLB #B\n(nic1向け)"] + end + + subgraph SpokeA["スポークVPC A"] + VMA["ワークロードVM"] + RouteA["静的ルート:\nnext-hop = ILB_A"] + end + + subgraph SpokeB["スポークVPC B"] + VMB["ワークロードVM"] + RouteB["静的ルート:\nnext-hop = ILB_B"] + end + + VMA -->|"VPC Peering / NCC経由"| RouteA + RouteA --> ILB_A + ILB_A --> NVA1 + ILB_A --> NVA2 + NVA1 -->|"検査・SNAT/ルーティング"| ILB_B + NVA2 -->|"検査・SNAT/ルーティング"| ILB_B + ILB_B --> RouteB + RouteB --> VMB + + style NVA1 fill:#1a73e8,color:#fff + style NVA2 fill:#1a73e8,color:#fff +``` + +> **出典**: [Internal passthrough Network Load Balancers as next hops](https://docs.cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) + +> **ベストプラクティス**: マルチNIC NVAを自前で構築・運用する前に、Cloud NGFW Enterprise(Part 2参照)やNetwork Security Integration(本Partの4.4節参照)で同等の要件を満たせないか検討してください。セルフマネージドNVAはGoogle管理サービスに比べて構成・パッチ適用・スケーリングの運用負荷が高く、可能な限りマネージドサービスへの移行を優先することが長期的な運用コストの削減につながります。 + +--- + +### 4.2 HA構成: 内部パススルーNLBをネクストホップにする + +内部パススルーNetwork Load Balancer(ILB)は、静的ルートのネクストホップとして指定できます。これにより、マルチNIC NVAを冗長構成(インスタンスグループの複数VM)にした上で、ヘルスチェックによる自動フェイルオーバーを実現できます。 + +| 用途 | 説明 | +|---|---| +| デフォルトルートのネクストホップ | インターネットへのトラフィックを、負荷分散されたゲートウェイVM群経由でルーティング | +| 複数方向へのトラフィック分散 | 同一のマルチNIC VMセットを、方向ごとに異なるILB(nic0向け・nic1向け)の背後に配置し、双方向トラフィックを処理 | +| タグベースの複数ネクストホップ | Network Tagsを使い、クライアントVMごとに異なるILBネクストホップへ振り分け(ECMPは同一優先度・同一タグの複数ルート間では非対応) | + +```mermaid +flowchart LR + Client["クライアントVM群"] --> Route["スタティックルート\n(0.0.0.0/0)\nnext-hop = ILB"] + Route --> ILB["内部パススルーNLB\n(5-tupleハッシュで負荷分散)"] + ILB --> HC{"ヘルスチェック"} + HC -->|"healthy"| ActiveVM["アクティブNVA VM"] + HC -->|"unhealthy"| Failover["トラフィックを\n他の健全なVMへ自動転送"] + + ActiveVM --> Backend["バックエンドVMインスタンス\n(インスタンスグループ)"] + Failover --> Backend + + style ActiveVM fill:#188038,color:#fff + style Failover fill:#f9ab00,color:#000 +``` + +ILBネクストホップの背後にあるバックエンドVMは、**IP転送(IP forwarding)を有効化**する必要があります。ILBがネクストホップの場合、クライアントVM側のゲストOSには特別な設定は不要です(クライアントはロードバランサーの背後にあるバックエンドを経由してパケットを送信するだけです)。 + +> **出典**: +> - [Internal passthrough Network Load Balancers as next hops](https://docs.cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) +> - [Deploy a hub-and-spoke network by using a load balancer as the next hop](https://docs.cloud.google.com/load-balancing/docs/internal/deploying-ilb-next-hop-vm) +> - [Set up an internal passthrough Network Load Balancer as next hop (with tags)](https://docs.cloud.google.com/load-balancing/docs/internal/setting-up-internal-next-hop-tags) + +> **ベストプラクティス**: FortiGateなど商用NVAのHAクラスタを構成する場合、アクティブ/パッシブの判定にベンダー固有のヘルスチェックプローブレスポンダー(アクティブなクラスタメンバーのみが応答するプローブ)を使用し、Cloud Load Balancingのヘルスチェックと連携させてください。フェイルオーバー時の既存TCP接続の維持には、Cloud Load Balancingのコネクショントラッキング機能が有効に機能します。タグベースのネクストホップルートはVPC Network Peering経由ではエクスポート/インポートされない点に注意し、Peering先での経路設計を別途検討してください。 + +--- + +### 4.3 HA マルチNIC VMルーティングのためのポリシーベースルート + +ポリシーベースルート(Policy-Based Routes, PBR)は、パケットの**宛先IPアドレスだけでなく、プロトコルや送信元IPアドレスも加味して**ネクストホップを選択できるルーティング機構です。 + +| 項目 | 仕様 | +|---|---| +| マッチ条件 | 宛先IP、プロトコル、送信元IPアドレス | +| 適用対象 | 同一VPC内の全VMインスタンス/Interconnect VLANアタッチメント/VPNトンネル、または特定のNetwork Tagsを持つVMのみ、または特定リージョンのVLANアタッチメントのみ | +| ネクストホップ | 有効な内部パススルーNLBである必要がある(同一VPC、またはVPC Network Peering接続先のVPC) | +| バックエンド要件 | ネクストホップILBの背後のVMインスタンスはIP転送を有効化する必要がある | +| 評価順序 | サブネットルート・スタティックルート・ダイナミックルートより先、特殊経路(special routing paths)より後に評価される | +| 同一優先度の競合 | 複数のポリシーベースルートが同一優先度でマッチする場合、Google Cloudが内部アルゴリズムで1つを選択(最も詳細なマッチが選ばれるとは限らない) | + +```mermaid +flowchart TD + Pkt(["パケット到着"]) --> Special["① 特殊経路\n(default internet gateway等)"] + Special --> PBR["② ポリシーベースルート\n(宛先IP + プロトコル + 送信元IPでマッチ)"] + PBR -->|"マッチ"| ILBNext["内部パススルーNLBへ\n(NVA/ファイアウォールへ挿入)"] + PBR -->|"未マッチ"| Subnet["③ サブネットルート"] + Subnet --> Static["④ スタティックルート"] + Static --> Dynamic["⑤ ダイナミックルート\n(Cloud Router BGP)"] + + style ILBNext fill:#1a73e8,color:#fff +``` + +ポリシーベースルートは、通常のスタティックルート(宛先IPのみでマッチ)よりも粒度の高い制御が必要な場合、たとえば「特定のプロトコル(TCP/443のみ)や特定の送信元サブネットのトラフィックのみをNVA経由でインスペクションしたい」といったユースケースで使用します。 + +> **出典**: [Policy-based routes](https://docs.cloud.google.com/vpc/docs/policy-based-routes) + +> **ベストプラクティス**: マルチNIC NVAをHA構成にする際は、ポリシーベースルートのネクストホップにも内部パススルーNLBを指定し、静的ルート(4.2節)と組み合わせることで、プロトコル/送信元単位の柔軟なトラフィック挿入と、ロードバランサーによる自動フェイルオーバーの両方を実現してください。同一優先度でのルート競合は選択結果が保証されないため、意図した経路制御には優先度を明示的に分離してください。 + +--- + +### 4.4 アウトオブバンドのNetwork Security Integration戦略 + +Network Security Integration(NSI)のアウトオブバンド統合は、Packet Mirroring技術を基盤としつつ、**プロデューサー(検査サービス提供側)とコンシューマー(トラフィックを検査してほしい側)を分離したモデル**を提供する、よりスケーラブルなアーキテクチャです。トラフィックはGeneveカプセル化によって元のパケットを保持したまま転送され、VPCネットワーク識別子が付与されるため、重複するIPアドレス範囲を持つ複数VPCが存在する環境でも正しく識別できます。 + +| コンポーネント | 役割 | +|---|---| +| ミラーリングデプロイグループ(プロデューサー側) | 複数ゾーンにまたがるミラーリングデプロイの集合。プロデューサーの検査サービスを表すグローバルなプロジェクトレベルリソース | +| ミラーリングエンドポイントグループ(コンシューマー側) | プロデューサーのデプロイグループを参照するコンシューマー側リソース | +| ミラーリングエンドポイントグループアソシエーション | エンドポイントグループを特定のVPCネットワークに関連付け、そのVPCのトラフィックを検査対象にする | +| カスタムミラーリングセキュリティプロファイル | ミラーリングエンドポイントグループを参照する検査設定。セキュリティプロファイルグループに含めてファイアウォールルールの`MIRROR`アクションから参照 | + +```mermaid +flowchart LR + subgraph Consumer["コンシューマーVPC(検査対象)"] + CVM["ワークロードVM"] + FWPolicy["ネットワークファイアウォールポリシー\n(ミラーリングルール: action=MIRROR)"] + MEG["ミラーリングエンドポイント\nグループ"] + Assoc["エンドポイントグループ\nアソシエーション"] + end + + subgraph Producer["プロデューサーVPC(検査サービス提供側)"] + MDG["ミラーリングデプロイグループ"] + MD["ミラーリングデプロイ\n(ゾーンごと)"] + ILB2["内部パススルーNLB"] + Collector["検査アプライアンス\n(サードパーティ製 等)"] + end + + CVM -->|"トラフィック"| FWPolicy + FWPolicy -->|"MIRROR一致"| Assoc + Assoc --> MEG + MEG -->|"Geneveカプセル化\n(VPC識別子付与)"| MDG + MDG --> MD + MD --> ILB2 + ILB2 --> Collector + + style MEG fill:#1a73e8,color:#fff + style MDG fill:#188038,color:#fff +``` + +NSIアウトオブバンド統合は、**ミラーリングコレクターのサービス化**という運用モデルもサポートします。セキュリティ管理者が所有する専用プロジェクトでミラーリングデプロイグループを一元運用し、各アプリケーションチームのVPC(コンシューマー)がそれをサービスとして利用する、という責任分界が可能です。 + +> **出典**: +> - [Out-of-band integration overview](https://docs.cloud.google.com/network-security-integration/docs/out-of-band/out-of-band-integration-overview) +> - [Mirroring endpoint groups overview](https://cloud.google.com/network-security-integration/docs/out-of-band/endpoint-groups-overview) +> - [Mirroring deployment groups overview](https://cloud.google.com/network-security-integration/docs/out-of-band/deployment-groups-overview) +> - [Set up out-of-band integration for a producer-consumer model](https://cloud.google.com/network-security-integration/docs/tutorial/out-of-band-integration-tutorial) + +> **ベストプラクティス**: 複数のアプリケーションチームが同じ検査基盤(IDS/NTAツール等)を共有する組織では、従来のPacket Mirroring(4.5節)よりも、プロデューサー/コンシューマーモデルのNetwork Security Integrationを優先的に検討してください。検査アプライアンスの運用をセキュリティチームに集約しつつ、各チームのVPCからはサービスとして疎結合に利用できるため、大規模組織でのスケーラビリティと運用分離の両方を実現できます。regional network firewall policiesはPacket Mirroringに対応していない点にも留意してください。 + +--- + +### 4.5 Packet Mirroring(セルフマネージドコレクター) + +従来のPacket Mirroring機能は、指定したVPC内のミラーリング対象インスタンス(mirrored sources)のトラフィックを複製し、内部パススルーNLBの背後にあるコレクターインスタンスグループへ転送します。ペイロードとヘッダーを含む全トラフィックをエクスポートするため、サンプリングベースのVPC Flow Logsでは検出できない詳細な脅威分析やアプリケーションパフォーマンス分析が可能です。 + +| 設定項目 | 内容 | +|---|---| +| ミラーリング対象(source) | サブネット、Network Tags、インスタンス名のいずれかで指定。複数指定した場合、いずれかにマッチするインスタンスが対象 | +| キャプチャ方向 | ingressのみ・egressのみ・両方向、を選択可能 | +| コレクター destination | 内部パススルーNLBの背後にあるインスタンスグループ(コレクターインスタンス) | +| スコープの制約 | ミラーリング対象は同一プロジェクト・同一VPCネットワーク・同一リージョン内である必要がある | + +```mermaid +flowchart TD + subgraph Sources["ミラーリング対象"] + S1["VM(サブネット指定)"] + S2["VM(Network Tags指定)"] + end + + Policy["Packet Mirroringポリシー\n(同一リージョン内で定義)"] --> Sources + Sources -->|"ingress / egress / 両方向を複製"| ILB3["内部パススルーNLB\n(collector destination)"] + ILB3 --> Collector1["コレクターVM #1"] + ILB3 --> Collector2["コレクターVM #2"] + Collector1 --> Analysis["セキュリティ分析ソフトウェア\n(脅威検知・異常検知)"] + Collector2 --> Analysis + + style ILB3 fill:#1a73e8,color:#fff +``` + +コレクターインスタンスは、ミラーリング対象からのトラフィックとGoogle Cloudヘルスチェックシステムからのトラフィックを受信できるファイアウォールルールが必要です。また、コレクターにはインターネットトラフィックが到達しないよう、内部IPアドレスのみを割り当てることが推奨されます。 + +VPC Flow Logsはミラーリングされたパケット自体をログに記録しませんが、コレクターインスタンスが配置されたサブネットでVPC Flow Logsが有効な場合、コレクター宛ての直接トラフィック(元の宛先IPがコレクターのIPと一致するフロー)はログに記録されます。 + +> **出典**: +> - [Packet Mirroring](https://docs.cloud.google.com/vpc/docs/packet-mirroring) +> - [Use Packet Mirroring](https://docs.cloud.google.com/vpc/docs/using-packet-mirroring) + +> **ベストプラクティス**: ミラーリング対象・コレクターともに同一プロジェクト・同一VPC・同一リージョンという制約があるため、複数リージョンにまたがる大規模環境では、リージョンごとに独立したPacket Mirroringポリシーとコレクター基盤を設計する必要があります。組織横断的な集約検査基盤が必要な場合は、4.4節のNetwork Security Integration(アウトオブバンド統合)への移行を検討してください。ミラーリングはVM側で追加の帯域を消費する点も、キャパシティプランニング時に考慮してください。 + +--- + +### 4.6 Part 4 ベストプラクティス一覧 + +| 領域 | ベストプラクティス | +|---|---| +| NVA導入の判断 | セルフマネージドNVAの前に、Cloud NGFW EnterpriseやNSIで要件を満たせないか検討する | +| HA設計 | 内部パススルーNLBをネクストホップにし、ヘルスチェックによる自動フェイルオーバーを構成する | +| IP転送 | ネクストホップILB背後のバックエンドVMでは必ずIP転送を有効化する | +| ポリシーベースルート | プロトコル/送信元単位の細かい制御が必要な場合はPBRを、シンプルなデフォルトルート挿入には静的ルートを使い分ける | +| タグベースルート | VPC Network Peering越しにはタグ付きルートがエクスポートされない点を設計に織り込む | +| 検査基盤の選定 | 複数チーム共有の検査基盤はNSI(プロデューサー/コンシューマーモデル)を優先し、単純な単一VPC内検査には従来のPacket Mirroringを使う | +| スコープ制約 | Packet Mirroringはプロジェクト/VPC/リージョンの境界を越えられないため、マルチリージョン環境ではリージョンごとに設計する | +| コレクター保護 | コレクターインスタンスには内部IPのみを割り当て、インターネットからの直接到達を防ぐ | + +--- + +## 設計・実装チェックリスト + +以下は、Section 6「ネットワークセキュリティの設計と実装」に関する設計・実装レビュー用のチェックリストです。 + +### Cloud Armor(6.1) +- [ ] 全ての公開バックエンドサービス/バックエンドバケットにCloud Armorセキュリティポリシーがアタッチされているか +- [ ] プリコンフィグドWAFルールをプレビューモードで検証済みか、感度レベルは段階的に設定されているか +- [ ] 外部パススルーNLB/プロトコルフォワーディング/パブリックIP VMを保護する場合、高度なネットワークDDoS防御への加入を検討したか +- [ ] Adaptive Protectionのアラートしきい値・自動デプロイ条件が保守的に設定されているか +- [ ] レート制限ルール(throttle/rate_based_ban)がAPIエンドポイントの特性に応じて設計されているか +- [ ] Bot管理・reCAPTCHA連携が必要なエンドポイントで有効化されているか +- [ ] Google Threat Intelligenceの適用範囲(Tor/悪意あるIP/bot/パブリッククラウド)が業務要件と整合しているか + +### Cloud NGFW / VPCファイアウォール(6.2) +- [ ] ファイアウォール戦略(階層 vs グローバル/リージョンネットワーク vs VPC classic)が組織のガバナンス方針と整合しているか +- [ ] ネットワークファイアウォールポリシー適用順序(AFTER/BEFORE_CLASSIC_FIREWALL)を意図的に選択しているか +- [ ] 階層ファイアウォールポリシーの組織レベルルールが最小限に設計され、`goto_next`で下位へ適切に委譲されているか +- [ ] Cloud NGFWの利用ティア(Essentials/Standard/Enterprise)が要件とコストのバランスを考慮して選定されているか +- [ ] Essentials機能のルールが高優先度に配置され、有料ティアの評価対象が絞り込まれているか +- [ ] L7検査(TLS Inspection・URLフィルタリング・IDPS)が重要ワークロードに限定して適用されているか +- [ ] マイクロセグメンテーションの主軸としてSecure Tagsが採用されているか(Network Tagsへの新規依存を避けているか) +- [ ] ファイアウォールルールロギングがコンプライアンス上重要な境界に対して有効化されているか +- [ ] VPCファイアウォールルールからの移行計画がGKE自動生成ルールの除外を考慮しているか +- [ ] GKEワークロードにおけるPodレベル制御(NetworkPolicy)とクラスタ境界制御(Cloud NGFW)の責任分界が明確か + +### Cloud NAT・Secure Web Proxy(6.3) +- [ ] サードパーティ連携でIP許可リストが必要な場合、手動IPアドレス割り当てが選択されているか +- [ ] ポート割り当て方式(静的/動的)がワークロードの接続パターンに応じて選定されているか +- [ ] NATポート枯渇(`allocation_status="DROPPED"`)の監視・アラートが設定されているか +- [ ] IPアドレス変更時のドレイン手順が運用手順書に含まれているか +- [ ] Secure Web Proxyのデフォルト拒否ポリシーの例外(許可ルール)が最小権限で設計されているか +- [ ] Secure Web ProxyのTLS Inspectionと証明書ピンニングを行うアプリケーションとの互換性が確認済みか + +### セルフマネージドNVA・Packet Mirroring(6.4) +- [ ] セルフマネージドNVA導入前にCloud NGFW Enterprise/NSIでの代替可能性を検討したか +- [ ] マルチNIC NVAのHA構成で内部パススルーNLBネクストホップとヘルスチェックが構成されているか +- [ ] ネクストホップILB背後のバックエンドVMでIP転送が有効化されているか +- [ ] ポリシーベースルートと静的ルートの使い分けが要件(プロトコル/送信元単位の制御要否)に基づいているか +- [ ] 複数チーム共有の検査基盤にNetwork Security Integration(プロデューサー/コンシューマーモデル)が検討されているか +- [ ] Packet Mirroringのスコープ制約(同一プロジェクト/VPC/リージョン)がマルチリージョン設計に織り込まれているか +- [ ] コレクターインスタンスに内部IPのみが割り当てられ、インターネットから直接到達不可能になっているか + +--- + +## 参考文献 + +### Cloud Armor +- [Cloud Armor overview](https://docs.cloud.google.com/armor/docs/cloud-armor-overview) +- [Security policy overview](https://docs.cloud.google.com/armor/docs/security-policy-overview) +- [Use cases for security policies](https://docs.cloud.google.com/armor/docs/common-use-cases) +- [Create and manage security policies](https://docs.cloud.google.com/armor/docs/configure-security-policies) +- [Preconfigured WAF rules overview](https://docs.cloud.google.com/armor/docs/waf-rules) +- [Tune Cloud Armor preconfigured WAF rules](https://docs.cloud.google.com/armor/docs/rule-tuning) +- [Configure custom rules language attributes](https://docs.cloud.google.com/armor/docs/rules-language-reference) +- [Configure advanced network DDoS protection](https://docs.cloud.google.com/armor/docs/advanced-network-ddos) +- [Configure network edge security policies](https://docs.cloud.google.com/armor/docs/network-edge-policies) +- [Adaptive Protection overview](https://docs.cloud.google.com/armor/docs/adaptive-protection-overview) +- [Adaptive Protection use cases](https://docs.cloud.google.com/armor/docs/adaptive-protection-use-cases) +- [Automatically deploy Adaptive Protection suggested rules](https://docs.cloud.google.com/armor/docs/adaptive-protection-auto-deploy) +- [Rate limiting overview](https://docs.cloud.google.com/armor/docs/rate-limiting-overview) +- [Configure rate limiting](https://docs.cloud.google.com/armor/docs/configure-rate-limiting) +- [Bot management overview](https://docs.cloud.google.com/armor/docs/bot-management) +- [Apply Google Threat Intelligence](https://docs.cloud.google.com/armor/docs/threat-intelligence) + +### Cloud NGFW / VPCファイアウォール +- [Cloud NGFW overview](https://docs.cloud.google.com/firewall/docs/about-firewalls) +- [Key terms](https://docs.cloud.google.com/firewall/docs/key-terms) +- [Firewall policies and rules](https://docs.cloud.google.com/firewall/docs/firewall-policies-overview) +- [Evaluation order for firewall policies and rules](https://docs.cloud.google.com/firewall/docs/firewall-policies-rule-eval-order) +- [Hierarchical firewall policies](https://docs.cloud.google.com/firewall/docs/firewall-policies) +- [Create hierarchical firewall policies and rules](https://docs.cloud.google.com/firewall/docs/using-firewall-policies) +- [Manage hierarchical firewall policies and rules](https://docs.cloud.google.com/firewall/docs/manage-hierarchical-firewall-policies) +- [Cloud NGFW tiers](https://docs.cloud.google.com/firewall/docs/ngfw_tiers) +- [Cloud Next Generation Firewall pricing](https://cloud.google.com/firewall/pricing) +- [Application layer inspection overview](https://docs.cloud.google.com/firewall/docs/about-app-layer-inspection) +- [URL filtering service overview](https://docs.cloud.google.com/firewall/docs/about-url-filtering) +- [Create and manage URL filtering security profiles](https://docs.cloud.google.com/firewall/docs/configure-urlf-security-profiles) +- [TLS inspection overview](https://docs.cloud.google.com/firewall/docs/about-tls-inspection) +- [Secure tags for firewalls](https://docs.cloud.google.com/firewall/docs/tags-firewalls-overview) +- [Logging for firewall policy rules](https://docs.cloud.google.com/firewall/docs/firewall-policy-rules-logging-overview) +- [VPC firewall rules migration overview](https://cloud.google.com/firewall/docs/migrate-vpc-firewall-rules-overview) +- [Migrate VPC firewall rules that don't use network tags and service accounts](https://cloud.google.com/firewall/docs/migrate-firewall-rules-no-dependencies) +- [From VPC firewall rules to Cloud NGFW network firewall policies (blog)](https://cloud.google.com/blog/products/networking/from-vpc-firewall-rules-to-cloud-ngfw-network-firewall-policies) + +### Cloud NAT・Secure Web Proxy +- [IP addresses and ports | Cloud NAT](https://docs.cloud.google.com/nat/docs/ports-and-addresses) +- [Quickstart: Set up and manage network address translation with Public NAT](https://docs.cloud.google.com/nat/docs/set-up-manage-network-address-translation) +- [Secure Web Proxy overview](https://docs.cloud.google.com/secure-web-proxy/docs/overview) +- [Secure Web Proxy policies overview](https://docs.cloud.google.com/secure-web-proxy/docs/policies-overview) +- [TLS inspection overview | Secure Web Proxy](https://docs.cloud.google.com/secure-web-proxy/docs/tls-inspection-overview) +- [Assign static IP addresses for outbound traffic](https://docs.cloud.google.com/secure-web-proxy/docs/assign-static-ip-addresses-for-egress-traffic) +- [Secure Web Proxy (SWP) — product page](https://cloud.google.com/security/products/secure-web-proxy) + +### セルフマネージドNVA・Packet Mirroring・Network Security Integration +- [Internal passthrough Network Load Balancers as next hops](https://docs.cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) +- [Deploy a hub-and-spoke network by using a load balancer as the next hop](https://docs.cloud.google.com/load-balancing/docs/internal/deploying-ilb-next-hop-vm) +- [Set up an internal passthrough Network Load Balancer as next hop (with tags)](https://docs.cloud.google.com/load-balancing/docs/internal/setting-up-internal-next-hop-tags) +- [Policy-based routes](https://docs.cloud.google.com/vpc/docs/policy-based-routes) +- [Packet Mirroring](https://docs.cloud.google.com/vpc/docs/packet-mirroring) +- [Use Packet Mirroring](https://docs.cloud.google.com/vpc/docs/using-packet-mirroring) +- [Out-of-band integration overview | Network Security Integration](https://docs.cloud.google.com/network-security-integration/docs/out-of-band/out-of-band-integration-overview) +- [Mirroring endpoint groups overview](https://cloud.google.com/network-security-integration/docs/out-of-band/endpoint-groups-overview) +- [Mirroring deployment groups overview](https://cloud.google.com/network-security-integration/docs/out-of-band/deployment-groups-overview) +- [Set up out-of-band integration for a producer-consumer model](https://cloud.google.com/network-security-integration/docs/tutorial/out-of-band-integration-tutorial) + +### 試験ガイド・認定情報 + +- [Google Cloud Certified - Professional Cloud Network Engineer](https://cloud.google.com/learn/certification/cloud-network-engineer) +- [Professional Cloud Network Engineer Certification exam guide (PDF)](https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf) diff --git a/Gcp-service-account-iam-best-practices.html b/Gcp-service-account-iam-best-practices.html new file mode 100644 index 000000000..253e1d57b --- /dev/null +++ b/Gcp-service-account-iam-best-practices.html @@ -0,0 +1,1560 @@ + + + + + + + GCPチャレンジラボ徹底解説: サービスアカウント運用とIAM権限管理のベストプラクティス + + + + + + + +
+ + +
+
+
Google Cloud / IAM / Service Accounts
+

+ GCPチャレンジラボ徹底解説: + サービスアカウント運用とIAM権限管理のベストプラクティス +

+

+ 対象ラボ: + Working with Service Accounts and Custom IAM Roles (challenge lab) + / 対象読者: gcloud CLI・IAM・Compute + Engine・BigQueryを学び始めた初学者からジュニアクラウドアーキテクト +

+
+ +
+

このガイドについて

+

+ このラボでは「サービスアカウント (Service Account)」を軸に、gcloud + CLIでのリソース作成、IAM権限の付与、カスタムロールの作成、クライアントライブラリを使ったBigQueryへのアクセスまでを一気通貫で体験します。 +

+

+ 本ガイドは各タスクをステップバイステップで解説しながら、それぞれの操作の裏にある「なぜそうするのか」というベストプラクティスと、その根拠となる公式ドキュメントのURLを併記します。ラボ本体の指示は抽象的な部分が多いため、実務でも通用する判断基準を身につけることを目的としています。 +

+
+ +
+

全体像: 6つのタスクの流れ

+

+ まず全体の依存関係を俯瞰します。Task 2〜4は「devops + サービスアカウント」を中心とした一連の流れ、Task + 5は独立したカスタムロール作成、Task 6は別の「bigquery-qwiklab + サービスアカウント」を使う独立した流れです。 +

+
+
Loading diagram...
+
+

+ タスクごとに独立したサービスアカウントとロールが登場するため、「誰が」「何に」「どの権限を」持っているかを常に意識しながら読み進めてください。 +

+
+ +
+

事前準備: サービスアカウントとIAMロールの基礎知識

+ +

サービスアカウントとは

+

+ サービスアカウントは、人間ではなくアプリケーションやVMなどのワークロードが使う特殊なGoogleアカウントです。多くの場合、VMなどのリソースにサービスアカウントを「アタッチ(付与)」し、そのリソース上で動くコードがサービスアカウントとして認証できるようにします。この方式では鍵ファイルの管理が不要になるため、最も推奨される認証方法です。 +

+ +

事前定義ロール vs カスタムロール

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目事前定義ロール(Predefined Role)カスタムロール(Custom Role)
管理者Google Cloudが作成・管理するユーザー自身が作成・管理する
権限の粒度サービス単位でまとめられた粗めの粒度個別のpermissionを自由に組み合わせ可能
メンテナンスGoogle Cloud側が自動更新変更や新権限追加は手動対応が必要
典型的な用途一般的なユースケース、迅速な権限付与最小権限の原則を厳密に適用したい場合
適用範囲プロジェクト/組織/リソースレベルプロジェクトレベルまたは組織レベルのみ
+ +
+ +
+
Task 2
+

gcloud CLIでのサービスアカウント作成

+ +

手順

+

1. Google Cloud Consoleから lab-vm にSSH接続する

+

2. 現在の認証状態とデフォルト設定を確認する

+
gcloud auth list
+gcloud config list project
+

3. devops という名前のサービスアカウントを作成する

+
gcloud iam service-accounts create devops \
+  --display-name="devops"
+ +

ベストプラクティス

+
+ 命名規則を統一する +

+ サービスアカウント名は用途がひと目でわかるものにします(例: + devops, + bigquery-qwiklab)。運用が増えるほど「どのSAが何に使われているか」が追跡しにくくなるため、命名段階から意識することが重要です。 +

+
+
+ 作成権限も最小限に絞る +

+ サービスアカウントを作成するには + roles/iam.serviceAccountCreator + ロールがあれば十分であり、プロジェクトオーナー権限を必要としません。作成専任のオペレーターにはこの粒度のロールだけを渡すべきです。 +

+
+
+ 1サービス1アカウントを意識する +

+ 複数のワークロードで1つのSAを使い回すと、片方の障害や侵害がもう片方に波及します。用途ごとにSAを分けることが推奨されています。 +

+
+ +

根拠ソース

+ +
+ +
+
Task 3
+

gcloud CLIによるIAM権限付与

+ +

手順

+

+ 1. + プロジェクトIDとサービスアカウントのメールアドレスをローカル変数に保存する(何度も使うためタイプミス防止にもなる) +

+
export PROJECT_ID=$(gcloud config get-value project)
+export SA="devops@${PROJECT_ID}.iam.gserviceaccount.com"
+

+ 2. devops サービスアカウントに + roles/iam.serviceAccountUser を付与する +

+
gcloud projects add-iam-policy-binding $PROJECT_ID \
+  --member="serviceAccount:${SA}" \
+  --role="roles/iam.serviceAccountUser"
+

3. 続けて roles/compute.instanceAdmin.v1 を付与する

+
gcloud projects add-iam-policy-binding $PROJECT_ID \
+  --member="serviceAccount:${SA}" \
+  --role="roles/compute.instanceAdmin.v1"
+

+ このタスクの狙いは、devops SAに「Compute + Engineインスタンスを管理する権限」と「他のリソース上で自分自身をサービスアカウントとして使わせる権限」の両方を持たせることです。これによりTask + 4で作成する vm-2 は、devops + SAとして自らインスタンスの作成・一覧取得を行えるようになります。 +

+ +

ベストプラクティス(最小権限の原則)

+ + + + + + + + + + + + + + + + + + + + + + + + + +
原則内容
最小権限(Least Privilege) + 実行に必要な権限のみを付与し、roles/editor や + roles/owner のような基本ロール(Basic + Role)は避ける +
付与先の粒度を絞る + 可能な限りプロジェクト/フォルダ全体ではなく、個々のリソース単位でロールを付与する +
なりすまし(Impersonation)の管理 + roles/iam.serviceAccountUser + はSAへの「なりすまし」を許可する強力なロールのため、付与対象を必要最小限のプリンシパルに絞る +
変更履歴の追跡 + add-iam-policy-binding + はread-modify-writeで既存ポリシーに追記する。定期的に + get-iam-policy + で現在の許可ポリシーを確認する習慣をつける +
+ +
+ プロジェクトレベル付与のトレードオフ +

+ roles/iam.serviceAccountUser + はユーザーやSAに「あるサービスアカウントを利用してリソースを操作する権限」を与える強力なロールです。本番運用では、プロジェクト全体ではなく対象のサービスアカウント単位(gcloud iam service-accounts add-iam-policy-binding)で付与するほうがより安全とされています。ラボでは学習を優先してプロジェクトレベルで付与していますが、実務ではこのトレードオフを理解した上で選択してください。 +

+
+ +

根拠ソース

+ +
+ +
+
Task 4
+

サービスアカウントを紐付けたComputeインスタンス作成

+ +

手順

+

+ 1. lab-vm から、devops SAを紐付けた + vm-2 を作成する +

+
gcloud compute instances create vm-2 \
+  --service-account=$SA \
+  --scopes=https://www.googleapis.com/auth/cloud-platform \
+  --zone=ZONE
+

+ 2. + vm-2 + にSSH接続し、実際にインスタンスの作成・一覧取得ができるか検証する +

+
gcloud compute instances list
+gcloud compute instances create test-instance --zone=ZONE
+

+ これが成功すれば、Task 3で付与した + roles/compute.instanceAdmin.v1 + が正しく機能していることが確認できます。 +

+ +

アクセススコープとIAMロールの役割分担

+

+ 初学者がつまずきやすいポイントとして、「アクセススコープ(Access + Scope)」と「IAMロール」は別の仕組みだという点があります。 +

+
+
Loading diagram...
+
+ + + + + + + + + + + + + + + + + +
仕組み役割
アクセススコープ + VMインスタンス側の設定で、OAuth2トークンが要求できる範囲の「上限」を決めるレガシーな仕組み +
IAMロール + サービスアカウント側の設定で、実際に許可される操作を決める仕組み +
+ +

ベストプラクティス

+
+ スコープは cloud-platform に統一し、権限制御はIAMロールに任せる +

+ 公式ドキュメントでも、VMには + cloud-platform + の完全アクセススコープを設定し、実際のアクセス制御はIAMロールで行うことが推奨されています。スコープを個別に絞る運用は複雑化しやすく、IAM側での一元管理のほうが保守性が高いためです。 +

+
+
+ カスタムSAをVMに紐付ける際は最小権限のロールのみ付与する +

+ デフォルトサービスアカウント(Compute Engine default service + account)は広範な権限を持つため、本番環境では避け、用途別のカスタムSAを使うべきです。 +

+
+
+ VMへのシェルアクセスを制限する +

+ 強い権限を持つSAがアタッチされたVMには、SSHアクセスできるユーザーを最小限に絞ります。VM内で実行されるコードは、アタッチされたSAとして振る舞えてしまうためです。 +

+
+ +

根拠ソース

+ +
+ +
+
Task 5
+

YAMLファイルによるカスタムロール作成

+ +

手順

+

+ 1. role-definition.yaml を作成し、Cloud + SQLに接続するための最小権限を定義する +

+
title: "CloudSQLConnector"
+description: "Cloud SQLインスタンスへの接続と参照のみを許可するカスタムロール"
+stage: "GA"
+includedPermissions:
+- cloudsql.instances.connect
+- cloudsql.instances.get
+

2. プロジェクトレベルでカスタムロールを作成する

+
gcloud iam roles create CloudSQLConnector \
+  --project=$PROJECT_ID \
+  --file=role-definition.yaml
+ +

ベストプラクティス

+
+ stage は用途に応じて明示する +

+ ALPHA / BETA / GA / + DISABLED の4段階があり、検証中のロールは + ALPHA + にしておくと誤って本番運用に組み込まれるリスクを減らせます。 +

+
+
+ 既存の事前定義ロールから権限一覧を借りる +

+ ゼロから権限を洗い出すのではなく、近いロールを + gcloud iam roles describe roles/ROLE_NAME + --format="yaml(includedPermissions)" + で確認し、必要な権限だけを抜き出すと精度が上がります。 +

+
+
+ サポートされていない権限に注意する +

+ すべての権限がカスタムロールに含められるわけではありません。gcloud iam list-testable-permissions + でカスタムロールに含められる権限かどうかを事前に確認できます。 +

+
+
+ 更新は etag で競合を防ぐ +

+ gcloud iam roles describe の出力にある + etag + を使うことで、他の変更との衝突を検知しながら安全に更新できます。 +

+
+ +

根拠ソース

+ +
+ +
+
Task 6
+

クライアントライブラリを使ったBigQueryアクセス

+ +

手順

+

1. bigquery-qwiklab サービスアカウントを作成する

+
gcloud iam service-accounts create bigquery-qwiklab \
+  --display-name="bigquery-qwiklab"
+

+ 2. + BigQuery Data Viewer(roles/bigquery.dataViewer)と + BigQuery User(roles/bigquery.user)を付与する +

+
export BQ_SA="bigquery-qwiklab@${PROJECT_ID}.iam.gserviceaccount.com"
+
+gcloud projects add-iam-policy-binding $PROJECT_ID \
+  --member="serviceAccount:${BQ_SA}" \
+  --role="roles/bigquery.dataViewer"
+
+gcloud projects add-iam-policy-binding $PROJECT_ID \
+  --member="serviceAccount:${BQ_SA}" \
+  --role="roles/bigquery.user"
+

+ 3. bigquery-qwiklab を紐付けた + bigquery-instance を作成する +

+
gcloud compute instances create bigquery-instance \
+  --service-account=$BQ_SA \
+  --scopes=https://www.googleapis.com/auth/cloud-platform \
+  --zone=ZONE
+

+ 4. + bigquery-instance にSSH接続し、依存ライブラリをインストールする +

+
sudo apt-get update
+sudo apt-get install -y python3-pip
+pip3 install google-cloud-bigquery pandas db-dtypes
+

+ 5. query.py を作成する(YOUR_SERVICE_ACCOUNT と + YOUR_PROJECT_ID は実際の値に置き換える) +

+
from google.auth import compute_engine
+from google.cloud import bigquery
+
+credentials = compute_engine.Credentials(
+    service_account_email='bigquery-qwiklab@YOUR_PROJECT_ID.iam.gserviceaccount.com')
+
+query = """
+SELECT name, SUM(number) as total_people
+FROM `bigquery-public-data.usa_names.usa_1910_2013`
+WHERE state = 'TX'
+GROUP BY name, state
+ORDER BY total_people DESC
+LIMIT 20
+"""
+
+client = bigquery.Client(
+    project='YOUR_PROJECT_ID',
+    credentials=credentials)
+
+print(client.query(query).to_dataframe())
+

6. 実行する

+
python3 query.py
+ +

認証フロー

+

+ compute_engine.Credentials + を使うと、VM内のコードはメタデータサーバー経由でアタッチ済みのサービスアカウントの短期アクセストークンを自動取得します。鍵ファイルをVM内に配置する必要はありません。 +

+
+
Loading diagram...
+
+ +

ベストプラクティス

+
+ compute_engine.Credentials を使い、鍵ファイル(JSON + key)は使わない +

+ VMにアタッチされたサービスアカウントを使う方法は、Google + Cloud上で動くコードにとって推奨される認証方式です。鍵ファイルは漏洩・不正利用のリスクがあるため、Application + Default + Credentials(ADC)や本ラボのようなアタッチ済みSAでの認証を優先してください。 +

+
+
+ 明示的に認証情報を渡す vs 暗黙的に解決させる +

+ bigquery.Client() + は環境から自動でADCを解決できますが、本ラボのように特定のSAを明示指定する場合は + compute_engine.Credentials(service_account_email=...) + のように明示するほうが意図が明確になります。 +

+
+
+ 付与するロールを役割ごとに分ける +

+ BigQuery Data Viewer はデータの読み取りのみ、BigQuery User + はジョブの実行(クエリの発行)に必要な権限です。この2つを分けて付与することで、「クエリは実行できるがデータの中身は見えない」といったより細かい制御も可能になります。 +

+
+ +

根拠ソース

+ +
+ +
+

セキュリティベストプラクティスまとめ

+

+ ラボ全体を通じて登場した論点を、実務で使えるチェックリスト形式で整理します。 +

+ +

サービスアカウントキーのリスクと対策

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
リスク内容対策
認証情報の漏洩鍵ファイルが誤ってリポジトリ等に混入する + 鍵ファイルを作らず、アタッチ済みSAやWorkload Identity連携を使う +
権限昇格漏洩した鍵を使って権限を昇格される + roles/editor + などの強力な基本ロールを避け、最小権限のカスタムロールを使う +
メタデータの漏洩鍵ファイル自体がプロジェクトIDなどの情報を含む鍵を発行しない運用を徹底する
なりすましの追跡困難鍵を使った操作は本人特定が難しいIAM Conditionsや監査ログで利用状況を追跡する
+ +

タスク別ロール早見表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Taskサービスアカウント付与ロール目的
2〜4devops + roles/iam.serviceAccountUser, + roles/compute.instanceAdmin.v1 + Computeインスタンスの作成・管理とSAとしての操作
5(プロジェクト全体)カスタムロール CloudSQLConnectorCloud SQLへの接続・参照のみを許可
6bigquery-qwiklab + roles/bigquery.dataViewer, + roles/bigquery.user + BigQueryデータの読み取りとクエリ実行
+ +

最小権限の原則を実践する3つの視点

+
+
Loading diagram...
+
+
+ +
+

参考文献

+

本ガイド内で参照した公式ドキュメントの一覧です。

+ + +
+ 本ガイドは公開されているGoogle + Cloud公式ドキュメントに基づいて作成されています。実際の操作前に、各リンク先の最新情報をご確認ください。 +
+
+
+
+ + + + From 14f88153fc0119317332cbed5bc094ac462240c0 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:43 +0900 Subject: [PATCH 004/123] docs(gcp): add GKE Prometheus, Knowledge Catalog, and ML API challenge lab guides --- ...anaged-prometheus-challenge-lab-guide.html | 1867 +++++++++++++++++ Gke-managed-prometheus-challenge-lab-guide.md | 387 ++++ ...-catalog-challenge-lab-best-practices.html | 1502 +++++++++++++ ...ge-catalog-challenge-lab-best-practices.md | 247 +++ Ml-api-challenge-lab-guide.html | 1113 ++++++++++ Ml-api-challenge-lab-guide.md | 311 +++ 6 files changed, 5427 insertions(+) create mode 100644 Gke-managed-prometheus-challenge-lab-guide.html create mode 100644 Gke-managed-prometheus-challenge-lab-guide.md create mode 100644 Knowledge-catalog-challenge-lab-best-practices.html create mode 100644 Knowledge-catalog-challenge-lab-best-practices.md create mode 100644 Ml-api-challenge-lab-guide.html create mode 100644 Ml-api-challenge-lab-guide.md diff --git a/Gke-managed-prometheus-challenge-lab-guide.html b/Gke-managed-prometheus-challenge-lab-guide.html new file mode 100644 index 000000000..a15d33db2 --- /dev/null +++ b/Gke-managed-prometheus-challenge-lab-guide.html @@ -0,0 +1,1867 @@ + + + + + + GKE + Cloud Managed Service for Prometheus チャレンジラボ完全攻略ガイド + + + + + +
+ + +
+
+
+ Google Cloud Skills Boost / Challenge Lab +
+

+ GKE + Cloud Managed Service for Prometheus
チャレンジラボ完全攻略ガイド +

+

+ 「Monitor Environments with Google Cloud managed Service for + Prometheus」コースの Challenge Lab を、 + 手順の丸暗記ではなく「なぜそのコマンドを実行するのか」を公式ドキュメントの根拠とともに理解しながら + 完了できるように整理したステップバイステップガイドです。 +

+
+ +
+ 初学者〜中級者向け +
+
+ Task 1〜4 / 全4工程 +
+
+
+ +
+
+ +

1. Challenge Lab とは何か

+
+

+ Challenge Lab + は通常のハンズオンラボと異なり、詳細な「手順書」が存在しません。コース内で学んだ知識を + もとに、シナリオとタスクの説明だけを頼りに自力で構築し、自動採点システムが結果を判定します。 + 新しい概念を教わる場ではなく、これまで学んだスキルを応用し、エラーメッセージを読んで自分で修正する + 力が試されます。 +

+ +

Lab Objectives

+

本ラボで到達すべき目標は次の3点です。

+
+ + + + + + + + + + + + + + + + + + + + + +
#目標
1Managed Service for Prometheus のデプロイ
2 + メトリクスをスクレイピングするための自己管理型(self + managed)データ収集設定の作成 +
3 + メトリクスを問い合わせるためのアプリケーションのデプロイ +
+
+ +

タスクの全体像

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Task内容
Task 1 + 指定された ZONE に GKE クラスタをデプロイする +
Task 2Managed Collection をデプロイする
Task 3 + サンプルアプリケーションをデプロイし、Prometheus + の稼働を確認する +
Task 4エクスポートするメトリクスをフィルタリングする
+
+ +
+ +
+ +
+
読み進め方のヒント
+

+ 各セクションには「なぜそのコマンド・設定が必要か」を説明する補足と、公式ドキュメントへのリンクを + 添えています。採点に通すだけでなく、GMP + の設計思想を理解しながら進めることを意識してください。 +

+
+
+
+ +
+
+ +

2. アーキテクチャ全体像

+
+

+ 作業に入る前に、Managed Service for Prometheus(以下 + GMP)がどのように動くかを理解しておくと、 各タスクの意味が腹落ちします。GMP + の Managed Collection は、Prometheus 互換の collector を DaemonSet + としてクラスタ内で動かし、各ノード上の Pod だけをスクレイピング(/metrics + エンドポイントからメトリクスを収集)します。収集したデータは Google + 側から取りに来るのではなく、 collector 側から Cloud Monitoring + のバックエンドである Monarch へ push する設計です。 + これにより、Google がクラスタに直接アクセスすることはありません。 +

+ +
+ +

コンポーネントの役割

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
コンポーネント種別役割
gmp-operatorDeployment + GMP 用 Kubernetes + operator。CRD(PodMonitoring等)を監視し設定を配布する +
collectorDaemonSet + 同一ノード上の Pod だけをスクレイピングして水平スケールする +
rule-evaluatorDeploymentアラート・記録ルールの評価を行う
alertmanagerStatefulSet発火したアラートを通知チャネルへ送信する
+ PodMonitoring / + ClusterPodMonitoring + CRD + どの Pod + をどの間隔でスクレイピングするか定義する、いわば「自己管理型データ収集」の本体 +
OperatorConfigCRD認証情報・メトリクスフィルタなど operator 全体の設定
+
+ +
+ +
+
初学者向け補足
+

+ 「Managed(マネージド)」なのは collector や operator の実行・スケーリング・アップグレード + であって、「どの Pod をスクレイピングするか」はユーザーが + PodMonitoring リソースで 自分で定義します。これが Lab + Objectives にある「self managed data collection」の正体です。 +

+
+
+ + +
+ +
+
+ +

3. 事前準備(環境変数の整理)

+
+

+ Cloud Shell + を開き、以下の変数をラボの指示に沿って設定しておくと、以降のコマンドをコピー&ペースト + しやすくなります。 +

+ bash +
export PROJECT_ID=$(gcloud config get-value project)
+export ZONE=<ラボが指定するZONE>          # 例: asia-northeast1-a
+export CLUSTER_NAME=gmp-cluster
+export NAMESPACE_NAME=gmp-test
+
+ +
+
+ なぜ gcloud config get-value project を使うのか +
+

+ 現在アクティブなプロジェクトを自動取得することで、プロジェクトIDを手入力してタイプミスする + リスクを避けられます。Challenge Lab + は自動採点のため、こうした入力ミスの排除が特に重要です。 +

+
+
+
+ +
+
参考ソース
+ +
+
+
+ +
+
+ +

4. Task 1: GKE クラスタのデプロイ

+
+ +
+
+ 1 +

クラスタを作成する

+
+ bash +
gcloud container clusters create ${CLUSTER_NAME} \
+  --zone ${ZONE} \
+  --enable-managed-prometheus \
+  --num-nodes=2
+
+ +
+ +
+
+ なぜ --enable-managed-prometheus が必要か +
+

+ --enable-managed-prometheus は GKE クラスタ作成時に + Managed Service for Prometheus の + マネージドコレクションを有効化する専用フラグです。公式リファレンスにも、クラスタ内で + Managed Collection + を有効にするためのフラグであると明記されています。 +

+

+ なお GKE Autopilot(v1.25以降)や GKE Standard(v1.27以降)では + Managed Collection がデフォルトで + 有効になっていますが、明示的にフラグを付けることで意図を明確にし、バージョン差異による + 有効化漏れを防げるため、Challenge Lab + のように採点される環境では明示指定が推奨されます。 +

+
+
+ +
+
+ 2 +

クラスタの認証情報を取得し、検証する

+
+ bash +
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE}
+kubectl get nodes
+

+ kubectl get nodes でノードが + Ready になっていれば、クラスタ自体は正常です(GMP + コンポーネントの確認は Task 2/3 で行います)。 +

+
+ + +
+ +
+
+ +

5. Task 2: Managed Collection のデプロイ

+
+

+ Task 1 でフラグを付けた時点で、現行バージョンの GKE では + gmp-system / gmp-public + の各 Namespace と operator + 関連リソースは自動的に作成されます。しかし本ラボの指示は、明示的に + setup manifest と operator manifest を + GoogleCloudPlatform/prometheus-engine + リポジトリから適用することを求めています。これは元々「GKE 以外の Kubernetes + クラスタ」向けの手順として + 公式ドキュメントに記載されている方法ですが、Challenge Lab + の採点ロジックがこれらのマニフェスト適用を + 明示的なタスク達成条件としているため、GKE 上でも同じ手順を踏みます。 +

+ +
+
+ 1 +

setup manifest と operator manifest を適用する

+
+ bash +
kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/manifests/setup.yaml
+
+kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/manifests/operator.yaml
+
+ +
+ +
+
バージョン一貫性のベストプラクティス
+

+ このラボは examples/example-app.yaml を + v0.2.3 タグで参照するよう 指定しています。setup.yaml + / operator.yaml / example-app.yaml + は同一リリースタグで揃えるのが安全です。CRD + スキーマとオペレータの実装は同じリリース内で整合性が + 取られているため、タグを混在させると CRD + 未対応フィールドなどの予期しないエラーが発生する + 可能性があります。実務(ラボ以外)では、 + releases ページ + で最新の安定版タグを確認し、常に最新タグへ揃えることを推奨します。 +

+
+
+ +
+
+ 2 +

デプロイを検証する

+
+ bash +
kubectl get ns | grep gmp
+kubectl get pods -n gmp-system
+kubectl get pods -n gmp-public
+

+ gmp-system に + collector(DaemonSet)、gmp-operator、rule-evaluator + が Running になっていることを確認します。 +

+
+ + +
+ +
+
+ +

6. Task 3: サンプルアプリのデプロイと動作確認

+
+ +
+
+ 1 +

専用 Namespace を作成する

+
+ bash +
kubectl create ns ${NAMESPACE_NAME}
+

+ 公式ドキュメントは、サンプル構成用に専用の Namespace(推奨名 + gmp-test)を 作成することを推奨しています。Namespace + を分離しておくことで後片付け(テアダウン)が容易になり、 + 既存アプリへの影響も避けられます。 +

+
+ +
+
+ 2 +

サンプルアプリをデプロイする

+
+ bash +
kubectl -n ${NAMESPACE_NAME} apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/examples/example-app.yaml
+

+ このマニフェストは example_requests_total(カウンタ)や + example_random_numbers(ヒストグラム)などのメトリクスを + metrics + という名前のポートで公開する Pod を3レプリカ起動します。 +

+
+ +
+
+ 3 +

PodMonitoring を作成する(=自己管理型データ収集の本体)

+
+

+ Managed Collection はメトリクスエンドポイントを自動発見しません。PodMonitoring + カスタムリソースで「どのラベルを持つ Pod + を、どのポートで、どの間隔でスクレイピングするか」を + 明示的に定義する必要があります。これが Lab Objectives の「self managed + data collection」に 対応する作業です。 +

+ pod-monitoring.yaml +
apiVersion: monitoring.googleapis.com/v1
+kind: PodMonitoring
+metadata:
+  name: prom-example
+spec:
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: prom-example
+  endpoints:
+  - port: metrics
+    interval: 30s
+ bash +
kubectl -n ${NAMESPACE_NAME} apply -f pod-monitoring.yaml
+
+ +
+
+ 4 +

Prometheus が正しくデプロイされているか確認する

+
+ bash +
# operator / collector / rule-evaluator の稼働確認
+kubectl get pods -n gmp-system
+
+# PodMonitoring が意図した Namespace に存在するか
+kubectl get podmonitoring -A
+
+# PodMonitoring が GMP に認識されているか
+kubectl -n ${NAMESPACE_NAME} describe podmonitoring prom-example
+
+ +
+ +
+
+ より詳細なターゲット疎通確認をしたい場合 +
+

+ OperatorConfig で + features.targetStatus.enabled: true を設定すると、 + describe podmonitoring の出力に + Active Targets や Health: up + といったステータスが表示されるようになります。ただし公式ドキュメントは、この機能は大規模クラスタで + operator + のメモリ枯渇を招く可能性があるため、常時有効化ではなく一時的なデバッグ用途に + 限定することを推奨しています。 +

+
+
+ + operator-config-debug.yaml(一時的なデバッグ用) +
apiVersion: monitoring.googleapis.com/v1
+kind: OperatorConfig
+metadata:
+  namespace: gmp-public
+  name: config
+features:
+  targetStatus:
+    enabled: true
+ + +
+ +
+
+ +

7. Task 4: エクスポートするメトリクスのフィルタリング

+
+ +

なぜフィルタリングするのか

+

+ 大量のメトリクスを収集し続けると Cloud Monitoring + への書き込みコストが増加します。 + OperatorConfig の collection.filter に + matchOneOf(許可リスト) を設定することで、指定した Prometheus + 時系列マッチャに一致するメトリクスだけを Cloud Monitoring へ + エクスポートできます。これは Prometheus の federation エンドポイントの + match[] + パラメータと同等の仕組みです。 +

+ yaml(追記するフィルタ設定) +
collection:
+  filter:
+    matchOneOf:
+    - '{job="prom-example"}'
+    - '{__name__=~"job:.+"}'
+ +

手順(既存設定を壊さないベストプラクティス)

+

+ ラボの指示は「config.yaml を作成し、operatorconfig + の内容をコピーする」というものですが、 + ゼロから書くのではなく、必ずクラスタ上の現在の設定をエクスポートしてから編集するのが 安全です。手動で新規作成すると、既存の + collection.credentials などの設定を誤って + 消してしまうリスクがあります。 +

+ +
+
+ 1 +

現在の OperatorConfig をエクスポートする

+
+ bash +
kubectl -n gmp-public get operatorconfig config -o yaml > op-config.yaml
+
+ +
+
+ 2 +

filter ブロックを追記する

+
+ bash +
vi op-config.yaml
+

追記後のイメージ:

+
apiVersion: monitoring.googleapis.com/v1
+kind: OperatorConfig
+metadata:
+  namespace: gmp-public
+  name: config
+collection:
+  filter:
+    matchOneOf:
+    - '{job="prom-example"}'
+    - '{__name__=~"job:.+"}'
+
+ +
+
+ 3 +

クラスタへ反映する(重要)

+
+ bash +
kubectl -n gmp-public apply -f op-config.yaml
+
+ +
+ +
+
見落としがちな落とし穴
+

+ ラボの手順文だけを読むと「ファイルを作って GCS + にアップロードするだけ」に見えますが、 + 実際にメトリクスフィルタを機能させるには + kubectl apply でクラスタに反映する工程が + 不可欠です。ここを忘れると、ローカルの YAML + を編集しただけでクラスタの挙動は変わりません。 GCS + へのアップロードはあくまで自動採点システムがあなたの設定内容を検証するための手段であり、 + クラスタの実際の動作を変えるものではありません。 +

+
+
+ +

採点用ファイルのアップロード

+

+ ラボの自動採点システムが設定内容を検証できるように、作成した + op-config.yaml を Cloud Storage バケットへアップロードします。 +

+ bash +
export PROJECT=$(gcloud config get-value project)
+gsutil mb -p ${PROJECT} gs://${PROJECT}
+gsutil cp op-config.yaml gs://${PROJECT}
+gsutil -m acl set -R -a public-read gs://${PROJECT}
+ +
+ +
+
+ セキュリティ上の重要な注意(本番運用では絶対に真似しないこと) +
+

+ acl set ... public-read はバケットの中身をインターネット上の誰でも 読み取り可能にする設定です。これはこの Challenge Lab + の自動採点システムがファイルを + 検証するために必要な手順であり、ラボという使い捨て環境だからこそ許容されます。実際の業務環境で + これを行うと、設定ファイルや認証情報の漏えいにつながる重大なセキュリティインシデントになり得ます。 +

+

+ 実運用では以下のような代替手段を検討してください。 +

+
    +
  • + Uniform bucket-level access + IAM + で読み取り権限を必要な担当者・サービスアカウントのみに限定する +
  • +
  • 一時的な共有が必要な場合は署名付きURL(Signed URL)を使う
  • +
  • + ラボ終了後は + gsutil rm -r gs://${PROJECT} + でバケットを削除し、公開状態を残さない +
  • +
+
+
+ +

検証

+ bash +
# クラスタ側で設定が反映されているか
+kubectl -n gmp-public get operatorconfig config -o yaml
+
+# GCSに正しくアップロードされているか
+gsutil ls gs://${PROJECT}
+

+ Cloud Monitoring の + Metrics Management + ページでは、フィルタ適用後にどのメトリクスが + 実際に取り込まれているか、取り込み量の推移とあわせて確認できます。 +

+ + +
+ +
+
+ +

8. トラブルシューティング

+
+

メトリクスが Cloud Monitoring に見えない場合は、以下の順で切り分けます。

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状想定原因対処
+ kubectl get pods -n gmp-system で Pod + が見つからない + + クラスタ作成時に + --enable-managed-prometheus + を付け忘れた、または setup/operator manifest が未適用 + + gcloud container clusters update ${CLUSTER_NAME} + --enable-managed-prometheus --zone ${ZONE} + を実行、または Task 2 の manifest を再適用 +
+ PodMonitoring を作成してもメトリクスが Cloud + Monitoring に出ない + + selector.matchLabels が Pod + の実際のラベルと一致していない + + kubectl get pods -n ${NAMESPACE_NAME} + --show-labels + でラベルを確認し修正 +
+ gsutil mb が + Bucket already exists で失敗する + + プロジェクトID由来のバケット名がグローバルに既に使用されている + 一意なサフィックスを付けたバケット名を利用する
OperatorConfig を編集したのにフィルタが効かない + kubectl apply + でクラスタへ反映していない(ローカルファイルの編集のみ) + Task 4 の手順3を実施し、クラスタへ反映する
+ Autopilot クラスタでフラグが効かない/自動有効化されない + GKE バージョンに起因する既知の挙動 + gcloud container clusters update <CLUSTER_NAME> + --enable-managed-prometheus + で明示的に有効化 +
+
+ + +
+ +
+
+ +

9. ベストプラクティスまとめチェックリスト

+
+
    +
  • + GKE クラスタ作成コマンドに + --enable-managed-prometheus を明示的に付与した +
  • +
  • + setup.yaml / + operator.yaml / + example-app.yaml のリリースタグを揃えた +
  • +
  • + サンプルアプリ専用の + Namespace(gmp-test)を分離した +
  • +
  • + PodMonitoring の + selector.matchLabels を Pod のラベルと突き合わせて確認した +
  • +
  • + OperatorConfig + はゼロから書かず、既存設定をエクスポートしてから編集した +
  • +
  • + OperatorConfig 編集後、必ず + kubectl apply でクラスタへ反映したことを確認した +
  • +
  • + GCS バケットを + public-read + にしたのはラボの採点用途のみであり、実運用では行わないことを理解している +
  • +
  • + ラボ終了後、不要なリソース(GKE クラスタ、GCS + バケット)の削除を検討した +
  • +
+
+ +
+
+ +

10. 参考文献一覧

+
+ +
+ +
+

+ 本ガイドは学習・検証目的の参考資料です。実際のラボ採点結果やコマンド出力は、実行時のプロジェクト設定・GKE/GMPのバージョンにより異なる場合があります。 +

+
+
+
+ + + + + + + + diff --git a/Gke-managed-prometheus-challenge-lab-guide.md b/Gke-managed-prometheus-challenge-lab-guide.md new file mode 100644 index 000000000..89a386ba6 --- /dev/null +++ b/Gke-managed-prometheus-challenge-lab-guide.md @@ -0,0 +1,387 @@ +# GKE + Google Cloud Managed Service for Prometheus チャレンジラボ完全攻略ガイド + +> 対象ラボ: *Monitor Environments with Google Cloud managed Service for Prometheus* コースの Challenge Lab +> 想定読者: GKE / Kubernetes / Prometheus に触れたことがある初学者〜中級者 +> 本ガイドの方針: 「なぜそのコマンドを打つのか」を Google Cloud 公式ドキュメントの根拠とともに解説します。コマンドを丸暗記するのではなく、仕組みを理解して自走できることを目指します。 + +--- + +## 目次 + +1. [Challenge Lab とは何か](#1-challenge-lab-とは何か) +2. [アーキテクチャ全体像](#2-アーキテクチャ全体像) +3. [事前準備(環境変数の整理)](#3-事前準備環境変数の整理) +4. [Task 1: GKE クラスタのデプロイ](#4-task-1-gke-クラスタのデプロイ) +5. [Task 2: Managed Collection のデプロイ](#5-task-2-managed-collection-のデプロイ) +6. [Task 3: サンプルアプリのデプロイと動作確認](#6-task-3-サンプルアプリのデプロイと動作確認) +7. [Task 4: エクスポートするメトリクスのフィルタリング](#7-task-4-エクスポートするメトリクスのフィルタリング) +8. [トラブルシューティング](#8-トラブルシューティング) +9. [ベストプラクティスまとめチェックリスト](#9-ベストプラクティスまとめチェックリスト) +10. [参考文献一覧](#10-参考文献一覧) + +--- + +## 1. Challenge Lab とは何か + +Challenge Lab は、通常のハンズオンラボと異なり「手順書」が存在しません。コース内の他のラボで学んだ知識をもとに、シナリオとタスクだけを頼りに自力で構築し、自動採点システムがその結果を判定します。 + +本ラボの目的(Lab Objectives)は次の3点です。 + +- Managed Service for Prometheus のデプロイ +- メトリクスをスクレイピングするための自己管理型(self managed)データ収集設定の作成 +- メトリクスを問い合わせるためのアプリケーションのデプロイ + +これらは以下の4つのタスクに分解されています。 + +| Task | 内容 | +|---|---| +| Task 1 | `ZONE` に GKE クラスタをデプロイする | +| Task 2 | Managed Collection をデプロイする | +| Task 3 | サンプルアプリケーションをデプロイし、Prometheus の稼働を確認する | +| Task 4 | エクスポートするメトリクスをフィルタリングする | + +--- + +## 2. アーキテクチャ全体像 + +作業に入る前に、Managed Service for Prometheus(以下 GMP)がどう動くかを理解しておくと、各タスクの意味が腹落ちします。 + +GMP の Managed Collection は、Prometheus 互換の collector を DaemonSet としてクラスタ内で動かし、各ノード上の Pod だけをスクレイピング(`/metrics` エンドポイントからメトリクスを収集)します。収集したデータは Google 側から取りに来るのではなく、collector 側から Cloud Monitoring のバックエンドである Monarch へ **push** する設計です。これにより、Google がクラスタに直接アクセスすることはありません。 + +```mermaid +flowchart LR + subgraph GKE["GKEクラスタ (--enable-managed-prometheus)"] + APP["prom-exampleアプリ 3レプリカ"] + PM["PodMonitoring CR"] + OP["gmp-operator Deployment"] + COL["collector DaemonSet"] + RE["rule-evaluator"] + AM["alertmanager StatefulSet"] + end + GCM["Cloud Monitoring / Monarch"] + NOTIFY["通知先チャネル"] + + PM -->|"スクレイピング設定を定義"| OP + OP -->|"設定を配布"| COL + APP -->|"/metrics を公開"| COL + COL -->|"push"| GCM + RE -->|"ルール評価結果をpush"| GCM + AM -->|"アラート通知"| NOTIFY +``` + +各コンポーネントの役割は次のとおりです。 + +| コンポーネント | 種別 | 役割 | +|---|---|---| +| `gmp-operator` | Deployment | GMP 用 Kubernetes operator。CRD(PodMonitoring 等)を監視し設定を配布 | +| `collector` | DaemonSet | 同一ノード上の Pod だけをスクレイピングして水平スケール | +| `rule-evaluator` | Deployment | アラート・記録ルールの評価 | +| `alertmanager` | StatefulSet | 発火したアラートを通知チャネルへ送信 | +| `PodMonitoring` / `ClusterPodMonitoring` | CRD | どの Pod をどの間隔でスクレイピングするか定義する、いわば「自己管理型データ収集」の本体 | +| `OperatorConfig` | CRD | 認証情報・メトリクスフィルタなど operator 全体の設定 | + +> **初学者向け補足**: 「Managed(マネージド)」なのは collector や operator の *実行・スケーリング・アップグレード* であって、「どの Pod をスクレイピングするか」はユーザーが `PodMonitoring` リソースで自分で定義します。これが Lab Objectives にある「self managed data collection」の正体です。 + +**参考ソース** +- Get started with managed collection — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed +- Managed Service for Prometheus Overview — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus + +--- + +## 3. 事前準備(環境変数の整理) + +Cloud Shell を開き、以下の変数をラボの指示に沿って設定しておくと、以降のコマンドをコピー&ペーストしやすくなります。 + +```bash +export PROJECT_ID=$(gcloud config get-value project) +export ZONE=<ラボが指定するZONE> # 例: asia-northeast1-a +export CLUSTER_NAME=gmp-cluster +export NAMESPACE_NAME=gmp-test +``` + +`gcloud config get-value project` で現在アクティブなプロジェクトを取得するのは、プロジェクトIDを手入力してタイプミスするリスクを避けるベストプラクティスです。 + +**参考ソース** +- gcloud config — https://docs.cloud.google.com/sdk/gcloud/reference/config + +--- + +## 4. Task 1: GKE クラスタのデプロイ + +### 手順 + +```bash +gcloud container clusters create ${CLUSTER_NAME} \ + --zone ${ZONE} \ + --enable-managed-prometheus \ + --num-nodes=2 +``` + +### なぜこのフラグが必要か + +`--enable-managed-prometheus` は GKE クラスタ作成時に Managed Service for Prometheus のマネージドコレクションを有効化する専用フラグです。公式リファレンスにも、クラスタ内で Managed Collection を有効にするためのフラグであると明記されています。 + +なお、GKE Autopilot(v1.25以降)や GKE Standard(v1.27以降)では Managed Collection がデフォルトで有効になっていますが、明示的にフラグを付けることで意図を明確にし、バージョン差異による有効化漏れを防げるため、Challenge Lab のように採点される環境では明示指定が推奨されます。 + +### 検証 + +```bash +gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} +kubectl get nodes +``` + +`kubectl get nodes` でノードが `Ready` になっていれば、クラスタ自体は正常です(GMP コンポーネントの確認は Task 2/3 で行います)。 + +**参考ソース** +- gcloud container clusters create リファレンス(`--enable-managed-prometheus`) — https://docs.cloud.google.com/sdk/gcloud/reference/container/clusters/create +- Get started with managed collection(GKE 有効化手順) — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed + +--- + +## 5. Task 2: Managed Collection のデプロイ + +Task 1 でフラグを付けた時点で、現行バージョンの GKE では `gmp-system` / `gmp-public` の各 Namespace と operator 関連リソースは自動的に作成されます。しかし本ラボの指示は、明示的に **setup manifest と operator manifest** を `GoogleCloudPlatform/prometheus-engine` リポジトリから適用することを求めています。これは元々「GKE 以外の Kubernetes クラスタ」向けの手順として公式ドキュメントに記載されている方法ですが、Challenge Lab の採点ロジックがこれらのマニフェスト適用を明示的なタスク達成条件としているため、GKE 上でも同じ手順を踏みます。 + +### 手順 + +```bash +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/manifests/setup.yaml + +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/manifests/operator.yaml +``` + +> **バージョン一貫性のベストプラクティス**: このラボは `examples/example-app.yaml` を `v0.2.3` タグで参照するよう指定しています。`setup.yaml` / `operator.yaml` / `example-app.yaml` は同一リリースタグで揃えるのが安全です。CRD スキーマとオペレータの実装は同じリリース内で整合性が取られているため、タグを混在させると CRD 未対応フィールドなどの予期しないエラーが発生する可能性があります。実務(ラボ以外)では、`releases` ページで最新の安定版タグを確認し、常に最新タグへ揃えることを推奨します。 + +### 検証 + +```bash +kubectl get ns | grep gmp +kubectl get pods -n gmp-system +kubectl get pods -n gmp-public +``` + +`gmp-system` に `collector`(DaemonSet)、`gmp-operator`、`rule-evaluator` が `Running` になっていることを確認します。 + +**参考ソース** +- Get started with managed collection(kubectl CLI での setup/operator manifest 適用手順) — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed#kubectl-cli +- GoogleCloudPlatform/prometheus-engine リポジトリ — https://github.com/GoogleCloudPlatform/prometheus-engine +- prometheus-engine Releases(最新タグ確認用) — https://github.com/GoogleCloudPlatform/prometheus-engine/releases + +--- + +## 6. Task 3: サンプルアプリのデプロイと動作確認 + +### 6.1 Namespace の作成 + +```bash +kubectl create ns ${NAMESPACE_NAME} +``` + +公式ドキュメントは、サンプル構成用に専用の Namespace(推奨名 `gmp-test`)を作成することを推奨しています。Namespace を分離しておくことで、後片付け(テアダウン)が容易になり、既存アプリへの影響も避けられます。 + +### 6.2 サンプルアプリのデプロイ + +```bash +kubectl -n ${NAMESPACE_NAME} apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.2.3/examples/example-app.yaml +``` + +このマニフェストは `example_requests_total`(カウンタ)や `example_random_numbers`(ヒストグラム)などのメトリクスを `metrics` という名前のポートで公開する Pod を3レプリカ起動します。 + +### 6.3 PodMonitoring の作成(=自己管理型データ収集の本体) + +Managed Collection はメトリクスエンドポイントを自動発見しません。`PodMonitoring` カスタムリソースで「どのラベルを持つ Pod を、どのポートで、どの間隔でスクレイピングするか」を明示的に定義する必要があります。これが Lab Objectives の「self managed data collection」に対応する作業です。 + +```yaml +# pod-monitoring.yaml +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring +metadata: + name: prom-example +spec: + selector: + matchLabels: + app.kubernetes.io/name: prom-example + endpoints: + - port: metrics + interval: 30s +``` + +```bash +kubectl -n ${NAMESPACE_NAME} apply -f pod-monitoring.yaml +``` + +### 6.4 Prometheus が正しくデプロイされているかの確認 + +```bash +# operator / collector / rule-evaluator の稼働確認 +kubectl get pods -n gmp-system + +# PodMonitoring が意図した Namespace に存在するか +kubectl get podmonitoring -A + +# PodMonitoring がGMPに認識されているか +kubectl -n ${NAMESPACE_NAME} describe podmonitoring prom-example +``` + +より詳細なターゲットの疎通確認をしたい場合は、`OperatorConfig` で `features.targetStatus.enabled: true` を設定すると、`describe podmonitoring` の出力に `Active Targets` や `Health: up` といったステータスが表示されるようになります。ただし公式ドキュメントは、この機能は大規模クラスタで operator のメモリ枯渇を招く可能性があるため、常時有効化ではなく一時的なデバッグ用途に限定することを推奨しています。 + +```yaml +apiVersion: monitoring.googleapis.com/v1 +kind: OperatorConfig +metadata: + namespace: gmp-public + name: config +features: + targetStatus: + enabled: true +``` + +**参考ソース** +- Get started with managed collection(Namespace作成 / example-appデプロイ / PodMonitoring / target status) — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed +- PodMonitoring API リファレンス — https://github.com/GoogleCloudPlatform/prometheus-engine/blob/main/doc/api.md#podmonitoring + +--- + +## 7. Task 4: エクスポートするメトリクスのフィルタリング + +### 7.1 なぜフィルタリングするのか + +大量のメトリクスを収集し続けると Cloud Monitoring への書き込みコストが増加します。`OperatorConfig` の `collection.filter` に `matchOneOf`(許可リスト)を設定することで、指定した Prometheus 時系列マッチャに一致するメトリクスだけを Cloud Monitoring へエクスポートできます。これは Prometheus の federation エンドポイントの `match[]` パラメータと同等の仕組みです。 + +```yaml +collection: + filter: + matchOneOf: + - '{job="prom-example"}' + - '{__name__=~"job:.+"}' +``` + +### 7.2 手順(既存設定を壊さないベストプラクティス) + +ラボの指示は「config.yaml を作成し、operatorconfig の内容をコピーする」というものですが、**ゼロから書くのではなく、必ずクラスタ上の現在の設定をエクスポートしてから編集する**のが安全です。手動で新規作成すると、既存の `collection.credentials` などの設定を誤って消してしまうリスクがあります。 + +```bash +# 1. 現在のOperatorConfigをエクスポート +kubectl -n gmp-public get operatorconfig config -o yaml > op-config.yaml +``` + +```bash +# 2. エディタで op-config.yaml を開き、collection.filter.matchOneOf ブロックを追記 +vi op-config.yaml +``` + +追記後のイメージ: + +```yaml +apiVersion: monitoring.googleapis.com/v1 +kind: OperatorConfig +metadata: + namespace: gmp-public + name: config +collection: + filter: + matchOneOf: + - '{job="prom-example"}' + - '{__name__=~"job:.+"}' +``` + +```bash +# 3. 【重要】編集したファイルをクラスタへ反映する +# ここを忘れると、ローカルのYAMLを編集しただけでクラスタの挙動は変わらない +kubectl -n gmp-public apply -f op-config.yaml +``` + +> **見落としがちな落とし穴**: ラボの手順文だけを読むと「ファイルを作ってGCSにアップロードするだけ」に見えますが、実際にメトリクスフィルタを機能させるには `kubectl apply` でクラスタに反映する工程が不可欠です。GCS へのアップロードはあくまで自動採点システムがあなたの設定内容を検証するための手段であり、クラスタの実際の動作を変えるものではありません。 + +### 7.3 採点用ファイルのアップロード + +ラボの自動採点システムが設定内容を検証できるように、作成した `op-config.yaml` を Cloud Storage バケットへアップロードします。 + +```bash +export PROJECT=$(gcloud config get-value project) +gsutil mb -p ${PROJECT} gs://${PROJECT} +gsutil cp op-config.yaml gs://${PROJECT} +gsutil -m acl set -R -a public-read gs://${PROJECT} +``` + +> **セキュリティ上の重要な注意(本番運用では絶対に真似しないこと)**: `acl set ... public-read` はバケットの中身を **インターネット上の誰でも読み取り可能** にする設定です。これはこの Challenge Lab の自動採点システムがファイルを検証するために必要な手順であり、ラボという使い捨て環境だからこそ許容されます。実際の業務環境でこれを行うと、設定ファイルや認証情報の漏えいにつながる重大なセキュリティインシデントになり得ます。実運用では以下のような代替手段を検討してください。 +> - Uniform bucket-level access + IAM で読み取り権限を必要な担当者・サービスアカウントのみに限定する +> - 一時的な共有が必要な場合は署名付きURL(Signed URL)を使う +> - ラボ終了後は `gsutil rm -r gs://${PROJECT}` でバケットを削除し、公開状態を残さない + +### 7.4 検証 + +```bash +# クラスタ側で設定が反映されているか +kubectl -n gmp-public get operatorconfig config -o yaml + +# GCSに正しくアップロードされているか +gsutil ls gs://${PROJECT} +``` + +Cloud Monitoring の **Metrics Management** ページでは、フィルタ適用後にどのメトリクスが実際に取り込まれているか、取り込み量の推移とあわせて確認できます。 + +**参考ソース** +- Filter exported metrics(`collection.filter` の設定方法) — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed#gmp-filter-metrics +- OperatorConfig / ExportFilters API リファレンス — https://github.com/GoogleCloudPlatform/prometheus-engine/blob/main/doc/api.md +- gsutil acl コマンドリファレンス — https://cloud.google.com/storage/docs/gsutil/commands/acl +- 均一なバケットレベルのアクセス(Uniform bucket-level access) — https://cloud.google.com/storage/docs/uniform-bucket-level-access + +--- + +## 8. トラブルシューティング + +```mermaid +flowchart TD + START["メトリクスが見えない"] --> Q1{"gmp-systemのPodは全てRunningか"} + Q1 -->|"No"| FIX1["Task2のsetup.yaml/operator.yamlを再適用"] + Q1 -->|"Yes"| Q2{"PodMonitoringは存在するか"} + Q2 -->|"No"| FIX2["pod-monitoring.yamlをapply"] + Q2 -->|"Yes"| Q3{"selector.matchLabelsはPodのラベルと一致するか"} + Q3 -->|"No"| FIX3["labelを修正して再apply"] + Q3 -->|"Yes"| Q4{"OperatorConfigのfilterで意図せず除外していないか"} + Q4 -->|"Yes"| FIX4["matchOneOfの正規表現を見直す"] + Q4 -->|"No"| CHECK["targetStatusを一時的に有効化して調査"] +``` + +| 症状 | 想定原因 | 対処 | +|---|---|---| +| `kubectl get pods -n gmp-system` で Pod が見つからない | クラスタ作成時に `--enable-managed-prometheus` を付け忘れた、または setup/operator manifest が未適用 | `gcloud container clusters update ${CLUSTER_NAME} --enable-managed-prometheus --zone ${ZONE}` を実行、または Task 2 の manifest を再適用 | +| `PodMonitoring` を作成してもメトリクスが Cloud Monitoring に出ない | `selector.matchLabels` が Pod の実際のラベルと一致していない | `kubectl get pods -n ${NAMESPACE_NAME} --show-labels` でラベルを確認し修正 | +| `gsutil mb` が `Bucket already exists` で失敗する | プロジェクトID由来のバケット名がグローバルに既に使用されている | 一意なサフィックスを付けたバケット名を利用する | +| OperatorConfig を編集したのにフィルタが効かない | `kubectl apply` でクラスタへ反映していない(ローカルファイルの編集のみ) | 7.2節の手順3を実施し、クラスタへ反映する | +| Autopilot クラスタでフラグが効かない/自動有効化されない | GKEバージョンに起因する既知の挙動 | `gcloud container clusters update --enable-managed-prometheus` で明示的に有効化 | + +**参考ソース** +- Managed Service for Prometheus トラブルシューティング — https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/troubleshooting +- prometheus-engine GitHub Discussions(Autopilot 有効化に関する既知の挙動) — https://github.com/GoogleCloudPlatform/prometheus-engine/discussions/418 + +--- + +## 9. ベストプラクティスまとめチェックリスト + +- [ ] GKE クラスタ作成コマンドに `--enable-managed-prometheus` を明示的に付与した +- [ ] `setup.yaml` / `operator.yaml` / `example-app.yaml` のリリースタグを揃えた +- [ ] サンプルアプリ専用の Namespace(`gmp-test`)を分離した +- [ ] `PodMonitoring` の `selector.matchLabels` を Pod のラベルと突き合わせて確認した +- [ ] `OperatorConfig` は **ゼロから書かず、既存設定をエクスポートしてから編集**した +- [ ] `OperatorConfig` 編集後、必ず `kubectl apply` でクラスタへ反映したことを確認した +- [ ] GCS バケットを `public-read` にしたのはラボの採点用途のみであり、実運用では行わないことを理解している +- [ ] ラボ終了後、不要なリソース(GKEクラスタ、GCSバケット)の削除を検討した + +--- + +## 10. 参考文献一覧 + +| # | タイトル | URL | +|---|---|---| +| 1 | Get started with managed collection | https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/setup-managed | +| 2 | Managed Service for Prometheus Overview | https://docs.cloud.google.com/stackdriver/docs/managed-prometheus | +| 3 | gcloud container clusters create リファレンス | https://docs.cloud.google.com/sdk/gcloud/reference/container/clusters/create | +| 4 | GoogleCloudPlatform/prometheus-engine(リポジトリ本体) | https://github.com/GoogleCloudPlatform/prometheus-engine | +| 5 | prometheus-engine Releases | https://github.com/GoogleCloudPlatform/prometheus-engine/releases | +| 6 | prometheus-engine API リファレンス(PodMonitoring / OperatorConfig / ExportFilters) | https://github.com/GoogleCloudPlatform/prometheus-engine/blob/main/doc/api.md | +| 7 | Managed Service for Prometheus トラブルシューティング | https://docs.cloud.google.com/stackdriver/docs/managed-prometheus/troubleshooting | +| 8 | gsutil acl コマンドリファレンス | https://cloud.google.com/storage/docs/gsutil/commands/acl | +| 9 | Uniform bucket-level access | https://cloud.google.com/storage/docs/uniform-bucket-level-access | +| 10 | prometheus-engine Discussions #418(Autopilotでの既知の挙動) | https://github.com/GoogleCloudPlatform/prometheus-engine/discussions/418 | \ No newline at end of file diff --git a/Knowledge-catalog-challenge-lab-best-practices.html b/Knowledge-catalog-challenge-lab-best-practices.html new file mode 100644 index 000000000..b92bd4eb0 --- /dev/null +++ b/Knowledge-catalog-challenge-lab-best-practices.html @@ -0,0 +1,1502 @@ + + + + + + Knowledge Catalog チャレンジラボ攻略ガイド + + + + +
+ + +
+
+
Google Cloud / Knowledge Catalog
+

+ Knowledge Catalog チャレンジラボ攻略ガイド ― Lake / Zone / Asset / Aspect + Type 実装のベストプラクティス +

+

+ Google Cloud + の初学者でも迷わずタスクを完了できるように、チャレンジラボの3つのタスクをステップバイステップで解説します。「なぜその設定が必要なのか」「実務でハマりやすい落とし穴は何か」まで踏み込んで扱います。 +

+ +
+ +
+

iこの記事について

+

+ 各手順の根拠は、末尾の「参考文献・出典」セクションに公式ドキュメントの URL + としてまとめています。まずは前提知識として、名称変更に関する重要な注意点を押さえておきましょう。 +

+
+ 重要な用語の前提知識
+ このラボが扱うサービスは、2026年4月10日付けで + Dataplex Universal Catalog から + Knowledge Catalog へ名称変更されました。ただし + API・クライアントライブラリ・CLI(gcloud dataplex ...)・IAM + のロール名は変更されておらず、引き続き + dataplex という名前空間のままです。コンソール上の表示名は + Knowledge Catalog + でも、コマンドや権限名を調べるときは「Dataplex」で検索するのが正解です。 +
+
+ +
+

0全体像を理解する:リソース階層

+

+ タスクに着手する前に、Knowledge Catalog + が扱うリソースの親子関係を押さえておくと、迷わず作業を進められます。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
リソース役割本ラボでの名称
Lakeデータドメインや事業部門を表す最上位の論理コンテナCustomer Engagements
ZoneLake 内のサブドメイン。データの成熟度(raw / curated)で分類Raw Event Data(Raw Zone)
Asset + Zone に紐づく実データへのポインタ(Cloud Storage バケット or + BigQuery データセット) + Raw Event Files(Cloud Storage バケット)
Aspect Type + メタデータのスキーマ(テンプレート)。フィールドと型を定義する再利用可能な雛形 + Protected Raw Data Aspect
AspectAspect Type のインスタンス。実際に Zone やカラムに付与される値Protected Raw Data Flag = Y/N
+
+

図1: Lake / Zone / Asset / Aspect Type の関係

+

+ Zone には Raw Zone と + Curated Zone の2種類があります。Raw Zone + はスキーマ検証を行わずどのような形式のデータでも受け入れる「着地帯」であるのに対し、Curated + Zone は構造化・検証済みのデータを格納する用途です。今回作成する「Raw Event + Data」は生イベントデータの着地帯なので Raw Zone が適切です。 +

+
+ +
+

1事前準備:必要な API を有効化する

+

+ Knowledge Catalog のリソースを作成する前に、Dataplex + API(コンソール上の検索名は「Cloud Dataplex + API」)が有効化されている必要があります。プロジェクトによってはデフォルトで有効な場合もありますが、必ず確認しましょう。 +

+
    +
  1. + Google Cloud コンソールの検索バーに + Cloud Dataplex API と入力する。 +
  2. +
  3. 検索結果から「Cloud Dataplex API」をクリックする。
  4. +
  5. + 「有効にする(Enable)」ボタンが表示されている場合はクリックする。既に有効な場合は「API + が有効です」と表示される。 +
  6. +
+
+
+ ベストプラクティス +
+
    +
  • + 本番運用では + gcloud services enable dataplex.googleapis.com + のようにコマンドで有効化し、Infrastructure as Code(Terraform + 等)で管理すると環境間の再現性が高まる。 +
  • +
+
+
+ +
+

2Task 1: Lake と raw zone を作成する

+ +

2-1. Lake「Customer Engagements」を作成する

+
    +
  1. + ナビゲーションメニューから「View all products」→ Analytics + 配下の「Knowledge Catalog」を開く。 +
  2. +
  3. 左ペインの「Manage lakes」から「Manage」をクリックする。
  4. +
  5. 「Create Lake」をクリックする。
  6. +
  7. 以下のプロパティを設定する。
  8. +
+ + + + + + + + + + + + + +
項目値
Display NameCustomer Engagements
Region<REGION>
+
    +
  1. 「Create」をクリックする。
  2. +
+
+
+ ベストプラクティス +
+
    +
  • + Lake ID は Display Name + から自動生成されるが、組織の命名規則がある場合は手動で指定する(作成後に + ID は変更できない)。 +
  • +
  • + Region は後から変更できないリソース属性。課題文の指示どおり + <REGION> を一字一句正確に選択する。 +
  • +
  • + 作成直後は Lake + のステータスが「Active」になるまで数分かかることがある。Active + になってから次の Zone + 作成に進むと、失敗によるロールバックを避けられる。 +
  • +
+
+ +

2-2. raw zone「Raw Event Data」を Lake に追加する

+
    +
  1. 「Lakes」一覧で作成した「Customer Engagements」をクリックする。
  2. +
  3. 「Zones」タブで「Add zone」をクリックする。
  4. +
  5. 以下のプロパティを設定する。
  6. +
+ + + + + + + + + + + + + + + + + +
項目値
Display NameRaw Event Data
TypeRaw Zone
Data locationsRegional
+
    +
  1. 「Create」をクリックする。
  2. +
+
+
+ ベストプラクティス +
+
    +
  • + Zone の Type は後から変更できないため、用途(raw か curated + か)を最初に確定させる。 +
  • +
  • + 「Data locations」を Regional にすると、この Zone + に追加できるアセットが Lake + と同一リージョンの単一リージョンデータに限定される。マルチリージョン運用を想定する場合はこの設計判断を事前にチームで合意しておく。 +
  • +
  • + Zone の作成中も Lake 自体は引き続き利用可能。複数の Zone + を並行して追加できる。 +
  • +
+
+
+ +
+

+ 3Task 2: Cloud Storage バケットを作成し、Zone + にアセットとして追加する +

+ +

3-1. Cloud Storage バケットを作成する

+
    +
  1. ナビゲーションメニューから「Cloud Storage」→「Buckets」を開く。
  2. +
  3. 「Create」をクリックする。
  4. +
  5. バケット名に「Project ID」(現在のプロジェクト ID)を入力する。
  6. +
  7. + ロケーションタイプを Region、リージョンを + <REGION> に設定する。 +
  8. +
  9. 残りの設定はデフォルトのまま「Create」をクリックする。
  10. +
+
+
+ ベストプラクティス +
+
    +
  • + Cloud Storage + のバケット名はグローバルに一意である必要がある。プロジェクト ID は + Google Cloud + 全体で一意なので、バケット名として利用するのはよくある命名パターン。 +
  • +
  • + バケットの Region は、後の手順で Zone にアタッチする際に + Lake / Zone のリージョンと重なっている必要がある。一致しない場合、Zone に追加できずエラーになる。 +
  • +
+
+ +

+ 3-2. バケットを Regional アセット「Raw Event Files」として Zone + にアタッチする +

+
    +
  1. 「Zones」一覧で「Raw Event Data」をクリックする。
  2. +
  3. + 「Assets」タブで「+ Add Assets」(または「Add an + asset」)をクリックする。 +
  4. +
  5. 以下のプロパティを設定する。
  6. +
+ + + + + + + + + + + + + + + + + + + + + +
項目値
TypeCloud Storage bucket
Display NameRaw Event Files
バケット手順3-1で作成したバケット
Data locationsRegional
+
    +
  1. 「Continue」→「Submit」の順にクリックする。
  2. +
+
+
+ ベストプラクティス +
+
    +
  • + 1つの Zone に複数のアセットを同時に追加でき、追加処理中もその Zone + を継続して利用できる。 +
  • +
  • + Cloud Storage バケットをアセットとして追加すると、Knowledge Catalog + はバケット内のテーブルに対応する BigQuery + 外部テーブルを自動的に公開する。ディスカバリー設定(Discovery + settings)を Zone + レベルから継承するか個別設定するかも、この画面で決められる。 +
  • +
+
+
+ +
+

+ 4Task 3: Aspect Type を作成し、Zone に Aspect + を追加する +

+ +

4-1. Aspect Type「Protected Raw Data Aspect」を作成する

+

+ Aspect Type は Aspect + の再利用可能なテンプレート。フィールドの型や必須/任意といった制約を定義し、メタデータの一貫性を担保します。 +

+
    +
  1. 左ペインの「Manage Metadata」から「Metadata Types」を開く。
  2. +
  3. 「Aspect types」タブを選択し、「Create」をクリックする。
  4. +
  5. 以下のプロパティを設定する。
  6. +
+ + + + + + + + + + + + + +
項目値
Display NameProtected Raw Data Aspect
Location<REGION>
+
    +
  1. + 「Template」セクションで「Add + field」をクリックし、フィールドを追加する。 +
  2. +
+ + + + + + + + + + + + + +
項目値
Field Display NameProtected Raw Data Flag
TypeEnum
+
    +
  1. + 「Add an enum value」で値 + Y を追加し「Done」をクリックする。 +
  2. +
  3. + 再度「Add an enum value」で値 + N を追加し「Done」をクリックする。 +
  4. +
  5. 「Save」をクリックする。
  6. +
+
+
+ ベストプラクティス +
+
    +
  • + Aspect Type の Location は作成後に変更できない。Zone や Asset + に付与する予定であれば、原則としてそれらと同じリージョン(または + Global)を選択する。Global な Aspect Type はどのリージョンの Entry + にも付与できるため、複数リージョンで再利用したい共通メタデータ(例:データ分類ラベル)には + Global が向いている。 +
  • +
  • + 機密データ/保護対象データを識別する Enum + フィールドは、自由記述の文字列型ではなく Enum + 型で定義するのがベストプラクティス。値のブレを防ぎ、後続の検索・フィルタリングが安定する。 +
  • +
  • + Aspect Type の作成には数分かかることがある。「Check my + progress」が成功と判定するまで少し待ってから確認する。 +
  • +
+
+ +

4-2. Zone「Raw Event Data」に Aspect を追加する

+

+ Aspect Type + はあくまでテンプレートであり、実際にメタデータとして意味を持たせるには対象の + Entry(この場合は Zone)に Aspect を付与する必要があります。 +

+
    +
  1. 左メニューの「Discover」配下にある「Search」を開く。
  2. +
  3. + 検索プラットフォームを Knowledge Catalog に設定し、Zone「Raw Event + Data」を検索して開く(またはコンソール上の Zone + 詳細ページから直接遷移する)。 +
  4. +
  5. + Entry + 詳細ページの「Details」タブにある「Aspects」セクションで、「Optional + aspects」の「Add」をクリックする。 +
  6. +
  7. + フィルターに Protected Raw Data Aspect と入力し、該当の + Aspect Type を選択する。 +
  8. +
  9. + 「Protected Raw Data Flag」で値(Y または + N)を選択する。 +
  10. +
  11. 「Save」をクリックする。
  12. +
+
+
+ ベストプラクティス +
+
    +
  • + Aspect は Entry(またはそのカラム)に紐づけて保存される。Aspect Type + と付与先の Entry が異なる Google Cloud Organization + に属している場合は付与できない。 +
  • +
  • + 必須(Required)ではなく任意(Optional)の Aspect + として設計しておくと、既存の Entry + に後から段階的にメタデータを充実させていく運用がしやすい。 +
  • +
  • + この操作も反映まで数分かかることがある。「Check my + progress」がすぐに成功しなくても慌てず再確認する。 +
  • +
+
+
+ +
+

5全体ワークフロー

+

3つのタスクを通しで俯瞰すると、以下のような一直線のフローになります。

+
+

図2: タスク全体のワークフロー

+
+ +
+

6ベストプラクティスまとめ

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
カテゴリベストプラクティス理由
リージョン + Lake / Zone / バケット / Aspect Type のリージョンをすべて + <REGION> に統一する + + Region + は後から変更できず、リージョン不一致はアセット追加時のエラーの主因になる +
命名 + Display Name は人間が読める名前、ID + は組織の命名規則に沿って明示指定する + ID は作成後に変更できないため、後工程での混乱を防ぐ
バケット名グローバルに一意な名前が必要な場合は Project ID を利用するProject ID は Google Cloud 全体で重複しないことが保証されている
Aspect Type 設計選択肢が限定されるメタデータは Enum 型で定義する自由入力の文字列よりも値の表記ゆれを防ぎ、検索性が向上する
API 有効化 + リソース作成前に対象 API(Dataplex + API)を有効化しているか必ず確認する + 未有効化のまま操作すると作成処理がエラーになる
作業順序 + 各リソースのステータスが Active + になったことを確認してから次の手順に進む + + 作成失敗時は自動的に前の状態へロールバックされるため、待たずに進むと手戻りが発生する +
IAM + 実務では最小権限の原則に従い、Dataplex 用のロール(例: + roles/dataplex.editor)と Storage 用のロール(例: + roles/storage.admin)を必要な範囲だけ付与する + + ラボの学生アカウントには広い権限が事前付与されているが、本番環境ではそのまま使うべきではない +
+
+ +
+

7よくあるエラーと対処法

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
症状主な原因対処法
Zone にバケットを追加できないバケットのリージョンと Lake / Zone のリージョンが重なっていない + バケットのロケーションを Zone の Data locations + 設定と一致させて作り直す +
Lake / Zone の作成が失敗するDataplex API が有効化されていない、または権限不足 + API の有効化状況を確認し、必要な IAM + ロールが付与されているか確認する +
Aspect Type が Search 結果に出てこない + 作成直後で反映が完了していない、または Location が Entry + と一致しない(かつ Global でもない) + + 数分待って再検索する。Location の不一致が疑われる場合は Aspect Type + を Global で作り直す(既存の Location 変更は不可) +
Check my progress がなかなか成功しないバックグラウンド処理の反映待ち + 対象リソースのステータスが Active + になっているか確認し、数分後に再チェックする +
+
+ +
+

8CLI での実装例(任意・上級者向け)

+

+ Optionalコンソール操作と同じ内容は + gcloud コマンドでも実行できます。IaC + 化やスクリプト化を検討する際の参考にしてください。 +

+ +

API 有効化と Task 1(Lake / raw zone)

+
+ + +

Task 2(Cloud Storage バケットとアセット)

+
+ + +

Task 3(Aspect Type の作成と Aspect の付与)

+

+ Aspect Type の作成にはフィールド定義を記述した JSON/YAML + ファイルが必要です。 +

+
+ +
+ +
+ 補足
+ update-aspects の + --aspects に渡すファイルは、キーが + <PROJECT_ID>.<REGION>.protected-raw-data-aspect + の形式になる Aspect の内容を定義する JSON/YAML です。Zone を表す Entry + の正確な ID や Entry Group 名は環境によって異なるため、事前に + entries search や Search UI で確認してください。 +
+
+ +
+

9参考文献・出典

+
+ + 1 + +

About lakes and zones

+

+ Lake / Zone の用語解説、Knowledge Catalog への改称について +

+ docs.cloud.google.com/dataplex/docs/terminology +
+
+ + 2 + +

Create a Knowledge Catalog lake

+

Lake 作成手順

+ docs.cloud.google.com/dataplex/docs/create-lake +
+
+ + 3 + +

Add a zone

+

Zone 追加手順

+ docs.cloud.google.com/dataplex/docs/add-zone +
+
+ + 4 + +

Manage data assets in a lake

+

アセットの追加・リージョン制約

+ docs.cloud.google.com/dataplex/docs/manage-assets +
+
+ + 5 + +

Knowledge Catalog locations

+

リージョン設計と改称の正式アナウンス日

+ docs.cloud.google.com/dataplex/docs/locations +
+
+ + 6 + +

Create a bucket

+

Cloud Storage バケット作成

+ docs.cloud.google.com/storage/docs/creating-buckets +
+
+ + 7 + +

Manage aspects and enrich metadata

+

+ Aspect Type / Aspect の作成・付与手順(gcloud + コマンドの言及を含む) +

+ docs.cloud.google.com/dataplex/docs/enrich-entries-metadata +
+
+ + 8 + +

Establish foundational data context with Knowledge Catalog

+

Aspect Type 作成のチュートリアル

+ docs.cloud.google.com/dataplex/docs/establish-foundational-data-context +
+
+ + 9 + +

About metadata management in Knowledge Catalog

+

Aspect Type の Location 制約など

+ docs.cloud.google.com/dataplex/docs/catalog-overview +
+
+ + 10 + +

Getting started with Cloud APIs

+

API の有効化手順

+ docs.cloud.google.com/apis/docs/getting-started +
+
+ + 11 + +

Enable and disable services

+

gcloud services enable 等の解説

+ docs.cloud.google.com/service-usage/docs/enable-disable +
+
+ + 12 + +

gcloud dataplex zones create

+

+ Zone 作成コマンドのリファレンス(Type / resource-location-type + の仕様) +

+ docs.cloud.google.com/sdk/gcloud/reference/dataplex/zones/create +
+
+ + 13 + +

gcloud dataplex assets create

+

アセット作成コマンドのリファレンス

+ docs.cloud.google.com/sdk/gcloud/reference/dataplex/assets/create +
+
+ + 14 + +

gcloud dataplex aspect-types create

+

+ Aspect Type + 作成コマンドのリファレンス(メタデータテンプレートファイルの仕様) +

+ docs.cloud.google.com/sdk/gcloud/reference/dataplex/aspect-types/create +
+
+ + 15 + +

gcloud dataplex entries update-aspects

+

Entry への Aspect 付与コマンドのリファレンス

+ docs.cloud.google.com/sdk/gcloud/reference/dataplex/entries/update-aspects +
+
+ + 16 + +

+ Create and Add Aspects to Knowledge Catalog Assets(同系統のラボ + GSP1145) +

+

+ Lake / Zone / Asset / Aspect Type 作成の UI 操作を実機で確認 +

+ www.skills.google/focuses/62711?parent=catalog +
+
+
+
+ + +
+
+ + + + + + + diff --git a/Knowledge-catalog-challenge-lab-best-practices.md b/Knowledge-catalog-challenge-lab-best-practices.md new file mode 100644 index 000000000..edde37d5b --- /dev/null +++ b/Knowledge-catalog-challenge-lab-best-practices.md @@ -0,0 +1,247 @@ +# Knowledge Catalog チャレンジラボ攻略ガイド +## Lake / Zone / Asset / Aspect Type 実装のベストプラクティス + +対象ラボ: [Create and Add Aspects to Knowledge Catalog Assets](https://www.skills.google/course_templates/726/labs/629895)(Challenge Lab) + +--- + +## この記事について + +このガイドは、Google Cloud の初学者でも迷わずタスクを完了できるように、チャレンジラボの3つのタスクをステップバイステップで解説するものです。単なる操作手順の再掲ではなく、「なぜその設定が必要なのか」「実務でハマりやすい落とし穴は何か」まで含めて解説します。 + +各手順の根拠は、末尾の「参考文献・出典」セクションに公式ドキュメントの URL としてまとめています。 + +> **重要な用語の前提知識** +> このラボが扱うサービスは、2026年4月10日付けで **Dataplex Universal Catalog** から **Knowledge Catalog** へ名称変更されました。ただし API・クライアントライブラリ・CLI(`gcloud dataplex ...`)・IAM のロール名は変更されておらず、引き続き `dataplex` という名前空間のままです。コンソール上の表示名は Knowledge Catalog でも、コマンドや権限名を調べるときは「Dataplex」で検索するのが正解です。 + +--- + +## 0. 全体像を理解する:Knowledge Catalog のリソース階層 + +タスクに着手する前に、Knowledge Catalog が扱うリソースの親子関係を押さえておくと、迷わず作業を進められます。 + +| リソース | 役割 | 本ラボでの名称 | +| --- | --- | --- | +| **Lake** | データドメインや事業部門を表す最上位の論理コンテナ | Customer Engagements | +| **Zone** | Lake 内のサブドメイン。データの成熟度(raw / curated)で分類 | Raw Event Data(Raw Zone) | +| **Asset** | Zone に紐づく実データへのポインタ(Cloud Storage バケット or BigQuery データセット) | Raw Event Files(Cloud Storage バケット) | +| **Aspect Type** | メタデータのスキーマ(テンプレート)。フィールドと型を定義する再利用可能な雛形 | Protected Raw Data Aspect | +| **Aspect** | Aspect Type のインスタンス。実際に Zone やカラムに付与される値 | Protected Raw Data Flag = Y/N | + +```mermaid +flowchart TB + subgraph LAKE["Lake: Customer Engagements"] + subgraph ZONE["Zone: Raw Event Data (Raw Zone / Regional)"] + ASSET["Asset: Raw Event Files (Cloud Storage bucket)"] + end + end + ATYPE["Aspect Type: Protected Raw Data Aspect (Enum: Y / N)"] -->|"アスペクトとして付与"| ZONE +``` + +Zone には **Raw Zone** と **Curated Zone** の2種類があります。Raw Zone はスキーマ検証を行わずどのような形式のデータでも受け入れる「着地帯」であるのに対し、Curated Zone は構造化・検証済みのデータを格納する用途です。今回作成する「Raw Event Data」は生イベントデータの着地帯なので Raw Zone が適切です。 + +--- + +## 1. 事前準備:必要な API を有効化する + +Knowledge Catalog のリソースを作成する前に、Dataplex API(コンソール上の検索名は「Cloud Dataplex API」)が有効化されている必要があります。プロジェクトによってはデフォルトで有効な場合もありますが、必ず確認しましょう。 + +1. Google Cloud コンソールの検索バーに `Cloud Dataplex API` と入力する。 +2. 検索結果から「Cloud Dataplex API」をクリックする。 +3. 「有効にする(Enable)」ボタンが表示されている場合はクリックする。既に有効な場合は「API が有効です」と表示される。 + +**ベストプラクティス**: 本番運用では `gcloud services enable dataplex.googleapis.com` のようにコマンドで有効化し、Infrastructure as Code(Terraform 等)で管理すると、環境間の再現性が高まります。 + +--- + +## 2. Task 1: Lake と raw zone を作成する + +### 2-1. Lake「Customer Engagements」を作成する + +1. ナビゲーションメニューから「View all products」→ Analytics 配下の「Knowledge Catalog」を開く。 +2. 左ペインの「Manage lakes」から「Manage」をクリックする。 +3. 「Create Lake」をクリックする。 +4. 以下のプロパティを設定する。 + +| 項目 | 値 | +| --- | --- | +| Display Name | Customer Engagements | +| Region | `` | + +5. 「Create」をクリックする。 + +**ベストプラクティス** + +- Lake ID は Display Name から自動生成されますが、命名規則が組織で決まっている場合は手動で指定しましょう(作成後に ID は変更できません)。 +- Region は後から変更できないリソース属性です。課題文の指示どおり `` を一字一句正確に選択してください。 +- 作成直後は Lake のステータスが「Active」になるまで数分かかることがあります。ステータスが Active になってから次の Zone 作成に進むと、失敗によるロールバックを避けられます。 + +### 2-2. raw zone「Raw Event Data」を Lake に追加する + +1. 「Lakes」一覧で作成した「Customer Engagements」をクリックする。 +2. 「Zones」タブで「Add zone」をクリックする。 +3. 以下のプロパティを設定する。 + +| 項目 | 値 | +| --- | --- | +| Display Name | Raw Event Data | +| Type | Raw Zone | +| Data locations | Regional | + +4. 「Create」をクリックする。 + +**ベストプラクティス** + +- Zone の Type は後から変更できないため、用途(raw か curated か)を最初に確定させることが重要です。 +- 「Data locations」を Regional に設定すると、この Zone に追加できるアセットのロケーションが Lake と同一リージョンの単一リージョンデータに限定されます。マルチリージョンのデータを扱う予定がある場合は、この設計判断を事前チームで合意しておきましょう。 +- Zone の作成中も Lake 自体は引き続き利用可能です。複数の Zone を並行して追加できます。 + +--- + +## 3. Task 2: Cloud Storage バケットを作成し、Zone にアセットとして追加する + +### 3-1. Cloud Storage バケットを作成する + +1. ナビゲーションメニューから「Cloud Storage」→「Buckets」を開く。 +2. 「Create」をクリックする。 +3. バケット名に「Project ID」(現在のプロジェクト ID)を入力する。 +4. ロケーションタイプを Region、リージョンを `` に設定する。 +5. 残りの設定はデフォルトのまま「Create」をクリックする。 + +**ベストプラクティス** + +- Cloud Storage のバケット名はグローバルに一意である必要があります。プロジェクト ID は Google Cloud 全体で一意なので、バケット名として利用するのはよくある命名パターンです。 +- バケットの Region は、後の手順で Zone にアタッチする際に **Lake / Zone のリージョンと重なっている必要があります**。リージョンが一致しない場合、Zone に追加できずエラーになります。 + +### 3-2. バケットを Regional アセット「Raw Event Files」として Zone にアタッチする + +1. 「Zones」一覧で「Raw Event Data」をクリックする。 +2. 「Assets」タブで「+ Add Assets」(または「Add an asset」)をクリックする。 +3. 以下のプロパティを設定する。 + +| 項目 | 値 | +| --- | --- | +| Type | Cloud Storage bucket | +| Display Name | Raw Event Files | +| バケット | 手順3-1で作成したバケット | +| Data locations | Regional | + +4. 「Continue」→「Submit」の順にクリックする。 + +**ベストプラクティス** + +- 1つの Zone に複数のアセットを同時に追加でき、追加処理中もその Zone を継続して利用できます。 +- Cloud Storage バケットをアセットとして追加すると、Knowledge Catalog はバケット内のテーブルに対応する BigQuery 外部テーブルを自動的に公開します。ディスカバリー設定(Discovery settings)を Zone レベルから継承するか個別設定するかも、この画面で決められます。 + +--- + +## 4. Task 3: Aspect Type を作成し、Zone に Aspect を追加する + +### 4-1. Aspect Type「Protected Raw Data Aspect」を作成する + +Aspect Type は Aspect の再利用可能なテンプレートです。フィールドの型や必須/任意といった制約を定義し、メタデータの一貫性を担保します。 + +1. 左ペインの「Manage Metadata」から「Metadata Types」を開く。 +2. 「Aspect types」タブを選択し、「Create」をクリックする。 +3. 以下のプロパティを設定する。 + +| 項目 | 値 | +| --- | --- | +| Display Name | Protected Raw Data Aspect | +| Location | `` | + +4. 「Template」セクションで「Add field」をクリックし、フィールドを追加する。 + +| 項目 | 値 | +| --- | --- | +| Field Display Name | Protected Raw Data Flag | +| Type | Enum | + +5. 「Add an enum value」で値 `Y` を追加し「Done」をクリックする。 +6. 再度「Add an enum value」で値 `N` を追加し「Done」をクリックする。 +7. 「Save」をクリックする。 + +**ベストプラクティス** + +- Aspect Type の Location は作成後に変更できません。Zone や Asset に付与する予定であれば、原則としてそれらと同じリージョン(または Global)を選択してください。Global な Aspect Type はどのリージョンの Entry にも付与できるため、複数リージョンで再利用したい共通メタデータ(例:データ分類ラベル)には Global が向いています。 +- 機密データ/保護対象データを識別するための Enum フィールドは、値のブレを防ぐために自由記述の文字列型ではなく Enum 型で定義するのがベストプラクティスです。今回の `Y` / `N` のように選択肢を固定すると、後続の検索・フィルタリングが安定します。 +- Aspect Type の作成には数分かかることがあります。「Check my progress」が成功と判定するまで、少し待ってから確認しましょう。 + +### 4-2. Zone「Raw Event Data」に Aspect を追加する + +Aspect Type はあくまでテンプレートであり、実際にメタデータとして意味を持たせるには対象の Entry(この場合は Zone)に Aspect を付与する必要があります。 + +1. 左メニューの「Discover」配下にある「Search」を開く。 +2. 検索プラットフォームを Knowledge Catalog に設定し、Zone「Raw Event Data」を検索して開く(またはコンソール上の Zone 詳細ページから直接遷移する)。 +3. Entry 詳細ページの「Details」タブにある「Aspects」セクションで、「Optional aspects」の「Add」をクリックする。 +4. フィルターに `Protected Raw Data Aspect` と入力し、該当の Aspect Type を選択する。 +5. 「Protected Raw Data Flag」で値(`Y` または `N`)を選択する。 +6. 「Save」をクリックする。 + +**ベストプラクティス** + +- Aspect は Entry(またはそのカラム)に紐づけて保存される点に注意してください。Aspect Type と付与先の Entry が異なる Google Cloud Organization に属している場合は付与できません。 +- 必須(Required)ではなく任意(Optional)の Aspect として設計しておくと、既存の Entry に後から段階的にメタデータを充実させていく運用がしやすくなります。 +- この操作も反映まで数分かかることがあるため、「Check my progress」がすぐに成功しなくても慌てず再確認しましょう。 + +--- + +## 5. 全体ワークフロー + +3つのタスクを通しで俯瞰すると、以下のような一直線のフローになります。 + +```mermaid +flowchart TB + START(["開始"]) --> API["Dataplex API を有効化"] + API --> T1_1["Task1: Lake『Customer Engagements』を作成"] + T1_1 --> T1_2["Task1: raw zone『Raw Event Data』を追加"] + T1_2 --> T2_1["Task2: Cloud Storage バケットを作成 (名前 = Project ID)"] + T2_1 --> T2_2["Task2: バケットを Regional アセット『Raw Event Files』として追加"] + T2_2 --> T3_1["Task3: Aspect Type『Protected Raw Data Aspect』を作成 (Enum: Y/N)"] + T3_1 --> T3_2["Task3: Zone『Raw Event Data』に Aspect を追加"] + T3_2 --> DONE(["完了 - Check my progress で検証"]) +``` + +--- + +## 6. ベストプラクティスまとめ + +| カテゴリ | ベストプラクティス | 理由 | +| --- | --- | --- | +| リージョン | Lake / Zone / バケット / Aspect Type のリージョンをすべて `` に統一する | Region は後から変更できず、リージョン不一致はアセット追加時のエラーの主因になる | +| 命名 | Display Name は人間が読める名前、ID は組織の命名規則に沿って明示指定する | ID は作成後に変更できないため、後工程での混乱を防ぐ | +| バケット名 | グローバルに一意な名前が必要な場合は Project ID を利用する | Project ID は Google Cloud 全体で重複しないことが保証されている | +| Aspect Type 設計 | 選択肢が限定されるメタデータは Enum 型で定義する | 自由入力の文字列よりも値の表記ゆれを防ぎ、検索性が向上する | +| API 有効化 | リソース作成前に対象 API(Dataplex API)を有効化しているか必ず確認する | 未有効化のまま操作すると作成処理がエラーになる | +| 作業順序 | 各リソースのステータスが Active になったことを確認してから次の手順に進む | 作成失敗時は自動的に前の状態へロールバックされるため、待たずに進むと手戻りが発生する | +| IAM | 実務では最小権限の原則に従い、Dataplex 用のロール(例: `roles/dataplex.editor`)と Storage 用のロール(例: `roles/storage.admin`)を必要な範囲だけ付与する | ラボの学生アカウントには広い権限が事前付与されているが、本番環境ではそのまま使うべきではない | + +--- + +## 7. よくあるエラーと対処法 + +| 症状 | 主な原因 | 対処法 | +| --- | --- | --- | +| Zone にバケットを追加できない | バケットのリージョンと Lake / Zone のリージョンが重なっていない | バケットのロケーションを Zone の Data locations 設定と一致させて作り直す | +| Lake / Zone の作成が失敗する | Dataplex API が有効化されていない、または権限不足 | API の有効化状況を確認し、必要な IAM ロールが付与されているか確認する | +| Aspect Type が Search 結果に出てこない | 作成直後で反映が完了していない、または Location が Entry と一致しない(かつ Global でもない) | 数分待って再検索する。Location の不一致が疑われる場合は Aspect Type を Global で作り直す(既存の Location 変更は不可) | +| Check my progress がなかなか成功しない | バックグラウンド処理の反映待ち | 対象リソースのステータスが Active になっているか確認し、数分後に再チェックする | + +--- + +## 8. 参考文献・出典 + +| No. | タイトル | URL | +| --- | --- | --- | +| 1 | About lakes and zones(Lake / Zone の用語解説、Knowledge Catalog への改称について) | https://docs.cloud.google.com/dataplex/docs/terminology | +| 2 | Create a Knowledge Catalog lake(Lake 作成手順) | https://docs.cloud.google.com/dataplex/docs/create-lake | +| 3 | Add a zone(Zone 追加手順) | https://docs.cloud.google.com/dataplex/docs/add-zone | +| 4 | Manage data assets in a lake(アセットの追加・リージョン制約) | https://docs.cloud.google.com/dataplex/docs/manage-assets | +| 5 | Knowledge Catalog locations(リージョン設計と改称の正式アナウンス日) | https://docs.cloud.google.com/dataplex/docs/locations | +| 6 | Create a bucket(Cloud Storage バケット作成) | https://docs.cloud.google.com/storage/docs/creating-buckets | +| 7 | Manage aspects and enrich metadata(Aspect Type / Aspect の作成・付与手順) | https://docs.cloud.google.com/dataplex/docs/enrich-entries-metadata | +| 8 | Establish foundational data context with Knowledge Catalog(Aspect Type 作成のチュートリアル) | https://docs.cloud.google.com/dataplex/docs/establish-foundational-data-context | +| 9 | About metadata management in Knowledge Catalog(Aspect Type の Location 制約など) | https://docs.cloud.google.com/dataplex/docs/catalog-overview | +| 10 | Getting started with Cloud APIs(API の有効化手順) | https://docs.cloud.google.com/apis/docs/getting-started | +| 11 | Enable and disable services(`gcloud services enable` 等) | https://docs.cloud.google.com/service-usage/docs/enable-disable | +| 12 | Create and Add Aspects to Knowledge Catalog Assets(同系統のラボ GSP1145。Lake/Zone/Asset/Aspect Type 作成の UI 操作を実機で確認) | https://www.skills.google/focuses/62711?parent=catalog | diff --git a/Ml-api-challenge-lab-guide.html b/Ml-api-challenge-lab-guide.html new file mode 100644 index 000000000..b0ab5619a --- /dev/null +++ b/Ml-api-challenge-lab-guide.html @@ -0,0 +1,1113 @@ + + + + + + Machine Learning APIs チャレンジラボ 攻略ガイド + + + + +
+ + +
+
+
+ Google Cloud Skills Boost / Challenge Lab 攻略ガイド +
+

Machine Learning APIs チャレンジラボ 攻略ガイド

+

+ Vision API × Translation API × BigQuery + によるサイン画像テキスト抽出パイプライン +

+ 対象ラボ: Integrate with Machine Learning APIs: Challenge Lab +
+ +
+ このガイドの使い方
+ チャレンジラボは「学んだスキルを自力で組み合わせて使えるか」を確認するためのものです。このガイドはコードを丸暗記させるものではなく、なぜそのAPI呼び出しが必要なのか・なぜその権限が必要なのかを理解しながら進められるように構成しています。各セクションの根拠は公式ドキュメントのURLとして明記しているので、実装時は必ず一次情報を確認してください。 +
+ +
+

1. 全体像を理解する

+

+ このラボで構築するのは、Cloud + Storage上の看板画像から文字を抽出し、必要に応じて翻訳し、結果をBigQueryに集約する小さなETL(Extract-Transform-Load)パイプラインです。 +

+ +

1-1. データフロー(アーキテクチャ)

+
+ +

1-2. タスクの実行順序

+
+ +

+ 順序には理由があります。認証情報(Task + 1・2)がなければAPI呼び出し自体が失敗し、Vision + APIの出力(抽出テキストとロケール)がなければTranslation + APIをいつ呼ぶべきか判定できません。必ずこの順で進め、各Taskの動作確認をしてから次に進んでください。 +

+
+ +
+

2. Task 1: サービスアカウントとIAM権限の準備

+ +

2-1. なぜサービスアカウントが必要か

+

+ Pythonスクリプトはユーザーの代わりに、ユーザーが介在しないバックグラウンド処理としてGoogle + CloudのAPIを呼び出します。この用途にはユーザーアカウントではなく、アプリケーション用のIDであるサービスアカウントを使うのがベストプラクティスです。 +

+ + +

2-2. 付与すべきロール

+

+ ラボの指示は「BigQuery Role」と「Cloud Storage + Role」を付与することですが、これは事前定義ロールの中でも管理者権限を持つ以下の2つを指します。 +

+ + + + + + + + + + + + + + + + + + + + + + + +
用途ロール名ロールID主な権限
Cloud Storageの読み書きStorage Adminroles/storage.adminバケット・オブジェクトの作成/読み取り/更新/削除など全操作
BigQueryへのデータ投入BigQuery Adminroles/bigquery.adminデータセット/テーブルの管理、ジョブ実行、データ挿入
+
+ 参考:
+ Cloud Storage IAM ロール一覧 — + IAM roles for Cloud Storage
+ BigQuery IAM ロール一覧 — + BigQuery IAM roles and permissions +
+

+ ベストプラクティス補足: 本番運用であれば + storage.objectAdmin や + bigquery.dataEditor + のようなより権限を絞ったロールを選び、最小権限の原則を守るべきです。このラボでは学習目的のため管理者ロールを使用します。 +

+ +

2-3. gcloud コマンドでの実装例

+
# プロジェクトIDを変数に格納
+export PROJECT_ID=$(gcloud config get-value project)
+
+# サービスアカウントを作成
+gcloud iam service-accounts create ml-api-sa \
+  --display-name="ML API Challenge Lab Service Account"
+
+# 作成したサービスアカウントのメールアドレスを変数化
+export SA_EMAIL="ml-api-sa@${PROJECT_ID}.iam.gserviceaccount.com"
+
+# Storage Admin ロールを付与
+gcloud projects add-iam-policy-binding ${PROJECT_ID} \
+  --member="serviceAccount:${SA_EMAIL}" \
+  --role="roles/storage.admin"
+
+# BigQuery Admin ロールを付与
+gcloud projects add-iam-policy-binding ${PROJECT_ID} \
+  --member="serviceAccount:${SA_EMAIL}" \
+  --role="roles/bigquery.admin"
+

+ Cloud + Consoleから作成する場合は「IAMと管理」→「サービスアカウント」→「サービスアカウントを作成」からGUIでも同様の設定が可能です。 +

+
+ +
+

3. Task 2: 認証情報ファイルの発行と環境変数設定

+ +

3-1. JSONキーファイルの作成

+
gcloud iam service-accounts keys create ~/key.json \
+  --iam-account="${SA_EMAIL}"
+

+ または Cloud Console の「サービスアカウント」詳細画面 →「キー」タブ + →「鍵を追加」→「新しい鍵を作成」→ JSON形式、でも取得できます。 +

+ +

3-2. 環境変数 GOOGLE_APPLICATION_CREDENTIALS の設定

+

+ Pythonクライアントライブラリは、明示的に認証情報を渡さない限りApplication Default Credentials(ADC)という仕組みで認証情報を探します。ADCが最初に確認するのがこの環境変数です。 +

+
export GOOGLE_APPLICATION_CREDENTIALS="$HOME/key.json"
+
+ 参考: ADCとGOOGLE_APPLICATION_CREDENTIALS環境変数の役割 — + Dense document text detection tutorial | Cloud Vision API +
+
+ よくある落とし穴: Cloud + Shellのセッションが切れると環境変数もリセットされます。スクリプトが急に + DefaultCredentialsError + を出すようになったら、まずこの環境変数が現在のシェルに残っているか + echo $GOOGLE_APPLICATION_CREDENTIALS で確認してください。 +
+
+ +
+

4. Task 3: Vision API でテキストを抽出する

+ +

4-1. TEXT_DETECTION と DOCUMENT_TEXT_DETECTION の使い分け

+

+ Vision + APIには文字検出用の機能が2種類あります。看板や標識のような比較的短いテキストが対象のこのラボでは、密度の高い文書向けの + DOCUMENT_TEXT_DETECTION(document_text_detection + メソッド)を使うのが適切です。こちらは行・段落単位の構造情報や、検出した言語(ロケール)の情報も返してくれるためです。 +

+ + + + + + + + + + + + + + + + + + + + +
手法主な用途言語ロケール情報
text_detection短いテキスト(看板・ラベル・ナンバープレートなど)個別の language_code 情報は限定的
document_text_detection密なテキスト(文書・書籍・領収書、標識も含む)page.property.detected_languages で取得可能
+
+ 参考: 2つの検出方式の違い — + Detect and extract text from images | Cloud Vision API +
+ +

4-2. レスポンスの構造(full_text_annotation)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
階層内容
TextAnnotation(full_text_annotation)画像全体のOCR結果
→ Page + ページ単位。property.detected_languages にロケール情報を持つ +
→ → Blockテキストブロック
→ → → Paragraph段落
→ → → → Word単語
→ → → → → Symbol1文字単位
+
+ 参考: TextAnnotationの階層構造 — + Package google.cloud.vision.v1 | Cloud Vision API +
+ +

4-3. 実装のポイント(# TBD 箇所の考え方)

+

+ 以下は実装の考え方を示す例です。実際の変数名・関数構造は配布されたスクリプトのコメントに合わせてください。 +

+
from google.cloud import vision
+
+def detect_text(bucket_name, filename):
+    """Cloud Storage上の画像からテキストとロケールを抽出する"""
+    client = vision.ImageAnnotatorClient()
+
+    image = vision.Image()
+    image.source.image_uri = f"gs://{bucket_name}/{filename}"
+
+    # TBD: document_text_detection を呼び出す
+    response = client.document_text_detection(image=image)
+
+    text = response.full_text_annotation.text
+
+    # ロケール(検出言語)を取得する
+    locale = "und"  # und = undetermined(未確定)のデフォルト値
+    pages = response.full_text_annotation.pages
+    if pages and pages[0].property.detected_languages:
+        locale = pages[0].property.detected_languages[0].language_code
+
+    return text, locale
+

+ 抽出したテキストは、同じCloud + Storageバケットに元のファイル名を使ったテキストファイルとして書き戻します(例: + sign1.jpg → sign1.jpg.txt)。 +

+
+ 動作確認のコツ: + この段階で一度スクリプトを実行し、バケットにテキストファイルが生成されること・locale + にそれらしい言語コード(en、ja、zh + など)が入ることを確認してから、次のTaskに進んでください。全部を実装してから一括デバッグするより、段階ごとの確認の方がエラーの切り分けが容易です。 +
+
+ +
+

5. Task 4: Translation API でテキストを翻訳する

+ +

5-1. なぜ「ロケールが基準言語と異なる場合だけ」翻訳するのか

+

+ すべてのテキストを無条件に翻訳すると、API呼び出し回数が不必要に増え、コストと実行時間が増加します。Vision + APIがすでに検出したロケール情報を使って「翻訳が必要なものだけ」を絞り込むのが効率的な設計です。 +

+ +

5-2. Translation API(v2)の呼び出し方

+

+ このラボのスクリプトは + google.cloud.translate_v2 を使う設計になっています。 +

+
from google.cloud import translate_v2 as translate
+
+TARGET_LANGUAGE = "en"  # スクリプトの基準言語(配布されたコードの定義に合わせる)
+
+def translate_text(text, source_locale):
+    """基準言語と異なる場合のみ翻訳する"""
+    if source_locale == TARGET_LANGUAGE or source_locale == "und":
+        return text  # 翻訳不要、または言語判定不能な場合はそのまま返す
+
+    translate_client = translate.Client()
+
+    # TBD: translate を呼び出す
+    result = translate_client.translate(
+        text,
+        target_language=TARGET_LANGUAGE,
+    )
+
+    return result["translatedText"]
+
+ 参考:
+ translate_v2.Client.translate の使い方 — + Cloud Translation client libraries | Google Cloud Documentation
+ 翻訳結果のサンプルコード — + Translating text | Cloud Translation +
+

+ ベストプラクティス補足: result + は辞書型で返り、translatedText のほかに detectedSourceLanguage + も含まれます。Vision APIのロケール判定に自信が持てない場合は、Translation + API側の detectedSourceLanguage と突き合わせて整合性を確認するのも有効です。 +

+
+ +
+

6. Task 5: BigQueryへの書き込みを有効化する

+ +

6-1. 事前にテーブルスキーマを確認する

+

+ 配布されたスクリプトの末尾にあるBigQuery書き込み処理はコメントアウトされています。有効化する前に、書き込み先テーブルの実際のカラム構成を必ず確認してください。ラボごと・スクリプトのバージョンごとにカラム名が異なる場合があるため、想定で実装せずに次のコマンドで確認するのがベストプラクティスです。 +

+
bq show --schema --format=prettyjson image_classification_dataset.image_text_detail
+ +

6-2. データ挿入の実装パターン

+

+ BigQueryへのデータ投入には、ストリーミング挿入用の + insert_rows_json メソッドを使うのが一般的です。 +

+
from google.cloud import bigquery
+
+def upload_to_bigquery(project_id, dataset_id, table_id, rows):
+    client = bigquery.Client(project=project_id)
+    table_ref = f"{project_id}.{dataset_id}.{table_id}"
+
+    # rows は確認した実際のスキーマに合わせた dict のリスト
+    # 例: [{"uri": ..., "text": ..., "locale": ..., "translated_text": ...}, ...]
+    errors = client.insert_rows_json(table_ref, rows)
+
+    if errors:
+        print(f"BigQuery insert errors: {errors}")
+    else:
+        print(f"{len(rows)} 件のレコードを {table_ref} に書き込みました")
+
+ 参考:
+ insert_rows_json のシグネチャとリトライ挙動 — + Class Client | Python client libraries | Google Cloud Documentation
+ ストリーミング挿入の基本パターン — + Use the legacy streaming API | BigQuery +
+ +

6-3. コメントアウトの解除

+

+ 配布スクリプトの最終行(BigQuery書き込みを実行する行)の先頭にある + # を削除して有効化します。Vision APIとTranslation API双方の動作確認が完了してからこの行を有効化してください。デバッグ中に無効なデータを何度もBigQueryに投入すると、テーブルの重複行の削除など余計な後処理が発生します。 +

+
+ +
+

7. 検証: 最頻出言語を確認する

+

+ すべての画像の処理とBigQueryへの投入が終わったら、以下のクエリで結果を確認します。 +

+
SELECT locale, COUNT(locale) AS lcount
+FROM image_classification_dataset.image_text_detail
+GROUP BY locale
+ORDER BY lcount DESC
+

+ このクエリが空の結果を返す、または想定より行数が少ない場合は、Task + 3・4のロジック(特にロケール判定条件)に戻って確認してください。 +

+
+ +
+

8. トラブルシューティング早見表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状主な原因対処
PermissionDenied: 403IAMロールの反映待ち、または付与漏れ + IAMポリシー反映には数十秒〜数分かかることがある。ロールを再確認し、少し待って再実行 +
DefaultCredentialsErrorGOOGLE_APPLICATION_CREDENTIALSが未設定 + Cloud Shellのセッションが切れると環境変数はリセットされる。echo + $GOOGLE_APPLICATION_CREDENTIALS で確認し再設定 +
ModuleNotFoundError必要なクライアントライブラリ未インストール + pip install google-cloud-vision google-cloud-translate + google-cloud-bigquery google-cloud-storage +
Translation APIでエラーロケールが und(未確定)のまま翻訳を試行 + ロケール判定前のガード処理(source_locale == "und")を確認 +
BigQueryに行が入らない最終行のコメントアウト解除忘れ、またはスキーマ不一致 + # の削除を確認し、bq show --schema + で実際のカラム名と型を突き合わせる +
+
+ +
+

9. まとめ: このラボで押さえるべきベストプラクティス

+
    +
  1. + 最小権限ではなく管理者ロールを使う場面と理由を理解する。学習用ラボでは + *.admin ロールで進めるが、実務では権限を絞ったロールを検討する +
  2. +
  3. + 段階的に動作確認する。Vision API → Translation API → + BigQueryの順に、各段階で出力を確認してから次に進む +
  4. +
  5. + APIレスポンスの構造を理解してから実装する。full_text_annotation + の階層(Page→Block→Paragraph→Word→Symbol)とロケール情報の位置を把握する +
  6. +
  7. + 想定ではなく実際のスキーマを確認する。BigQueryへの書き込み前に bq show + --schema で実テーブル構成を確認する +
  8. +
  9. + 条件分岐でAPIコストと実行時間を最適化する。ロケールが基準言語と一致する場合は翻訳をスキップする設計にする +
  10. +
+
+ +
+

参考文献(一次情報)

+ +
+ +
Machine Learning APIs チャレンジラボ 攻略ガイド
+
+
+ + + + + + + + + diff --git a/Ml-api-challenge-lab-guide.md b/Ml-api-challenge-lab-guide.md new file mode 100644 index 000000000..aa47d3b17 --- /dev/null +++ b/Ml-api-challenge-lab-guide.md @@ -0,0 +1,311 @@ +# Machine Learning APIs チャレンジラボ 攻略ガイド +### 〜 Vision API × Translation API × BigQuery によるサイン画像テキスト抽出パイプライン 〜 + +対象ラボ: [Integrate with Machine Learning APIs: Challenge Lab](https://www.skills.google/course_templates/630/labs/612231) + +> **このガイドの使い方** +> チャレンジラボは「学んだスキルを自力で組み合わせて使えるか」を確認するためのものです。このガイドはコードを丸暗記させるものではなく、**なぜそのAPI呼び出しが必要なのか・なぜその権限が必要なのか**を理解しながら進められるように構成しています。各セクションの根拠は公式ドキュメントのURLとして明記しているので、実装時は必ず一次情報を確認してください。 + +--- + +## 1. 全体像を理解する + +このラボで構築するのは、Cloud Storage上の看板画像から文字を抽出し、必要に応じて翻訳し、結果をBigQueryに集約する小さなETL(Extract-Transform-Load)パイプラインです。 + +### 1-1. データフロー(アーキテクチャ) + +```mermaid +flowchart TB + A["Cloud Storage
入力: 画像ファイル群"] --> B["Python スクリプト
analyze-images-v2.py"] + B --> C["Vision API
document_text_detection"] + C --> D["Cloud Storage
出力: 抽出テキストファイル (.txt)"] + C --> E{"検出ロケールは
基準言語と一致するか"} + E -->|"一致する"| G["翻訳をスキップ
original_text をそのまま使用"] + E -->|"一致しない"| F["Translation API
translate"] + F --> H["結果をメモリ上のリストに保持"] + G --> H + H --> I["BigQuery
image_classification_dataset.image_text_detail"] +``` + +### 1-2. タスクの実行順序 + +```mermaid +flowchart LR + T1["Task 1
サービスアカウント作成
IAM権限付与"] --> T2["Task 2
認証情報ファイル発行
環境変数設定"] + T2 --> T3["Task 3
Vision API 実装"] + T3 --> T4["Task 4
Translation API 実装"] + T4 --> T5["Task 5
BigQuery書き込み有効化
結果検証"] +``` + +順序には理由があります。認証情報(Task 1・2)がなければAPI呼び出し自体が失敗し、Vision APIの出力(抽出テキストとロケール)がなければTranslation APIをいつ呼ぶべきか判定できません。**必ずこの順で進め、各Taskの動作確認をしてから次に進んでください。** + +--- + +## 2. Task 1: サービスアカウントとIAM権限の準備 + +### 2-1. なぜサービスアカウントが必要か + +Pythonスクリプトはユーザーの代わりに、ユーザーが介在しないバックグラウンド処理としてGoogle CloudのAPIを呼び出します。この用途にはユーザーアカウントではなく、アプリケーション用のIDである**サービスアカウント**を使うのがベストプラクティスです。 + +> 参考: IAMの基本ロールと事前定義ロールの違い — [Roles and permissions | IAM (Google Cloud 公式ドキュメント)](https://docs.cloud.google.com/iam/docs/roles-overview) + +### 2-2. 付与すべきロール + +ラボの指示は「BigQuery Role」と「Cloud Storage Role」を付与することですが、これは事前定義ロールの中でも管理者権限を持つ以下の2つを指します。 + +| 用途 | ロール名 | ロールID | 主な権限 | +|---|---|---|---| +| Cloud Storageの読み書き | Storage Admin | `roles/storage.admin` | バケット・オブジェクトの作成/読み取り/更新/削除など全操作 | +| BigQueryへのデータ投入 | BigQuery Admin | `roles/bigquery.admin` | データセット/テーブルの管理、ジョブ実行、データ挿入 | + +> 参考: +> - Cloud Storage IAM ロール一覧 — [IAM roles for Cloud Storage](https://docs.cloud.google.com/storage/docs/access-control/iam-roles) +> - BigQuery IAM ロール一覧 — [BigQuery IAM roles and permissions](https://docs.cloud.google.com/bigquery/docs/access-control) + +**ベストプラクティス補足**: 本番運用であれば`storage.objectAdmin`や`bigquery.dataEditor`のようなより権限を絞ったロールを選び、最小権限の原則を守るべきです。このラボでは学習目的のため管理者ロールを使用します。 + +### 2-3. gcloud コマンドでの実装例 + +```bash +# プロジェクトIDを変数に格納 +export PROJECT_ID=$(gcloud config get-value project) + +# サービスアカウントを作成 +gcloud iam service-accounts create ml-api-sa \ + --display-name="ML API Challenge Lab Service Account" + +# 作成したサービスアカウントのメールアドレスを変数化 +export SA_EMAIL="ml-api-sa@${PROJECT_ID}.iam.gserviceaccount.com" + +# Storage Admin ロールを付与 +gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member="serviceAccount:${SA_EMAIL}" \ + --role="roles/storage.admin" + +# BigQuery Admin ロールを付与 +gcloud projects add-iam-policy-binding ${PROJECT_ID} \ + --member="serviceAccount:${SA_EMAIL}" \ + --role="roles/bigquery.admin" +``` + +Cloud Consoleから作成する場合は「IAMと管理」→「サービスアカウント」→「サービスアカウントを作成」からGUIでも同様の設定が可能です。 + +--- + +## 3. Task 2: 認証情報ファイルの発行と環境変数設定 + +### 3-1. JSONキーファイルの作成 + +```bash +gcloud iam service-accounts keys create ~/key.json \ + --iam-account="${SA_EMAIL}" +``` + +または Cloud Console の「サービスアカウント」詳細画面 → 「キー」タブ → 「鍵を追加」→「新しい鍵を作成」→ JSON形式、でも取得できます。 + +### 3-2. 環境変数 `GOOGLE_APPLICATION_CREDENTIALS` の設定 + +Pythonクライアントライブラリは、明示的に認証情報を渡さない限り**Application Default Credentials(ADC)**という仕組みで認証情報を探します。ADCが最初に確認するのがこの環境変数です。 + +```bash +export GOOGLE_APPLICATION_CREDENTIALS="$HOME/key.json" +``` + +> 参考: ADCとGOOGLE_APPLICATION_CREDENTIALS環境変数の役割 — [Dense document text detection tutorial | Cloud Vision API](https://docs.cloud.google.com/vision/docs/fulltext-annotations) + +**よくある落とし穴**: Cloud Shellのセッションが切れると環境変数もリセットされます。スクリプトが急に `DefaultCredentialsError` を出すようになったら、まずこの環境変数が現在のシェルに残っているか `echo $GOOGLE_APPLICATION_CREDENTIALS` で確認してください。 + +--- + +## 4. Task 3: Vision API でテキストを抽出する + +### 4-1. TEXT_DETECTION と DOCUMENT_TEXT_DETECTION の使い分け + +Vision APIには文字検出用の機能が2種類あります。看板や標識のような比較的短いテキストが対象のこのラボでは、密度の高い文書向けの`DOCUMENT_TEXT_DETECTION`(`document_text_detection`メソッド)を使うのが適切です。こちらは行・段落単位の構造情報や、検出した言語(ロケール)の情報も返してくれるためです。 + +| 手法 | 主な用途 | 言語ロケール情報 | +|---|---|---| +| `text_detection` | 短いテキスト(看板・ラベル・ナンバープレートなど) | 個別の`language_code`情報は限定的 | +| `document_text_detection` | 密なテキスト(文書・書籍・領収書、標識も含む) | `page.property.detected_languages`で取得可能 | + +> 参考: 2つの検出方式の違い — [Detect and extract text from images | Cloud Vision API](https://docs.cloud.google.com/vision/docs/ocr) + +### 4-2. レスポンスの構造(`full_text_annotation`) + +Vision APIのレスポンスは以下のような階層構造を持ちます。 + +| 階層 | 内容 | +|---|---| +| `TextAnnotation` (`full_text_annotation`) | 画像全体のOCR結果 | +| → `Page` | ページ単位。`property.detected_languages`にロケール情報を持つ | +| → → `Block` | テキストブロック | +| → → → `Paragraph` | 段落 | +| → → → → `Word` | 単語 | +| → → → → → `Symbol` | 1文字単位 | + +> 参考: `TextAnnotation`の階層構造 — [Package google.cloud.vision.v1 | Cloud Vision API](https://docs.cloud.google.com/vision/docs/reference/rpc/google.cloud.vision.v1) + +### 4-3. 実装のポイント(`# TBD` 箇所の考え方) + +以下は実装の考え方を示す例です。実際の変数名・関数構造は配布されたスクリプトのコメントに合わせてください。 + +```python +from google.cloud import vision + +def detect_text(bucket_name, filename): + """Cloud Storage上の画像からテキストとロケールを抽出する""" + client = vision.ImageAnnotatorClient() + + image = vision.Image() + image.source.image_uri = f"gs://{bucket_name}/{filename}" + + # TBD: document_text_detection を呼び出す + response = client.document_text_detection(image=image) + + text = response.full_text_annotation.text + + # ロケール(検出言語)を取得する + locale = "und" # und = undetermined(未確定)のデフォルト値 + pages = response.full_text_annotation.pages + if pages and pages[0].property.detected_languages: + locale = pages[0].property.detected_languages[0].language_code + + return text, locale +``` + +抽出したテキストは、同じCloud Storageバケットに元のファイル名を使ったテキストファイルとして書き戻します(例: `sign1.jpg` → `sign1.jpg.txt`)。 + +**動作確認のコツ**: この段階で一度スクリプトを実行し、バケットにテキストファイルが生成されること・`locale`にそれらしい言語コード(`en`、`ja`、`zh`など)が入ることを確認してから、次のTaskに進んでください。全部を実装してから一括デバッグするより、段階ごとの確認の方がエラーの切り分けが容易です。 + +--- + +## 5. Task 4: Translation API でテキストを翻訳する + +### 5-1. なぜ「ロケールが基準言語と異なる場合だけ」翻訳するのか + +すべてのテキストを無条件に翻訳すると、API呼び出し回数が不必要に増え、コストと実行時間が増加します。Vision APIがすでに検出したロケール情報を使って「翻訳が必要なものだけ」を絞り込むのが効率的な設計です。 + +### 5-2. Translation API(v2)の呼び出し方 + +このラボのスクリプトは `google.cloud.translate_v2` を使う設計になっています。 + +```python +from google.cloud import translate_v2 as translate + +TARGET_LANGUAGE = "en" # スクリプトの基準言語(配布されたコードの定義に合わせる) + +def translate_text(text, source_locale): + """基準言語と異なる場合のみ翻訳する""" + if source_locale == TARGET_LANGUAGE or source_locale == "und": + return text # 翻訳不要、または言語判定不能な場合はそのまま返す + + translate_client = translate.Client() + + # TBD: translate を呼び出す + result = translate_client.translate( + text, + target_language=TARGET_LANGUAGE, + ) + + return result["translatedText"] +``` + +> 参考: +> - `translate_v2.Client.translate`の使い方 — [Cloud Translation client libraries | Google Cloud Documentation](https://docs.cloud.google.com/translate/docs/reference/libraries/v2/python) +> - 翻訳結果のサンプルコード — [Translating text | Cloud Translation](https://docs.cloud.google.com/translate/docs/samples/translate-translate-text) + +**ベストプラクティス補足**: `result`は辞書型で返り、`translatedText`のほかに`detectedSourceLanguage`も含まれます。Vision APIのロケール判定に自信が持てない場合は、Translation API側の`detectedSourceLanguage`と突き合わせて整合性を確認するのも有効です。 + +--- + +## 6. Task 5: BigQueryへの書き込みを有効化する + +### 6-1. 事前にテーブルスキーマを確認する + +配布されたスクリプトの末尾にあるBigQuery書き込み処理はコメントアウトされています。有効化する前に、書き込み先テーブルの実際のカラム構成を必ず確認してください。ラボごと・スクリプトのバージョンごとにカラム名が異なる場合があるため、想定で実装せずに次のコマンドで確認するのがベストプラクティスです。 + +```bash +bq show --schema --format=prettyjson image_classification_dataset.image_text_detail +``` + +### 6-2. データ挿入の実装パターン + +BigQueryへのデータ投入には、ストリーミング挿入用の`insert_rows_json`メソッドを使うのが一般的です。 + +```python +from google.cloud import bigquery + +def upload_to_bigquery(project_id, dataset_id, table_id, rows): + client = bigquery.Client(project=project_id) + table_ref = f"{project_id}.{dataset_id}.{table_id}" + + # rows は confirm した実際のスキーマに合わせた dict のリスト + # 例: [{"uri": ..., "text": ..., "locale": ..., "translated_text": ...}, ...] + errors = client.insert_rows_json(table_ref, rows) + + if errors: + print(f"BigQuery insert errors: {errors}") + else: + print(f"{len(rows)} 件のレコードを {table_ref} に書き込みました") +``` + +> 参考: +> - `insert_rows_json`のシグネチャとリトライ挙動 — [Class Client | Python client libraries | Google Cloud Documentation](https://docs.cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client) +> - ストリーミング挿入の基本パターン — [Use the legacy streaming API | BigQuery](https://docs.cloud.google.com/bigquery/docs/streaming-data-into-bigquery) + +### 6-3. コメントアウトの解除 + +配布スクリプトの最終行(BigQuery書き込みを実行する行)の先頭にある `#` を削除して有効化します。**Vision APIとTranslation API双方の動作確認が完了してから**この行を有効化してください。デバッグ中に無効なデータを何度もBigQueryに投入すると、テーブルの重複行の削除など余計な後処理が発生します。 + +--- + +## 7. 検証: 最頻出言語を確認する + +すべての画像の処理とBigQueryへの投入が終わったら、以下のクエリで結果を確認します。 + +```sql +SELECT locale, COUNT(locale) AS lcount +FROM image_classification_dataset.image_text_detail +GROUP BY locale +ORDER BY lcount DESC +``` + +このクエリが空の結果を返す、または想定より行数が少ない場合は、Task 3・4のロジック(特にロケール判定条件)に戻って確認してください。 + +--- + +## 8. トラブルシューティング早見表 + +| 症状 | 主な原因 | 対処 | +|---|---|---| +| `PermissionDenied: 403` | IAMロールの反映待ち、または付与漏れ | IAMポリシー反映には数十秒〜数分かかることがある。ロールを再確認し、少し待って再実行 | +| `DefaultCredentialsError` | `GOOGLE_APPLICATION_CREDENTIALS`が未設定 | Cloud Shellのセッションが切れると環境変数はリセットされる。`echo $GOOGLE_APPLICATION_CREDENTIALS`で確認し再設定 | +| `ModuleNotFoundError` | 必要なクライアントライブラリ未インストール | `pip install google-cloud-vision google-cloud-translate google-cloud-bigquery google-cloud-storage` | +| Translation APIでエラー | ロケールが`und`(未確定)のまま翻訳を試行 | ロケール判定前のガード処理(`source_locale == "und"`)を確認 | +| BigQueryに行が入らない | 最終行のコメントアウト解除忘れ、またはスキーマ不一致 | `#`の削除を確認し、`bq show --schema`で実際のカラム名と型を突き合わせる | + +--- + +## 9. まとめ: このラボで押さえるべきベストプラクティス + +1. **最小権限ではなく管理者ロールを使う場面と理由を理解する** — 学習用ラボでは`*.admin`ロールで進めるが、実務では権限を絞ったロールを検討する。 +2. **段階的に動作確認する** — Vision API → Translation API → BigQueryの順に、各段階で出力を確認してから次に進む。 +3. **APIレスポンスの構造を理解してから実装する** — `full_text_annotation`の階層(Page→Block→Paragraph→Word→Symbol)とロケール情報の位置を把握する。 +4. **想定ではなく実際のスキーマを確認する** — BigQueryへの書き込み前に`bq show --schema`で実テーブル構成を確認する。 +5. **条件分岐でAPIコストと実行時間を最適化する** — ロケールが基準言語と一致する場合は翻訳をスキップする設計にする。 + +--- + +## 参考文献(一次情報) + +- [Roles and permissions | Identity and Access Management (IAM)](https://docs.cloud.google.com/iam/docs/roles-overview) +- [IAM roles for Cloud Storage](https://docs.cloud.google.com/storage/docs/access-control/iam-roles) +- [BigQuery IAM roles and permissions](https://docs.cloud.google.com/bigquery/docs/access-control) +- [Detect and extract text from images | Cloud Vision API](https://docs.cloud.google.com/vision/docs/ocr) +- [Dense document text detection tutorial | Cloud Vision API](https://docs.cloud.google.com/vision/docs/fulltext-annotations) +- [Package google.cloud.vision.v1 | Cloud Vision API](https://docs.cloud.google.com/vision/docs/reference/rpc/google.cloud.vision.v1) +- [Cloud Translation client libraries (Python v2)](https://docs.cloud.google.com/translate/docs/reference/libraries/v2/python) +- [Translating text | Cloud Translation](https://docs.cloud.google.com/translate/docs/samples/translate-translate-text) +- [Class Client | BigQuery Python client libraries](https://docs.cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client) +- [Use the legacy streaming API | BigQuery](https://docs.cloud.google.com/bigquery/docs/streaming-data-into-bigquery) +- [ラボ本体: Integrate with Machine Learning APIs: Challenge Lab](https://www.skills.google/course_templates/630/labs/612231) From 60a7b683199a770acfc3edb745b982b9de4ae08a Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:44 +0900 Subject: [PATCH 005/123] docs(workspace): add Google Workspace challenge lab guide --- Google-workspace-challenge-lab-guide.html | 1337 +++++++++++++++++++++ Google-workspace-challenge-lab-guide.md | 0 2 files changed, 1337 insertions(+) create mode 100644 Google-workspace-challenge-lab-guide.html create mode 100644 Google-workspace-challenge-lab-guide.md diff --git a/Google-workspace-challenge-lab-guide.html b/Google-workspace-challenge-lab-guide.html new file mode 100644 index 000000000..64ef50ee7 --- /dev/null +++ b/Google-workspace-challenge-lab-guide.html @@ -0,0 +1,1337 @@ + + + + + + Google Workspace 入門チャレンジラボ 完全攻略ガイド + + + + + +
+ + +
+
+
+ Google Workspace Challenge Lab +

Google Workspace 入門チャレンジラボ 完全攻略ガイド

+

+ Gmail・カレンダー・Drive・Sheets・AppSheet + を横断する初心者向けチャレンジラボを、ステップバイステップかつベストプラクティス付きで解説します。 +

+
+ +

+ このチャレンジラボは、手順を逐一教えてもらう「ガイド付きラボ」ではなく、これまで学んだ知識を使って自力でタスクを完了させる形式です。しかし初めて + Google Workspace + に触れる方にとっては、どの画面のどのボタンを押せばよいか迷う場面が多くあります。 +

+

+ 本ガイドでは6つのタスクそれぞれについて、目的・具体的な操作手順・実務で通用するベストプラクティス・陥りやすいミスと対処法・根拠となる公式ドキュメントのURLを整理しています。 +

+
+ +
+

事前準備とベストプラクティス

+

課題に取り組む前に、以下の点を必ず確認してください。

+ + + + + + + + + + + + + + + + + + + + + +
項目内容
ブラウザ + シークレットモード(プライベートウィンドウ)を推奨。個人アカウントとラボ用アカウントの競合を防ぐため。 +
アカウント + 必ずラボが発行する一時的な学生アカウント(Username / + Password)を使用する。個人のGoogleアカウントは使わない。 +
タイマー + ラボは一時停止できない。Start Lab + を押した瞬間からカウントダウンが始まるため、事前にこのガイド全体に目を通してから開始する。 +
進捗確認 + 各タスク完了後は必ず + Check my progress をクリックして自動採点を受ける。 +
+
+ +
+

全体の流れ(タスクマップ)

+

+ 6つのタスクは独立していますが、実務上は「個人設定 → + チームコラボレーション」という自然な順序になっています。前半3つ(タスク1〜3)は個人の作業環境を整えるフェーズ、後半3つ(タスク4〜6)は同僚2名とのコラボレーションを設定するフェーズと捉えると理解しやすくなります。 +

+
+
+ +
+
+ 1 + +

Gmail署名の作成

+
+

+ 目的: + 新規メール作成時に、氏名・役職・連絡先が自動的に挿入されるようにし、対外的なコミュニケーションを効率化・プロフェッショナル化します。 +

+ +
    +
  1. Gmail を開く
  2. +
  3. + 右上の歯車アイコン(設定)をクリックし、「すべての設定を表示 (See all + settings)」を選択 +
  4. +
  5. + 「全般 (General)」タブを開き、「署名 + (Signature)」セクションまでスクロール +
  6. +
  7. 「新規作成 (Create new)」をクリックし、署名に任意の名前を付ける
  8. +
  9. + テキストボックスに + 氏名・役職(Position)・連絡先情報 を入力する +
  10. +
  11. + 「新規メール用の署名」と「返信または転送時の署名」の両方に、作成した署名をデフォルトとして設定する +
  12. +
  13. ページ最下部の「変更を保存 (Save Changes)」をクリック
  14. +
+ +
+ +
+
ベストプラクティス
+
+ 署名を「作成」するだけでは不十分です。デフォルトの署名として選択されているかを必ず確認してください。ラボの採点は「新規作成時に自動挿入されるか」を見ています。画像を挿入する場合は文字数カウントに含まれる点にも注意しましょう。 +
+
+
+ +
+ 参考ソース: Create a Gmail signature(Google Gmail + ヘルプ)― + support.google.com/mail/answer/8395 +
+
+ +
+
+ 2 + +

カレンダーに休暇予定を追加

+
+

+ 目的: + 今後3日間、オリエンテーションのため不在になることをチームに共有し、ダブルブッキングを防ぎます。 +

+ +
    +
  1. Google Calendar を開く
  2. +
  3. 左上の「作成 (Create)」ボタン →「予定 (Event)」を選択
  4. +
  5. タイトルに 正確に「OOO Orientation」 と入力する
  6. +
  7. 「終日 (All day)」をオンにする
  8. +
  9. 開始日を本日、終了日を3日後(本日を含め3日間)に設定する
  10. +
  11. 「保存 (Save)」をクリック
  12. +
+ +
+ +
+
初学者がつまずきやすいポイント
+
+ Google Calendarには「Out of + office」という専用の予定タイプが存在します。しかしこのタイプで作成すると、タイトルが自動的に「Out + of office」に固定され、タスクが要求する「OOO + Orientation」というタイトルにはなりません。このタスクでは通常の終日予定(All-day + event)としてタイトルを手動入力するのが正しい進め方です。 +
+
+
+ +
+ +
+ +
+
ベストプラクティス(実務向け補足)
+
+ 実際の業務では専用の「Out of + office」機能を使うことで、不在期間中に届いた新規会議の招待を自動的に辞退し、送信者にカスタムメッセージを返すことができます。ラボのようにタイトル要件がない場面では、こちらを使うのが望ましい運用です。 +
+
+
+ +
+ 参考ソース: Set your working hours & location(Out + of office を含む)― + support.google.com/calendar/answer/7638168 +
+
+ +
+
+ 3 + +

Google Driveにフォルダを作成

+
+

+ 目的: + プロジェクト関連のドキュメント・画像・ファイルを整理するための保管場所を用意します。 +

+ +
    +
  1. drive.google.com を開く
  2. +
  3. 左上の「新規 (New)」ボタンをクリック
  4. +
  5. 「新しいフォルダ (New folder)」を選択
  6. +
  7. わかりやすいフォルダ名を入力する(例: Project Files)
  8. +
  9. 「作成 (Create)」をクリック
  10. +
+ +
+ +
+
ベストプラクティス
+
+ 命名規則(naming + convention)を統一し、短く・わかりやすい名前を付けましょう。色分け(color-code)でフォルダの種類を視覚的に識別できるようにするのも有効です。将来のサブフォルダによる階層化を見据えて、最初の粒度を決めすぎないようにします。 +
+
+
+ +
+ 参考ソース: Organize your files in Google Drive ― + support.google.com/drive/answer/2375091 +
+
+ +
+
+ 4 + +

週次ステータスミーティングの設定

+
+

+ 目的: + チームメンバー2名と定期的な情報共有の場を設け、コラボレーションを円滑にします。 +

+ +
    +
  1. Google Calendar で「作成 (Create)」→「予定 (Event)」をクリック
  2. +
  3. タイトルを入力する(例: Weekly Status Meeting)
  4. +
  5. 開催日時を設定する
  6. +
  7. + 「繰り返さない (Does not repeat)」の下矢印をクリックし、「毎週 + (Weekly)」を選択 +
  8. +
  9. + 「ゲストを追加 (Add guests)」欄に、ラボ情報パネルに記載された + Colleague 1 と + Colleague 2 のメールアドレスを入力 +
  10. +
  11. 「保存 (Save)」をクリックし、招待メールを送信する
  12. +
+ +
+ +
+ +
+
ベストプラクティス
+
+ タスクの要件「すべての繰り返しイベントが同じであること」を満たすには、最初の1件を作成する時点で正しい繰り返しパターン(毎週)とゲストを設定するのが確実です。後から一部のイベントだけを編集すると、「このイベントのみ更新」か「シリーズ全体を更新」かの選択を誤り、イベントごとに設定がバラつく原因になります。繰り返し予定には最大730回までという上限があることも覚えておきましょう。 +
+
+
+ +
+ 参考ソース: Create a recurring event ― + support.google.com/calendar/answer/37115 + / Invite people to your Calendar event ― + support.google.com/calendar/answer/37161 +
+
+ +
+
+ 5 + +

タスク管理スプレッドシートの作成と共有

+
+

+ 目的: + チームのタスク・担当者・優先度・進捗状況を一元管理できるシートを用意し、同僚2名と共同編集できるようにします。 +

+ +
    +
  1. + sheets.google.com を開き、空白のスプレッドシート (Blank spreadsheet) + を新規作成 +
  2. +
  3. + 左上のタイトル部分をクリックし、ファイル名を + Project Task Sheet に変更 +
  4. +
  5. 1行目に以下のヘッダーを入力する
  6. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
列ヘッダー名
ATasks
BOwner
CPriority
DStatus
EComments
+ +
    +
  1. 右上の「共有 (Share)」ボタンをクリック
  2. +
  3. + 「ユーザーやグループを追加」欄に、タスク4と同じ Colleague 1 と Colleague + 2 のメールアドレスを入力 +
  4. +
  5. + 権限(Viewer / Commenter / Editor)を選択する(共同作業のため Editor + を推奨) +
  6. +
  7. 「通知 (Notify people)」をオンのまま「送信 (Send)」をクリック
  8. +
+ +
+ +
+ +
+
ベストプラクティス
+
+ 採点システムは列見出しの文字列を厳密にチェックすることが多いため、表記ゆれ(大文字小文字・スペル)に注意して正確に入力しましょう。100人以上に共有する予定がある場合はWeb公開機能を検討するなど、共有規模に応じた方式選択も実務では重要です。 +
+
+
+ +
+ 参考ソース: Share files from Google Drive ― + support.google.com/docs/answer/2494822 + / Stop, limit, or change sharing ― + support.google.com/docs/answer/2494893 +
+
+ +
+
+ 6 + +

AppSheetアプリケーションの作成

+
+

+ 目的: ノーコード開発プラットフォーム AppSheet + にログインし、初期アプリ環境を作成できることを確認します(コーディングは不要です)。 +

+ +
    +
  1. AppSheet(appsheet.google.com もしくは提供されたリンク)を開く
  2. +
  3. ラボの学生アカウントで認証(サインイン)する
  4. +
  5. 「作成 (+ Create)」→「アプリ (App)」をクリック
  6. +
  7. + アプリの種類は問われないため、最も簡単な + Blank app(空白のアプリ) を選択する +
  8. +
  9. アプリ名を入力し、作成が完了して App Editor が開けば完了
  10. +
+ +
+ +
+
ベストプラクティス
+
+ Google Workspace 契約に含まれる AppSheet Core + エディションは追加コストなしで利用でき、開発・テスト段階では有料プランへの登録は不要です。タスクの要件は「認証してアプリ環境を作成すること」のみなので、複雑なデータ連携やロジック構築は行わずに時間を節約しましょう。 +
+
+
+ +
+ 参考ソース: Create apps: The Essentials(AppSheet + ヘルプ)― + support.google.com/appsheet/answer/11980957 + / Get started with AppSheet ― + support.google.com/appsheet/answer/11581986 +
+
+ +
+

全タスク チェックリスト

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#タスク主なアクション公式ドキュメント
1Gmail署名の作成署名を作成しデフォルトに設定 + support.google.com/mail/answer/8395 +
2OOO予定の追加終日予定「OOO Orientation」を3日間作成 + support.google.com/calendar/answer/7638168 +
3Driveフォルダ作成新規フォルダを作成 + support.google.com/drive/answer/2375091 +
4週次ミーティング設定毎週繰り返しの予定にColleague1・2を招待 + support.google.com/calendar/answer/37115 +
5タスクシート作成・共有5列ヘッダーのシートを作成しColleague1・2に共有 + support.google.com/docs/answer/2494822 +
6AppSheetアプリ作成サインインし空白アプリを作成 + support.google.com/appsheet/answer/11980957 +
+
+ +
+

よくあるミスと対処法

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状原因対処法
署名タスクが未達成と判定される署名を作成したがデフォルト設定にしていない「新規メール用の署名」欄に作成した署名を選択し直す
OOOタスクが未達成と判定される「Out of office」タイプで作成しタイトルが固定された + 通常の終日予定を作り直し、タイトルを「OOO Orientation」に手入力する +
週次ミーティングが未達成と判定される + 繰り返し設定を後から個別イベントに追加し、シリーズ全体に反映されていない + + 予定を削除し、最初から「毎週」の繰り返し設定とゲスト追加を同時に行って作り直す +
シートの共有が未達成と判定されるファイル名や列見出しの表記が指定と異なる + ファイル名を「Project Task + Sheet」に、列見出しを指定の5つに正確に修正する +
AppSheetタスクが未達成と判定されるラボアカウントとは別のGoogleアカウントでサインインしている一度サインアウトし、ラボが発行した学生アカウントで再認証する
+
+ +
+

まとめ

+

+ このチャレンジラボは、Gmail・Calendar・Drive・Sheets・AppSheet という Google + Workspace + の主要アプリを横断して、個人の作業環境構築からチームコラボレーションの設定までを一通り体験する内容になっています。特に以下の3点を意識すると、初学者でも迷わず完了できます。 +

+
    +
  1. + 設定は「作成」しただけで終わらせず、必ずデフォルト適用や保存まで確認する(署名タスクが典型例) +
  2. +
  3. + タスク文の指示(タイトル・列名など)は文字列レベルで正確に一致させる(採点は完全一致を見ることが多い) +
  4. +
  5. + 繰り返し設定やゲスト招待は、最初の1回で正しく設定する(後からの部分修正はシリーズ全体に反映されにくい) +
  6. +
+
+ +
+

参考文献一覧

+
+
+ +
+
Create a Gmail signature
+ +
+
+
+ +
+
+ Set your working hours & location (Out of office) +
+ +
+
+
+ +
+
Create a recurring event
+ +
+
+
+ +
+
+ Invite people to your Calendar event +
+ +
+
+
+ +
+
+ Organize your files in Google Drive +
+ +
+
+
+ +
+
Share files from Google Drive
+ +
+
+
+ +
+
Stop, limit, or change sharing
+ +
+
+
+ +
+
+ Create apps: The Essentials (AppSheet) +
+ +
+
+
+ +
+
Get started with AppSheet
+ +
+
+
+ +
+
ラボ課題本体(サインイン必須)
+ +
+
+
+
+ +
+ Google Workspace 入門チャレンジラボ 完全攻略ガイド ― + 各手順は執筆時点のGoogle公式ヘルプに基づいています。UIは更新される場合があるため、最新の公式ドキュメントも合わせてご確認ください。 +
+
+
+ + + + diff --git a/Google-workspace-challenge-lab-guide.md b/Google-workspace-challenge-lab-guide.md new file mode 100644 index 000000000..e69de29bb From f107c3d9bfff632b5693e70aebcfe1519b243721 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 20:07:46 +0900 Subject: [PATCH 006/123] docs(pcne): add CDN/DNS/IPAM, Load Balancing, and Network Ops monitoring guides --- Pcne-s4-cdn-dns-ipam.html | 3562 +++++++++++++++++++++ Pcne-s4-cdn-dns-ipam.md | 986 ++++++ S3-load-balancing-traffic-management.html | 2053 ++++++++++++ S3-load-balancing-traffic-management.md | 406 +++ S6-network-ops-monitoring.html | 3234 +++++++++++++++++++ S6-network-ops-monitoring.md | 846 +++++ 6 files changed, 11087 insertions(+) create mode 100644 Pcne-s4-cdn-dns-ipam.html create mode 100644 Pcne-s4-cdn-dns-ipam.md create mode 100644 S3-load-balancing-traffic-management.html create mode 100644 S3-load-balancing-traffic-management.md create mode 100644 S6-network-ops-monitoring.html create mode 100644 S6-network-ops-monitoring.md diff --git a/Pcne-s4-cdn-dns-ipam.html b/Pcne-s4-cdn-dns-ipam.html new file mode 100644 index 000000000..902972d49 --- /dev/null +++ b/Pcne-s4-cdn-dns-ipam.html @@ -0,0 +1,3562 @@ + + + + + + GCP PCNE試験 S4: CDN・DNS・IPアドレス管理 | 技術ガイド + + + + + + +
+ + +
+
+
+ Google Cloud Professional Cloud Network Engineer 試験対策 +
+

S4: CDN・DNS・IPアドレス管理

+

+ Cloud CDN・Cloud DNS・IPアドレス管理(IPAM)の3領域を、公式Exam + Guideのタスク定義に沿って中級者〜上級者向けに解説します。各項目の末尾には一次情報源(Google + Cloud公式ドキュメント)のURLを明記しています。 +

+
+ +

本ガイドについて

+

+ 本ガイドはGoogle Cloud Professional Cloud Network + Engineer(PCNE)認定試験の対策として、「CDN・DNS・IPアドレス管理」の3領域を中級者〜上級者向けに解説するドキュメントです。 +

+

+ 公式Exam + Guide(professional_cloud_network_engineer_exam_guide_english.pdf)を直接確認したうえで、本ガイドは以下の出題タスクに対応しています。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
出題領域対応する公式Exam Guideのタスク本ガイドでの扱い
Cloud CDNSection 3, Task 3.2「Configuring Cloud CDN」 + Part + 1で全項目を網羅(対応オリジン、外部バックエンド、キャッシュ無効化) +
Cloud DNSSection 3, Task 3.3「Configuring Cloud DNS」 + Part + 2で全項目を網羅(ゾーン管理、移行、ルーティングポリシー、DNSSEC、フォワーディング、split-horizon、クロスプロジェクトバインディング・ピアリング、GKE向けCloud + DNS) +
IPアドレス管理(IPAM) + Section 1, Task 1.2「Planning the IP address management (IPAM) + strategy」の内容を、Section 2/Section + 6の実装・運用視点から深掘り + + Part + 3で、サブネット設計・PUPI・IPv6・内部レンジによるIPAM自動化・BYOIP・Private + Service ConnectやServerless VPC AccessのIP割当・Cloud + NATのIPアドレス/ポート管理までを一気通貫で解説 +
+
+

+ ロードバランシング(Task 3.1)は別ガイドで既に扱っているため、本ガイドではCloud + CDN・Cloud DNS・IPAMの3本柱に集中します。 +

+

+ ASCII図解は使用せず、フローチャートはすべてMermaid、図解や表はすべてMarkdown記法で記載しています。各項目の末尾には根拠となる一次情報源(Google + Cloud公式ドキュメント)のURLを「出典」として明記しています。 +

+ +

Part 1: Cloud CDN

+

+ 1.1 Cloud CDNのアーキテクチャと動作原理 +

+

+ Cloud CDN(Content Delivery + Network)は、Googleのグローバルなエッジネットワークを使ってコンテンツをユーザーの近くから配信するサービスです。Cloud + CDNは単独では機能せず、必ずグローバル外部Application Load + Balancerまたはクラシック Application Load + Balancerと組み合わせて使用します。ロードバランサがフロントエンドのIPアドレスとポートを提供し、Cloud + CDNはそのバックエンド(Google + Cloudでは「オリジンサーバー」と呼ぶ)からのレスポンスをエッジでキャッシュします。 +

+

+ リクエストの処理はGoogle Front + End(GFE)で行われます。GFEはユーザーに最も近いGoogleネットワークのエッジに位置し、Cloud + CDNが有効なバックエンドサービス・バックエンドバケットへのリクエストであれば、まずキャッシュを検索します。 +

+
    +
  • + キャッシュヒット: + GFEがキャッシュキーに対応するレスポンスを保持していれば、そのままユーザーへ返却します(オリジンへの往復が発生しないため低レイテンシ)。 +
  • +
  • + キャッシュミス: + GFEはリクエストをロードバランサ経由でオリジンサーバーへ転送します。レスポンスがキャッシュ可能であれば、次回以降のためにキャッシュへ格納します(この処理を「cache + fill」と呼び、キャッシュからクライアントへ配信することを「cache + egress」と呼びます)。 +
  • +
  • + 部分ヒット(partial hit): + バイトレンジリクエストに対応したオリジンの場合、要求されたコンテンツの一部だけがキャッシュ済みで、残りをオリジンから取得するケースもあります。 +
  • +
+
+flowchart TD
+    A[クライアントからのリクエスト] --> B{最寄りのGFEが<br/>Cloud CDNキャッシュを検索}
+    B -->|キャッシュヒット| C[キャッシュから直接応答<br/>cache egress]
+    B -->|キャッシュミス| D[External Application<br/>Load Balancerへ転送]
+    D --> E[オリジンサーバーへ転送<br/>MIG・バケット・サーバーレスNEG等]
+    E --> F{レスポンスは<br/>キャッシュ可能か}
+    F -->|Yes| G[Cloud CDNキャッシュに格納<br/>cache fill]
+    F -->|No| H[クライアントへ直接返却]
+    G --> I[クライアントへ応答]
+

+ 「キャッシュヒット率」は、リクエストされたオブジェクトがキャッシュから配信された割合を示す重要指標です。ヒット率が低い場合は、後述するキャッシュキーの設定やTTL設定を見直します。 +

+

+ キャッシュされたコンテンツは、有効期限切れ(expiration)または削除(eviction)のいずれかが発生するまで配信対象となります。両者は独立した概念です。 +

+
    +
  • + Expiration(期限切れ): + レスポンスに設定されたTTL(max-age・s-maxage・Expires)に基づき、鮮度が切れているかどうかを判定します。 +
  • +
  • + Eviction(削除): + キャッシュ容量が満杯になった際、直近でアクセスされていないコンテンツから削除されます。期限切れかどうかに関わらず発生し、複数のGoogle + Cloudプロジェクトが同じGFE群のキャッシュ容量を共有するため、人気度は複数プロジェクトを横断して比較されます。30日間アクセスがなければ無条件に削除されます。 +
  • +
+
+

+ 出典: + Cloud CDN overview +

+
+

+ 1.2 対応オリジン(バックエンドタイプ) +

+

+ Cloud CDNは、External Application Load + Balancerが対応する以下のバックエンドタイプすべてに対して有効化できます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
バックエンドタイプ概要
インスタンスグループ(MIG) + Compute + EngineのマネージドインスタンスグループをVMベースのオリジンとして使用 +
ゾーンNEG(Network Endpoint Group)ゾーン単位でエンドポイントを指定するバックエンド
サーバーレスNEG + Cloud Run、Cloud Run functions(旧Cloud Functions)、App + Engineのいずれか1つ以上のサービスをオリジンとして使用 +
Internet NEG(外部バックエンド) + Google + Cloud外部(オンプレミスや他クラウド)のエンドポイントをオリジンとして使用 +
Cloud StorageバックエンドバケットCloud Storageバケットを静的コンテンツのオリジンとして使用
+
+
+flowchart LR
+    LB[External Application<br/>Load Balancer + Cloud CDN]
+    LB --> A[マネージドインスタンスグループ<br/>ゾーンNEG]
+    LB --> B[サーバーレスNEG<br/>Cloud Run / functions / App Engine]
+    LB --> C[Cloud Storage<br/>バックエンドバケット]
+    LB --> D[Internet NEG<br/>外部バックエンド]
+    D --> E[オンプレミス<br/>データセンター]
+    D --> F[他クラウド環境]
+

+ キャッシュヒット・ミスの挙動は、Compute Engine・バックエンドバケット・GKE + Ingress・GKE + Gatewayを含むすべての対応バックエンドタイプで一貫しています。GKEワークロードに対しては、GKE + Ingressコントローラのバックエンド設定、またはGKE + GatewayのGCPHTTPFilterカスタムリソースを使ってCloud + CDNを構成できます。 +

+
+

出典:

+ +
+ +

+ 1.3 外部バックエンド(Internet NEG)とハイブリッド/マルチクラウド構成 +

+

+ オンプレミスや他クラウドにホストされたコンテンツも、Cloud + CDNのグローバルエッジキャッシュ経由で配信できます。この際に使用するのが「Internet + NEG」(外部バックエンドを指定するAPIリソース)です。 +

+

Internet NEGのエンドポイントタイプは2種類あります。

+
+ + + + + + + + + + + + + + + + + + + + +
エンドポイントアドレスタイプ使いどころ
ホスト名 + 任意のポートINTERNET_FQDN_PORT + 外部バックエンドをパブリックDNSで解決可能なFQDNで指定する場合のベストプラクティス。IPアドレス変更の影響を受けにくい +
IPアドレス + 任意のポートINTERNET_IP_PORTパブリックにアクセス可能なIPアドレスを直接指定する場合
+
+

+ Internet + NEGの作成後、この2種類のエンドポイントタイプを相互に変更することはできません(新規作成が必要)。また、Cloud + CDNは1つのサービスにつき単一の外部バックエンドからのフェッチのみをサポートし、複数の外部バックエンド間でのロードバランシングや、外部バックエンドとGoogle + Cloudバックエンドとの間でのロードバランシングは行いません。 +

+
+flowchart LR
+    U[インターネット利用者] --> GFE[Cloud CDN<br/>Google Front End]
+    GFE --> LB[External Application<br/>Load Balancer]
+    LB -->|/images/*| GCS[Cloud Storage<br/>バケット]
+    LB -->|/video/*| NEG[Internet NEG]
+    NEG --> DC[オンプレミス<br/>データセンター / 他クラウド]
+

+ この構成は、段階的なクラウド移行やマルチクラウド戦略において、一部のコンテンツ(例: + 画像)はGoogle Cloudへ、他のコンテンツ(例: + 動画)はオンプレミスに残したまま、URLマップのパスルール(/images/*、/video/*など)で振り分けるユースケースに有効です。 +

+

+ 外部バックエンドが特定のHostヘッダーを期待する場合は、バックエンドサービス側でカスタムリクエストヘッダーとしてHostを明示的に設定する必要があります(未設定の場合、クライアントが接続時に使用したHostヘッダーがそのまま引き継がれます)。 +

+
+

+ 出典: + External backends specified by using internet NEGs +

+
+

+ 1.4 キャッシュモードとキャッシュ可否の判定 +

+

+ Cloud + CDNには3つのキャッシュモードがあり、オリジンからのキャッシュ指示(Cache-Controlヘッダー等)をどこまで尊重するかを制御します。 +

+
+ + + + + + + + + + + + + + + + + + + + + +
キャッシュモード動作
CACHE_ALL_STATIC(デフォルト) + 静的コンテンツタイプの成功レスポンスを自動キャッシュ。オリジンが有効なキャッシュ指示を送っていればそれも尊重する。gcloud + CLIやREST APIで作成したCloud CDN対応バックエンドのデフォルト動作 +
USE_ORIGIN_HEADERS + オリジンの成功レスポンスに有効なキャッシュ指示・キャッシュヘッダーが含まれていることを必須とする。指示がなければキャッシュせずそのままオリジンから転送 +
FORCE_CACHE_ALL + オリジンが設定したキャッシュ指示を無視し、成功レスポンスを無条件にキャッシュ。動的なHTML・APIレスポンス等、ユーザー固有のコンテンツを扱うバックエンドには非推奨。プライベートバケットアクセスを有効化したバケットでは、このモードが必須になる場合がある +
+
+
+flowchart TD
+    A[Cloud CDNキャッシュモードを選択] --> B["CACHE_ALL_STATIC<br/>(デフォルト)"]
+    A --> C[USE_ORIGIN_HEADERS]
+    A --> D[FORCE_CACHE_ALL]
+    B --> B1[静的コンテンツタイプを自動キャッシュ<br/>Cache-Controlがなくても可]
+    C --> C1[オリジンのCache-Control /<br/>Expiresヘッダーが必須]
+    D --> D1[オリジンの指示を無視し<br/>常に強制キャッシュ]
+    D --> D2[個人情報を含む動的<br/>コンテンツには非推奨]
+

+ CACHE_ALL_STATICモードでオリジンからのキャッシュ指示がない場合、以下のMIMEタイプが自動的にキャッシュ対象となります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
カテゴリMIMEタイプ
Webアセット + text/css、text/ecmascript、text/javascript、application/javascript +
フォントfont/*に一致するすべて
画像image/*に一致するすべて
動画video/*に一致するすべて
音声audio/*に一致するすべて
ドキュメント + application/pdf、application/postscript +
+
+

+ text/htmlやapplication/jsonは、動的(ユーザー固有)なレスポンスであることが多いため、デフォルトではキャッシュ対象になりません。これらをキャッシュしたい場合は、オリジン側で明示的なCache-Controlヘッダーを設定する必要があります。 +

+

キャッシュ可否のデフォルト値は以下のとおりです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
パラメータデフォルト値説明
Cache modeCACHE_ALL_STATIC一般的な静的コンテンツタイプを自動キャッシュ
Client TTL3600秒クライアントブラウザキャッシュのmax-age
Default TTL3600秒オリジンがヘッダーを返さない場合のキャッシュ期間
Include Hosttrueキャッシュキーにホストを含める
Include ProtocoltrueHTTP/HTTPSを別オブジェクトとしてキャッシュ
Include Query Stringtrueクエリ文字列全体をキャッシュキーに含める
Max TTL86400秒キャッシュに残る絶対最大時間(24時間)
Negative Cachingfalse404などのエラーレスポンスはデフォルトでキャッシュしない
Serve While Stale86400秒オリジンに到達不能な場合、最大24時間古いコンテンツを配信
+
+

+ 以下のいずれかに該当するレスポンスはキャッシュされません(FORCE_CACHE_ALLの一部を除く)。 +

+
    +
  • Set-Cookieヘッダーを持つ
  • +
  • 許可されたもの以外のVaryヘッダー値を持つ
  • +
  • + Cache-Control: no-storeまたはprivateディレクティブを持つ +
  • +
  • + リクエストにAuthorizationヘッダーがあり、レスポンス側でオーバーライドされていない +
  • +
  • + 最大サイズ(バイトレンジ対応オリジンで100 GiB、非対応オリジンで10 + MiB)を超える +
  • +
+
+

+ 出典: + Caching overview +

+
+

1.5 キャッシュキーのカスタマイズ

+

+ Cloud + CDNのキャッシュキーは、デフォルトでリクエストURIの全体(バックエンドサービスの場合)またはプロトコル・ホストを除いたURI(バックエンドバケットの場合)を使用します。キャッシュヒット率を最適化するため、以下の要素を個別に含める・除外することができます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
URIパートの調整効果
プロトコルを除外 + http:// と + https:// を同一キャッシュキーとして扱う +
ホストを除外 + 複数ホスト名(同一コンテンツを配信する複数ドメイン等)を同一キャッシュとして扱う +
クエリ文字列を除外クエリパラメータ違いを同一キャッシュとして扱う
クエリ文字列の含め・除外リスト + 特定パラメータのみ含める(include + list)、または特定パラメータのみ除外する(exclude + list)。両方を同時指定することはできない +
HTTPリクエストヘッダーの追加 + デバイスタイプ・言語などに応じてバリエーションをキャッシュ(Authorization、Cookie、Referer、User-Agent等の高カーディナリティなヘッダーは追加不可) +
名前付きCookieの追加(バックエンドサービスのみ) + 最大5つまでのCookie名を指定し、A/Bテストやカナリアリリースなどのバリエーションをキャッシュ +
+
+

+ クエリパラメータの順序はキャッシュキーの一致判定に影響しません(a=1&b=2とb=2&a=1は同一キーになります)。 +

+

+ Cloud + Storageバックエンドバケットに対しては、キャッシュバスティング(更新されたファイルを即座に反映させる仕組み)のためにクエリ文字列のinclude + listを使う手法が有効です。たとえば?version=VERSIONや?hash=HASHのようなパラメータをキャッシュキーに含めることで、明示的な無効化なしに新しいバージョンを配信できます。 +

+
+

+ 出典: + Caching overview +

+
+

+ 1.6 キャッシュの無効化(Invalidation) +

+

+ キャッシュ無効化(cache + purging)は、正規の期限切れ前に特定のコンテンツをキャッシュから強制的に削除する操作です。 +

+
    +
  • + パスパターン(例: + /picture*)またはホスト単位で無効化を指定できます。 +
  • +
  • + クエリ文字列違いだけで個別のオブジェクトを無効化することはできません(/images.php?image=fred.pngのようなURLを個別無効化する場合は/images.phpをパスパターンとして指定する必要があります)。 +
  • +
  • + キャッシュタグ(Cache-Tagレスポンスヘッダーで指定する「サロゲートキー」)を使うと、任意のメタデータ単位で一括無効化できます。1オブジェクトあたり最大50タグ、合計4 + KiBまで、1回のリクエストで最大10タグを論理OR条件として指定可能です。 +
  • +
  • + 無効化リクエストはレート制限されており、1分あたり最大500件、反映には約10秒かかります。 +
  • +
+
+flowchart LR
+    A[無効化リクエスト] --> B{一致条件}
+    B -->|パスパターン| C["/picture* のようなプレフィックス一致"]
+    B -->|ホスト指定| D[特定ホストのみ対象]
+    B -->|Cache-Tag| E["release-v1,frontend 等の<br/>論理OR条件"]
+    C --> F[該当キャッシュエントリを<br/>破棄し次回リクエストで<br/>オリジンから再取得]
+    D --> F
+    E --> F
+

+ ベストプラクティスとして、無効化は「例外的な状況」(法的理由や誤アップロードの是正など)のためのものであり、通常のデプロイフローの一部として多用すべきではありません。日常的なコンテンツ更新には、TTL設計やバージョン付きURL(file.css?v=2のような)を優先します。 +

+

+ Shared + VPCのクロスプロジェクトサービス参照を使う構成では、キャッシュ無効化はロードバランサのフロントエンド(転送規則・ターゲットプロキシ・URLマップ)を持つプロジェクト側で行う必要があり、サービスプロジェクト側の管理者はデフォルトでは無効化権限を持ちません。 +

+
+

+ 出典: + Cache invalidation overview +

+
+

+ 1.7 コンテンツのアクセス制御(署名付きURL・署名付きCookie) +

+

Cloud CDNは、コンテンツへのアクセスを制御する3つの手段を提供します。

+
+ + + + + + + + + + + + + + + + + + + + + +
手法用途
署名付きURL(Signed URL) + Googleアカウントの有無に関わらず、URLを保持する誰でも一定期間アクセス可能にする。単一または少数のリソースを保護する場合に適する +
署名付きCookie(Signed Cookie) + 特定のURLプレフィックス(例: + https://media.example.com/videos/)配下のすべてのリクエストを、1つのCookieで一定期間認可する。HLS/DASHのようにマニフェスト内の多数のURLを個別に署名するのが非現実的な場合に有効 +
プライベートオリジン認証 + Amazon S3や互換オブジェクトストアなど、Cloud + CDN外の第三者オリジンへの直接アクセスを防ぎ、Cloud + CDN経由の接続のみを許可する +
+
+

+ 署名付きURL・署名付きCookieはURLマップでは直接設定できず、バックエンドサービスまたはバックエンドバケット単位で設定します。署名の検証はCloud + CDN自体では行われないため、オリジン側のWebサーバーが署名を検証し、不正なリクエストにはHTTP + 403を返す実装が必須です。署名済みリクエストと未署名リクエストは別々にキャッシュされるため、キャッシュ可能なステータスコードを不正なリクエストに返すと、以降の正当なリクエストが誤って拒否される可能性がある点に注意します。 +

+
+

+ 出典: + Content access control +

+
+

1.8 Cloud CDNのベストプラクティス

+

+ Google公式のベストプラクティスドキュメントは、キャッシュヒット率・パフォーマンス・セキュリティ・キャッシュ運用・アップロード整合性・監視の6領域に整理されています。 +

+

キャッシュヒット率の最適化

+
    +
  • + オリジンのCache-Controlヘッダーに詳しくない場合は、CACHE_ALL_STATIC(デフォルト)のまま静的コンテンツを自動キャッシュさせるのが推奨。 +
  • +
  • ユーザー固有のコンテンツはCloud CDNでキャッシュしない。
  • +
  • + キャッシュキーからホストやプロトコルを除外し、不要なキャッシュの分散(シャーディング)を避ける。 +
  • +
  • + GKE + Gatewayを使う場合は、単一のグローバルキャッシュポリシーではなくGCPHTTPFilterでパスごとにcacheKeyPolicyとTTLをカスタマイズする(例: + /static/*はクエリ文字列を除外してヒット率を最大化、/api/*は特定クエリ文字列を含めて動的応答を正しく区別)。 +
  • +
+

パフォーマンスの最適化

+
    +
  • HTTP/3・QUICプロトコルサポートを有効化する。
  • +
  • + GKE + Gatewayでは、Podの再起動・一時的な到達不能に備えserveWhileStaleを24時間以上に設定し、requestCoalescingを有効化してオリジンへの同時キャッシュフィルリクエストを集約する。 +
  • +
  • + ネガティブキャッシングを活用し、エラーや리다이렉트のレスポンスも適切なTTLでキャッシュしてオリジン負荷を下げる。 +
  • +
  • + TLS Early + Data(0-RTT)を有効化し、再開接続のパフォーマンスを30〜50%改善する。 +
  • +
+

セキュリティの最適化

+
    +
  • + Cloud + Armorをキャッシュ済みコンテンツ(エッジセキュリティポリシー)とキャッシュミス・動的コンテンツ(バックエンドセキュリティポリシー)の両方に適用する。 +
  • +
  • + 署名付きURLを使う場合は、パブリック用とプライベート用でCloud + Storageバケットを分離する。 +
  • +
  • + GKE Gateway環境でIAPとCloud + CDNを併用する場合、両者は同一ルートで併存できないため、GCPBackendPolicyでIAPが有効なパスにGCPHTTPFilterのキャッシュ設定を併用しないよう構成する。 +
  • +
+

キャッシュの運用

+
    +
  • + コンテンツのカテゴリ(ほぼリアルタイム、頻繁に更新、稀に更新)ごとにTTLを設計する。 +
  • +
  • + バージョン付きURL(クエリパラメータ、ファイル名、パスへのバージョン番号付与)を、無効化に代わるデフォルトの更新手法として採用する。 +
  • +
  • 無効化は最終手段として最小限にとどめる。
  • +
+

アップロードの整合性

+
    +
  • + 既存ファイルの上書きより、バージョン番号や日付を付けた新規ファイル名でのアップロードを優先する。 +
  • +
  • + 既存ファイルを更新する場合は、一時的な名前でアップロードしてから目的の名前へリネームすることでアトミック性を担保する。 +
  • +
  • + バイトレンジキャッシュされたファイルを更新する場合は、無効化リクエストを併用する。 +
  • +
+

監視・ロギング

+
    +
  • すべてのCloud CDN対応バックエンドでロギングを有効化する。
  • +
  • Cloud CDN用のカスタムモニタリングダッシュボードを定期的に確認する。
  • +
+
+

+ 出典: + Content delivery best practices +

+
+
+

Part 2: Cloud DNS

+

+ 2.1 Cloud DNSの基本アーキテクチャとゾーンタイプ +

+

+ Cloud + DNSは低レイテンシかつ高可用なDNSゾーンサービスであり、インターネットに公開される「パブリックゾーン」と、指定したVPCネットワーク内からのみ参照可能な「プライベートゾーン」の両方に対して権威DNSサーバーとして機能します。 +

+

Cloud DNSが提供する主なゾーンの種類は以下のとおりです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ゾーンタイプ概要
パブリックゾーン + インターネットに公開される権威ゾーン。ゾーンApexにはNS/SOAレコードが存在し削除不可 +
プライベートゾーン指定したVPCネットワークからのみクエリ可能なゾーン
フォワーディングゾーン + プライベートゾーンの一種。レコードを持たず、代わりにフォワーディングターゲット(DNSサーバー)を指定する +
ピアリングゾーン(DNSピアリング) + 別のVPCネットワーク(DNSプロデューサーネットワーク)のDNS解決結果をそのまま参照するプライベートゾーン +
マネージドリバースルックアップゾーン + Compute + EngineのDNSデータに対してPTRルックアップを行う特殊なプライベートゾーン +
Service Directoryゾーン + Service + Directoryのネームスペースをバックエンドとするプライベートゾーン。レコードは直接追加できず、Service + Directory側の登録内容から自動的に導出される +
ゾーナルCloud DNSゾーン + GKEのクラスタスコープ選択時に作成される、単一のGoogle + Cloudゾーンにスコープされたプライベートゾーン +
+
+

+ Cloud + DNSはプロジェクトレベル・個別ゾーンレベルの両方でIAM権限を細かく設定できます。 +

+
+

+ 出典: + Cloud DNS overview +

+
+

+ 2.2 パブリックゾーンとプライベートゾーン、Split-Horizon DNS +

+

+ 同一のドメイン名でパブリックゾーンとプライベートゾーンの両方を作成すると、クエリの発信元に応じて異なる応答を返す「Split-Horizon + DNS」を実現できます。 +

+

+ 以下は、gcp.example.comというパブリックゾーンとプライベートゾーンを両方作成した場合の例です。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ゾーンレコードタイプTTLデータ
プライベートmyrecord1.gcp.example.comA510.128.1.35
パブリックmyrecord1.gcp.example.comA5104.198.6.142
パブリックmyrecord2.gcp.example.comA50104.198.7.145
+
+
+flowchart TD
+    Q1["クエリ: myrecord1.gcp.example.com"] --> S{発信元は?}
+    S -->|VPCネットワーク内のVM| P[Private Zone<br/>gcp.example.com]
+    S -->|インターネット| PUB[Public Zone<br/>gcp.example.com]
+    P --> R1["10.128.1.35 を応答"]
+    PUB --> R2["104.198.6.142 を応答"]
+

+ VPCネットワーク内のVMからmyrecord2.gcp.example.comを問い合わせた場合、プライベートゾーンに該当レコードが存在しないためNXDOMAINが返ります(同名のレコードがパブリックゾーンに存在していても影響しません)。これは、Google + Cloudの名前解決が「最長サフィックス一致」で該当ゾーンを特定し、そのゾーン内でレコードが見つからなければ他のゾーンにフォールバックしない、という仕様に基づきます。 +

+

+ 2つのゾーンが「オーバーラップ」する条件(片方のオリジンドメインがもう片方のサブドメインである、または完全一致する)についても整理しておきます。 +

+
    +
  • + パブリックゾーン同士のオーバーラップは、同一のCloud + DNSネームサーバー上では許可されません。 +
  • +
  • プライベートゾーンは任意のパブリックゾーンとオーバーラップ可能です。
  • +
  • + 異なるVPCネットワークにスコープされたプライベートゾーン同士は、オーバーラップしても構いません。 +
  • +
  • + 同一VPCネットワークに認可された2つのプライベートゾーンは、片方がもう片方のサブドメインでない限り、同一オリジンを持つことはできません。 +
  • +
+
+

+ 出典: + DNS zones overview +

+
+

+ 2.3 フォワーディングゾーンとピアリングゾーン +

+

+ フォワーディングゾーンは、レコードを保持せず、指定したフォワーディングターゲット(DNSサーバー)へクエリを転送するプライベートゾーンです。フォワーディングターゲットは4種類に分類されます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ターゲットタイプ定義想定される用途
Type 1 + 同一VPCネットワーク内のGoogle Cloud + VMまたは内部パススルーNetwork Load Balancerの内部IPアドレス + 同一VPC内のカスタムDNSサーバー
Type 2 + Cloud VPNまたはCloud + Interconnectで接続されたオンプレミスシステムのIPアドレス + オンプレミスDNSサーバーへの転送
Type 3インターネットからアクセス可能な外部IPアドレスパブリックなDNSサーバーや別VPCのVMの外部IP
Type 4標準・非標準の名前解決順序でIPv4/IPv6両方を解決できるFQDNIPアドレスが変動するターゲットの指定
+
+

+ ルーティング方式は「標準ルーティング」(RFC + 1918アドレスは認可済みVPC経由、それ以外はインターネット経由)と「プライベートルーティング」(RFC + 1918かどうかに関わらず常に認可済みVPC経由。Type + 1/2のみサポート)の2種類があります。 +

+

+ 重要な制約として、Cloud + DNSはフォワーディングターゲットへの推移的ルーティング(transitive + routing)をサポートしません。オンプレミスに接続されたvpc-net-aとピアリングされたvpc-net-bからvpc-net-a経由でオンプレミスのフォワーディングターゲットへ到達しようとしても失敗します。この場合は、vpc-net-bからvpc-net-aをターゲットとするピアリングゾーンを作成することで解決します。 +

+

+ ピアリングゾーン(DNS + Peering)は、別のVPCネットワーク(DNSプロデューサーネットワーク)で解決される内容を、認可されたVPCネットワーク(DNSコンシューマーネットワーク)からそのまま参照できるようにするプライベートゾーンです。DNSピアリングは一方向の関係であり、VPCネットワークピアリングとは全く別の仕組みです(VPCネットワークピアリングを設定しても、DNS情報は自動的には共有されません)。推移的なDNSピアリングは1ホップまでサポートされます(最大3つのVPCネットワークを、中間の1つがホップとなる形でチェーンできます)。 +

+
+flowchart LR
+    subgraph VPCB["消費側 VPC: vpc-net-b"]
+        VMB[VM]
+    end
+    subgraph VPCA["転送側 VPC: vpc-net-a"]
+        PZ["Peering Zone<br/>ターゲット: vpc-net-a"]
+        FZ["Forwarding Zone<br/>ターゲット: オンプレミスDNS"]
+    end
+    ONPREM[オンプレミス<br/>DNSサーバー]
+    VMB -->|1: DNSクエリ| PZ
+    PZ -->|2: vpc-net-aの解決順序で転送| FZ
+    FZ -->|3: 転送| ONPREM
+
+

+ 出典: + DNS zones overview +

+
+

+ 2.4 DNSルーティングポリシーとヘルスチェック +

+

+ Cloud + DNSは、パブリック・プライベート両方のゾーンのリソースレコードセットに対して3種類のルーティングポリシーを設定でき、トラフィックを特定の条件に応じて誘導できます。フォワーディングゾーン・DNSピアリングゾーン・マネージドリバースルックアップゾーン・Service + Directoryゾーンにはルーティングポリシーを設定できません。 +

+
+ + + + + + + + + + + + + + + + + + + + + +
ポリシー概要
Weighted Round Robin(WRR) + DNS名に対する各レコードセットに異なる重みを割り当て、その比率でトラフィックを分散する。Active-ActiveやActive-Passive構成、本番/実験バージョン間のトラフィック分割などに使用。Geolocationポリシーとの併用は不可 +
Geolocation + 送信元の地理的位置(Googleリージョン)を特定のDNSターゲットにマッピングする。送信元が完全一致しない場合は最も近いポリシーが適用される +
Failover + アクティブ/バックアップ構成による高可用性を実現する。アクティブ集合がすべて不健全になった場合にバックアップ集合へ切り替える +
+
+

+ Geolocationポリシーは、Geofence(地理フェンス)を併用することで、そのリージョン内のすべてのエンドポイントが不健全であっても強制的にそのリージョンへトラフィックを固定できます(Geofence無効時は自動的に次に近いリージョンへフェイルオーバーします)。 +

+

+ Failoverポリシーでは、バックアップ集合への切り替え時に「trickle」(徐々にトラフィックを流す)機能を使い、0〜1の割合でバックアップへのトラフィック比率を段階的に検証できます(典型値は0.1)。 +

+
+flowchart TD
+    A[DNSルーティングポリシーを選択] --> B["WRR<br/>(Weighted Round Robin)"]
+    A --> C[Geolocation]
+    A --> D[Failover]
+    B --> B1[重み比率でトラフィック分散<br/>ヘルスチェック対応]
+    C --> C1[送信元リージョンに<br/>最も近いターゲットへ]
+    C --> C2{Geofence有効か}
+    C2 -->|Yes| C3["不健全でもそのリージョンに固定<br/>(全IPを応答)"]
+    C2 -->|No| C4[次に近いリージョンへ<br/>自動フェイルオーバー]
+    D --> D1[Active集合を常に応答]
+    D --> D2{Active集合が<br/>全て不健全か}
+    D2 -->|Yes| D3["Backup集合へ切替<br/>(trickle比率設定可)"]
+

+ ヘルスチェックは、内部Application Load + Balancer(リージョン/クロスリージョン)、内部パススルーNetwork Load + Balancer、内部プロキシNetwork Load + Balancer(プレビュー)、そして外部エンドポイントに対応します。内部パススルーNetwork + Load Balancerの場合、Cloud + DNSはバックエンドインスタンス単位のヘルス情報を確認し、デフォルトで20%のインスタンスが健全であればエンドポイント全体を健全と判定します。外部エンドポイントに対するヘルスチェックは、3つのGoogle + Cloudソースリージョンからそれぞれ3つのプローバー(合計9プローバー)で実施され、TCP・HTTP・HTTPSプロトコルに対応します(SSL・HTTP/2・gRPCは非対応)。 +

+

+ DNSSECを有効化したマネージドゾーンでヘルスチェックを併用する場合、各ポリシーアイテム内で使用できるIPアドレスは1つのみに制限されます。 +

+

+ ルーティングポリシーがサポートするレコードタイプはA・AAAA・CNAME・MX・SRV・TXTですが、ヘルスチェックが有効なのはA・AAAAレコードのみです。 +

+
+

+ 出典: + DNS routing policies and health checks +

+
+

2.5 DNSSEC(DNS Security Extensions)

+

+ DNSSECは、DNSルックアップへの応答を認証する仕組みであり、プライバシー保護は提供しませんが、DNS応答の改ざん・ポイズニング攻撃を防止します。DNSSECを完全に機能させるには、以下の3か所すべてで有効化・設定が必要です。 +

+
    +
  1. + DNSゾーン: Cloud + DNSでDNSSECを有効化すると、DNSKEYレコードの作成・ローテーション、およびRRSIGレコードによるゾーンデータの署名が自動的に管理されます。 +
  2. +
  3. + トップレベルドメイン(TLD)レジストリ: + ドメインレジストラでDNSSECを有効化し、ゾーン内のDNSKEYレコードを認証するDSレコードをレジストリに登録する必要があります。レジストラ・レジストリの両方がDNSSECに対応していない場合、Cloud + DNS側でDNSSECを有効化しても効果がありません。 +
  4. +
  5. + DNSリゾルバ: + 完全な保護のためには、DNSSEC署名済みドメインの署名を検証するリゾルバを使用する必要があります(Google + Public DNSなどの検証対応パブリックリゾルバを利用可能)。 +
  6. +
+

+ Cloud + DNSは、DNSSECが有効化された状態のゾーンを、信頼チェーンを切断することなく他のDNSオペレータとの間で移行(マイグレーション)することもサポートしています。 +

+
+

+ 出典: + DNS Security Extensions (DNSSEC) overview +

+
+

+ 2.6 DNSサーバーポリシー(Inbound / Outbound) +

+

+ DNSサーバーポリシーは、VPCネットワーク単位でDNS解決に使用するDNSサーバーを制御する仕組みで、インバウンド・アウトバウンドのいずれか、または両方を同時に構成できます。 +

+

+ インバウンドサーバーポリシーは、VPCネットワークのCloud + DNS名前解決サービスを、Cloud VPNトンネル・Cloud Interconnect + VLANアタッチメント・Router + Applianceで接続されたオンプレミスネットワークからも利用可能にします。有効化すると、適用対象VPCネットワーク内のすべてのサブネット(プロキシ専用サブネットやPrivate + NAT用サブネットを除く)ごとに、プライマリIPv4範囲から内部IPv4アドレスの「インバウンドサーバーポリシーエントリポイント」が作成されます。 +

+

+ インバウンドサーバーポリシーエントリポイントはVPCネットワークピアリングやNetwork + Connectivity + Center(NCC)の境界を越えて到達できないため、必ずハイブリッド接続を受け取るVPCネットワーク自体にローカルポリシーとしてデプロイする必要があります(ピアリングされた別ネットワークのレコードを解決したい場合は、そちらにDNSピアリングゾーンを作成します)。 +

+

+ アウトバウンドサーバーポリシーは、代替ネームサーバーのリストを指定してVPCネットワークの名前解決順序を変更する仕組みです。代替ネームサーバーが1つでも設定されると、GKEクラスタスコープのレスポンスポリシーやプライベートゾーンにマッチしない限り、すべてのクエリが代替ネームサーバーへ送信されます。多くのCloud + DNS機能(プライベートゾーン、ピアリング等)の解決が無効化される点に注意が必要です。 +

+
+flowchart LR
+    subgraph ONPREM[オンプレミス]
+        OS[オンプレミスDNSサーバー]
+    end
+    subgraph VPC[VPCネットワーク]
+        IN["Inbound Server Policy<br/>Entry Point<br/>(サブネットごとの内部IP)"]
+        RES["VPCネットワーク内の<br/>プライベートゾーン等を解決"]
+        OUT["Outbound Server Policy<br/>(代替ネームサーバー指定)"]
+        MD["VMメタデータサーバー<br/>169.254.169.254"]
+    end
+    OS -->|1: 問い合わせ| IN
+    IN -->|2: 解決| RES
+    MD -->|3: 通常クエリ| OUT
+    OUT -->|4: 代替ネームサーバーへ転送| OS
+

+ 代替ネームサーバーの区分(Type + 1〜3)はフォワーディングターゲットと同様に、ルーティング方式・ネットワーク要件が定義されています。とくにType + 1・Type 2の場合、Cloud + DNSは35.199.192.0/19を送信元としてクエリを送るため、オンプレミス側・代替ネームサーバー側の双方で、このレンジからのTCP/UDPポート53を許可するファイアウォールルールが必要です。 +

+
+

+ 出典: + DNS server policies +

+
+

+ 2.7 クロスプロジェクトバインディング vs DNSピアリング +

+

+ Shared + VPC環境では、DNSネームスペースの所有権をどのプロジェクトに置くかという設計判断が発生します。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
観点DNSピアリングのみの構成クロスプロジェクトバインディング
ゾーンの作成・管理 + 各サービスプロジェクトが独自のVPCネットワークを持ち、そこにゾーンを作成してホストプロジェクトとピアリングする + + サービスプロジェクトが直接ゾーンを作成・管理し、Shared + VPCネットワークにバインドする +
プレースホルダーVPCの要否 + 各サービスプロジェクトに個別のVPCネットワークが必要になりがち + 不要(プレースホルダーVPCを用意する必要がない)
ホストプロジェクト管理者の負担サービスプロジェクトの管理も担うことが多い + サービスプロジェクトの管理はサービスプロジェクト側に委譲できる +
IAMの適用範囲プロジェクトレベルで適用同様にプロジェクトレベルで適用される
推移的な解決のホップ制限ピアリングは1ホップまで + すべてのDNSゾーンがShared + VPCネットワークに直接紐づくため、ホップ制限がなくHub&Spoke設計が可能 +
Any-to-Any解決個別設定が必要になりがち + Shared VPCネットワーク内のどのVMからも紐づくゾーンを解決可能 +
+
+
+flowchart TD
+    subgraph A[DNSピアリングのみの構成]
+        H1[ホストプロジェクト<br/>VPCネットワーク]
+        S1[サービスプロジェクト1<br/>個別VPC + Peering Zone]
+        S2[サービスプロジェクト2<br/>個別VPC + Peering Zone]
+    end
+    subgraph B[クロスプロジェクトバインディング構成]
+        H2[ホストプロジェクト<br/>Shared VPCネットワーク]
+        Z1[サービスプロジェクト1が<br/>作成・保有するゾーン]
+        Z2[サービスプロジェクト2が<br/>作成・保有するゾーン]
+        H2 -.バインド.-> Z1
+        H2 -.バインド.-> Z2
+    end
+

+ クロスプロジェクトバインディングは、Shared + VPCのサービスプロジェクトごとにDNSネームスペースの所有権を分離したい場合(部門やビジネスユニットが異なる組織構造など)に特に有効です。 +

+
+

+ 出典: + DNS zones overview +

+
+

2.8 GKEにおけるCloud DNS

+

+ GKEクラスタのDNSは、Kubernetesの標準的なService + Discoveryの延長として提供されます。デフォルトのDNSプロバイダはkube-dnsですが、Cloud + DNSをGKEのDNSプロバイダとして選択することもできます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目kube-dnsCloud DNS for GKE
実装形態クラスタ内で稼働するPod(自前でスケーリング・監視が必要)Googleがフルマネージドで提供する権威DNS
監視・スケーリングの手間必要不要(マネージドサービス)
Cloud LoggingとしてのDNS監視統合個別対応が必要Cloud Loggingとネイティブに統合
対応レコードA/AAAA/SRV/PTR等(PTRはレスポンスポリシールールで実装)同様にフルサポート
DNSスコープクラスタスコープのみ(*.cluster.local) + GKEクラスタスコープ、またはVPCスコープ(クラスタ内Serviceの名前をVPC全体から解決可能)を選択可能 +
+
+

+ GKEクラスタでCloud + DNSを使う場合でも、クラスタ外部からServiceを名前解決できるようにするには、引き続きLoad + Balancerでの公開とDNSインフラへの登録が必要です(Cloud + DNSがServiceのClusterIP・ヘッドレス・ExternalNameを自動登録するのは、あくまでクラスタ内部の解決のためです)。 +

+

+ NodeLocal DNSCacheは、各ノード上でDaemonSetとして動作するDNSキャッシュアドオンで、kube-dns・Cloud + DNSいずれの構成でも併用できます。GKE + Autopilotクラスタではデフォルトで有効(無効化不可)、GKE + Standardクラスタの新しいバージョンではデフォルトで有効(無効化可能)です。PodのDNSリクエストはまずノードローカルのキャッシュに向かい、キャッシュミス時にkube-dnsまたはCloud + DNSへフォワードされます。 +

+

+ 外部からGKEのService・IngressのDNSレコードを自動的に管理したい場合は、OSSのexternal-dnsコントローラを利用するのが一般的なパターンです。external-dnsはクラスタ内のService・Ingressリソースを監視し、対応するレコードをCloud + DNSへ自動的に反映します。 +

+
+flowchart TD
+    Pod[Pod] --> MD["ノードのメタデータサーバー<br/>169.254.169.254"]
+    MD --> NLD{NodeLocal DNSCache<br/>有効か}
+    NLD -->|Yes: ローカルキャッシュ| Cache[ノードローカル<br/>DNSキャッシュ]
+    NLD -->|No| Provider
+    Cache -->|キャッシュミス時| Provider{DNSプロバイダ}
+    Provider -->|kube-dns| KD["kube-dnsポッド<br/>(cluster.local)"]
+    Provider -->|Cloud DNS for GKE| CD[Cloud DNS<br/>コントローラ管理ゾーン]
+    Ext[external-dns<br/>コントローラ] -.Ingress/Service監視.-> CD
+
+

出典:

+ +
+ +

+ 2.9 他プロバイダからCloud DNSへの移行 +

+

+ 既存のDNSプロバイダからCloud + DNSへドメインを移行する場合の標準的な手順は以下のとおりです。 +

+
+flowchart TD
+    A[マネージドゾーンの作成<br/>gcloud dns managed-zones create] --> B[既存プロバイダから<br/>ゾーンファイルをエクスポート<br/>BIND形式 or YAML形式]
+    B --> C["gcloud dns record-sets import<br/>でレコードをインポート"]
+    C --> D[digコマンドで<br/>Cloud DNSネームサーバーへの<br/>反映を確認]
+    D --> E[レジストラの<br/>ネームサーバー設定を変更]
+    E --> F["dig +short NS<br/>で伝播を最終確認"]
+

+ インポート時の注意点として、インポートファイルにゾーンApexのNS・SOAレコードが含まれている場合、Cloud + DNSが自動生成するNS・SOAレコードと競合します。既存のCloud + DNSレコードを優先する(推奨)場合はインポートファイルからNS・SOAレコードを削除し、権威DNSが他プロバイダとの分割構成(マルチプロバイダ構成)でCloud + DNS以外のSOAを使いたい場合は--delete-all-existingフラグを使用します。 +

+

+ また、一部のDNS実装は末尾のピリオドなしでBINDゾーンファイルをエクスポートすることがあります。Cloud + DNSはRFC標準に従い、末尾ピリオドのないドメイン名をゾーンの相対名として解釈するため、インポート前に確認が必要です。 +

+

+ Google + Cloudは、複数のDNSプロバイダを併用してDNS基盤の可用性・冗長性を高める「マルチプロバイダDNS」構成も、OSSのoctoDNSをベースに公式にサポートしています。この構成ではCloud + DNSをActive-Active(推奨)またはActive-Passiveの一方として使い、レジストラ側のNSレコードに複数プロバイダのネームサーバーを含めます。 +

+
+

出典:

+ +
+ +

+ 2.10 ハイブリッドDNSのリファレンスアーキテクチャとベストプラクティス +

+

+ オンプレミスとGoogle + Cloudが混在するハイブリッド環境では、以下の3つのDNS解決方式のいずれかを選択できますが、Googleは「2つの権威DNSシステムを使うハイブリッドアプローチ」を推奨しています。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
アプローチ概要主なトレードオフ
ハイブリッド(2つの権威DNS、推奨) + Cloud DNSがGoogle + Cloud側を、既存のオンプレミスDNSサーバーがオンプレミス側を、それぞれ権威的に解決する + + 双方向フォワーディングの設定が必要になるが、レイテンシと運用の分離のバランスが良い +
オンプレミスに解決を集約 + オンプレミスDNSサーバーを唯一の権威とし、Google + Cloudからは代替ネームサーバーで全クエリを転送 + + 既存ツール・拒否リストを流用できるが、Google + Cloudからのクエリレイテンシが増加し、オートスケールとの相性が悪化しうる +
Cloud DNSに解決を集約 + Cloud + DNSを唯一の権威とし、インバウンドフォワーディングでオンプレミスからの問い合わせに対応 + + オンプレミス側の高可用DNSサーバー維持が不要になるが、オンプレミスからのクエリレイテンシが増加する +
+
+

+ 命名規則としては、オンプレミスとGoogle Cloudで別々のサブドメイン(例: + corp.example.comとgcp.example.com)を使う構成が推奨パターンです。同一ドメインを両者で共有する構成は、単一の権威DNSシステムでしか運用できず、ハイブリッド環境の管理を複雑にするため避けるべきとされています。 +

+

+ 代表的なリファレンスアーキテクチャの1つとして、ハブ&スポークVPC構成(VPCネットワークピアリングでハブとスポークを接続し、ハブがオンプレミスとの接続を集約する構成)を見てみます。 +

+
+flowchart TD
+    ONPREM["オンプレミス<br/>corp.example.com"] <-->|Interconnect/VPN| HUB
+    subgraph HUB[ハブVPCネットワーク]
+        HFWD["Forwarding Zone<br/>corp.example.com"]
+        HPOLICY[Inbound Server Policy]
+    end
+    HUB -->|DNS Peering| SPOKE1["スポークVPC1<br/>projectX.gcp.example.com"]
+    HUB -->|DNS Peering| SPOKE2["スポークVPC2<br/>projectY.gcp.example.com"]
+    SPOKE1 -->|DNS Peering| HUB
+    SPOKE2 -->|DNS Peering| HUB
+

この構成のポイントは次のとおりです。

+
    +
  1. + 各スポークVPCが自身のプライベートゾーン(例: + projectX.gcp.example.com)を保有する。 +
  2. +
  3. ハブVPCのホストプロジェクトでインバウンドサーバーポリシーを有効化する。
  4. +
  5. + ハブVPC内にcorp.example.com用のフォワーディングゾーンを作成し、オンプレミスDNSサーバーへアウトバウンド転送する。 +
  6. +
  7. + ハブVPCから各スポークVPCへ、それぞれのprojectX.gcp.example.comをターゲットとするDNSピアリングゾーンを作成する。 +
  8. +
  9. + 各スポークVPCからハブVPCへ、example.com(オンプレミス側)をターゲットとするDNSピアリングゾーンを作成する。 +
  10. +
  11. + オンプレミスDNS側でgcp.example.comをハブVPCのインバウンドフォワーダーIPアドレスへ転送するよう設定する。 +
  12. +
+

ベストプラクティスとして特に押さえておくべき点は以下のとおりです。

+
    +
  • + 複数のVPCネットワークが同じオンプレミスDNSサーバーへアウトバウンド転送する構成は、DNSピアリングを使わずに個別設定すると失敗します(すべてのクエリの送信元が35.199.192.0/19という共通レンジになるため、応答を正しくルーティングできません)。1つのVPCネットワークにアウトバウンド転送を集約し、他のVPCネットワークはそこへDNSピアリングする設計が推奨されます。 +
  • +
  • + VPCネットワークピアリングとDNSピアリングは別物であり、片方を設定しても他方は自動的には有効になりません。 +
  • +
  • + 自動生成される.internalゾーン(VMの内部DNS名)をオンプレミスから解決したい場合は、それらをハブプロジェクトにピアリングして集約するパターンが有効です。 +
  • +
  • + オンプレミス・Google + Cloud双方のファイアウォールで、35.199.192.0/19からのDNSトラフィック(TCP/UDPポート53)を許可する。 +
  • +
+
+

+ 出典: + Best practices for Cloud DNS +

+
+
+

Part 3: IPアドレス管理(IPAM)

+

3.1 IPアドレスの分類体系

+

+ Google CloudのIPアドレスは、複数の軸で分類されます。まずは全体像を整理します。 +

+
+flowchart TD
+    IP[Google CloudのIPアドレス] --> INT["内部IPアドレス<br/>(Internal)"]
+    IP --> EXT["外部IPアドレス<br/>(External)"]
+    INT --> PRIV["プライベートIP<br/>(RFC1918等)"]
+    INT --> PUPI["プライベート利用の<br/>パブリックIP (PUPI)"]
+    EXT --> PUB[パブリックルーティング可能]
+    INT --> EPH1[エフェメラル]
+    INT --> STAT1["静的 (予約済み)"]
+    EXT --> EPH2[エフェメラル]
+    EXT --> STAT2["静的 (予約済み)"]
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分類軸区分説明
到達性内部(Internal) + インターネットから到達不可。VPCネットワーク・ピアリング済みネットワーク・オンプレミス接続内でのみ有効 +
到達性外部(External) + インターネットに公開されるパブリックルーティング可能なアドレス +
ルーティング可否プライベート + インターネット上でルーティングされないアドレス空間(内部アドレスとしてのみ使用可能) +
ルーティング可否パブリック + インターネットルーティング可能なアドレス空間。外部IPは常にパブリックIPだが、サブネットのプライマリ/セカンダリ範囲としてパブリックIPを内部的に使う場合は「PUPI(プライベート利用のパブリックIP)」と呼ぶ +
スコープリージョナル特定リージョンのリソースに紐づく
スコープグローバル + PSC Google APIエンドポイントやPrivate Services + Accessの割当レンジなど、リージョンに依存しない +
ライフサイクルエフェメラル + リソースのライフサイクルに紐づき、リソース削除・停止時に解放される +
ライフサイクル静的(予約済み)明示的に解放するまでプロジェクトに割り当てられ続ける
+
+

+ Cloud NATの自動IPアドレス割当は、静的アドレスとして表示されますが、Cloud + NATゲートウェイの削除や手動アドレスへの切り替え時には削除される点、HA + VPNのインターフェースには静的IPを手動指定できず、ゲートウェイ作成時に自動生成される2つの外部IPが削除まで割り当てられ続ける点など、いくつかの例外があります。 +

+
+

+ 出典: + IP addresses +

+
+

+ 3.2 サブネットのIPv4アドレス範囲設計 +

+

+ サブネットのIPv4範囲設計は、IPAM戦略の中核です。まず、有効な内部IPv4範囲を整理します。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
カテゴリ範囲説明
プライベートIPv4アドレス + 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 + RFC 1918
プライベートIPv4アドレス100.64.0.0/10RFC 6598(共有アドレス空間)
プライベートIPv4アドレス192.0.0.0/24RFC 6890(IETFプロトコル割当)
プライベートIPv4アドレス + 192.0.2.0/24、198.51.100.0/24、203.0.113.0/24 + RFC 5737(ドキュメント用)
プライベートIPv4アドレス192.88.99.0/24RFC 7526(IPv6toIPv4リレー、非推奨)
プライベートIPv4アドレス198.18.0.0/15RFC 2544(ベンチマークテスト)
プライベートIPv4アドレス240.0.0.0/4Class E(将来利用のための予約)
プライベート利用のパブリックIPv4アドレス(PUPI)上記以外の任意のパブリックIPv4(禁止範囲を除く) + 通常はインターネットルーティング可能だが、VPCネットワーク内で私的に使用。Googleはこれらをインターネットへ広告せず、インターネットからのトラフィックもルーティングしない +
+
+

サブネット範囲には以下のような制約もあります。

+
    +
  • 最小のプライマリ・セカンダリ範囲サイズは8アドレス(/29)。
  • +
  • + 使用できる最大の範囲は/4ですが、多くの制約により実質的には/8程度に収めることが推奨されます。 +
  • +
  • + サブネット範囲は複数のRFC範囲にまたがることはできません(例: + 192.0.0.0/8は192.168.0.0/16と192.0.0.0/24の両方を含むため無効)。 +
  • +
  • + サブネット範囲は「制限範囲」と一致・より狭い・より広いいずれの形でも重ならないようにする必要があります(例: + 169.0.0.0/8はリンクローカル範囲169.254.0.0/16と重複するため無効)。 +
  • +
  • + Auto + ModeのVPCネットワークが使用する10.128.0.0/9ブロックの一部は、カスタムサブネットの範囲として使わないことが推奨されます(この範囲を使うと、Auto + ModeネットワークとのVPCネットワークピアリングやCloud + VPN接続ができなくなります)。 +
  • +
  • + ゲスト + OS内で172.17.0.0/16(Dockerのデフォルトブリッジネットワーク等)を使うソフトウェアに依存している場合、このレンジをサブネット範囲として使わないようにします。 +
  • +
+

+ サブネットのプライマリIPv4範囲の中で、最初の2つと最後の2つのアドレスは予約されており使用できません(セカンダリ範囲はすべて使用可能です)。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
予約アドレス説明
ネットワークアドレスプライマリ範囲の最初のアドレス
デフォルトゲートウェイアドレスプライマリ範囲の2番目のアドレス
Second-to-lastアドレスプライマリ範囲の最後から2番目(将来利用のための予約)
ブロードキャストアドレスプライマリ範囲の最後のアドレス
+
+
+flowchart LR
+    A["サブネット (例: 10.10.0.0/20)"] --> B["プライマリ範囲<br/>VM/内部LB/PGA/Cloud DNS<br/>インバウンドエントリポイント等"]
+    A --> C["セカンダリ範囲1<br/>(GKE Podレンジ等)"]
+    A --> D["セカンダリ範囲2<br/>(GKE Serviceレンジ等)"]
+    B --> E[エイリアスIP範囲としても<br/>利用可能]
+    C --> E
+

+ サブネットには「目的(purpose)」があり、通常のVM用サブネット(PRIVATE)以外にも、Private + Service + Connect公開用(PRIVATE_SERVICE_CONNECT)、プロキシ専用(GLOBAL_MANAGED_PROXY/REGIONAL_MANAGED_PROXY)、Private + NAT専用(PRIVATE_NAT)、Shared VPCサービスをPrivate Service + Connectへ移行するためのPEER_MIGRATIONなど複数の種類があり、多くの場合作成後に目的を変更することはできません。 +

+
+

+ 出典: + Subnets +

+
+

3.3 IPv6サポート

+

+ VPCネットワークのサブネットは、IPv4専用・デュアルスタック・IPv6専用の3種類のスタックタイプをサポートします。IPv6範囲を持つサブネットはカスタムモードのVPCネットワークでのみサポートされ、Auto + Modeネットワークやレガシーネットワークでは非対応です。 +

+

+ IPv6アドレスは、以下のようにVPCネットワーク→サブネット→VMインターフェースの階層でCIDRが割り当てられます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
リソース範囲サイズ説明
VPCネットワーク/48 + 内部ULA範囲を有効化した際に、fd20::/20内から割り当てられるユニークローカルアドレス範囲 +
サブネット/64 + 内部ならVPCの/48範囲から、外部ならGoogleが提供するリージョナル外部IPv6アドレスから割り当て +
VMインターフェース/96 + サブネットの/64範囲から割り当て。外部IPv6の場合、サブネットの/64の前半/65がVMインターフェース用、後半/65がCloud + Load Balancing用に予約されている +
+
+
+flowchart TD
+    VPC["VPCネットワーク<br/>/48 ULA範囲 (内部の場合)"] --> SUB["サブネット<br/>/64 範囲"]
+    SUB --> VM1["VMインターフェース<br/>/96 (前半/65)"]
+    SUB --> LB1["Cloud Load Balancing用<br/>/96 (外部の場合、後半/65)"]
+    EXT["Googleのリージョナル<br/>外部IPv6アドレス"] --> SUBE["サブネット<br/>/64 外部GUA範囲"]
+    BYOIP["BYOIP<br/>IPv6 sub-prefix"] -.代替ソース.-> SUB
+    BYOIP -.代替ソース.-> SUBE
+

+ 内部IPv6アドレスはUnique Local Address(ULA、RFC + 4193)であり、インターネットに公開されずVM間通信のみに使用されます。外部IPv6アドレスはGlobal + Unicast Address(GUA)であり、Premium + Tierでのみ利用可能です。VPCネットワークの/48ULA範囲は、Google + Cloud全体で一意である必要があり(VPCネットワークピアリング時のIPv6アドレス重複を防ぐため)、自動割当か任意の/48範囲の指定かを選択できます。一度割り当てた/48ULA範囲は変更・削除できません。 +

+

+ BYOIPを使う場合は、GUAをプライベートに(ULAと同様の役割で)内部IPv6サブネット範囲として使うことも、通常どおり外部IPv6範囲として使うことも可能です。 +

+

+ サブネットの内部/64範囲のうち、最初と最後の/96範囲はシステム用に予約されており、手動で割り当てることはできません。 +

+
+

+ 出典: + Subnets +

+
+

+ 3.4 内部レンジ(Internal Ranges)によるIPAM自動化 +

+

+ 「内部レンジ(Internal Range)」は、VPCネットワーク内の内部IPv4/IPv6 + CIDRブロックを予約し、その使われ方を制御するリソースです。VPCネットワークピアリング・Shared + VPC・Cloud VPN・Cloud + Interconnectなどでネットワークトポロジが複雑化した際に、IPAMを体系的に管理するための土台となります。 +

+

+ 内部レンジには「ピアリングタイプ」と「使用タイプ」という2つの重要な属性があります。 +

+

ピアリングタイプ(VPCネットワークピアリングに対する挙動)

+
+ + + + + + + + + + + + + + + + + + + + + +
ピアリングタイプ説明
FOR_SELF + 親VPCネットワークのみがこのCIDRブロックを使用可能。ピアリング先では使用不可 +
FOR_PEER + ピアリング先のネットワークのみが使用可能。親ネットワークでは使用不可 +
NOT_SHARED + 親ネットワーク・ピアリング先の両方が使用可能。ただしピアリング先での使用は親ネットワークから見えない形で行う必要がある +
+
+

+ 使用タイプ(親VPCネットワーク内の他リソースとの関連付け可否) +

+
+ + + + + + + + + + + + + + + + + + + + + +
使用タイプ説明
FOR_VPC(デフォルト)親VPCネットワーク内の他のGoogle Cloudリソースと関連付け可能
EXTERNAL_TO_VPC + 親VPCネットワーク内のリソースとは関連付け不可(オンプレミス専用の予約など) +
FOR_MIGRATIONサブネット範囲の移行(別ネットワークへの移行を含む)に使用
+
+

IPv4の内部レンジを自動割当する場合、以下の4種類の割当戦略から選択できます。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
戦略説明特徴
RANDOM(デフォルト)空いているCIDRブロックをランダムに割当同時並行での予約が最速だが、断片化しやすい
FIRST_AVAILABLE数値的に最も若い開始アドレスを持つブロックを割当 + 最も予測可能で連続空間を最大化するが、同時予約時の競合で遅くなりやすい +
RANDOM_FIRST_N_AVAILABLE + 若い順にN個の候補ブロックを集め、その中からランダムに1つを割当 + 競合を減らしつつ連続性もある程度確保できる
FIRST_SMALLEST_FITTING + 要求サイズを収容できる最小の空きブロック(最長プレフィックス)から、最も若いアドレスのブロックを割当 + 断片化の抑制に最も優れるが、競合による遅延が最大
+
+
+flowchart TD
+    A[内部レンジをIPAM<br/>自動化ツールとして活用] --> B[ピアリングタイプを選択<br/>FOR_SELF / FOR_PEER / NOT_SHARED]
+    A --> C[使用タイプを選択<br/>FOR_VPC / EXTERNAL_TO_VPC / FOR_MIGRATION]
+    A --> D[IPv4の場合 割当戦略を選択<br/>RANDOM等]
+    B --> E[サブネット作成時に<br/>内部レンジを参照して<br/>重複を機械的に防止]
+    C --> E
+    D --> E
+    E --> F["FOR_MIGRATIONの場合:<br/>サブネット削除後もCIDRを予約し<br/>移行先サブネットにのみ再割当可能"]
+

+ サブネット移行のユースケース(FOR_MIGRATION)では、サブネットを削除するとCIDR範囲は通常解放されますが、内部レンジで予約しておくことで、削除後・再作成前の間もそのCIDRを保持し、指定した移行先サブネットにのみ割当を許可できます。移行元・移行先が異なるプロジェクトであっても利用可能です。 +

+
+

+ 出典: + Internal ranges overview +

+
+

3.5 BYOIP(Bring Your Own IP)

+

+ BYOIPは、組織が自ら保有する(または利用権を持つ)パブリックIPv4/IPv6アドレスをGoogle + Cloudへ持ち込み、Google + Cloudのリソースに割り当てる機能です。インポート後は、いくつかの例外を除きGoogle提供のIPアドレスと同様に管理されます。BYOIPで持ち込んだアドレスは、それを持ち込んだ顧客のみが利用可能で、アイドル状態・使用中のいずれであっても追加課金は発生しません。 +

+

BYOIPのプロビジョニングは以下の階層で進みます。

+
+flowchart LR
+    A["Public Advertised Prefix<br/>(PAP) 作成 + 所有権検証<br/>(ROA / 逆引きDNS)"] --> B["Public Delegated Prefix<br/>(PDP) へ分割"]
+    B --> C["サブプレフィックス /<br/>個別IPアドレスの作成"]
+    C --> D["Compute Engine /<br/>Load Balancer等のリソースへ割当"]
+    B -.複数プロジェクトへ委譲.-> B
+
    +
  1. + Public Advertised Prefix(PAP): + 持ち込むIPプレフィックス全体を表すリソース。Route Origin + Authorization(ROA)や逆引きDNSによる所有権検証が必要です。 +
  2. +
  3. + Public Delegated Prefix(PDP): + PAPを分割し、特定のリージョンやプロジェクトに委譲するためのサブプレフィックス。 +
  4. +
  5. + サブプレフィックス・個別IPアドレスの作成: + PDPからさらに細かい単位(個々のIPアドレスやより小さなCIDR)を切り出し、実際のリソースに割り当てます。 +
  6. +
+

+ 重要な注意点として、Googleは重複するBYOIPのルート広告をサポートしません。たとえば203.0.112.0/23をインポートしようとしても、その全体または一部(203.0.112.0/24など)がGoogle以外の場所で既に広告されている場合はインポートできません。同一プレフィックスが複数の場所から異なる形で広告されると、予期しないルーティングやパケットロスが発生する可能性があります。 +

+

+ 組織設計としては、BYOIPアドレスの管理を専用の組織・専用プロジェクトに集約し、IAMロールでPAP・PDPの管理権限を明確に分離することが推奨されます。BYOIPアドレスはShared + VPCのホストプロジェクトには委譲できますが、サービスプロジェクトへ直接委譲することはできません(ホストプロジェクトに委譲されたアドレスは、サービスプロジェクトからも利用可能です)。 +

+

+ BYOIPのプロビジョニング・削除プロセスには数週間かかることがあるため、実際に必要となるタイミングのかなり前から計画しておくことが重要です。 +

+
+

出典:

+ +
+ +

+ 3.6 マネージドサービスへの接続とIPアドレス割当(PSA・PSC・Serverless VPC + Access) +

+

+ マネージドサービスへプライベートに接続する主要な3つの方式は、それぞれ異なるIPアドレス割当の考え方を持ちます。 +

+
+flowchart TD
+    C1[消費者VPCネットワーク] --> A1["Allocated Range<br/>(推奨 /16、PSA用)"]
+    A1 -->|VPC Peering| P1["サービスプロデューサー<br/>ネットワークにサブネット作成<br/>(通常/29〜/24)"]
+    C1 --> A2["PSCエンドポイント用<br/>内部IPアドレス<br/>(通常サブネット内)"]
+    A2 -->|Private Service Connect| P2["公開サービス<br/>/ Google API"]
+    C1 --> A3["Serverless VPC Access<br/>コネクタ専用 /28 サブネット"]
+    A3 --> P3[Cloud Run / Functions等から<br/>VPCへの送信トラフィック]
+

+ Private Services Access(PSA)は、サービスコンシューマーのVPCネットワークとサービスプロデューサーのVPCネットワークをVPCネットワークピアリングで接続する仕組みです。ルーティングの重複を避けるため、コンシューマー側に「Allocated + Range」を確保する必要があります。 +

+
    +
  • + Googleのサービス向けには最小/24、推奨/16ブロックが必要です。 +
  • +
  • + Allocated + Rangeは、現在・将来のサブネット範囲(VPCネットワークピアリングやNCCスポークで接続されたネットワークのサブネット範囲を含む)と完全に分離しておく必要があります。 +
  • +
  • + サービスプロデューサー側は通常、このAllocated + Rangeの中から/29〜/24程度のサブネットを選んでリソースを配置します。プロデューサー側のサブネット範囲自体は選択・変更できません。 +
  • +
+

+ Private Service Connect(PSC)のエンドポイントは、通常のサブネット内の内部IPアドレスとして構成されます(公開サービス用の場合はPRIVATE_SERVICE_CONNECT目的のサブネットを使用)。PSAとは異なりVPCネットワークピアリングを必要とせず、1つの内部IPアドレスで公開サービスやGoogle + APIへ到達できる点が特徴です。 +

+

+ Serverless VPC Accessは、Cloud Run・Cloud Run functions・App + EngineなどのサーバーレスワークロードからVPCネットワークへ送信トラフィックを送るためのコネクタです。コネクタには専用の/28サブネット(16アドレス)が必要で、他のリソースと共用できず、作成後にサイズを変更することもできません。Shared + VPCを使う場合、サービスプロジェクト側でコネクタを作成するには、ホストプロジェクトのネットワーク管理者が事前にそのサブネットを手動作成しておく必要があります。 +

+
+

出典:

+ +
+ +

+ 3.7 Cloud NATにおけるIPアドレスとポートの管理 +

+

+ Cloud NATは、Public NAT(インターネットへのアウトバウンド接続)とPrivate + NAT(VPC間・オンプレミス間などプライベート接続向けのアウトバウンド接続)の2種類を提供し、それぞれIPアドレスとポートの管理方法が異なります。 +

+

Public NATのIPアドレス割当

+
+ + + + + + + + + + + + + + + + + +
割当方法特徴
自動NAT IPアドレス割当 + 選択したネットワークサービスティア(Premium/Standard)、VM数、VMあたりのポート予約数に基づき、Googleがリージョナル外部IPアドレスを自動的に追加・削除する。追加されたアドレスは静的(予約済み)として扱われるがプロジェクトのクォータには計上されない。次にどのIPアドレスが割り当てられるかは予測できないため、許可リストのように事前に把握しておく必要がある用途には不向き +
手動NAT IPアドレス割当 + 静的外部IPアドレスを自身で作成し手動でゲートウェイに割り当てる。許可リストとの相性が良く、IPアドレスの「ドレイン(drain)」機能(新規接続には使わず、既存接続の正常終了のみ許可)を利用できる +
+
+

Private NATのIPアドレス割当

+

+ Private + NATのIPアドレスは、purpose=PRIVATE_NATのサブネットのプライマリIPv4範囲から供給される、リージョナル内部IPv4アドレスです。この範囲は自動割当ができず、ゲートウェイのルール作成時に明示的にサブネットを指定します。1つのPrivate + NATサブネットが提供できるNAT + IPアドレス数は、サブネットのプレフィックス長PREFIX_LENGTHを使って次の式で求められます。 +

+
利用可能なNAT IPアドレス数 = 2^(32 - PREFIX_LENGTH) - 4
+

(各サブネットには4つの未使用アドレスが存在するため4を減算します)

+

ポート割当方式

+
+ + + + + + + + + + + + + + + + + + + + + + + +
方式Public NATのデフォルトPrivate NATのデフォルト特徴
静的ポート割当○(デフォルト)選択可 + VMごとに固定のポート数を割り当てる。全VMのエグレス使用量が均一な場合に適する。Endpoint-Independent + Mappingを使う場合は静的ポート割当が必須 +
動的ポート割当選択可○(デフォルト) + 最小・最大ポート数を指定し、使用状況に応じて自動的に増減させる。ポート使用量にばらつきがある場合に有効 +
+
+
+flowchart TD
+    A[Cloud NATゲートウェイのタイプ] --> B[Public NAT]
+    A --> C[Private NAT]
+    B --> B1{IPアドレス割当方法}
+    B1 -->|自動| B2[Network Tierと使用量に応じ<br/>外部IPを自動増減]
+    B1 -->|手動| B3[静的外部IPを手動割当<br/>allowlist等に有効]
+    B --> B4{ポート割当方法}
+    B4 -->|静的 デフォルト| B5[VMごとに固定ポート数]
+    B4 -->|動的| B6[使用量に応じ<br/>min〜max間で自動増減]
+    C --> C1["専用サブネット<br/>(purpose=PRIVATE_NAT)から<br/>内部IPを使用"]
+    C --> C4{ポート割当方法}
+    C4 -->|動的 デフォルト| C6[使用量に応じ自動増減]
+    C4 -->|静的| C5[VMごとに固定ポート数]
+

+ 各NAT + IPアドレスは、TCP・UDPそれぞれ64,512個のソースポート(0〜1023のウェルノウンポートを除く)を提供します。ポート予約の計算例として、Public + NATで単一の手動NAT + IPアドレスを使い、VMあたり最小64ポートを設定した場合、以下のように最大1,008台のVMをサポートできます。 +

+
⌊(1 NAT IPアドレス) × (64,512ポート/アドレス) / (64ポート/VM)⌋ = 1,008台
+

+ Private + NATの場合、信頼性確保のためVMあたりの必要ポート数の「2倍」が割り当てられる点に注意が必要です。たとえば最小サイズの/29サブネット(8アドレス、うち4つが利用可能)でVMあたり最小64ポートを設定した場合は次のようになります。 +

+
⌊(2^(32-29) - 4) NAT IPアドレス × (64,512ポート/アドレス) / (64ポート/VM × 2)⌋ = 2,016台
+

+ IPアドレス・ポート割当のマッピングは時間とともに変化する可能性があるため、現在のマッピングを前提にネットワーク設定を構築すべきではない、という点も試験対策上のポイントです。 +

+
+

+ 出典: + IP addresses and ports (Cloud NAT) +

+
+

3.8 IPAM設計チェックリスト

+
+
+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

試験対策チェックリスト(横断)

+
+
+ 0 / 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

参考文献

+
+ + + + + + + +
+ +
+ Google Cloud Professional Cloud Network Engineer 試験対策ガイド · S4: + CDN・DNS・IPアドレス管理 +
+
+
+ + + + + diff --git a/Pcne-s4-cdn-dns-ipam.md b/Pcne-s4-cdn-dns-ipam.md new file mode 100644 index 000000000..6e2ae4ce0 --- /dev/null +++ b/Pcne-s4-cdn-dns-ipam.md @@ -0,0 +1,986 @@ +# Google Cloud Professional Cloud Network Engineer試験 S4: CDN・DNS・IPアドレス管理 + +## 本ガイドについて + +本ガイドはGoogle Cloud Professional Cloud Network Engineer(PCNE)認定試験の対策として、「CDN・DNS・IPアドレス管理」の3領域を中級者〜上級者向けに解説する単体Markdownドキュメントです。 + +公式Exam Guide(`professional_cloud_network_engineer_exam_guide_english.pdf`)を直接確認したうえで、本ガイドは以下の出題タスクに対応しています。 + +| 出題領域 | 対応する公式Exam Guideのタスク | 本ガイドでの扱い | +| --- | --- | --- | +| Cloud CDN | Section 3, Task 3.2「Configuring Cloud CDN」 | Part 1で全項目を網羅(対応オリジン、外部バックエンド、キャッシュ無効化) | +| Cloud DNS | Section 3, Task 3.3「Configuring Cloud DNS」 | Part 2で全項目を網羅(ゾーン管理、移行、ルーティングポリシー、DNSSEC、フォワーディング、split-horizon、クロスプロジェクトバインディング・ピアリング、GKE向けCloud DNS) | +| IPアドレス管理(IPAM) | Section 1, Task 1.2「Planning the IP address management (IPAM) strategy」の内容を、Section 2/Section 6の実装・運用視点から深掘り | Part 3で、サブネット設計・PUPI・IPv6・内部レンジによるIPAM自動化・BYOIP・Private Service ConnectやServerless VPC AccessのIP割当・Cloud NATのIPアドレス/ポート管理までを一気通貫で解説 | + +ロードバランシング(Task 3.1)は別ガイドで既に扱っているため、本ガイドではCloud CDN・Cloud DNS・IPAMの3本柱に集中します。 + +ASCII図解は使用せず、フローチャートはすべてMermaid、図解や表はすべてMarkdown記法で記載しています。各項目の末尾には根拠となる一次情報源(Google Cloud公式ドキュメント)のURLを「出典」として明記しています。 + +--- + +## 目次 + +- [Part 1: Cloud CDN](#part-1-cloud-cdn) + - [1.1 Cloud CDNのアーキテクチャと動作原理](#11-cloud-cdnのアーキテクチャと動作原理) + - [1.2 対応オリジン(バックエンドタイプ)](#12-対応オリジンバックエンドタイプ) + - [1.3 外部バックエンド(Internet NEG)とハイブリッド/マルチクラウド構成](#13-外部バックエンドinternet-negとハイブリッドマルチクラウド構成) + - [1.4 キャッシュモードとキャッシュ可否の判定](#14-キャッシュモードとキャッシュ可否の判定) + - [1.5 キャッシュキーのカスタマイズ](#15-キャッシュキーのカスタマイズ) + - [1.6 キャッシュの無効化(Invalidation)](#16-キャッシュの無効化invalidation) + - [1.7 コンテンツのアクセス制御(署名付きURL・署名付きCookie)](#17-コンテンツのアクセス制御署名付きurl署名付きcookie) + - [1.8 Cloud CDNのベストプラクティス](#18-cloud-cdnのベストプラクティス) +- [Part 2: Cloud DNS](#part-2-cloud-dns) + - [2.1 Cloud DNSの基本アーキテクチャとゾーンタイプ](#21-cloud-dnsの基本アーキテクチャとゾーンタイプ) + - [2.2 パブリックゾーンとプライベートゾーン、Split-Horizon DNS](#22-パブリックゾーンとプライベートゾーンsplit-horizon-dns) + - [2.3 フォワーディングゾーンとピアリングゾーン](#23-フォワーディングゾーンとピアリングゾーン) + - [2.4 DNSルーティングポリシーとヘルスチェック](#24-dnsルーティングポリシーとヘルスチェック) + - [2.5 DNSSEC(DNS Security Extensions)](#25-dnssecdns-security-extensions) + - [2.6 DNSサーバーポリシー(Inbound / Outbound)](#26-dnsサーバーポリシーinbound--outbound) + - [2.7 クロスプロジェクトバインディング vs DNSピアリング](#27-クロスプロジェクトバインディング-vs-dnsピアリング) + - [2.8 GKEにおけるCloud DNS](#28-gkeにおけるcloud-dns) + - [2.9 他プロバイダからCloud DNSへの移行](#29-他プロバイダからcloud-dnsへの移行) + - [2.10 ハイブリッドDNSのリファレンスアーキテクチャとベストプラクティス](#210-ハイブリッドdnsのリファレンスアーキテクチャとベストプラクティス) +- [Part 3: IPアドレス管理(IPAM)](#part-3-ipアドレス管理ipam) + - [3.1 IPアドレスの分類体系](#31-ipアドレスの分類体系) + - [3.2 サブネットのIPv4アドレス範囲設計](#32-サブネットのipv4アドレス範囲設計) + - [3.3 IPv6サポート](#33-ipv6サポート) + - [3.4 内部レンジ(Internal Ranges)によるIPAM自動化](#34-内部レンジinternal-rangesによるipam自動化) + - [3.5 BYOIP(Bring Your Own IP)](#35-byoipbring-your-own-ip) + - [3.6 マネージドサービスへの接続とIPアドレス割当(PSA・PSC・Serverless VPC Access)](#36-マネージドサービスへの接続とipアドレス割当psapscserverless-vpc-access) + - [3.7 Cloud NATにおけるIPアドレスとポートの管理](#37-cloud-natにおけるipアドレスとポートの管理) + - [3.8 IPAM設計チェックリスト](#38-ipam設計チェックリスト) +- [試験対策チェックリスト(横断)](#試験対策チェックリスト横断) +- [参考文献](#参考文献) + +--- + +## Part 1: Cloud CDN + +### 1.1 Cloud CDNのアーキテクチャと動作原理 + +Cloud CDN(Content Delivery Network)は、Googleのグローバルなエッジネットワークを使ってコンテンツをユーザーの近くから配信するサービスです。Cloud CDNは単独では機能せず、必ずグローバル外部Application Load Balancerまたはクラシック Application Load Balancerと組み合わせて使用します。ロードバランサがフロントエンドのIPアドレスとポートを提供し、Cloud CDNはそのバックエンド(Google Cloudでは「オリジンサーバー」と呼ぶ)からのレスポンスをエッジでキャッシュします。 + +リクエストの処理はGoogle Front End(GFE)で行われます。GFEはユーザーに最も近いGoogleネットワークのエッジに位置し、Cloud CDNが有効なバックエンドサービス・バックエンドバケットへのリクエストであれば、まずキャッシュを検索します。 + +- **キャッシュヒット**: GFEがキャッシュキーに対応するレスポンスを保持していれば、そのままユーザーへ返却します(オリジンへの往復が発生しないため低レイテンシ)。 +- **キャッシュミス**: GFEはリクエストをロードバランサ経由でオリジンサーバーへ転送します。レスポンスがキャッシュ可能であれば、次回以降のためにキャッシュへ格納します(この処理を「cache fill」と呼び、キャッシュからクライアントへ配信することを「cache egress」と呼びます)。 +- **部分ヒット(partial hit)**: バイトレンジリクエストに対応したオリジンの場合、要求されたコンテンツの一部だけがキャッシュ済みで、残りをオリジンから取得するケースもあります。 + +```mermaid +flowchart TD + A[クライアントからのリクエスト] --> B{最寄りのGFEが
Cloud CDNキャッシュを検索} + B -->|キャッシュヒット| C[キャッシュから直接応答
cache egress] + B -->|キャッシュミス| D[External Application
Load Balancerへ転送] + D --> E[オリジンサーバーへ転送
MIG・バケット・サーバーレスNEG等] + E --> F{レスポンスは
キャッシュ可能か} + F -->|Yes| G[Cloud CDNキャッシュに格納
cache fill] + F -->|No| H[クライアントへ直接返却] + G --> I[クライアントへ応答] +``` + +「キャッシュヒット率」は、リクエストされたオブジェクトがキャッシュから配信された割合を示す重要指標です。ヒット率が低い場合は、後述するキャッシュキーの設定やTTL設定を見直します。 + +キャッシュされたコンテンツは、有効期限切れ(expiration)または削除(eviction)のいずれかが発生するまで配信対象となります。両者は独立した概念です。 + +- **Expiration(期限切れ)**: レスポンスに設定されたTTL(`max-age`・`s-maxage`・`Expires`)に基づき、鮮度が切れているかどうかを判定します。 +- **Eviction(削除)**: キャッシュ容量が満杯になった際、直近でアクセスされていないコンテンツから削除されます。期限切れかどうかに関わらず発生し、複数のGoogle Cloudプロジェクトが同じGFE群のキャッシュ容量を共有するため、人気度は複数プロジェクトを横断して比較されます。30日間アクセスがなければ無条件に削除されます。 + +> **出典**: [Cloud CDN overview](https://docs.cloud.google.com/cdn/docs/overview) + +### 1.2 対応オリジン(バックエンドタイプ) + +Cloud CDNは、External Application Load Balancerが対応する以下のバックエンドタイプすべてに対して有効化できます。 + +| バックエンドタイプ | 概要 | +| --- | --- | +| インスタンスグループ(MIG) | Compute EngineのマネージドインスタンスグループをVMベースのオリジンとして使用 | +| ゾーンNEG(Network Endpoint Group) | ゾーン単位でエンドポイントを指定するバックエンド | +| サーバーレスNEG | Cloud Run、Cloud Run functions(旧Cloud Functions)、App Engineのいずれか1つ以上のサービスをオリジンとして使用 | +| Internet NEG(外部バックエンド) | Google Cloud外部(オンプレミスや他クラウド)のエンドポイントをオリジンとして使用 | +| Cloud Storageバックエンドバケット | Cloud Storageバケットを静的コンテンツのオリジンとして使用 | + +```mermaid +flowchart LR + LB[External Application
Load Balancer + Cloud CDN] + LB --> A[マネージドインスタンスグループ
ゾーンNEG] + LB --> B[サーバーレスNEG
Cloud Run / functions / App Engine] + LB --> C[Cloud Storage
バックエンドバケット] + LB --> D[Internet NEG
外部バックエンド] + D --> E[オンプレミス
データセンター] + D --> F[他クラウド環境] +``` + +キャッシュヒット・ミスの挙動は、Compute Engine・バックエンドバケット・GKE Ingress・GKE Gatewayを含むすべての対応バックエンドタイプで一貫しています。GKEワークロードに対しては、GKE Ingressコントローラのバックエンド設定、またはGKE Gatewayの`GCPHTTPFilter`カスタムリソースを使ってCloud CDNを構成できます。 + +> **出典**: +- [Cloud CDN overview](https://docs.cloud.google.com/cdn/docs/overview) +- [External backends specified by using internet NEGs](https://docs.cloud.google.com/cdn/docs/external-backends-internet-neg-overview) + +### 1.3 外部バックエンド(Internet NEG)とハイブリッド/マルチクラウド構成 + +オンプレミスや他クラウドにホストされたコンテンツも、Cloud CDNのグローバルエッジキャッシュ経由で配信できます。この際に使用するのが「Internet NEG」(外部バックエンドを指定するAPIリソース)です。 + +Internet NEGのエンドポイントタイプは2種類あります。 + +| エンドポイントアドレス | タイプ | 使いどころ | +| --- | --- | --- | +| ホスト名 + 任意のポート | `INTERNET_FQDN_PORT` | 外部バックエンドをパブリックDNSで解決可能なFQDNで指定する場合のベストプラクティス。IPアドレス変更の影響を受けにくい | +| IPアドレス + 任意のポート | `INTERNET_IP_PORT` | パブリックにアクセス可能なIPアドレスを直接指定する場合 | + +Internet NEGの作成後、この2種類のエンドポイントタイプを相互に変更することはできません(新規作成が必要)。また、Cloud CDNは1つのサービスにつき単一の外部バックエンドからのフェッチのみをサポートし、複数の外部バックエンド間でのロードバランシングや、外部バックエンドとGoogle Cloudバックエンドとの間でのロードバランシングは行いません。 + +```mermaid +flowchart LR + U[インターネット利用者] --> GFE[Cloud CDN
Google Front End] + GFE --> LB[External Application
Load Balancer] + LB -->|/images/*| GCS[Cloud Storage
バケット] + LB -->|/video/*| NEG[Internet NEG] + NEG --> DC[オンプレミス
データセンター / 他クラウド] +``` + +この構成は、段階的なクラウド移行やマルチクラウド戦略において、一部のコンテンツ(例: 画像)はGoogle Cloudへ、他のコンテンツ(例: 動画)はオンプレミスに残したまま、URLマップのパスルール(`/images/*`、`/video/*`など)で振り分けるユースケースに有効です。 + +外部バックエンドが特定の`Host`ヘッダーを期待する場合は、バックエンドサービス側でカスタムリクエストヘッダーとして`Host`を明示的に設定する必要があります(未設定の場合、クライアントが接続時に使用した`Host`ヘッダーがそのまま引き継がれます)。 + +> **出典**: [External backends specified by using internet NEGs](https://docs.cloud.google.com/cdn/docs/external-backends-internet-neg-overview) + +### 1.4 キャッシュモードとキャッシュ可否の判定 + +Cloud CDNには3つのキャッシュモードがあり、オリジンからのキャッシュ指示(`Cache-Control`ヘッダー等)をどこまで尊重するかを制御します。 + +| キャッシュモード | 動作 | +| --- | --- | +| `CACHE_ALL_STATIC`(デフォルト) | 静的コンテンツタイプの成功レスポンスを自動キャッシュ。オリジンが有効なキャッシュ指示を送っていればそれも尊重する。gcloud CLIやREST APIで作成したCloud CDN対応バックエンドのデフォルト動作 | +| `USE_ORIGIN_HEADERS` | オリジンの成功レスポンスに有効なキャッシュ指示・キャッシュヘッダーが含まれていることを必須とする。指示がなければキャッシュせずそのままオリジンから転送 | +| `FORCE_CACHE_ALL` | オリジンが設定したキャッシュ指示を無視し、成功レスポンスを無条件にキャッシュ。動的なHTML・APIレスポンス等、ユーザー固有のコンテンツを扱うバックエンドには非推奨。プライベートバケットアクセスを有効化したバケットでは、このモードが必須になる場合がある | + +```mermaid +flowchart TD + A[Cloud CDNキャッシュモードを選択] --> B["CACHE_ALL_STATIC
(デフォルト)"] + A --> C[USE_ORIGIN_HEADERS] + A --> D[FORCE_CACHE_ALL] + B --> B1[静的コンテンツタイプを自動キャッシュ
Cache-Controlがなくても可] + C --> C1[オリジンのCache-Control /
Expiresヘッダーが必須] + D --> D1[オリジンの指示を無視し
常に強制キャッシュ] + D --> D2[個人情報を含む動的
コンテンツには非推奨] +``` + +`CACHE_ALL_STATIC`モードでオリジンからのキャッシュ指示がない場合、以下のMIMEタイプが自動的にキャッシュ対象となります。 + +| カテゴリ | MIMEタイプ | +| --- | --- | +| Webアセット | `text/css`、`text/ecmascript`、`text/javascript`、`application/javascript` | +| フォント | `font/*`に一致するすべて | +| 画像 | `image/*`に一致するすべて | +| 動画 | `video/*`に一致するすべて | +| 音声 | `audio/*`に一致するすべて | +| ドキュメント | `application/pdf`、`application/postscript` | + +`text/html`や`application/json`は、動的(ユーザー固有)なレスポンスであることが多いため、デフォルトではキャッシュ対象になりません。これらをキャッシュしたい場合は、オリジン側で明示的な`Cache-Control`ヘッダーを設定する必要があります。 + +キャッシュ可否のデフォルト値は以下のとおりです。 + +| パラメータ | デフォルト値 | 説明 | +| --- | --- | --- | +| Cache mode | `CACHE_ALL_STATIC` | 一般的な静的コンテンツタイプを自動キャッシュ | +| Client TTL | `3600秒` | クライアントブラウザキャッシュの`max-age` | +| Default TTL | `3600秒` | オリジンがヘッダーを返さない場合のキャッシュ期間 | +| Include Host | `true` | キャッシュキーにホストを含める | +| Include Protocol | `true` | HTTP/HTTPSを別オブジェクトとしてキャッシュ | +| Include Query String | `true` | クエリ文字列全体をキャッシュキーに含める | +| Max TTL | `86400秒` | キャッシュに残る絶対最大時間(24時間) | +| Negative Caching | `false` | 404などのエラーレスポンスはデフォルトでキャッシュしない | +| Serve While Stale | `86400秒` | オリジンに到達不能な場合、最大24時間古いコンテンツを配信 | + +以下のいずれかに該当するレスポンスはキャッシュされません(`FORCE_CACHE_ALL`の一部を除く)。 + +- `Set-Cookie`ヘッダーを持つ +- 許可されたもの以外の`Vary`ヘッダー値を持つ +- `Cache-Control: no-store`または`private`ディレクティブを持つ +- リクエストに`Authorization`ヘッダーがあり、レスポンス側でオーバーライドされていない +- 最大サイズ(バイトレンジ対応オリジンで100 GiB、非対応オリジンで10 MiB)を超える + +> **出典**: [Caching overview](https://docs.cloud.google.com/cdn/docs/caching) + +### 1.5 キャッシュキーのカスタマイズ + +Cloud CDNのキャッシュキーは、デフォルトでリクエストURIの全体(バックエンドサービスの場合)またはプロトコル・ホストを除いたURI(バックエンドバケットの場合)を使用します。キャッシュヒット率を最適化するため、以下の要素を個別に含める・除外することができます。 + +| URIパートの調整 | 効果 | +| --- | --- | +| プロトコルを除外 | `http://` と `https://` を同一キャッシュキーとして扱う | +| ホストを除外 | 複数ホスト名(同一コンテンツを配信する複数ドメイン等)を同一キャッシュとして扱う | +| クエリ文字列を除外 | クエリパラメータ違いを同一キャッシュとして扱う | +| クエリ文字列の含め・除外リスト | 特定パラメータのみ含める(include list)、または特定パラメータのみ除外する(exclude list)。両方を同時指定することはできない | +| HTTPリクエストヘッダーの追加 | デバイスタイプ・言語などに応じてバリエーションをキャッシュ(`Authorization`、`Cookie`、`Referer`、`User-Agent`等の高カーディナリティなヘッダーは追加不可) | +| 名前付きCookieの追加(バックエンドサービスのみ) | 最大5つまでのCookie名を指定し、A/Bテストやカナリアリリースなどのバリエーションをキャッシュ | + +クエリパラメータの順序はキャッシュキーの一致判定に影響しません(`a=1&b=2`と`b=2&a=1`は同一キーになります)。 + +Cloud Storageバックエンドバケットに対しては、キャッシュバスティング(更新されたファイルを即座に反映させる仕組み)のためにクエリ文字列のinclude listを使う手法が有効です。たとえば`?version=VERSION`や`?hash=HASH`のようなパラメータをキャッシュキーに含めることで、明示的な無効化なしに新しいバージョンを配信できます。 + +> **出典**: [Caching overview](https://docs.cloud.google.com/cdn/docs/caching) + +### 1.6 キャッシュの無効化(Invalidation) + +キャッシュ無効化(cache purging)は、正規の期限切れ前に特定のコンテンツをキャッシュから強制的に削除する操作です。 + +- パスパターン(例: `/picture*`)またはホスト単位で無効化を指定できます。 +- クエリ文字列違いだけで個別のオブジェクトを無効化することはできません(`/images.php?image=fred.png`のようなURLを個別無効化する場合は`/images.php`をパスパターンとして指定する必要があります)。 +- キャッシュタグ(`Cache-Tag`レスポンスヘッダーで指定する「サロゲートキー」)を使うと、任意のメタデータ単位で一括無効化できます。1オブジェクトあたり最大50タグ、合計4 KiBまで、1回のリクエストで最大10タグを論理OR条件として指定可能です。 +- 無効化リクエストはレート制限されており、1分あたり最大500件、反映には約10秒かかります。 + +```mermaid +flowchart LR + A[無効化リクエスト] --> B{一致条件} + B -->|パスパターン| C["/picture* のようなプレフィックス一致"] + B -->|ホスト指定| D[特定ホストのみ対象] + B -->|Cache-Tag| E["release-v1,frontend 等の
論理OR条件"] + C --> F[該当キャッシュエントリを
破棄し次回リクエストで
オリジンから再取得] + D --> F + E --> F +``` + +ベストプラクティスとして、無効化は「例外的な状況」(法的理由や誤アップロードの是正など)のためのものであり、通常のデプロイフローの一部として多用すべきではありません。日常的なコンテンツ更新には、TTL設計やバージョン付きURL(`file.css?v=2`のような)を優先します。 + +Shared VPCのクロスプロジェクトサービス参照を使う構成では、キャッシュ無効化はロードバランサのフロントエンド(転送規則・ターゲットプロキシ・URLマップ)を持つプロジェクト側で行う必要があり、サービスプロジェクト側の管理者はデフォルトでは無効化権限を持ちません。 + +> **出典**: [Cache invalidation overview](https://docs.cloud.google.com/cdn/docs/cache-invalidation-overview) + +### 1.7 コンテンツのアクセス制御(署名付きURL・署名付きCookie) + +Cloud CDNは、コンテンツへのアクセスを制御する3つの手段を提供します。 + +| 手法 | 用途 | +| --- | --- | +| 署名付きURL(Signed URL) | Googleアカウントの有無に関わらず、URLを保持する誰でも一定期間アクセス可能にする。単一または少数のリソースを保護する場合に適する | +| 署名付きCookie(Signed Cookie) | 特定のURLプレフィックス(例: `https://media.example.com/videos/`)配下のすべてのリクエストを、1つのCookieで一定期間認可する。HLS/DASHのようにマニフェスト内の多数のURLを個別に署名するのが非現実的な場合に有効 | +| プライベートオリジン認証 | Amazon S3や互換オブジェクトストアなど、Cloud CDN外の第三者オリジンへの直接アクセスを防ぎ、Cloud CDN経由の接続のみを許可する | + +署名付きURL・署名付きCookieはURLマップでは直接設定できず、バックエンドサービスまたはバックエンドバケット単位で設定します。署名の検証はCloud CDN自体では行われないため、オリジン側のWebサーバーが署名を検証し、不正なリクエストにはHTTP 403を返す実装が必須です。署名済みリクエストと未署名リクエストは別々にキャッシュされるため、キャッシュ可能なステータスコードを不正なリクエストに返すと、以降の正当なリクエストが誤って拒否される可能性がある点に注意します。 + +> **出典**: [Content access control](https://docs.cloud.google.com/cdn/docs/authenticate-content) + +### 1.8 Cloud CDNのベストプラクティス + +Google公式のベストプラクティスドキュメントは、キャッシュヒット率・パフォーマンス・セキュリティ・キャッシュ運用・アップロード整合性・監視の6領域に整理されています。 + +**キャッシュヒット率の最適化** + +- オリジンの`Cache-Control`ヘッダーに詳しくない場合は、`CACHE_ALL_STATIC`(デフォルト)のまま静的コンテンツを自動キャッシュさせるのが推奨。 +- ユーザー固有のコンテンツはCloud CDNでキャッシュしない。 +- キャッシュキーからホストやプロトコルを除外し、不要なキャッシュの分散(シャーディング)を避ける。 +- GKE Gatewayを使う場合は、単一のグローバルキャッシュポリシーではなく`GCPHTTPFilter`でパスごとに`cacheKeyPolicy`とTTLをカスタマイズする(例: `/static/*`はクエリ文字列を除外してヒット率を最大化、`/api/*`は特定クエリ文字列を含めて動的応答を正しく区別)。 + +**パフォーマンスの最適化** + +- HTTP/3・QUICプロトコルサポートを有効化する。 +- GKE Gatewayでは、Podの再起動・一時的な到達不能に備え`serveWhileStale`を24時間以上に設定し、`requestCoalescing`を有効化してオリジンへの同時キャッシュフィルリクエストを集約する。 +- ネガティブキャッシングを活用し、エラーや리다이렉트のレスポンスも適切なTTLでキャッシュしてオリジン負荷を下げる。 +- TLS Early Data(0-RTT)を有効化し、再開接続のパフォーマンスを30〜50%改善する。 + +**セキュリティの最適化** + +- Cloud Armorをキャッシュ済みコンテンツ(エッジセキュリティポリシー)とキャッシュミス・動的コンテンツ(バックエンドセキュリティポリシー)の両方に適用する。 +- 署名付きURLを使う場合は、パブリック用とプライベート用でCloud Storageバケットを分離する。 +- GKE Gateway環境でIAPとCloud CDNを併用する場合、両者は同一ルートで併存できないため、`GCPBackendPolicy`でIAPが有効なパスに`GCPHTTPFilter`のキャッシュ設定を併用しないよう構成する。 + +**キャッシュの運用** + +- コンテンツのカテゴリ(ほぼリアルタイム、頻繁に更新、稀に更新)ごとにTTLを設計する。 +- バージョン付きURL(クエリパラメータ、ファイル名、パスへのバージョン番号付与)を、無効化に代わるデフォルトの更新手法として採用する。 +- 無効化は最終手段として最小限にとどめる。 + +**アップロードの整合性** + +- 既存ファイルの上書きより、バージョン番号や日付を付けた新規ファイル名でのアップロードを優先する。 +- 既存ファイルを更新する場合は、一時的な名前でアップロードしてから目的の名前へリネームすることでアトミック性を担保する。 +- バイトレンジキャッシュされたファイルを更新する場合は、無効化リクエストを併用する。 + +**監視・ロギング** + +- すべてのCloud CDN対応バックエンドでロギングを有効化する。 +- Cloud CDN用のカスタムモニタリングダッシュボードを定期的に確認する。 + +> **出典**: [Content delivery best practices](https://docs.cloud.google.com/cdn/docs/best-practices) + +--- + +## Part 2: Cloud DNS + +### 2.1 Cloud DNSの基本アーキテクチャとゾーンタイプ + +Cloud DNSは低レイテンシかつ高可用なDNSゾーンサービスであり、インターネットに公開される「パブリックゾーン」と、指定したVPCネットワーク内からのみ参照可能な「プライベートゾーン」の両方に対して権威DNSサーバーとして機能します。 + +Cloud DNSが提供する主なゾーンの種類は以下のとおりです。 + +| ゾーンタイプ | 概要 | +| --- | --- | +| パブリックゾーン | インターネットに公開される権威ゾーン。ゾーンApexにはNS/SOAレコードが存在し削除不可 | +| プライベートゾーン | 指定したVPCネットワークからのみクエリ可能なゾーン | +| フォワーディングゾーン | プライベートゾーンの一種。レコードを持たず、代わりにフォワーディングターゲット(DNSサーバー)を指定する | +| ピアリングゾーン(DNSピアリング) | 別のVPCネットワーク(DNSプロデューサーネットワーク)のDNS解決結果をそのまま参照するプライベートゾーン | +| マネージドリバースルックアップゾーン | Compute EngineのDNSデータに対してPTRルックアップを行う特殊なプライベートゾーン | +| Service Directoryゾーン | Service Directoryのネームスペースをバックエンドとするプライベートゾーン。レコードは直接追加できず、Service Directory側の登録内容から自動的に導出される | +| ゾーナルCloud DNSゾーン | GKEのクラスタスコープ選択時に作成される、単一のGoogle Cloudゾーンにスコープされたプライベートゾーン | + +Cloud DNSはプロジェクトレベル・個別ゾーンレベルの両方でIAM権限を細かく設定できます。 + +> **出典**: [Cloud DNS overview](https://docs.cloud.google.com/dns/docs/overview) + +### 2.2 パブリックゾーンとプライベートゾーン、Split-Horizon DNS + +同一のドメイン名でパブリックゾーンとプライベートゾーンの両方を作成すると、クエリの発信元に応じて異なる応答を返す「Split-Horizon DNS」を実現できます。 + +以下は、`gcp.example.com`というパブリックゾーンとプライベートゾーンを両方作成した場合の例です。 + +| ゾーン | レコード | タイプ | TTL | データ | +| --- | --- | --- | --- | --- | +| プライベート | `myrecord1.gcp.example.com` | A | 5 | `10.128.1.35` | +| パブリック | `myrecord1.gcp.example.com` | A | 5 | `104.198.6.142` | +| パブリック | `myrecord2.gcp.example.com` | A | 50 | `104.198.7.145` | + +```mermaid +flowchart TD + Q1["クエリ: myrecord1.gcp.example.com"] --> S{発信元は?} + S -->|VPCネットワーク内のVM| P[Private Zone
gcp.example.com] + S -->|インターネット| PUB[Public Zone
gcp.example.com] + P --> R1["10.128.1.35 を応答"] + PUB --> R2["104.198.6.142 を応答"] +``` + +VPCネットワーク内のVMから`myrecord2.gcp.example.com`を問い合わせた場合、プライベートゾーンに該当レコードが存在しないため`NXDOMAIN`が返ります(同名のレコードがパブリックゾーンに存在していても影響しません)。これは、Google Cloudの名前解決が「最長サフィックス一致」で該当ゾーンを特定し、そのゾーン内でレコードが見つからなければ他のゾーンにフォールバックしない、という仕様に基づきます。 + +2つのゾーンが「オーバーラップ」する条件(片方のオリジンドメインがもう片方のサブドメインである、または完全一致する)についても整理しておきます。 + +- パブリックゾーン同士のオーバーラップは、同一のCloud DNSネームサーバー上では許可されません。 +- プライベートゾーンは任意のパブリックゾーンとオーバーラップ可能です。 +- 異なるVPCネットワークにスコープされたプライベートゾーン同士は、オーバーラップしても構いません。 +- 同一VPCネットワークに認可された2つのプライベートゾーンは、片方がもう片方のサブドメインでない限り、同一オリジンを持つことはできません。 + +> **出典**: [DNS zones overview](https://docs.cloud.google.com/dns/docs/zones/zones-overview) + +### 2.3 フォワーディングゾーンとピアリングゾーン + +**フォワーディングゾーン**は、レコードを保持せず、指定したフォワーディングターゲット(DNSサーバー)へクエリを転送するプライベートゾーンです。フォワーディングターゲットは4種類に分類されます。 + +| ターゲットタイプ | 定義 | 想定される用途 | +| --- | --- | --- | +| Type 1 | 同一VPCネットワーク内のGoogle Cloud VMまたは内部パススルーNetwork Load Balancerの内部IPアドレス | 同一VPC内のカスタムDNSサーバー | +| Type 2 | Cloud VPNまたはCloud Interconnectで接続されたオンプレミスシステムのIPアドレス | オンプレミスDNSサーバーへの転送 | +| Type 3 | インターネットからアクセス可能な外部IPアドレス | パブリックなDNSサーバーや別VPCのVMの外部IP | +| Type 4 | 標準・非標準の名前解決順序でIPv4/IPv6両方を解決できるFQDN | IPアドレスが変動するターゲットの指定 | + +ルーティング方式は「標準ルーティング」(RFC 1918アドレスは認可済みVPC経由、それ以外はインターネット経由)と「プライベートルーティング」(RFC 1918かどうかに関わらず常に認可済みVPC経由。Type 1/2のみサポート)の2種類があります。 + +重要な制約として、Cloud DNSはフォワーディングターゲットへの推移的ルーティング(transitive routing)をサポートしません。オンプレミスに接続された`vpc-net-a`とピアリングされた`vpc-net-b`から`vpc-net-a`経由でオンプレミスのフォワーディングターゲットへ到達しようとしても失敗します。この場合は、`vpc-net-b`から`vpc-net-a`をターゲットとするピアリングゾーンを作成することで解決します。 + +**ピアリングゾーン**(DNS Peering)は、別のVPCネットワーク(DNSプロデューサーネットワーク)で解決される内容を、認可されたVPCネットワーク(DNSコンシューマーネットワーク)からそのまま参照できるようにするプライベートゾーンです。DNSピアリングは一方向の関係であり、VPCネットワークピアリングとは全く別の仕組みです(VPCネットワークピアリングを設定しても、DNS情報は自動的には共有されません)。推移的なDNSピアリングは1ホップまでサポートされます(最大3つのVPCネットワークを、中間の1つがホップとなる形でチェーンできます)。 + +```mermaid +flowchart LR + subgraph VPCB["消費側 VPC: vpc-net-b"] + VMB[VM] + end + subgraph VPCA["転送側 VPC: vpc-net-a"] + PZ["Peering Zone
ターゲット: vpc-net-a"] + FZ["Forwarding Zone
ターゲット: オンプレミスDNS"] + end + ONPREM[オンプレミス
DNSサーバー] + VMB -->|1: DNSクエリ| PZ + PZ -->|2: vpc-net-aの解決順序で転送| FZ + FZ -->|3: 転送| ONPREM +``` + +> **出典**: [DNS zones overview](https://docs.cloud.google.com/dns/docs/zones/zones-overview) + +### 2.4 DNSルーティングポリシーとヘルスチェック + +Cloud DNSは、パブリック・プライベート両方のゾーンのリソースレコードセットに対して3種類のルーティングポリシーを設定でき、トラフィックを特定の条件に応じて誘導できます。フォワーディングゾーン・DNSピアリングゾーン・マネージドリバースルックアップゾーン・Service Directoryゾーンにはルーティングポリシーを設定できません。 + +| ポリシー | 概要 | +| --- | --- | +| Weighted Round Robin(WRR) | DNS名に対する各レコードセットに異なる重みを割り当て、その比率でトラフィックを分散する。Active-ActiveやActive-Passive構成、本番/実験バージョン間のトラフィック分割などに使用。Geolocationポリシーとの併用は不可 | +| Geolocation | 送信元の地理的位置(Googleリージョン)を特定のDNSターゲットにマッピングする。送信元が完全一致しない場合は最も近いポリシーが適用される | +| Failover | アクティブ/バックアップ構成による高可用性を実現する。アクティブ集合がすべて不健全になった場合にバックアップ集合へ切り替える | + +Geolocationポリシーは、Geofence(地理フェンス)を併用することで、そのリージョン内のすべてのエンドポイントが不健全であっても強制的にそのリージョンへトラフィックを固定できます(Geofence無効時は自動的に次に近いリージョンへフェイルオーバーします)。 + +Failoverポリシーでは、バックアップ集合への切り替え時に「trickle」(徐々にトラフィックを流す)機能を使い、0〜1の割合でバックアップへのトラフィック比率を段階的に検証できます(典型値は0.1)。 + +```mermaid +flowchart TD + A[DNSルーティングポリシーを選択] --> B["WRR
(Weighted Round Robin)"] + A --> C[Geolocation] + A --> D[Failover] + B --> B1[重み比率でトラフィック分散
ヘルスチェック対応] + C --> C1[送信元リージョンに
最も近いターゲットへ] + C --> C2{Geofence有効か} + C2 -->|Yes| C3["不健全でもそのリージョンに固定
(全IPを応答)"] + C2 -->|No| C4[次に近いリージョンへ
自動フェイルオーバー] + D --> D1[Active集合を常に応答] + D --> D2{Active集合が
全て不健全か} + D2 -->|Yes| D3["Backup集合へ切替
(trickle比率設定可)"] +``` + +ヘルスチェックは、内部Application Load Balancer(リージョン/クロスリージョン)、内部パススルーNetwork Load Balancer、内部プロキシNetwork Load Balancer(プレビュー)、そして外部エンドポイントに対応します。内部パススルーNetwork Load Balancerの場合、Cloud DNSはバックエンドインスタンス単位のヘルス情報を確認し、デフォルトで20%のインスタンスが健全であればエンドポイント全体を健全と判定します。外部エンドポイントに対するヘルスチェックは、3つのGoogle Cloudソースリージョンからそれぞれ3つのプローバー(合計9プローバー)で実施され、TCP・HTTP・HTTPSプロトコルに対応します(SSL・HTTP/2・gRPCは非対応)。 + +DNSSECを有効化したマネージドゾーンでヘルスチェックを併用する場合、各ポリシーアイテム内で使用できるIPアドレスは1つのみに制限されます。 + +ルーティングポリシーがサポートするレコードタイプはA・AAAA・CNAME・MX・SRV・TXTですが、ヘルスチェックが有効なのはA・AAAAレコードのみです。 + +> **出典**: [DNS routing policies and health checks](https://docs.cloud.google.com/dns/docs/routing-policies-overview) + +### 2.5 DNSSEC(DNS Security Extensions) + +DNSSECは、DNSルックアップへの応答を認証する仕組みであり、プライバシー保護は提供しませんが、DNS応答の改ざん・ポイズニング攻撃を防止します。DNSSECを完全に機能させるには、以下の3か所すべてで有効化・設定が必要です。 + +1. **DNSゾーン**: Cloud DNSでDNSSECを有効化すると、DNSKEYレコードの作成・ローテーション、およびRRSIGレコードによるゾーンデータの署名が自動的に管理されます。 +2. **トップレベルドメイン(TLD)レジストリ**: ドメインレジストラでDNSSECを有効化し、ゾーン内のDNSKEYレコードを認証するDSレコードをレジストリに登録する必要があります。レジストラ・レジストリの両方がDNSSECに対応していない場合、Cloud DNS側でDNSSECを有効化しても効果がありません。 +3. **DNSリゾルバ**: 完全な保護のためには、DNSSEC署名済みドメインの署名を検証するリゾルバを使用する必要があります(Google Public DNSなどの検証対応パブリックリゾルバを利用可能)。 + +Cloud DNSは、DNSSECが有効化された状態のゾーンを、信頼チェーンを切断することなく他のDNSオペレータとの間で移行(マイグレーション)することもサポートしています。 + +> **出典**: [DNS Security Extensions (DNSSEC) overview](https://docs.cloud.google.com/dns/docs/dnssec) + +### 2.6 DNSサーバーポリシー(Inbound / Outbound) + +DNSサーバーポリシーは、VPCネットワーク単位でDNS解決に使用するDNSサーバーを制御する仕組みで、インバウンド・アウトバウンドのいずれか、または両方を同時に構成できます。 + +**インバウンドサーバーポリシー**は、VPCネットワークのCloud DNS名前解決サービスを、Cloud VPNトンネル・Cloud Interconnect VLANアタッチメント・Router Applianceで接続されたオンプレミスネットワークからも利用可能にします。有効化すると、適用対象VPCネットワーク内のすべてのサブネット(プロキシ専用サブネットやPrivate NAT用サブネットを除く)ごとに、プライマリIPv4範囲から内部IPv4アドレスの「インバウンドサーバーポリシーエントリポイント」が作成されます。 + +インバウンドサーバーポリシーエントリポイントはVPCネットワークピアリングやNetwork Connectivity Center(NCC)の境界を越えて到達できないため、必ずハイブリッド接続を受け取るVPCネットワーク自体にローカルポリシーとしてデプロイする必要があります(ピアリングされた別ネットワークのレコードを解決したい場合は、そちらにDNSピアリングゾーンを作成します)。 + +**アウトバウンドサーバーポリシー**は、代替ネームサーバーのリストを指定してVPCネットワークの名前解決順序を変更する仕組みです。代替ネームサーバーが1つでも設定されると、GKEクラスタスコープのレスポンスポリシーやプライベートゾーンにマッチしない限り、すべてのクエリが代替ネームサーバーへ送信されます。多くのCloud DNS機能(プライベートゾーン、ピアリング等)の解決が無効化される点に注意が必要です。 + +```mermaid +flowchart LR + subgraph ONPREM[オンプレミス] + OS[オンプレミスDNSサーバー] + end + subgraph VPC[VPCネットワーク] + IN["Inbound Server Policy
Entry Point
(サブネットごとの内部IP)"] + RES["VPCネットワーク内の
プライベートゾーン等を解決"] + OUT["Outbound Server Policy
(代替ネームサーバー指定)"] + MD["VMメタデータサーバー
169.254.169.254"] + end + OS -->|1: 問い合わせ| IN + IN -->|2: 解決| RES + MD -->|3: 通常クエリ| OUT + OUT -->|4: 代替ネームサーバーへ転送| OS +``` + +代替ネームサーバーの区分(Type 1〜3)はフォワーディングターゲットと同様に、ルーティング方式・ネットワーク要件が定義されています。とくにType 1・Type 2の場合、Cloud DNSは`35.199.192.0/19`を送信元としてクエリを送るため、オンプレミス側・代替ネームサーバー側の双方で、このレンジからのTCP/UDPポート53を許可するファイアウォールルールが必要です。 + +> **出典**: [DNS server policies](https://docs.cloud.google.com/dns/docs/server-policies-overview) + +### 2.7 クロスプロジェクトバインディング vs DNSピアリング + +Shared VPC環境では、DNSネームスペースの所有権をどのプロジェクトに置くかという設計判断が発生します。 + +| 観点 | DNSピアリングのみの構成 | クロスプロジェクトバインディング | +| --- | --- | --- | +| ゾーンの作成・管理 | 各サービスプロジェクトが独自のVPCネットワークを持ち、そこにゾーンを作成してホストプロジェクトとピアリングする | サービスプロジェクトが直接ゾーンを作成・管理し、Shared VPCネットワークにバインドする | +| プレースホルダーVPCの要否 | 各サービスプロジェクトに個別のVPCネットワークが必要になりがち | 不要(プレースホルダーVPCを用意する必要がない) | +| ホストプロジェクト管理者の負担 | サービスプロジェクトの管理も担うことが多い | サービスプロジェクトの管理はサービスプロジェクト側に委譲できる | +| IAMの適用範囲 | プロジェクトレベルで適用 | 同様にプロジェクトレベルで適用される | +| 推移的な解決のホップ制限 | ピアリングは1ホップまで | すべてのDNSゾーンがShared VPCネットワークに直接紐づくため、ホップ制限がなくHub&Spoke設計が可能 | +| Any-to-Any解決 | 個別設定が必要になりがち | Shared VPCネットワーク内のどのVMからも紐づくゾーンを解決可能 | + +```mermaid +flowchart TD + subgraph A[DNSピアリングのみの構成] + H1[ホストプロジェクト
VPCネットワーク] + S1[サービスプロジェクト1
個別VPC + Peering Zone] + S2[サービスプロジェクト2
個別VPC + Peering Zone] + end + subgraph B[クロスプロジェクトバインディング構成] + H2[ホストプロジェクト
Shared VPCネットワーク] + Z1[サービスプロジェクト1が
作成・保有するゾーン] + Z2[サービスプロジェクト2が
作成・保有するゾーン] + H2 -.バインド.-> Z1 + H2 -.バインド.-> Z2 + end +``` + +クロスプロジェクトバインディングは、Shared VPCのサービスプロジェクトごとにDNSネームスペースの所有権を分離したい場合(部門やビジネスユニットが異なる組織構造など)に特に有効です。 + +> **出典**: [DNS zones overview](https://docs.cloud.google.com/dns/docs/zones/zones-overview) + +### 2.8 GKEにおけるCloud DNS + +GKEクラスタのDNSは、Kubernetesの標準的なService Discoveryの延長として提供されます。デフォルトのDNSプロバイダはkube-dnsですが、Cloud DNSをGKEのDNSプロバイダとして選択することもできます。 + +| 項目 | kube-dns | Cloud DNS for GKE | +| --- | --- | --- | +| 実装形態 | クラスタ内で稼働するPod(自前でスケーリング・監視が必要) | Googleがフルマネージドで提供する権威DNS | +| 監視・スケーリングの手間 | 必要 | 不要(マネージドサービス) | +| Cloud LoggingとしてのDNS監視統合 | 個別対応が必要 | Cloud Loggingとネイティブに統合 | +| 対応レコード | A/AAAA/SRV/PTR等(PTRはレスポンスポリシールールで実装) | 同様にフルサポート | +| DNSスコープ | クラスタスコープのみ(`*.cluster.local`) | GKEクラスタスコープ、またはVPCスコープ(クラスタ内Serviceの名前をVPC全体から解決可能)を選択可能 | + +GKEクラスタでCloud DNSを使う場合でも、クラスタ外部からServiceを名前解決できるようにするには、引き続きLoad Balancerでの公開とDNSインフラへの登録が必要です(Cloud DNSがServiceのClusterIP・ヘッドレス・ExternalNameを自動登録するのは、あくまでクラスタ内部の解決のためです)。 + +**NodeLocal DNSCache**は、各ノード上でDaemonSetとして動作するDNSキャッシュアドオンで、kube-dns・Cloud DNSいずれの構成でも併用できます。GKE Autopilotクラスタではデフォルトで有効(無効化不可)、GKE Standardクラスタの新しいバージョンではデフォルトで有効(無効化可能)です。PodのDNSリクエストはまずノードローカルのキャッシュに向かい、キャッシュミス時にkube-dnsまたはCloud DNSへフォワードされます。 + +外部からGKEのService・IngressのDNSレコードを自動的に管理したい場合は、OSSの**external-dns**コントローラを利用するのが一般的なパターンです。external-dnsはクラスタ内のService・Ingressリソースを監視し、対応するレコードをCloud DNSへ自動的に反映します。 + +```mermaid +flowchart TD + Pod[Pod] --> MD["ノードのメタデータサーバー
169.254.169.254"] + MD --> NLD{NodeLocal DNSCache
有効か} + NLD -->|Yes: ローカルキャッシュ| Cache[ノードローカル
DNSキャッシュ] + NLD -->|No| Provider + Cache -->|キャッシュミス時| Provider{DNSプロバイダ} + Provider -->|kube-dns| KD["kube-dnsポッド
(cluster.local)"] + Provider -->|Cloud DNS for GKE| CD[Cloud DNS
コントローラ管理ゾーン] + Ext[external-dns
コントローラ] -.Ingress/Service監視.-> CD +``` + +> **出典**: +- [About Cloud DNS for GKE](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/about-cloud-dns) +- [Service discovery and DNS](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/service-discovery) +- [Set up NodeLocal DNSCache](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/nodelocal-dns-cache) + +### 2.9 他プロバイダからCloud DNSへの移行 + +既存のDNSプロバイダからCloud DNSへドメインを移行する場合の標準的な手順は以下のとおりです。 + +```mermaid +flowchart TD + A[マネージドゾーンの作成
gcloud dns managed-zones create] --> B[既存プロバイダから
ゾーンファイルをエクスポート
BIND形式 or YAML形式] + B --> C["gcloud dns record-sets import
でレコードをインポート"] + C --> D[digコマンドで
Cloud DNSネームサーバーへの
反映を確認] + D --> E[レジストラの
ネームサーバー設定を変更] + E --> F["dig +short NS
で伝播を最終確認"] +``` + +インポート時の注意点として、インポートファイルにゾーンApexのNS・SOAレコードが含まれている場合、Cloud DNSが自動生成するNS・SOAレコードと競合します。既存のCloud DNSレコードを優先する(推奨)場合はインポートファイルからNS・SOAレコードを削除し、権威DNSが他プロバイダとの分割構成(マルチプロバイダ構成)でCloud DNS以外のSOAを使いたい場合は`--delete-all-existing`フラグを使用します。 + +また、一部のDNS実装は末尾のピリオドなしでBINDゾーンファイルをエクスポートすることがあります。Cloud DNSはRFC標準に従い、末尾ピリオドのないドメイン名をゾーンの相対名として解釈するため、インポート前に確認が必要です。 + +Google Cloudは、複数のDNSプロバイダを併用してDNS基盤の可用性・冗長性を高める「マルチプロバイダDNS」構成も、OSSの`octoDNS`をベースに公式にサポートしています。この構成ではCloud DNSをActive-Active(推奨)またはActive-Passiveの一方として使い、レジストラ側のNSレコードに複数プロバイダのネームサーバーを含めます。 + +> **出典**: +- [Migrate to Cloud DNS](https://docs.cloud.google.com/dns/docs/migrating) +- [Best practices for Cloud DNS](https://docs.cloud.google.com/dns/docs/best-practices) + +### 2.10 ハイブリッドDNSのリファレンスアーキテクチャとベストプラクティス + +オンプレミスとGoogle Cloudが混在するハイブリッド環境では、以下の3つのDNS解決方式のいずれかを選択できますが、Googleは「2つの権威DNSシステムを使うハイブリッドアプローチ」を推奨しています。 + +| アプローチ | 概要 | 主なトレードオフ | +| --- | --- | --- | +| ハイブリッド(2つの権威DNS、推奨) | Cloud DNSがGoogle Cloud側を、既存のオンプレミスDNSサーバーがオンプレミス側を、それぞれ権威的に解決する | 双方向フォワーディングの設定が必要になるが、レイテンシと運用の分離のバランスが良い | +| オンプレミスに解決を集約 | オンプレミスDNSサーバーを唯一の権威とし、Google Cloudからは代替ネームサーバーで全クエリを転送 | 既存ツール・拒否リストを流用できるが、Google Cloudからのクエリレイテンシが増加し、オートスケールとの相性が悪化しうる | +| Cloud DNSに解決を集約 | Cloud DNSを唯一の権威とし、インバウンドフォワーディングでオンプレミスからの問い合わせに対応 | オンプレミス側の高可用DNSサーバー維持が不要になるが、オンプレミスからのクエリレイテンシが増加する | + +命名規則としては、オンプレミスとGoogle Cloudで別々のサブドメイン(例: `corp.example.com`と`gcp.example.com`)を使う構成が推奨パターンです。同一ドメインを両者で共有する構成は、単一の権威DNSシステムでしか運用できず、ハイブリッド環境の管理を複雑にするため避けるべきとされています。 + +代表的なリファレンスアーキテクチャの1つとして、ハブ&スポークVPC構成(VPCネットワークピアリングでハブとスポークを接続し、ハブがオンプレミスとの接続を集約する構成)を見てみます。 + +```mermaid +flowchart TD + ONPREM["オンプレミス
corp.example.com"] <-->|Interconnect/VPN| HUB + subgraph HUB[ハブVPCネットワーク] + HFWD["Forwarding Zone
corp.example.com"] + HPOLICY[Inbound Server Policy] + end + HUB -->|DNS Peering| SPOKE1["スポークVPC1
projectX.gcp.example.com"] + HUB -->|DNS Peering| SPOKE2["スポークVPC2
projectY.gcp.example.com"] + SPOKE1 -->|DNS Peering| HUB + SPOKE2 -->|DNS Peering| HUB +``` + +この構成のポイントは次のとおりです。 + +1. 各スポークVPCが自身のプライベートゾーン(例: `projectX.gcp.example.com`)を保有する。 +2. ハブVPCのホストプロジェクトでインバウンドサーバーポリシーを有効化する。 +3. ハブVPC内に`corp.example.com`用のフォワーディングゾーンを作成し、オンプレミスDNSサーバーへアウトバウンド転送する。 +4. ハブVPCから各スポークVPCへ、それぞれの`projectX.gcp.example.com`をターゲットとするDNSピアリングゾーンを作成する。 +5. 各スポークVPCからハブVPCへ、`example.com`(オンプレミス側)をターゲットとするDNSピアリングゾーンを作成する。 +6. オンプレミスDNS側で`gcp.example.com`をハブVPCのインバウンドフォワーダーIPアドレスへ転送するよう設定する。 + +ベストプラクティスとして特に押さえておくべき点は以下のとおりです。 + +- 複数のVPCネットワークが同じオンプレミスDNSサーバーへアウトバウンド転送する構成は、DNSピアリングを使わずに個別設定すると失敗します(すべてのクエリの送信元が`35.199.192.0/19`という共通レンジになるため、応答を正しくルーティングできません)。1つのVPCネットワークにアウトバウンド転送を集約し、他のVPCネットワークはそこへDNSピアリングする設計が推奨されます。 +- VPCネットワークピアリングとDNSピアリングは別物であり、片方を設定しても他方は自動的には有効になりません。 +- 自動生成される`.internal`ゾーン(VMの内部DNS名)をオンプレミスから解決したい場合は、それらをハブプロジェクトにピアリングして集約するパターンが有効です。 +- オンプレミス・Google Cloud双方のファイアウォールで、`35.199.192.0/19`からのDNSトラフィック(TCP/UDPポート53)を許可する。 + +> **出典**: [Best practices for Cloud DNS](https://docs.cloud.google.com/dns/docs/best-practices) + +--- + +## Part 3: IPアドレス管理(IPAM) + +### 3.1 IPアドレスの分類体系 + +Google CloudのIPアドレスは、複数の軸で分類されます。まずは全体像を整理します。 + +```mermaid +flowchart TD + IP[Google CloudのIPアドレス] --> INT["内部IPアドレス
(Internal)"] + IP --> EXT["外部IPアドレス
(External)"] + INT --> PRIV["プライベートIP
(RFC1918等)"] + INT --> PUPI["プライベート利用の
パブリックIP (PUPI)"] + EXT --> PUB[パブリックルーティング可能] + INT --> EPH1[エフェメラル] + INT --> STAT1["静的 (予約済み)"] + EXT --> EPH2[エフェメラル] + EXT --> STAT2["静的 (予約済み)"] +``` + +| 分類軸 | 区分 | 説明 | +| --- | --- | --- | +| 到達性 | 内部(Internal) | インターネットから到達不可。VPCネットワーク・ピアリング済みネットワーク・オンプレミス接続内でのみ有効 | +| 到達性 | 外部(External) | インターネットに公開されるパブリックルーティング可能なアドレス | +| ルーティング可否 | プライベート | インターネット上でルーティングされないアドレス空間(内部アドレスとしてのみ使用可能) | +| ルーティング可否 | パブリック | インターネットルーティング可能なアドレス空間。外部IPは常にパブリックIPだが、サブネットのプライマリ/セカンダリ範囲としてパブリックIPを内部的に使う場合は「PUPI(プライベート利用のパブリックIP)」と呼ぶ | +| スコープ | リージョナル | 特定リージョンのリソースに紐づく | +| スコープ | グローバル | PSC Google APIエンドポイントやPrivate Services Accessの割当レンジなど、リージョンに依存しない | +| ライフサイクル | エフェメラル | リソースのライフサイクルに紐づき、リソース削除・停止時に解放される | +| ライフサイクル | 静的(予約済み) | 明示的に解放するまでプロジェクトに割り当てられ続ける | + +Cloud NATの自動IPアドレス割当は、静的アドレスとして表示されますが、Cloud NATゲートウェイの削除や手動アドレスへの切り替え時には削除される点、HA VPNのインターフェースには静的IPを手動指定できず、ゲートウェイ作成時に自動生成される2つの外部IPが削除まで割り当てられ続ける点など、いくつかの例外があります。 + +> **出典**: [IP addresses](https://docs.cloud.google.com/vpc/docs/ip-addresses) + +### 3.2 サブネットのIPv4アドレス範囲設計 + +サブネットのIPv4範囲設計は、IPAM戦略の中核です。まず、有効な内部IPv4範囲を整理します。 + +| カテゴリ | 範囲 | 説明 | +| --- | --- | --- | +| プライベートIPv4アドレス | `10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16` | RFC 1918 | +| プライベートIPv4アドレス | `100.64.0.0/10` | RFC 6598(共有アドレス空間) | +| プライベートIPv4アドレス | `192.0.0.0/24` | RFC 6890(IETFプロトコル割当) | +| プライベートIPv4アドレス | `192.0.2.0/24`、`198.51.100.0/24`、`203.0.113.0/24` | RFC 5737(ドキュメント用) | +| プライベートIPv4アドレス | `192.88.99.0/24` | RFC 7526(IPv6toIPv4リレー、非推奨) | +| プライベートIPv4アドレス | `198.18.0.0/15` | RFC 2544(ベンチマークテスト) | +| プライベートIPv4アドレス | `240.0.0.0/4` | Class E(将来利用のための予約) | +| プライベート利用のパブリックIPv4アドレス(PUPI) | 上記以外の任意のパブリックIPv4(禁止範囲を除く) | 通常はインターネットルーティング可能だが、VPCネットワーク内で私的に使用。Googleはこれらをインターネットへ広告せず、インターネットからのトラフィックもルーティングしない | + +サブネット範囲には以下のような制約もあります。 + +- 最小のプライマリ・セカンダリ範囲サイズは8アドレス(`/29`)。 +- 使用できる最大の範囲は`/4`ですが、多くの制約により実質的には`/8`程度に収めることが推奨されます。 +- サブネット範囲は複数のRFC範囲にまたがることはできません(例: `192.0.0.0/8`は`192.168.0.0/16`と`192.0.0.0/24`の両方を含むため無効)。 +- サブネット範囲は「制限範囲」と一致・より狭い・より広いいずれの形でも重ならないようにする必要があります(例: `169.0.0.0/8`はリンクローカル範囲`169.254.0.0/16`と重複するため無効)。 +- Auto ModeのVPCネットワークが使用する`10.128.0.0/9`ブロックの一部は、カスタムサブネットの範囲として使わないことが推奨されます(この範囲を使うと、Auto ModeネットワークとのVPCネットワークピアリングやCloud VPN接続ができなくなります)。 +- ゲスト OS内で`172.17.0.0/16`(Dockerのデフォルトブリッジネットワーク等)を使うソフトウェアに依存している場合、このレンジをサブネット範囲として使わないようにします。 + +サブネットのプライマリIPv4範囲の中で、最初の2つと最後の2つのアドレスは予約されており使用できません(セカンダリ範囲はすべて使用可能です)。 + +| 予約アドレス | 説明 | +| --- | --- | +| ネットワークアドレス | プライマリ範囲の最初のアドレス | +| デフォルトゲートウェイアドレス | プライマリ範囲の2番目のアドレス | +| Second-to-lastアドレス | プライマリ範囲の最後から2番目(将来利用のための予約) | +| ブロードキャストアドレス | プライマリ範囲の最後のアドレス | + +```mermaid +flowchart LR + A["サブネット (例: 10.10.0.0/20)"] --> B["プライマリ範囲
VM/内部LB/PGA/Cloud DNS
インバウンドエントリポイント等"] + A --> C["セカンダリ範囲1
(GKE Podレンジ等)"] + A --> D["セカンダリ範囲2
(GKE Serviceレンジ等)"] + B --> E[エイリアスIP範囲としても
利用可能] + C --> E +``` + +サブネットには「目的(purpose)」があり、通常のVM用サブネット(`PRIVATE`)以外にも、Private Service Connect公開用(`PRIVATE_SERVICE_CONNECT`)、プロキシ専用(`GLOBAL_MANAGED_PROXY`/`REGIONAL_MANAGED_PROXY`)、Private NAT専用(`PRIVATE_NAT`)、Shared VPCサービスをPrivate Service Connectへ移行するための`PEER_MIGRATION`など複数の種類があり、多くの場合作成後に目的を変更することはできません。 + +> **出典**: [Subnets](https://docs.cloud.google.com/vpc/docs/subnets) + +### 3.3 IPv6サポート + +VPCネットワークのサブネットは、IPv4専用・デュアルスタック・IPv6専用の3種類のスタックタイプをサポートします。IPv6範囲を持つサブネットはカスタムモードのVPCネットワークでのみサポートされ、Auto Modeネットワークやレガシーネットワークでは非対応です。 + +IPv6アドレスは、以下のようにVPCネットワーク→サブネット→VMインターフェースの階層でCIDRが割り当てられます。 + +| リソース | 範囲サイズ | 説明 | +| --- | --- | --- | +| VPCネットワーク | `/48` | 内部ULA範囲を有効化した際に、`fd20::/20`内から割り当てられるユニークローカルアドレス範囲 | +| サブネット | `/64` | 内部ならVPCの`/48`範囲から、外部ならGoogleが提供するリージョナル外部IPv6アドレスから割り当て | +| VMインターフェース | `/96` | サブネットの`/64`範囲から割り当て。外部IPv6の場合、サブネットの`/64`の前半`/65`がVMインターフェース用、後半`/65`がCloud Load Balancing用に予約されている | + +```mermaid +flowchart TD + VPC["VPCネットワーク
/48 ULA範囲 (内部の場合)"] --> SUB["サブネット
/64 範囲"] + SUB --> VM1["VMインターフェース
/96 (前半/65)"] + SUB --> LB1["Cloud Load Balancing用
/96 (外部の場合、後半/65)"] + EXT["Googleのリージョナル
外部IPv6アドレス"] --> SUBE["サブネット
/64 外部GUA範囲"] + BYOIP["BYOIP
IPv6 sub-prefix"] -.代替ソース.-> SUB + BYOIP -.代替ソース.-> SUBE +``` + +内部IPv6アドレスはUnique Local Address(ULA、RFC 4193)であり、インターネットに公開されずVM間通信のみに使用されます。外部IPv6アドレスはGlobal Unicast Address(GUA)であり、Premium Tierでのみ利用可能です。VPCネットワークの`/48`ULA範囲は、Google Cloud全体で一意である必要があり(VPCネットワークピアリング時のIPv6アドレス重複を防ぐため)、自動割当か任意の`/48`範囲の指定かを選択できます。一度割り当てた`/48`ULA範囲は変更・削除できません。 + +BYOIPを使う場合は、GUAをプライベートに(ULAと同様の役割で)内部IPv6サブネット範囲として使うことも、通常どおり外部IPv6範囲として使うことも可能です。 + +サブネットの内部`/64`範囲のうち、最初と最後の`/96`範囲はシステム用に予約されており、手動で割り当てることはできません。 + +> **出典**: [Subnets](https://docs.cloud.google.com/vpc/docs/subnets) + +### 3.4 内部レンジ(Internal Ranges)によるIPAM自動化 + +「内部レンジ(Internal Range)」は、VPCネットワーク内の内部IPv4/IPv6 CIDRブロックを予約し、その使われ方を制御するリソースです。VPCネットワークピアリング・Shared VPC・Cloud VPN・Cloud Interconnectなどでネットワークトポロジが複雑化した際に、IPAMを体系的に管理するための土台となります。 + +内部レンジには「ピアリングタイプ」と「使用タイプ」という2つの重要な属性があります。 + +**ピアリングタイプ**(VPCネットワークピアリングに対する挙動) + +| ピアリングタイプ | 説明 | +| --- | --- | +| `FOR_SELF` | 親VPCネットワークのみがこのCIDRブロックを使用可能。ピアリング先では使用不可 | +| `FOR_PEER` | ピアリング先のネットワークのみが使用可能。親ネットワークでは使用不可 | +| `NOT_SHARED` | 親ネットワーク・ピアリング先の両方が使用可能。ただしピアリング先での使用は親ネットワークから見えない形で行う必要がある | + +**使用タイプ**(親VPCネットワーク内の他リソースとの関連付け可否) + +| 使用タイプ | 説明 | +| --- | --- | +| `FOR_VPC`(デフォルト) | 親VPCネットワーク内の他のGoogle Cloudリソースと関連付け可能 | +| `EXTERNAL_TO_VPC` | 親VPCネットワーク内のリソースとは関連付け不可(オンプレミス専用の予約など) | +| `FOR_MIGRATION` | サブネット範囲の移行(別ネットワークへの移行を含む)に使用 | + +IPv4の内部レンジを自動割当する場合、以下の4種類の割当戦略から選択できます。 + +| 戦略 | 説明 | 特徴 | +| --- | --- | --- | +| `RANDOM`(デフォルト) | 空いているCIDRブロックをランダムに割当 | 同時並行での予約が最速だが、断片化しやすい | +| `FIRST_AVAILABLE` | 数値的に最も若い開始アドレスを持つブロックを割当 | 最も予測可能で連続空間を最大化するが、同時予約時の競合で遅くなりやすい | +| `RANDOM_FIRST_N_AVAILABLE` | 若い順にN個の候補ブロックを集め、その中からランダムに1つを割当 | 競合を減らしつつ連続性もある程度確保できる | +| `FIRST_SMALLEST_FITTING` | 要求サイズを収容できる最小の空きブロック(最長プレフィックス)から、最も若いアドレスのブロックを割当 | 断片化の抑制に最も優れるが、競合による遅延が最大 | + +```mermaid +flowchart TD + A[内部レンジをIPAM
自動化ツールとして活用] --> B[ピアリングタイプを選択
FOR_SELF / FOR_PEER / NOT_SHARED] + A --> C[使用タイプを選択
FOR_VPC / EXTERNAL_TO_VPC / FOR_MIGRATION] + A --> D[IPv4の場合 割当戦略を選択
RANDOM等] + B --> E[サブネット作成時に
内部レンジを参照して
重複を機械的に防止] + C --> E + D --> E + E --> F["FOR_MIGRATIONの場合:
サブネット削除後もCIDRを予約し
移行先サブネットにのみ再割当可能"] +``` + +サブネット移行のユースケース(`FOR_MIGRATION`)では、サブネットを削除するとCIDR範囲は通常解放されますが、内部レンジで予約しておくことで、削除後・再作成前の間もそのCIDRを保持し、指定した移行先サブネットにのみ割当を許可できます。移行元・移行先が異なるプロジェクトであっても利用可能です。 + +> **出典**: [Internal ranges overview](https://docs.cloud.google.com/vpc/docs/internal-ranges) + +### 3.5 BYOIP(Bring Your Own IP) + +BYOIPは、組織が自ら保有する(または利用権を持つ)パブリックIPv4/IPv6アドレスをGoogle Cloudへ持ち込み、Google Cloudのリソースに割り当てる機能です。インポート後は、いくつかの例外を除きGoogle提供のIPアドレスと同様に管理されます。BYOIPで持ち込んだアドレスは、それを持ち込んだ顧客のみが利用可能で、アイドル状態・使用中のいずれであっても追加課金は発生しません。 + +BYOIPのプロビジョニングは以下の階層で進みます。 + +```mermaid +flowchart LR + A["Public Advertised Prefix
(PAP) 作成 + 所有権検証
(ROA / 逆引きDNS)"] --> B["Public Delegated Prefix
(PDP) へ分割"] + B --> C["サブプレフィックス /
個別IPアドレスの作成"] + C --> D["Compute Engine /
Load Balancer等のリソースへ割当"] + B -.複数プロジェクトへ委譲.-> B +``` + +1. **Public Advertised Prefix(PAP)**: 持ち込むIPプレフィックス全体を表すリソース。Route Origin Authorization(ROA)や逆引きDNSによる所有権検証が必要です。 +2. **Public Delegated Prefix(PDP)**: PAPを分割し、特定のリージョンやプロジェクトに委譲するためのサブプレフィックス。 +3. **サブプレフィックス・個別IPアドレスの作成**: PDPからさらに細かい単位(個々のIPアドレスやより小さなCIDR)を切り出し、実際のリソースに割り当てます。 + +重要な注意点として、Googleは重複するBYOIPのルート広告をサポートしません。たとえば`203.0.112.0/23`をインポートしようとしても、その全体または一部(`203.0.112.0/24`など)がGoogle以外の場所で既に広告されている場合はインポートできません。同一プレフィックスが複数の場所から異なる形で広告されると、予期しないルーティングやパケットロスが発生する可能性があります。 + +組織設計としては、BYOIPアドレスの管理を専用の組織・専用プロジェクトに集約し、IAMロールでPAP・PDPの管理権限を明確に分離することが推奨されます。BYOIPアドレスはShared VPCのホストプロジェクトには委譲できますが、サービスプロジェクトへ直接委譲することはできません(ホストプロジェクトに委譲されたアドレスは、サービスプロジェクトからも利用可能です)。 + +BYOIPのプロビジョニング・削除プロセスには数週間かかることがあるため、実際に必要となるタイミングのかなり前から計画しておくことが重要です。 + +> **出典**: +- [Bring your own IP addresses](https://docs.cloud.google.com/vpc/docs/bring-your-own-ip) +- [Planning for bring your own IP addresses](https://docs.cloud.google.com/vpc/docs/byoip-planning) +- [Create a public advertised prefix](https://docs.cloud.google.com/vpc/docs/create-pap) + +### 3.6 マネージドサービスへの接続とIPアドレス割当(PSA・PSC・Serverless VPC Access) + +マネージドサービスへプライベートに接続する主要な3つの方式は、それぞれ異なるIPアドレス割当の考え方を持ちます。 + +```mermaid +flowchart TD + C1[消費者VPCネットワーク] --> A1["Allocated Range
(推奨 /16、PSA用)"] + A1 -->|VPC Peering| P1["サービスプロデューサー
ネットワークにサブネット作成
(通常/29〜/24)"] + C1 --> A2["PSCエンドポイント用
内部IPアドレス
(通常サブネット内)"] + A2 -->|Private Service Connect| P2["公開サービス
/ Google API"] + C1 --> A3["Serverless VPC Access
コネクタ専用 /28 サブネット"] + A3 --> P3[Cloud Run / Functions等から
VPCへの送信トラフィック] +``` + +**Private Services Access**(PSA)は、サービスコンシューマーのVPCネットワークとサービスプロデューサーのVPCネットワークをVPCネットワークピアリングで接続する仕組みです。ルーティングの重複を避けるため、コンシューマー側に「Allocated Range」を確保する必要があります。 + +- Googleのサービス向けには最小`/24`、推奨`/16`ブロックが必要です。 +- Allocated Rangeは、現在・将来のサブネット範囲(VPCネットワークピアリングやNCCスポークで接続されたネットワークのサブネット範囲を含む)と完全に分離しておく必要があります。 +- サービスプロデューサー側は通常、このAllocated Rangeの中から`/29`〜`/24`程度のサブネットを選んでリソースを配置します。プロデューサー側のサブネット範囲自体は選択・変更できません。 + +**Private Service Connect**(PSC)のエンドポイントは、通常のサブネット内の内部IPアドレスとして構成されます(公開サービス用の場合は`PRIVATE_SERVICE_CONNECT`目的のサブネットを使用)。PSAとは異なりVPCネットワークピアリングを必要とせず、1つの内部IPアドレスで公開サービスやGoogle APIへ到達できる点が特徴です。 + +**Serverless VPC Access**は、Cloud Run・Cloud Run functions・App EngineなどのサーバーレスワークロードからVPCネットワークへ送信トラフィックを送るためのコネクタです。コネクタには専用の`/28`サブネット(16アドレス)が必要で、他のリソースと共用できず、作成後にサイズを変更することもできません。Shared VPCを使う場合、サービスプロジェクト側でコネクタを作成するには、ホストプロジェクトのネットワーク管理者が事前にそのサブネットを手動作成しておく必要があります。 + +> **出典**: +- [Private services access](https://docs.cloud.google.com/vpc/docs/private-services-access) +- [Configure private services access](https://docs.cloud.google.com/vpc/docs/configure-private-services-access) +- [Connect to a VPC network (Serverless VPC Access)](https://docs.cloud.google.com/vpc/docs/configure-serverless-vpc-access) + +### 3.7 Cloud NATにおけるIPアドレスとポートの管理 + +Cloud NATは、Public NAT(インターネットへのアウトバウンド接続)とPrivate NAT(VPC間・オンプレミス間などプライベート接続向けのアウトバウンド接続)の2種類を提供し、それぞれIPアドレスとポートの管理方法が異なります。 + +**Public NATのIPアドレス割当** + +| 割当方法 | 特徴 | +| --- | --- | +| 自動NAT IPアドレス割当 | 選択したネットワークサービスティア(Premium/Standard)、VM数、VMあたりのポート予約数に基づき、Googleがリージョナル外部IPアドレスを自動的に追加・削除する。追加されたアドレスは静的(予約済み)として扱われるがプロジェクトのクォータには計上されない。次にどのIPアドレスが割り当てられるかは予測できないため、許可リストのように事前に把握しておく必要がある用途には不向き | +| 手動NAT IPアドレス割当 | 静的外部IPアドレスを自身で作成し手動でゲートウェイに割り当てる。許可リストとの相性が良く、IPアドレスの「ドレイン(drain)」機能(新規接続には使わず、既存接続の正常終了のみ許可)を利用できる | + +**Private NATのIPアドレス割当** + +Private NATのIPアドレスは、`purpose=PRIVATE_NAT`のサブネットのプライマリIPv4範囲から供給される、リージョナル内部IPv4アドレスです。この範囲は自動割当ができず、ゲートウェイのルール作成時に明示的にサブネットを指定します。1つのPrivate NATサブネットが提供できるNAT IPアドレス数は、サブネットのプレフィックス長`PREFIX_LENGTH`を使って次の式で求められます。 + +``` +利用可能なNAT IPアドレス数 = 2^(32 - PREFIX_LENGTH) - 4 +``` + +(各サブネットには4つの未使用アドレスが存在するため4を減算します) + +**ポート割当方式** + +| 方式 | Public NATのデフォルト | Private NATのデフォルト | 特徴 | +| --- | --- | --- | --- | +| 静的ポート割当 | ○(デフォルト) | 選択可 | VMごとに固定のポート数を割り当てる。全VMのエグレス使用量が均一な場合に適する。Endpoint-Independent Mappingを使う場合は静的ポート割当が必須 | +| 動的ポート割当 | 選択可 | ○(デフォルト) | 最小・最大ポート数を指定し、使用状況に応じて自動的に増減させる。ポート使用量にばらつきがある場合に有効 | + +```mermaid +flowchart TD + A[Cloud NATゲートウェイのタイプ] --> B[Public NAT] + A --> C[Private NAT] + B --> B1{IPアドレス割当方法} + B1 -->|自動| B2[Network Tierと使用量に応じ
外部IPを自動増減] + B1 -->|手動| B3[静的外部IPを手動割当
allowlist等に有効] + B --> B4{ポート割当方法} + B4 -->|静的 デフォルト| B5[VMごとに固定ポート数] + B4 -->|動的| B6[使用量に応じ
min〜max間で自動増減] + C --> C1["専用サブネット
(purpose=PRIVATE_NAT)から
内部IPを使用"] + C --> C4{ポート割当方法} + C4 -->|動的 デフォルト| C6[使用量に応じ自動増減] + C4 -->|静的| C5[VMごとに固定ポート数] +``` + +各NAT IPアドレスは、TCP・UDPそれぞれ64,512個のソースポート(0〜1023のウェルノウンポートを除く)を提供します。ポート予約の計算例として、Public NATで単一の手動NAT IPアドレスを使い、VMあたり最小64ポートを設定した場合、以下のように最大1,008台のVMをサポートできます。 + +``` +⌊(1 NAT IPアドレス) × (64,512ポート/アドレス) / (64ポート/VM)⌋ = 1,008台 +``` + +Private NATの場合、信頼性確保のためVMあたりの必要ポート数の「2倍」が割り当てられる点に注意が必要です。たとえば最小サイズの`/29`サブネット(8アドレス、うち4つが利用可能)でVMあたり最小64ポートを設定した場合は次のようになります。 + +``` +⌊(2^(32-29) - 4) NAT IPアドレス × (64,512ポート/アドレス) / (64ポート/VM × 2)⌋ = 2,016台 +``` + +IPアドレス・ポート割当のマッピングは時間とともに変化する可能性があるため、現在のマッピングを前提にネットワーク設定を構築すべきではない、という点も試験対策上のポイントです。 + +> **出典**: [IP addresses and ports (Cloud NAT)](https://docs.cloud.google.com/nat/docs/ports-and-addresses) + +### 3.8 IPAM設計チェックリスト + +- [ ] オンプレミス・マルチクラウド・Google Cloudの各環境で、重複しないRFC 1918アドレス空間を設計している +- [ ] RFC 1918アドレス空間が枯渇する、または断片化している場合の代替(非RFC1918範囲、PUPI、IPv6)を検討している +- [ ] Auto ModeネットワークのCIDR(`10.128.0.0/9`)や、ゲストOS内で使用中のレンジ(`172.17.0.0/16`等)との重複を避けている +- [ ] GKE用にPod範囲・Service範囲を含むセカンダリ範囲を十分な余裕を持って設計している +- [ ] 内部レンジ(Internal Ranges)を使って、サブネット作成前のIPAM予約とバッティング防止を自動化している +- [ ] Private Services Access用のAllocated Range(推奨/16)を、将来のサブネット拡張分も見込んで確保している +- [ ] Serverless VPC Access用に、各コネクタ専用の`/28`サブネットを確保している(拡張不可であることを踏まえたサイジング) +- [ ] IPv6導入時、VPCネットワークの`/48`ULA範囲、サブネットの`/64`範囲、VMの`/96`範囲という階層を理解し、外部/内部の使い分けを設計している +- [ ] BYOIPを使う場合、PAP/PDP/サブプレフィックスの委譲構造と、プロビジョニングに数週間かかることを踏まえたスケジュールを組んでいる +- [ ] Cloud NATについて、Public NAT(自動/手動IP割当、静的/動的ポート割当)とPrivate NAT(専用サブネットからの内部IP、デフォルト動的ポート割当)の違いを理解し、必要なVM数・ポート数から適切なIPアドレス数を逆算している +- [ ] IPアドレス・ポートのマッピングが時間とともに変化しうることを前提に、固定マッピングに依存したネットワーク設計(許可リスト等)を避けている、またはドレイン機能を活用した安全な運用を組んでいる + +--- + +## 試験対策チェックリスト(横断) + +- [ ] Cloud CDNが単体では機能せず、必ずExternal Application Load Balancerと組み合わせる点を説明できる +- [ ] Cloud CDNの5つの対応バックエンドタイプ(MIG・ゾーンNEG・サーバーレスNEG・Internet NEG・Cloud Storageバケット)と、それぞれの用途を区別できる +- [ ] 3つのキャッシュモード(`CACHE_ALL_STATIC`・`USE_ORIGIN_HEADERS`・`FORCE_CACHE_ALL`)の違いと、`FORCE_CACHE_ALL`のリスクを説明できる +- [ ] キャッシュキーのカスタマイズ(プロトコル/ホスト/クエリ文字列/ヘッダー/Cookie)がキャッシュヒット率に与える影響を理解している +- [ ] キャッシュ無効化とキャッシュタグの違い、無効化のレート制限、無効化を最終手段とすべき理由を説明できる +- [ ] 署名付きURLと署名付きCookieの使い分け(単一リソース vs URLプレフィックス配下の複数リソース)を説明できる +- [ ] Cloud DNSのゾーンタイプ(パブリック・プライベート・フォワーディング・ピアリング・マネージドリバースルックアップ・Service Directory・ゾーナル)を区別できる +- [ ] Split-Horizon DNSの仕組みと、最長サフィックス一致による名前解決順序を説明できる +- [ ] DNSルーティングポリシー(WRR・Geolocation・Failover)とヘルスチェック対応の対象(内部LB・外部エンドポイント)を説明できる +- [ ] DNSSECを機能させるために必要な3か所(ゾーン・レジストリ・リゾルバ)の設定を説明できる +- [ ] インバウンド/アウトバウンドサーバーポリシーとフォワーディングゾーンの違い、代替ネームサーバー使用時の副作用を説明できる +- [ ] クロスプロジェクトバインディングとDNSピアリングの違い(ホップ制限、所有権分離)を説明できる +- [ ] GKEにおけるkube-dnsとCloud DNS for GKEの違い、NodeLocal DNSCacheとexternal-dnsの役割を説明できる +- [ ] IPアドレスの分類(内部/外部、プライベート/パブリック/PUPI、リージョナル/グローバル、エフェメラル/静的)を体系的に説明できる +- [ ] サブネットのプライマリ・セカンダリ範囲、有効なCIDR範囲、予約済みアドレス(先頭2つ・末尾2つ)を説明できる +- [ ] IPv6のVPC(/48)→サブネット(/64)→VM(/96)という階層構造と、内部ULA・外部GUAの違いを説明できる +- [ ] 内部レンジ(Internal Ranges)のピアリングタイプ・使用タイプ・自動割当戦略の違いを説明できる +- [ ] BYOIPのPAP→PDP→サブプレフィックスという階層と、重複広告が許されない理由を説明できる +- [ ] Private Services Access・PSC・Serverless VPC Accessそれぞれで、どのようにIPアドレス範囲が確保・使用されるかを区別できる +- [ ] Cloud NATのPublic NAT/Private NATの違い、自動/手動IP割当、静的/動的ポート割当のデフォルトと使い分けを説明できる + +--- + +## 参考文献 + +**Cloud CDN** + +- [Cloud CDN overview](https://docs.cloud.google.com/cdn/docs/overview) +- [Caching overview](https://docs.cloud.google.com/cdn/docs/caching) +- [Cache invalidation overview](https://docs.cloud.google.com/cdn/docs/cache-invalidation-overview) +- [External backends specified by using internet NEGs](https://docs.cloud.google.com/cdn/docs/external-backends-internet-neg-overview) +- [Content access control](https://docs.cloud.google.com/cdn/docs/authenticate-content) +- [Content delivery best practices](https://docs.cloud.google.com/cdn/docs/best-practices) +- [Choose a CDN product](https://docs.cloud.google.com/cdn/docs/choose-cdn-product) + +**Cloud DNS** + +- [Cloud DNS overview](https://docs.cloud.google.com/dns/docs/overview) +- [DNS zones overview](https://docs.cloud.google.com/dns/docs/zones/zones-overview) +- [DNS routing policies and health checks](https://docs.cloud.google.com/dns/docs/routing-policies-overview) +- [DNS server policies](https://docs.cloud.google.com/dns/docs/server-policies-overview) +- [DNS Security Extensions (DNSSEC) overview](https://docs.cloud.google.com/dns/docs/dnssec) +- [Migrate to Cloud DNS](https://docs.cloud.google.com/dns/docs/migrating) +- [Best practices for Cloud DNS](https://docs.cloud.google.com/dns/docs/best-practices) +- [Key terms (Cloud DNS)](https://docs.cloud.google.com/dns/docs/key-terms) + +**GKEとDNS** + +- [About Cloud DNS for GKE](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/about-cloud-dns) +- [Use Cloud DNS for GKE](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/cloud-dns) +- [Service discovery and DNS (GKE)](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/service-discovery) +- [Set up NodeLocal DNSCache](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/nodelocal-dns-cache) + +**IPアドレス管理(IPAM)** + +- [IP addresses (VPC)](https://docs.cloud.google.com/vpc/docs/ip-addresses) +- [Subnets](https://docs.cloud.google.com/vpc/docs/subnets) +- [Internal ranges overview](https://docs.cloud.google.com/vpc/docs/internal-ranges) +- [Create and use internal ranges](https://docs.cloud.google.com/vpc/docs/create-use-internal-ranges) +- [Bring your own IP addresses](https://docs.cloud.google.com/vpc/docs/bring-your-own-ip) +- [Planning for bring your own IP addresses](https://docs.cloud.google.com/vpc/docs/byoip-planning) +- [Create a public advertised prefix](https://docs.cloud.google.com/vpc/docs/create-pap) + +**マネージドサービスへの接続** + +- [Private services access](https://docs.cloud.google.com/vpc/docs/private-services-access) +- [Configure private services access](https://docs.cloud.google.com/vpc/docs/configure-private-services-access) +- [Connect to a VPC network (Serverless VPC Access)](https://docs.cloud.google.com/vpc/docs/configure-serverless-vpc-access) +- [Private Service Connect overview](https://docs.cloud.google.com/vpc/docs/private-service-connect) + +**Cloud NAT** + +- [IP addresses and ports (Cloud NAT)](https://docs.cloud.google.com/nat/docs/ports-and-addresses) +- [Public NAT](https://docs.cloud.google.com/nat/docs/public-nat) +- [Private NAT](https://docs.cloud.google.com/nat/docs/private-nat) + +**公式試験情報** + +- [Google Cloud Professional Cloud Network Engineer certification](https://cloud.google.com/learn/certification/cloud-network-engineer) +- [Professional Cloud Network Engineer Exam Guide (PDF)](https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf) diff --git a/S3-load-balancing-traffic-management.html b/S3-load-balancing-traffic-management.html new file mode 100644 index 000000000..7ef16521b --- /dev/null +++ b/S3-load-balancing-traffic-management.html @@ -0,0 +1,2053 @@ + + + + + + S3: ロードバランシングとトラフィック管理 | PCNE試験対策ガイド + + + + + + + + + +
+ + +
+
+ PCNE ・ Section 3, Task 3.1 +

S3: ロードバランシングとトラフィック管理

+
+ Professional Cloud Network Engineer 試験対応ガイド — Section + 3「Configuring managed network services」Task 3.1「Configuring load + balancing」 +
+
+ +
+

+ 本ガイドは、Google Cloud Professional Cloud Network + Engineer(PCNE)認定試験の公式Exam Guideに定義されたTask 3.1「Configuring + load + balancing」の出題範囲を、中級者から上級者のネットワークエンジニア向けに実装レベルまで掘り下げて解説するものです。Section + 3全体の出題比率は約16%で、3.1(ロードバランシング)・3.2(Cloud + CDN)・3.3(Cloud + DNS)の3タスクから構成されますが、本ガイドは3.1のみを対象とします。 +

+
+ +
+

+ はじめに:このタスクの位置づけと出題範囲 +

+

+ 公式Exam Guideは、Task 3.1「Configuring load + balancing」を次の5つの観点で定義しています。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
観点内容
① LBソリューションの決定 + internal/external、regional/global、application/proxy/passthroughの区別 +
② バックエンドサービスの設定NEG・MIGを含むオートスケーリング構成
③ バックエンドの詳細設定 + バランシング方式・セッションアフィニティ・サービング容量・URLマップ・ヘルスチェック・グローバルアクセス +
④ GKEにおけるLB理解GKE Gateway controller・GKE Ingress controller・NEG
⑤ トラフィック管理 + トラフィックスプリッティング・トラフィックミラーリング・URL書き換え +
+
+

+ Section + 1(設計)で問われる「どのLBを選ぶべきか」というアーキテクチャ設計の視点に対し、Section + 3(本タスク)では実装・設定の視点、すなわち「選んだLBをどのパラメータでどう構成するか」が主眼になります。試験では、シナリオ形式で「この要件を満たすバランシングモードはどれか」「このトラフィック分割を実現するにはどのURLマップ構成が必要か」といった設定レベルの判断が問われる点に注意してください。 +

+
+

+ ロードバランサーの全体アーキテクチャと選択基準 +

+

+ Google Cloudロードバランサーの分類軸 +

+

+ Google + Cloudのロードバランサーは、次の3つの独立した軸の組み合わせで整理すると理解しやすくなります。 +

+
    +
  • + プロキシ方式:Application Load + Balancer(L7、HTTP/HTTPS/HTTP2/gRPC)/ Proxy Network Load + Balancer(L4プロキシ、TCP/SSL)/ Passthrough Network Load + Balancer(L4パススルー、クライアント送信元IPを保持) +
  • +
  • + 公開範囲:External(インターネット向け)/ + Internal(VPC内部向け) +
  • +
  • + スコープ:Global(複数リージョンにまたがる)/ + Regional(単一リージョン)/ + Cross-region(内部LBのみ、グローバルバックエンドを持つリージョナルVIP) +
  • +
+

公式ドキュメントは選択の出発点を次のように整理しています。

+
+

+ フレキシブルな機能セットが必要なHTTP(S)トラフィックにはApplication Load + Balancerを、複数リージョンのバックエンドへのTCPプロキシロードバランシングにはProxy + Network Load + Balancerを、クライアント送信元IPの保持やUDP・ESP・ICMPなどの追加プロトコルサポートが必要な場合はPassthrough + Network Load Balancerを選択します。 +

+
+

選択フローチャート

+
+flowchart TD
+    A{トラフィックの種類は?} -->|HTTP/HTTPS/HTTP2/gRPC| B[Application Load Balancer]
+    A -->|複数リージョンへのTCP/SSLプロキシ| C[Proxy Network Load Balancer]
+    A -->|送信元IP保持・UDP/ESP/ICMP等| D[Passthrough Network Load Balancer]
+    B --> E{公開範囲は?}
+    E -->|外部公開 external| F{バックエンドの分散は?}
+    E -->|VPC内部のみ internal| G{バックエンドの分散は?}
+    F -->|グローバル・マルチリージョン| H[グローバル外部<br/>Application Load Balancer]
+    F -->|単一リージョンで十分| I[リージョン外部<br/>Application Load Balancer]
+    G -->|複数リージョンのバックエンド| J[クロスリージョン内部<br/>Application Load Balancer]
+    G -->|単一リージョンのバックエンド| K[リージョン内部<br/>Application Load Balancer]
+
+

+ 出典: + https://docs.cloud.google.com/load-balancing/docs/choosing-load-balancer +

+
+

主要ロードバランサー比較表

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ロードバランサースコープ公開範囲実装方式主なユースケース
グローバル外部 Application LBグローバルExternalGFE(管理型)世界中のユーザー向けWebアプリ、マルチリージョン公開API
リージョン外部 Application LBリージョンExternalEnvoy(管理型)特定リージョンに閉じたコンプライアンス要件のあるWeb公開
リージョン内部 Application LBリージョンInternalEnvoy(管理型)マイクロサービス間のL7ロードバランシング
クロスリージョン内部 Application LBクロスリージョンInternalEnvoy(管理型)複数リージョンに分散した内部サービスへの高可用アクセス
Proxy Network LB(TCP Proxy)グローバル/リージョンExternal/InternalGFE/Envoy複数リージョンのTCPバックエンドへの単一エニーキャストIP
外部パススルー Network LBリージョンExternalパススルー(非プロキシ)送信元IP保持が必要なUDP/TCPワークロード
内部パススルー Network LBリージョンInternalパススルー(非プロキシ)内部L4ロードバランシング、NVAの次ホップ
+
+
+

出典

+ +
+

ネットワークサービスティアとの関係

+

+ ロードバランサーの種類ごとに利用可能なネットワークサービスティア(Premium/Standard)は異なります。この設計判断はSection + 1(1.1)で扱う領域と重複するため、本ガイドでは詳細を割愛しますが、実装時には「Standard + Tierではグローバル外部Application Load + Balancerを利用できない」といった制約がある点だけ押さえておいてください。 +

+
+

+ バックエンドサービスとオートスケーリングの設定 +

+

バックエンドの種類:MIG vs NEG

+

バックエンドサービスにアタッチできるバックエンドは、大きく分けて2種類です。

+
    +
  • + マネージドインスタンスグループ(MIG):Compute Engine + VMの集合。オートスケーラーと直接連携し、UTILIZATION(CPU使用率ベース)を含む全バランシングモードを利用可能。 +
  • +
  • + ネットワークエンドポイントグループ(NEG):VMやコンテナ、サーバーレスリソースなど、より粒度の細かいエンドポイントの集合。UTILIZATIONバランシングモードはサポートされません。 +
  • +
+

NEGの6分類

+
+flowchart TD
+    NEG[Network Endpoint Group] --> Z["ゾーンNEG<br/>GCE_VM_IP / GCE_VM_IP_PORT"]
+    NEG --> S["サーバーレスNEG<br/>Cloud Run / App Engine / Cloud Run functions"]
+    NEG --> I["インターネットNEG<br/>グローバル / リージョナル"]
+    NEG --> H["ハイブリッド接続NEG<br/>オンプレミス・他クラウド"]
+    NEG --> P["PSC NEG<br/>Private Service Connect"]
+    NEG --> PM["ポートマッピングNEG<br/>同一IPで複数コンテナポート"]
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NEGタイプエンドポイント形式主な用途制約
ゾーンNEG(GCE_VM_IP_PORT)IPアドレス+ポートプロキシ型LBの標準バックエンド、GKEのコンテナネイティブLBUTILIZATIONバランシング非対応。RATE/CONNECTIONのみ
ゾーンNEG(GCE_VM_IP)IPアドレスのみ(ポート指定不可) + 内部パススルーNetwork LB、外部パススルーNetwork LB(リージョン) + ポート指定不可、デュアルスタックエンドポイント不可
サーバーレスNEGCloud Run / App Engine / Cloud Run functionsサーバーレスサービスをLB配下に統合Proxy/Passthrough Network LBからは利用不可
インターネットNEGFQDN:Port または IP:Port(RFC 1918外)GCP外部(オンプレミス・他社クラウド)のバックエンドを統合 + グローバルは単一エンドポイント・ヘルスチェック非対応、リージョナルは最大256エンドポイント +
ハイブリッド接続NEGハイブリッド接続経由のオンプレミスエンドポイントCloud Interconnect/VPN経由でのオンプレミスバックエンド統合ハイブリッド接続の構成が前提
PSC NEGPrivate Service Connectで公開されたサービス別プロジェクト・別VPCのサービスへの越境接続PSCエンドポイント経由でのみ解決
+
+
+

出典

+ +
+

オートスケーリングとの連携

+

+ MIGバックエンドにオートスケーラーをアタッチすると、オートスケーラーは「ロードバランシングのサービング容量の一定割合」を維持するようにインスタンス数を増減します。たとえばMIGのサービング容量が1インスタンスあたり100RPSと定義されており、オートスケーラーの目標使用率を80%に設定した場合、オートスケーラーは各インスタンスが80RPSを維持するようにインスタンスを追加・削除します。 +

+
+

+ ベストプラクティス:NEGバックエンド(特にGKEのコンテナネイティブLB)を使う場合はUTILIZATIONが使えないため、RATEまたはCONNECTIONベースでキャパシティ計画を行い、Pod単位のHorizontal + Pod Autoscalerと組み合わせて容量を制御します。 +

+
+
+

+ 出典: + https://docs.cloud.google.com/compute/docs/autoscaler/scaling-load-balancing +

+
+
+

+ ロードバランサーとバックエンドの詳細設定 +

+

+ バランシングモードとキャパシティスケーラー +

+

+ バックエンドサービスは、バックエンドごとに「バランシングモード」と「ターゲット容量」を持ち、これに「キャパシティスケーラー」を乗算した値が実効容量になります。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
バランシングモード容量の測定基準対応バックエンド備考
UTILIZATIONインスタンスグループのCPU使用率(近似値)MIGのみ(NEG非対応)セッションアフィニティはNONEと併用すること
RATE新規HTTPリクエストのレート(RPS)MIG・NEG両方グループ全体またはエンドポイント単位で指定可能
CONNECTION新規TCPコネクション数MIG・NEG両方L4系ロードバランサーで使用
IN-FLIGHT処理中(未完了)のHTTPリクエスト数MIG・NEG両方リクエスト処理に1秒以上かかる場合、RATEの代わりに使用
+
+

+ キャパシティスケーラーは0.0または0.1〜1.0の範囲で設定でき、次のような運用パターンに使えます。 +

+
    +
  • + 段階的なドレイン:キャパシティスケーラーを0.5にすると、そのバックエンドの実効容量が半分になり、新規トラフィックの流入が抑制されます。 +
  • +
  • + 完全ドレイン:0に設定すると新規トラフィックは一切送られなくなります(バックエンドサービスに他のバックエンドが存在する場合のみ設定可能)。 +
  • +
+
+

出典

+ +
+

セッションアフィニティ

+

+ セッションアフィニティは、同一クライアントからの後続リクエストを可能な限り同じバックエンドに送るための仕組みです。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
アフィニティ種別ハッシュ対象適したケース注意点
NONEなし(デフォルト)ステートレスなアプリケーション最も均等な分散が得られる
CLIENT_IP送信元・宛先IPの2-tupleNAT配下にクライアントが少ないL4/L7ワークロード + 多数のクライアントが同一送信元IP(NAT)を共有すると偏りが生じる +
GENERATED_COOKIELBが発行するCookieHTTP(S)ワークロードでの一般的な選択肢NATやIPアドレス変化の影響を受けない
HTTP_COOKIEアプリケーション側が発行する既存Cookieアプリケーションが既にセッションCookieを持つ場合Cookie名の指定が必要
HEADER_FIELD指定したHTTPヘッダーの値ユーザーIDなどをヘッダーで伝搬するAPIクライアント + ロードバランシングロケーションポリシーがRING_HASHまたはMAGLEVである必要がある +
+
+
+

+ セッションアフィニティは認証やセキュリティの目的では使用しないでください。バックエンドの健全性やスケール状況によって、ベストエフォートでしか維持されません。 +

+
+
+

+ ベストプラクティス:UTILIZATIONバランシングモードと組み合わせて使用しないこと。ウェイト付きトラフィックスプリッティングを設定した場合、セッションアフィニティの設定より分割設定が優先されるため、両者を同時に有効化しないことが推奨されています。 +

+
+
+

出典

+ +
+

URLマップの構造

+
+flowchart TD
+    UM[URLマップ] --> HR["ホストルール<br/>例: example.com"]
+    HR --> PM2[パスマッチャー]
+    PM2 --> PR1["パスルール /video/*"]
+    PM2 --> PR2["パスルール /images/*"]
+    PM2 --> DEF[デフォルトサービス]
+    PR1 --> BS1["バックエンドサービス: video"]
+    PR2 --> BS2["バックエンドサービス: images"]
+    DEF --> BS3["バックエンドサービス: web-default"]
+

+ URLマップはホストルール(どのドメインに適用するか)→パスマッチャー(パスパターンの集合)→パスルール(個々のパスと転送先)という階層構造を持ちます。パスルールの代わりにルートルール(routeRules)を使うことも可能ですが、両者は同一のパスマッチャー内で併用できません。ルートルールは順序評価される点がパスルールと異なります。 +

+
+

+ 出典: + https://docs.cloud.google.com/load-balancing/docs/https/traffic-management +

+
+

ヘルスチェック

+
+flowchart TD
+    P["ヘルスチェックプローブ<br/>送信元: 130.211.0.0/22, 35.191.0.0/16"] --> FW{ファイアウォールルール<br/>ingress allow}
+    FW -->|許可| VM["バックエンドVM / Pod"]
+    FW -->|未許可| Fail[全バックエンドがUNHEALTHYに]
+    VM --> Resp{応答}
+    Resp -->|200 OK| Healthy[HEALTHY]
+    Resp -->|それ以外・タイムアウト・リダイレクト| Unhealthy[UNHEALTHY]
+

+ 多くのGoogle + Cloudロードバランサーのヘルスチェックプローブは、130.211.0.0/22と35.191.0.0/16のアドレス範囲から送信されます。外部パススルーNetwork + Load + Balancerでは、これに加えて209.85.152.0/22と209.85.204.0/22も使用されます。VPCファイアウォールがデフォルト拒否である以上、これらの範囲からのIngressを明示的に許可するファイアウォールルールがなければ、アプリケーションが正常に動作していても全バックエンドがUNHEALTHYと判定されます。これは試験でも実務でも最頻出のトラブルシューティングシナリオです。 +

+

+ 判定基準は「チェック間隔」「タイムアウト」「healthy閾値(連続成功回数)」「unhealthy閾値(連続失敗回数)」の4パラメータで構成され、プロトコルはHTTP/HTTPS/HTTP2/TCP/SSL/gRPCから選択できます。ヘルスチェックはHTTPリダイレクト(3xx)を失敗として扱うため、HTTPをHTTPSへ強制リダイレクトしているアプリケーションでヘルスチェックパスまでリダイレクトしてしまうと誤検知の原因になります。 +

+
+

+ ベストプラクティス:ヘルスチェックには本番トラフィックのエンドポイントとは別の軽量な専用パス(例:/healthz)を用意し、200固定を返すようにします。GKEのNEGバックエンドでは、ヘルスチェックはノードIPではなくPod + IPに対して直接行われるため、NetworkPolicyやPodのファイアウォール設定も併せて確認する必要があります。 +

+
+
+

出典

+ +
+

+ グローバルアクセス(内部ロードバランサー) +

+
+flowchart TD
+    C1["クライアント(asia-east1)"] -->|グローバルアクセス有効| ILB["内部LB VIP<br/>us-central1"]
+    C2["クライアント(europe-west1)"] -->|グローバルアクセス有効| ILB
+    C3["クライアント(us-central1・同一リージョン)"] -->|常にアクセス可能| ILB
+    ILB --> BE["バックエンド(us-central1)"]
+

+ リージョン内部Application Load + Balancerは、デフォルトでは同一リージョンのクライアントからのみアクセス可能です。フォワーディングルールで「グローバルアクセス」を有効化すると、VPC内の任意のリージョンからクライアントがアクセスできるようになります。一方、クロスリージョン内部Application + Load + Balancerはグローバルアクセスが常に有効であり、さらにバックエンド自体を複数リージョンに配置できる点がリージョン内部LBとの決定的な違いです。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
比較項目リージョン内部 Application LBクロスリージョン内部 Application LB
VIPの割り当て特定リージョンのサブネットから割り当て + 特定リージョンのサブネットから割り当て(複数リージョンのVIPが同一バックエンドサービスを共有可) +
クライアントアクセスデフォルトは同一リージョンのみ、グローバルアクセスで拡張可常にグローバルアクセス可能
バックエンドの分散単一リージョンのみ複数リージョンに分散可能
フェイルオーバーリージョン内のみリージョンをまたいだ自動フェイルオーバー
+
+
+

出典

+ +
+
+

GKEにおけるロードバランシング

+

+ GKEのロードバランシングは、レガシーなGKE Ingress controllerと、Kubernetes公式仕様に準拠したGKE Gateway controllerの2系統が併存しています。両者の違いを理解しておくことは、GKEネットワーキング設計(Section + 1.4)と実装(本タスク)の橋渡しとして重要です。 +

+

GKE Ingress controller(レガシー)

+
+flowchart TD
+    Ing[Ingressリソース] --> IC[GKE Ingress controller]
+    IC --> CLB["Classic Application Load Balancer<br/>固定"]
+    CLB --> NEG1["GCE_VM_IP_PORT ゾーンNEG<br/>(インスタンスグループも可)"]
+

+ GKE Ingress controllerが作成する外部Ingressは常にClassic Application Load + Balancerとして実装されます。GKE + ServiceのNEGアノテーションを使えばGCE_VM_IP_PORTゾーンNEGを優先的にバックエンドとして利用しますが、インスタンスグループバックエンドもサポートされます。 +

+

+ GKE Gateway controller(Gateway API) +

+
+flowchart TD
+    GC[GatewayClass] --> GW[Gatewayリソース]
+    GW --> HR2[HTTPRouteリソース]
+    HR2 --> BS4[バックエンドサービス1]
+    HR2 --> BS5[バックエンドサービス2]
+

+ GKE Gateway controllerはKubernetes Gateway + APIの実装であり、責務が3つのリソースに分離されている点がIngressとの本質的な違いです。 +

+
    +
  • + GatewayClass:使用するロードバランサーの実装を決定するクラスタスコープのテンプレート(GKEが提供) +
  • +
  • + Gateway:実際のロードバランサーインスタンスを表すリソース(フロントエンド設定) +
  • +
  • + HTTPRoute:ルーティングルールを定義するリソース(アプリケーションチームが管理) +
  • +
+

+ この分離により、プラットフォームチームがGatewayのインフラ設定を管理し、アプリケーションチームがクラスタ全体の権限を持たずに自分たちのHTTPRouteだけを管理する、という役割分担が可能になります。GKE + Gateway + controllerは常にGCE_VM_IP_PORTゾーンNEGバックエンドを使用し、Ingressと異なりヘルスチェックパラメータを自動推測しないため、明示的なHealthCheckPolicyの設定が必要です。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
比較項目GKE Ingress controllerGKE Gateway controller
準拠仕様GKE独自のIngress拡張(アノテーションベース)Kubernetes Gateway API(標準仕様)
実装されるLB常にClassic Application Load BalancerGatewayClasseに応じて外部/内部・global/regionalを選択可能
リソース構成Ingressリソース1つに集約GatewayClass/Gateway/HTTPRouteに分離
トラフィック分割非対応(1ルートにつき1バックエンドのみ)HTTPRouteでネイティブにトラフィックスプリッティング対応
マルチテナンシーIngressリソースの所有者が全ルールを管理名前空間をまたいだルーティング委譲が可能
ヘルスチェックパラメータを自動推測HealthCheckPolicyによる明示設定が必要
+
+
+

出典

+ +
+

+ NEGとContainer-Native Load Balancing +

+

+ GKEでNEGアノテーションを使うと、ロードバランサーはノードIPではなくPod + IPに対して直接ヘルスチェック・トラフィック送信を行います(Container-Native Load + Balancing)。これにより、ノードを経由するiptables/kube-proxyのホップが省略され、レイテンシが改善するとともに、ロードバランサーがPodの正確な健全性を把握できるようになります。 +

+
+

+ ベストプラクティス:新規のGKEワークロードでは、レガシーのIngress + + アノテーションではなく、GKE Gateway + controllerとHTTPRouteの組み合わせを第一候補として設計します。標準仕様に準拠しているため、将来的な移植性が高く、トラフィックスプリッティングやヘッダーベースルーティングもアノテーション無しでネイティブに扱えます。 +

+
+
+

+ Application Load Balancerでのトラフィック管理 +

+

+ Application Load + Balancer(グローバル外部・リージョン外部・内部いずれも共通の枠組み)は、URLマップのルートアクションとして、単一バックエンドへの転送に加えて次の高度なトラフィック管理機能を提供します。 +

+

+ トラフィックスプリッティング(カナリアリリース) +

+
+flowchart LR
+    C[クライアント] --> LB[ロードバランサー]
+    LB -->|重み 950/1000 = 95%| SvcA["バックエンドサービスA<br/>安定版"]
+    LB -->|重み 50/1000 = 5%| SvcB["バックエンドサービスB<br/>カナリア版"]
+

+ weightedBackendServicesを使うと、0〜1000の重みで複数のバックエンドサービスにトラフィックを配分できます。カナリアリリースやブルー/グリーンデプロイの段階的なロールアウトに使われる代表的な手法です。 +

+
+

+ 注意:ウェイト付きトラフィックスプリッティングとセッションアフィニティは同時に設定しないでください。両方が設定された場合、トラフィックスプリッティングの重みが優先されます。 +

+
+

トラフィックミラーリング

+
+sequenceDiagram
+    participant Client as クライアント
+    participant LB as ロードバランサー
+    participant Primary as プライマリbackend
+    participant Mirror as ミラーbackend
+    Client->>LB: リクエスト送信
+    LB->>Primary: リクエスト転送
+    LB--)Mirror: リクエストを複製送信(fire-and-forget)
+    Primary-->>LB: レスポンス
+    LB-->>Client: レスポンス返却
+    Note over Mirror: レスポンスは待たず破棄。<br/>ログ・メトリクスも記録されない
+

+ requestMirrorPolicyは、選択されたバックエンドサービスへ本来のリクエストを転送すると同時に、同一内容のリクエストを別のミラー用バックエンドサービスへ「投げっぱなし(fire-and-forget)」で複製送信します。ロードバランサーはミラー先からの応答を待ちません。デフォルトではトラフィックスプリッティングの分割設定に関わらずミラーバックエンドは全リクエストを受信しますが、mirrorPercent(0〜100.0)を指定することでミラー対象の割合を制御できます。ミラーされたリクエストはCloud + Logging/Cloud Monitoringにログやメトリクスを一切生成しません。 +

+
+

+ ユースケース:新バージョンのバックエンドに本番トラフィックの複製を流し込んで性能検証する、あるいは本番で発生したエラーをデバッグ版バックエンドで再現・調査する、といった用途に使われます。 +

+
+

URL書き換え(Rewrite)とリダイレクト

+
+flowchart TD
+    Req["受信リクエスト /love-to-fetch/dog.jpg"] --> Match{パスルールにマッチ?}
+    Match -->|Yes| RW["URL書き換え<br/>パスプレフィックスを /love-to-fetch/ → / に変換"]
+    RW --> FwdReq["バックエンドへの実送信 /dog.jpg"]
+    Match -->|No| Default[デフォルトサービスへ]
+

+ urlRewriteアクションは、バックエンドサービスへリクエストを送信する前に、ホスト名やパスの一部を書き換える機能です。書き換え・リダイレクトはURLマップの3つの階層(パスルール/パスマッチャー/URLマップ自体)のいずれでも設定でき、それぞれ「パスがマッチしたとき」「パスマッチャー内でどのパスにもマッチしなかったとき」「どのホストルールにもマッチしなかったとき」に適用されます。 +

+

+ これらのルートアクションは互いに組み合わせ可能で、トラフィックスプリッティング・ミラーリング・URL書き換え・リトライポリシー・タイムアウト・フォルトインジェクション・ヘッダー操作を1つのルートルールに同時設定できます。 +

+
+

出典

+ +
+
+

設計・実装ベストプラクティスまとめ

+
+
+ チェックリスト0 / 10 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

参考文献

+
+ +
+
+
+ + + + + diff --git a/S3-load-balancing-traffic-management.md b/S3-load-balancing-traffic-management.md new file mode 100644 index 000000000..86bd484fe --- /dev/null +++ b/S3-load-balancing-traffic-management.md @@ -0,0 +1,406 @@ +# S3: ロードバランシングとトラフィック管理 + +**Professional Cloud Network Engineer 試験対応ガイド — Section 3「Configuring managed network services」Task 3.1「Configuring load balancing」** + +> 本ガイドは、Google Cloud Professional Cloud Network Engineer(PCNE)認定試験の公式Exam Guideに定義されたTask 3.1「Configuring load balancing」の出題範囲を、中級者から上級者のネットワークエンジニア向けに実装レベルまで掘り下げて解説するものです。Section 3全体の出題比率は約16%で、3.1(ロードバランシング)・3.2(Cloud CDN)・3.3(Cloud DNS)の3タスクから構成されますが、本ガイドは3.1のみを対象とします。 + +--- + +## 目次 + +1. [はじめに:このタスクの位置づけと出題範囲](#はじめにこのタスクの位置づけと出題範囲) +2. [ロードバランサーの全体アーキテクチャと選択基準](#ロードバランサーの全体アーキテクチャと選択基準) +3. [バックエンドサービスとオートスケーリングの設定](#バックエンドサービスとオートスケーリングの設定) +4. [ロードバランサーとバックエンドの詳細設定](#ロードバランサーとバックエンドの詳細設定) +5. [GKEにおけるロードバランシング](#gkeにおけるロードバランシング) +6. [Application Load Balancerでのトラフィック管理](#application-load-balancerでのトラフィック管理) +7. [設計・実装ベストプラクティスまとめ](#設計実装ベストプラクティスまとめ) +8. [参考文献](#参考文献) + +--- + +## はじめに:このタスクの位置づけと出題範囲 + +公式Exam Guideは、Task 3.1「Configuring load balancing」を次の5つの観点で定義しています。 + +| 観点 | 内容 | +|---|---| +| ① LBソリューションの決定 | internal/external、regional/global、application/proxy/passthroughの区別 | +| ② バックエンドサービスの設定 | NEG・MIGを含むオートスケーリング構成 | +| ③ バックエンドの詳細設定 | バランシング方式・セッションアフィニティ・サービング容量・URLマップ・ヘルスチェック・グローバルアクセス | +| ④ GKEにおけるLB理解 | GKE Gateway controller・GKE Ingress controller・NEG | +| ⑤ トラフィック管理 | トラフィックスプリッティング・トラフィックミラーリング・URL書き換え | + +Section 1(設計)で問われる「どのLBを選ぶべきか」という**アーキテクチャ設計の視点**に対し、Section 3(本タスク)では**実装・設定の視点**、すなわち「選んだLBをどのパラメータでどう構成するか」が主眼になります。試験では、シナリオ形式で「この要件を満たすバランシングモードはどれか」「このトラフィック分割を実現するにはどのURLマップ構成が必要か」といった設定レベルの判断が問われる点に注意してください。 + +--- + +## ロードバランサーの全体アーキテクチャと選択基準 + +### Google Cloudロードバランサーの分類軸 + +Google Cloudのロードバランサーは、次の3つの独立した軸の組み合わせで整理すると理解しやすくなります。 + +- **プロキシ方式**:Application Load Balancer(L7、HTTP/HTTPS/HTTP2/gRPC)/ Proxy Network Load Balancer(L4プロキシ、TCP/SSL)/ Passthrough Network Load Balancer(L4パススルー、クライアント送信元IPを保持) +- **公開範囲**:External(インターネット向け)/ Internal(VPC内部向け) +- **スコープ**:Global(複数リージョンにまたがる)/ Regional(単一リージョン)/ Cross-region(内部LBのみ、グローバルバックエンドを持つリージョナルVIP) + +公式ドキュメントは選択の出発点を次のように整理しています。 + +> フレキシブルな機能セットが必要なHTTP(S)トラフィックにはApplication Load Balancerを、複数リージョンのバックエンドへのTCPプロキシロードバランシングにはProxy Network Load Balancerを、クライアント送信元IPの保持やUDP・ESP・ICMPなどの追加プロトコルサポートが必要な場合はPassthrough Network Load Balancerを選択します。 + +### 選択フローチャート + +```mermaid +flowchart TD + A{トラフィックの種類は?} -->|HTTP/HTTPS/HTTP2/gRPC| B[Application Load Balancer] + A -->|複数リージョンへのTCP/SSLプロキシ| C[Proxy Network Load Balancer] + A -->|送信元IP保持・UDP/ESP/ICMP等| D[Passthrough Network Load Balancer] + B --> E{公開範囲は?} + E -->|外部公開 external| F{バックエンドの分散は?} + E -->|VPC内部のみ internal| G{バックエンドの分散は?} + F -->|グローバル・マルチリージョン| H[グローバル外部
Application Load Balancer] + F -->|単一リージョンで十分| I[リージョン外部
Application Load Balancer] + G -->|複数リージョンのバックエンド| J[クロスリージョン内部
Application Load Balancer] + G -->|単一リージョンのバックエンド| K[リージョン内部
Application Load Balancer] +``` + +> **出典**: https://docs.cloud.google.com/load-balancing/docs/choosing-load-balancer + +### 主要ロードバランサー比較表 + +| ロードバランサー | スコープ | 公開範囲 | 実装方式 | 主なユースケース | +|---|---|---|---|---| +| グローバル外部 Application LB | グローバル | External | GFE(管理型) | 世界中のユーザー向けWebアプリ、マルチリージョン公開API | +| リージョン外部 Application LB | リージョン | External | Envoy(管理型) | 特定リージョンに閉じたコンプライアンス要件のあるWeb公開 | +| リージョン内部 Application LB | リージョン | Internal | Envoy(管理型) | マイクロサービス間のL7ロードバランシング | +| クロスリージョン内部 Application LB | クロスリージョン | Internal | Envoy(管理型) | 複数リージョンに分散した内部サービスへの高可用アクセス | +| Proxy Network LB(TCP Proxy) | グローバル/リージョン | External/Internal | GFE/Envoy | 複数リージョンのTCPバックエンドへの単一エニーキャストIP | +| 外部パススルー Network LB | リージョン | External | パススルー(非プロキシ) | 送信元IP保持が必要なUDP/TCPワークロード | +| 内部パススルー Network LB | リージョン | Internal | パススルー(非プロキシ) | 内部L4ロードバランシング、NVAの次ホップ | + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/features +> - https://docs.cloud.google.com/load-balancing/docs/application-load-balancer + +### ネットワークサービスティアとの関係 + +ロードバランサーの種類ごとに利用可能なネットワークサービスティア(Premium/Standard)は異なります。この設計判断はSection 1(1.1)で扱う領域と重複するため、本ガイドでは詳細を割愛しますが、実装時には「Standard Tierではグローバル外部Application Load Balancerを利用できない」といった制約がある点だけ押さえておいてください。 + +--- + +## バックエンドサービスとオートスケーリングの設定 + +### バックエンドの種類:MIG vs NEG + +バックエンドサービスにアタッチできるバックエンドは、大きく分けて2種類です。 + +- **マネージドインスタンスグループ(MIG)**:Compute Engine VMの集合。オートスケーラーと直接連携し、UTILIZATION(CPU使用率ベース)を含む全バランシングモードを利用可能。 +- **ネットワークエンドポイントグループ(NEG)**:VMやコンテナ、サーバーレスリソースなど、より粒度の細かいエンドポイントの集合。UTILIZATIONバランシングモードはサポートされません。 + +### NEGの6分類 + +```mermaid +flowchart TD + NEG[Network Endpoint Group] --> Z["ゾーンNEG
GCE_VM_IP / GCE_VM_IP_PORT"] + NEG --> S["サーバーレスNEG
Cloud Run / App Engine / Cloud Run functions"] + NEG --> I["インターネットNEG
グローバル / リージョナル"] + NEG --> H["ハイブリッド接続NEG
オンプレミス・他クラウド"] + NEG --> P["PSC NEG
Private Service Connect"] + NEG --> PM["ポートマッピングNEG
同一IPで複数コンテナポート"] +``` + +| NEGタイプ | エンドポイント形式 | 主な用途 | 制約 | +|---|---|---|---| +| ゾーンNEG(GCE_VM_IP_PORT) | IPアドレス+ポート | プロキシ型LBの標準バックエンド、GKEのコンテナネイティブLB | UTILIZATIONバランシング非対応。RATE/CONNECTIONのみ | +| ゾーンNEG(GCE_VM_IP) | IPアドレスのみ(ポート指定不可) | 内部パススルーNetwork LB、外部パススルーNetwork LB(リージョン) | ポート指定不可、デュアルスタックエンドポイント不可 | +| サーバーレスNEG | Cloud Run / App Engine / Cloud Run functions | サーバーレスサービスをLB配下に統合 | Proxy/Passthrough Network LBからは利用不可 | +| インターネットNEG | FQDN:Port または IP:Port(RFC 1918外) | GCP外部(オンプレミス・他社クラウド)のバックエンドを統合 | グローバルは単一エンドポイント・ヘルスチェック非対応、リージョナルは最大256エンドポイント | +| ハイブリッド接続NEG | ハイブリッド接続経由のオンプレミスエンドポイント | Cloud Interconnect/VPN経由でのオンプレミスバックエンド統合 | ハイブリッド接続の構成が前提 | +| PSC NEG | Private Service Connectで公開されたサービス | 別プロジェクト・別VPCのサービスへの越境接続 | PSCエンドポイント経由でのみ解決 | + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/negs +> - https://docs.cloud.google.com/load-balancing/docs/negs/zonal-neg-concepts +> - https://docs.cloud.google.com/load-balancing/docs/negs/serverless-neg-concepts +> - https://docs.cloud.google.com/load-balancing/docs/negs/internet-neg-concepts + +### オートスケーリングとの連携 + +MIGバックエンドにオートスケーラーをアタッチすると、オートスケーラーは「ロードバランシングのサービング容量の一定割合」を維持するようにインスタンス数を増減します。たとえばMIGのサービング容量が1インスタンスあたり100RPSと定義されており、オートスケーラーの目標使用率を80%に設定した場合、オートスケーラーは各インスタンスが80RPSを維持するようにインスタンスを追加・削除します。 + +> **ベストプラクティス**:NEGバックエンド(特にGKEのコンテナネイティブLB)を使う場合はUTILIZATIONが使えないため、RATEまたはCONNECTIONベースでキャパシティ計画を行い、Pod単位のHorizontal Pod Autoscalerと組み合わせて容量を制御します。 + +> **出典**: https://docs.cloud.google.com/compute/docs/autoscaler/scaling-load-balancing + +--- + +## ロードバランサーとバックエンドの詳細設定 + +### バランシングモードとキャパシティスケーラー + +バックエンドサービスは、バックエンドごとに「バランシングモード」と「ターゲット容量」を持ち、これに「キャパシティスケーラー」を乗算した値が実効容量になります。 + +| バランシングモード | 容量の測定基準 | 対応バックエンド | 備考 | +|---|---|---|---| +| UTILIZATION | インスタンスグループのCPU使用率(近似値) | MIGのみ(NEG非対応) | セッションアフィニティはNONEと併用すること | +| RATE | 新規HTTPリクエストのレート(RPS) | MIG・NEG両方 | グループ全体またはエンドポイント単位で指定可能 | +| CONNECTION | 新規TCPコネクション数 | MIG・NEG両方 | L4系ロードバランサーで使用 | +| IN-FLIGHT | 処理中(未完了)のHTTPリクエスト数 | MIG・NEG両方 | リクエスト処理に1秒以上かかる場合、RATEの代わりに使用 | + +キャパシティスケーラーは0.0または0.1〜1.0の範囲で設定でき、次のような運用パターンに使えます。 + +- **段階的なドレイン**:キャパシティスケーラーを0.5にすると、そのバックエンドの実効容量が半分になり、新規トラフィックの流入が抑制されます。 +- **完全ドレイン**:0に設定すると新規トラフィックは一切送られなくなります(バックエンドサービスに他のバックエンドが存在する場合のみ設定可能)。 + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/backend-service +> - https://cloud.google.com/python/docs/reference/compute/0.4.2/google.cloud.compute_v1.types.Backend + +### セッションアフィニティ + +セッションアフィニティは、同一クライアントからの後続リクエストを可能な限り同じバックエンドに送るための仕組みです。 + +| アフィニティ種別 | ハッシュ対象 | 適したケース | 注意点 | +|---|---|---|---| +| NONE | なし(デフォルト) | ステートレスなアプリケーション | 最も均等な分散が得られる | +| CLIENT_IP | 送信元・宛先IPの2-tuple | NAT配下にクライアントが少ないL4/L7ワークロード | 多数のクライアントが同一送信元IP(NAT)を共有すると偏りが生じる | +| GENERATED_COOKIE | LBが発行するCookie | HTTP(S)ワークロードでの一般的な選択肢 | NATやIPアドレス変化の影響を受けない | +| HTTP_COOKIE | アプリケーション側が発行する既存Cookie | アプリケーションが既にセッションCookieを持つ場合 | Cookie名の指定が必要 | +| HEADER_FIELD | 指定したHTTPヘッダーの値 | ユーザーIDなどをヘッダーで伝搬するAPIクライアント | ロードバランシングロケーションポリシーがRING_HASHまたはMAGLEVである必要がある | + +> セッションアフィニティは認証やセキュリティの目的では使用しないでください。バックエンドの健全性やスケール状況によって、ベストエフォートでしか維持されません。 + +> **ベストプラクティス**:UTILIZATIONバランシングモードと組み合わせて使用しないこと。ウェイト付きトラフィックスプリッティングを設定した場合、セッションアフィニティの設定より分割設定が優先されるため、両者を同時に有効化しないことが推奨されています。 + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/l7-internal +> - https://docs.cloud.google.com/load-balancing/docs/https/request-distribution + +### URLマップの構造 + +```mermaid +flowchart TD + UM[URLマップ] --> HR["ホストルール
例: example.com"] + HR --> PM2[パスマッチャー] + PM2 --> PR1["パスルール /video/*"] + PM2 --> PR2["パスルール /images/*"] + PM2 --> DEF[デフォルトサービス] + PR1 --> BS1["バックエンドサービス: video"] + PR2 --> BS2["バックエンドサービス: images"] + DEF --> BS3["バックエンドサービス: web-default"] +``` + +URLマップはホストルール(どのドメインに適用するか)→パスマッチャー(パスパターンの集合)→パスルール(個々のパスと転送先)という階層構造を持ちます。パスルールの代わりにルートルール(routeRules)を使うことも可能ですが、両者は同一のパスマッチャー内で併用できません。ルートルールは順序評価される点がパスルールと異なります。 + +> **出典**: https://docs.cloud.google.com/load-balancing/docs/https/traffic-management + +### ヘルスチェック + +```mermaid +flowchart TD + P["ヘルスチェックプローブ
送信元: 130.211.0.0/22, 35.191.0.0/16"] --> FW{ファイアウォールルール
ingress allow} + FW -->|許可| VM["バックエンドVM / Pod"] + FW -->|未許可| Fail[全バックエンドがUNHEALTHYに] + VM --> Resp{応答} + Resp -->|200 OK| Healthy[HEALTHY] + Resp -->|それ以外・タイムアウト・リダイレクト| Unhealthy[UNHEALTHY] +``` + +多くのGoogle Cloudロードバランサーのヘルスチェックプローブは、`130.211.0.0/22`と`35.191.0.0/16`のアドレス範囲から送信されます。外部パススルーNetwork Load Balancerでは、これに加えて`209.85.152.0/22`と`209.85.204.0/22`も使用されます。VPCファイアウォールがデフォルト拒否である以上、これらの範囲からのIngressを明示的に許可するファイアウォールルールがなければ、アプリケーションが正常に動作していても全バックエンドがUNHEALTHYと判定されます。これは試験でも実務でも最頻出のトラブルシューティングシナリオです。 + +判定基準は「チェック間隔」「タイムアウト」「healthy閾値(連続成功回数)」「unhealthy閾値(連続失敗回数)」の4パラメータで構成され、プロトコルはHTTP/HTTPS/HTTP2/TCP/SSL/gRPCから選択できます。ヘルスチェックはHTTPリダイレクト(3xx)を失敗として扱うため、HTTPをHTTPSへ強制リダイレクトしているアプリケーションでヘルスチェックパスまでリダイレクトしてしまうと誤検知の原因になります。 + +> **ベストプラクティス**:ヘルスチェックには本番トラフィックのエンドポイントとは別の軽量な専用パス(例:`/healthz`)を用意し、200固定を返すようにします。GKEのNEGバックエンドでは、ヘルスチェックはノードIPではなくPod IPに対して直接行われるため、NetworkPolicyやPodのファイアウォール設定も併せて確認する必要があります。 + +> **出典** +> +> - https://docs.cloud.google.com/compute/docs/instance-groups/autohealing-instances-in-migs +> - https://docs.cloud.google.com/load-balancing/docs/internal/setting-up-failover + +### グローバルアクセス(内部ロードバランサー) + +```mermaid +flowchart TD + C1["クライアント(asia-east1)"] -->|グローバルアクセス有効| ILB["内部LB VIP
us-central1"] + C2["クライアント(europe-west1)"] -->|グローバルアクセス有効| ILB + C3["クライアント(us-central1・同一リージョン)"] -->|常にアクセス可能| ILB + ILB --> BE["バックエンド(us-central1)"] +``` + +リージョン内部Application Load Balancerは、デフォルトでは同一リージョンのクライアントからのみアクセス可能です。フォワーディングルールで「グローバルアクセス」を有効化すると、VPC内の任意のリージョンからクライアントがアクセスできるようになります。一方、クロスリージョン内部Application Load Balancerはグローバルアクセスが常に有効であり、さらにバックエンド自体を複数リージョンに配置できる点がリージョン内部LBとの決定的な違いです。 + +| 比較項目 | リージョン内部 Application LB | クロスリージョン内部 Application LB | +|---|---|---| +| VIPの割り当て | 特定リージョンのサブネットから割り当て | 特定リージョンのサブネットから割り当て(複数リージョンのVIPが同一バックエンドサービスを共有可) | +| クライアントアクセス | デフォルトは同一リージョンのみ、グローバルアクセスで拡張可 | 常にグローバルアクセス可能 | +| バックエンドの分散 | 単一リージョンのみ | 複数リージョンに分散可能 | +| フェイルオーバー | リージョン内のみ | リージョンをまたいだ自動フェイルオーバー | + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/l7-internal +> - https://cloud.google.com/blog/products/networking/enhancing-cloud-load-balancing/ + +--- + +## GKEにおけるロードバランシング + +GKEのロードバランシングは、レガシーな**GKE Ingress controller**と、Kubernetes公式仕様に準拠した**GKE Gateway controller**の2系統が併存しています。両者の違いを理解しておくことは、GKEネットワーキング設計(Section 1.4)と実装(本タスク)の橋渡しとして重要です。 + +### GKE Ingress controller(レガシー) + +```mermaid +flowchart TD + Ing[Ingressリソース] --> IC[GKE Ingress controller] + IC --> CLB["Classic Application Load Balancer
固定"] + CLB --> NEG1["GCE_VM_IP_PORT ゾーンNEG
(インスタンスグループも可)"] +``` + +GKE Ingress controllerが作成する外部Ingressは常にClassic Application Load Balancerとして実装されます。GKE ServiceのNEGアノテーションを使えばGCE_VM_IP_PORTゾーンNEGを優先的にバックエンドとして利用しますが、インスタンスグループバックエンドもサポートされます。 + +### GKE Gateway controller(Gateway API) + +```mermaid +flowchart TD + GC[GatewayClass] --> GW[Gatewayリソース] + GW --> HR2[HTTPRouteリソース] + HR2 --> BS4[バックエンドサービス1] + HR2 --> BS5[バックエンドサービス2] +``` + +GKE Gateway controllerはKubernetes Gateway APIの実装であり、責務が3つのリソースに分離されている点がIngressとの本質的な違いです。 + +- **GatewayClass**:使用するロードバランサーの実装を決定するクラスタスコープのテンプレート(GKEが提供) +- **Gateway**:実際のロードバランサーインスタンスを表すリソース(フロントエンド設定) +- **HTTPRoute**:ルーティングルールを定義するリソース(アプリケーションチームが管理) + +この分離により、プラットフォームチームがGatewayのインフラ設定を管理し、アプリケーションチームがクラスタ全体の権限を持たずに自分たちのHTTPRouteだけを管理する、という役割分担が可能になります。GKE Gateway controllerは常にGCE_VM_IP_PORTゾーンNEGバックエンドを使用し、Ingressと異なりヘルスチェックパラメータを自動推測しないため、明示的なHealthCheckPolicyの設定が必要です。 + +| 比較項目 | GKE Ingress controller | GKE Gateway controller | +|---|---|---| +| 準拠仕様 | GKE独自のIngress拡張(アノテーションベース) | Kubernetes Gateway API(標準仕様) | +| 実装されるLB | 常にClassic Application Load Balancer | GatewayClasseに応じて外部/内部・global/regionalを選択可能 | +| リソース構成 | Ingressリソース1つに集約 | GatewayClass/Gateway/HTTPRouteに分離 | +| トラフィック分割 | 非対応(1ルートにつき1バックエンドのみ) | HTTPRouteでネイティブにトラフィックスプリッティング対応 | +| マルチテナンシー | Ingressリソースの所有者が全ルールを管理 | 名前空間をまたいだルーティング委譲が可能 | +| ヘルスチェック | パラメータを自動推測 | HealthCheckPolicyによる明示設定が必要 | + +> **出典** +> +> - https://docs.cloud.google.com/kubernetes-engine/docs/concepts/gateway-api +> - https://docs.cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways +> - https://docs.cloud.google.com/load-balancing/docs/https + +### NEGとContainer-Native Load Balancing + +GKEでNEGアノテーションを使うと、ロードバランサーはノードIPではなくPod IPに対して直接ヘルスチェック・トラフィック送信を行います(Container-Native Load Balancing)。これにより、ノードを経由するiptables/kube-proxyのホップが省略され、レイテンシが改善するとともに、ロードバランサーがPodの正確な健全性を把握できるようになります。 + +> **ベストプラクティス**:新規のGKEワークロードでは、レガシーのIngress + アノテーションではなく、GKE Gateway controllerとHTTPRouteの組み合わせを第一候補として設計します。標準仕様に準拠しているため、将来的な移植性が高く、トラフィックスプリッティングやヘッダーベースルーティングもアノテーション無しでネイティブに扱えます。 + +--- + +## Application Load Balancerでのトラフィック管理 + +Application Load Balancer(グローバル外部・リージョン外部・内部いずれも共通の枠組み)は、URLマップのルートアクションとして、単一バックエンドへの転送に加えて次の高度なトラフィック管理機能を提供します。 + +### トラフィックスプリッティング(カナリアリリース) + +```mermaid +flowchart LR + C[クライアント] --> LB[ロードバランサー] + LB -->|重み 950/1000 = 95%| SvcA["バックエンドサービスA
安定版"] + LB -->|重み 50/1000 = 5%| SvcB["バックエンドサービスB
カナリア版"] +``` + +`weightedBackendServices`を使うと、0〜1000の重みで複数のバックエンドサービスにトラフィックを配分できます。カナリアリリースやブルー/グリーンデプロイの段階的なロールアウトに使われる代表的な手法です。 + +> **注意**:ウェイト付きトラフィックスプリッティングとセッションアフィニティは同時に設定しないでください。両方が設定された場合、トラフィックスプリッティングの重みが優先されます。 + +### トラフィックミラーリング + +```mermaid +sequenceDiagram + participant Client as クライアント + participant LB as ロードバランサー + participant Primary as プライマリbackend + participant Mirror as ミラーbackend + Client->>LB: リクエスト送信 + LB->>Primary: リクエスト転送 + LB--)Mirror: リクエストを複製送信(fire-and-forget) + Primary-->>LB: レスポンス + LB-->>Client: レスポンス返却 + Note over Mirror: レスポンスは待たず破棄。
ログ・メトリクスも記録されない +``` + +`requestMirrorPolicy`は、選択されたバックエンドサービスへ本来のリクエストを転送すると同時に、同一内容のリクエストを別のミラー用バックエンドサービスへ「投げっぱなし(fire-and-forget)」で複製送信します。ロードバランサーはミラー先からの応答を待ちません。デフォルトではトラフィックスプリッティングの分割設定に関わらずミラーバックエンドは全リクエストを受信しますが、`mirrorPercent`(0〜100.0)を指定することでミラー対象の割合を制御できます。ミラーされたリクエストはCloud Logging/Cloud Monitoringにログやメトリクスを一切生成しません。 + +> **ユースケース**:新バージョンのバックエンドに本番トラフィックの複製を流し込んで性能検証する、あるいは本番で発生したエラーをデバッグ版バックエンドで再現・調査する、といった用途に使われます。 + +### URL書き換え(Rewrite)とリダイレクト + +```mermaid +flowchart TD + Req["受信リクエスト /love-to-fetch/dog.jpg"] --> Match{パスルールにマッチ?} + Match -->|Yes| RW["URL書き換え
パスプレフィックスを /love-to-fetch/ → / に変換"] + RW --> FwdReq["バックエンドへの実送信 /dog.jpg"] + Match -->|No| Default[デフォルトサービスへ] +``` + +`urlRewrite`アクションは、バックエンドサービスへリクエストを送信する前に、ホスト名やパスの一部を書き換える機能です。書き換え・リダイレクトはURLマップの3つの階層(パスルール/パスマッチャー/URLマップ自体)のいずれでも設定でき、それぞれ「パスがマッチしたとき」「パスマッチャー内でどのパスにもマッチしなかったとき」「どのホストルールにもマッチしなかったとき」に適用されます。 + +これらのルートアクションは互いに組み合わせ可能で、トラフィックスプリッティング・ミラーリング・URL書き換え・リトライポリシー・タイムアウト・フォルトインジェクション・ヘッダー操作を1つのルートルールに同時設定できます。 + +> **出典** +> +> - https://docs.cloud.google.com/load-balancing/docs/https/traffic-management-global +> - https://docs.cloud.google.com/load-balancing/docs/https/setting-up-url-rewrite + +--- + +## 設計・実装ベストプラクティスまとめ + +- [ ] トラフィックの種類(HTTP系かTCP/UDP系か、送信元IP保持が必要か)を最初に確定し、Application/Proxy/Passthroughの3系統から絞り込む +- [ ] GKEワークロードのバックエンドはNEG(GCE_VM_IP_PORT)を優先し、UTILIZATIONバランシングモードが使えない前提でRATE/CONNECTIONベースの容量設計を行う +- [ ] UTILIZATIONバランシングモードとセッションアフィニティは併用しない +- [ ] ウェイト付きトラフィックスプリッティングを使う場合はセッションアフィニティを設定しない(設定しても分割設定が優先される) +- [ ] ヘルスチェックプローブ範囲(130.211.0.0/22、35.191.0.0/16、外部パススルーLBでは追加で209.85.152.0/22・209.85.204.0/22)を許可するファイアウォールルールを必ず作成する +- [ ] ヘルスチェック専用の軽量エンドポイントを用意し、リダイレクトを発生させない +- [ ] 内部LBで複数リージョンにまたがるアクセスが必要な場合、リージョン内部LB+グローバルアクセスではなく、クロスリージョン内部LBによるマルチリージョンバックエンド構成を優先的に検討する +- [ ] 新規GKE Ingress実装はレガシーのIngress controllerではなく、Gateway API(GKE Gateway controller)を第一候補とする +- [ ] トラフィックミラーリング先のバックエンドはログ・メトリクスが記録されないため、検証用の独立した監視手段を別途用意する +- [ ] カナリアリリースではトラフィックスプリッティングの重みを段階的に引き上げつつ、Cloud Monitoringでエラー率・レイテンシを比較しながらロールアウトする + +--- + +## 参考文献 + +- **Choose a load balancer** — https://docs.cloud.google.com/load-balancing/docs/choosing-load-balancer +- **Application Load Balancer overview** — https://docs.cloud.google.com/load-balancing/docs/application-load-balancer +- **External Application Load Balancer overview** — https://docs.cloud.google.com/load-balancing/docs/https +- **Internal Application Load Balancer overview** — https://docs.cloud.google.com/load-balancing/docs/l7-internal +- **Load balancer feature comparison** — https://docs.cloud.google.com/load-balancing/docs/features +- **Backend services overview** — https://docs.cloud.google.com/load-balancing/docs/backend-service +- **Scaling based on load balancing serving capacity** — https://docs.cloud.google.com/compute/docs/autoscaler/scaling-load-balancing +- **Advanced load balancing optimizations** — https://docs.cloud.google.com/load-balancing/docs/service-lb-policy +- **Network endpoint groups overview** — https://docs.cloud.google.com/load-balancing/docs/negs +- **Zonal network endpoint groups overview** — https://docs.cloud.google.com/load-balancing/docs/negs/zonal-neg-concepts +- **Serverless network endpoint groups overview** — https://docs.cloud.google.com/load-balancing/docs/negs/serverless-neg-concepts +- **Internet network endpoint groups overview** — https://docs.cloud.google.com/load-balancing/docs/negs/internet-neg-concepts +- **Request distribution for external Application Load Balancers** — https://docs.cloud.google.com/load-balancing/docs/https/request-distribution +- **Traffic management overview for global external Application Load Balancers** — https://docs.cloud.google.com/load-balancing/docs/https/traffic-management-global +- **Traffic management overview for internal Application Load Balancers** — https://cloud.google.com/load-balancing/docs/l7-internal/traffic-management +- **Traffic management overview for a classic Application Load Balancer** — https://docs.cloud.google.com/load-balancing/docs/https/traffic-management +- **Set up URL rewrite for a classic Application Load Balancer** — https://docs.cloud.google.com/load-balancing/docs/https/setting-up-url-rewrite +- **Set up an application-based health check and autohealing** — https://docs.cloud.google.com/compute/docs/instance-groups/autohealing-instances-in-migs +- **Configure failover for internal passthrough Network Load Balancers** — https://docs.cloud.google.com/load-balancing/docs/internal/setting-up-failover +- **Enhancing Cloud Load Balancing(クロスリージョン内部LB発表ブログ)** — https://cloud.google.com/blog/products/networking/enhancing-cloud-load-balancing/ +- **About Gateway API(GKE networking)** — https://docs.cloud.google.com/kubernetes-engine/docs/concepts/gateway-api +- **Deploying Gateways(GKE networking)** — https://docs.cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways +- **External Application Load Balancer performance best practices** — https://docs.cloud.google.com/load-balancing/docs/https/http-load-balancing-best-practices +- **Professional Cloud Network Engineer Certification exam guide** — https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf +- **Google Cloud Professional Cloud Network Engineer 認定ページ** — https://cloud.google.com/learn/certification/cloud-network-engineer diff --git a/S6-network-ops-monitoring.html b/S6-network-ops-monitoring.html new file mode 100644 index 000000000..dd85e7b87 --- /dev/null +++ b/S6-network-ops-monitoring.html @@ -0,0 +1,3234 @@ + + + + + + PCNE試験対策ガイド S6: ネットワーク操作と監視 + + + + + + + + + + +
+ + +
+
+
+ Section 5 + 出題比率 約14% +
+

PCNE試験対策ガイド S6: ネットワーク操作と監視

+

+ Google Cloud Professional Cloud Network Engineer(PCNE)認定試験 — Section 5: + Managing, monitoring, and troubleshooting network operations(出題比率 + 約14%) +

+
+
+ +

この記事について(スコープ対応表)

+

+ 本ガイドは、本シリーズで先行して公開したS1(Section 1 設計・計画)、S2(Section 4 + ハイブリッド接続)、S3(Section 2 VPC実装 / Section 3 Task 3.1 + ロードバランシング)、S4(Section 3 Task 3.2-3.3 CDN・DNS・IPAM)、S5(Section 6 + ネットワークセキュリティ)の続編として、公式Exam Guide(PDF)のSection 5「Managing, monitoring, and troubleshooting network + operations」(出題比率約14%)に厳密に対応する範囲を扱います。ユーザー呼称の「S6」は公式セクション番号とは一致しませんが、これまでのシリーズと同じ命名慣行を踏襲しています。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
本ガイドの構成公式Exam GuideのTask主な内容
Part 1 + Task 5.1 Logging and monitoring with Google Cloud Observability + + ネットワークコンポーネント別のCloud Loggingログ、Cloud + Monitoringメトリクス +
Part 2 + Task 5.2 Maintaining and troubleshooting connectivity issues + + ALBのトラフィックドレイン、VPN/Interconnect/Cloud Router + BGPのトラブルシューティング、Flow Logs・Firewall Logs・Packet + Mirroringの活用 +
Part 3 + Task 5.3 Using Network Intelligence Center to monitor and + troubleshoot common networking issues + + Network Topology、Connectivity Tests、Performance + Dashboard、Firewall Insights、Network Analyzer、Flow Analyzer +
+
+
+
+ 🔗出典 +
+ + +
+
+

全体像

+

+ Section 5は「作る」フェーズ(Section + 1〜4)を終えたネットワークを、日々どう観測し、維持し、壊れたときに直すかを問う領域です。試験ガイドは3つのTaskに分かれていますが、実務的には次の1本の流れとして理解すると整理しやすくなります。 +

+
+flowchart TB
+    A["ネットワークコンポーネント<br/>VPC/Router/VPN/Interconnect/NAT/DNS/LB/Armor/NCC"] --> B["Task 5.1<br/>Cloud Observabilityで収集する"]
+    B --> C{"異常や問い合わせが<br/>発生した"}
+    C -->|"Yes"| D["Task 5.2<br/>コンポーネント別に<br/>トラブルシューティングする"]
+    C -->|"No(平常時)"| E["Task 5.3<br/>Network Intelligence Centerで<br/>可視化・予防診断する"]
+    D --> F["Flow Logs / Firewall Logs /<br/>Packet Mirroringで<br/>根本原因を特定する"]
+    E --> G["Connectivity Tests / Network Analyzer /<br/>Firewall Insightsで<br/>構成起因の問題を先回りで検知する"]
+    F --> H["是正・再発防止"]
+    G --> H
+    H --> B
+

+ この循環の中で、Task 5.1(収集) が土台であり、Flow + Logs・Firewall Rules Logging・各種メトリクスが有効化されていなければ、Task + 5.2の切り分けもTask 5.3のNetwork Analyzer/Firewall + Insightsのログベースインサイトも機能しません。試験でも「まずロギングとモニタリングが有効化されているか」を問う設問が土台になっている点を意識してください。 +

+
+

+ Part 1: Google Cloud Observabilityによるロギングとモニタリング(Task 5.1) +

+

+ 1.1 Google Cloud Observabilityの基本構造 +

+

+ Google Cloud Observability(旧Stackdriver)は、Cloud Logging(ログ)とCloud Monitoring(メトリクス)を中核としたスイートです。ネットワークコンポーネントの大半は、追加のエージェント導入なしにログとメトリクスを自動的に送信します。 +

+
+flowchart LR
+    subgraph Sources["ネットワークコンポーネント"]
+        direction TB
+        S1["Cloud VPN"]
+        S2["Cloud Router"]
+        S3["VPC Service Controls"]
+        S4["Cloud NGFW / VPC Firewall"]
+        S5["VPC Flow Logs"]
+        S6["Cloud DNS"]
+        S7["Cloud NAT"]
+        S8["Network Connectivity Center"]
+    end
+
+    Sources --> L["Cloud Logging<br/>(Logs Explorer)"]
+    Sources --> M["Cloud Monitoring<br/>(Metrics Explorer)"]
+
+    L --> LS1["ログシンク経由でエクスポート<br/>BigQuery / Cloud Storage / Pub/Sub"]
+    L --> LS2["ログベースメトリクス /<br/>ログベースアラート"]
+    M --> MS1["事前定義ダッシュボード"]
+    M --> MS2["カスタムダッシュボード /<br/>アラートポリシー"]
+    LS1 --> SIEM["Flow Analyzer / BigQuery分析 /<br/>外部SIEM"]
+

+ 試験ガイドが明示的に列挙しているロギング対象コンポーネントは、Cloud VPN、Cloud Router、VPC Service Controls、Cloud NGFW、Firewall + Insights、VPC Flow Logs、Cloud DNS、Cloud NAT、Network Connectivity + Centerの8つです。これらはすべてCloud + Loggingに自動で書き込まれ、追加課金なしで有効化できるものがほとんどですが、VPC + Flow LogsとFirewall Rules Loggingは明示的な設定が必要です。 +

+
+
+ ⚠注意 +
+

+ Cloud LoggingとCloud + Monitoringは無料枠を超えると、取り込み量(ログ)やサンプル数(メトリクス)に応じた課金が発生します。特にVPC + Flow + Logsはトラフィック量に比例して急増しやすいため、サンプリングレートやメタデータ注釈の絞り込みが設計上の重要な検討事項になります。 +

+
+

+ 1.2 ネットワークコンポーネント別ロギング +

+

1.2.1 VPC Flow Logs

+

+ VPC Flow + Logsは、VPCネットワーク内を流れるパケットをサンプリングし、5-tuple(送信元/宛先IP、ポート、プロトコル)単位で集約したフローログを生成します。対象となるトラフィックは次のとおりです。 +

+
    +
  • VMインスタンス(GKEノードを含む)が送受信するパケット
  • +
  • Direct VPC Egressを構成したCloud Runリソースが送受信するパケット
  • +
  • + Cloud InterconnectのVLANアタッチメントやCloud VPNトンネルを通過するパケット +
  • +
+
+flowchart LR
+    A["VPCネットワーク内の<br/>パケット"] --> B["サンプリング<br/>(primary sampling rate)"]
+    B --> C["5-tuple単位で集約<br/>(aggregation interval)"]
+    C --> D["メタデータ注釈付与<br/>(送信元/宛先の名前解決、地理情報など)"]
+    D --> E["フィルタリング<br/>(任意)"]
+    E --> F["Cloud Logging<br/>(vpc_flows)"]
+    F --> G["Logs Explorer /<br/>ログシンクでエクスポート"]
+    F --> H["Flow Analyzer<br/>(Log Analytics有効化時)"]
+

+ VPC Flow + Logsは組織レベル・プロジェクトレベル・サブネット/VLANアタッチメント/VPNトンネル単位で個別に構成でき、組織レベルの構成を持つ場合はShared + VPC・VPC Network Peering・Network Connectivity + Center経由のフローに「クロスプロジェクト注釈」が付与されます。GKEのPodからインターネット向けの通信は、IP + masqueradeによって送信元IPがノードIPに変換されるため、既定ではPodの注釈が付きません(Podの注釈を取得したい場合はCloud + NATと組み合わせる必要があります)。 +

+

+ 用途としては、ネットワークフォレンジック(侵害されたIPの特定)、キャパシティプランニング、コスト最適化(トップトーカーの特定)が代表的です。 +

+
+
+ 🔗出典 +
+ + +
+

+ 1.2.2 ファイアウォールルールロギング(VPC Firewall Rules / 階層型ポリシー / Cloud + NGFW) +

+

+ VPC Firewall Rules LoggingはCompute Engine + VM(GKEノードを含む)への/からのトラフィックを対象とし、ルールが許可または拒否した通信のたびに「接続レコード」というログエントリを生成します。Allow系ルールとDeny系ルールでログの挙動が大きく異なる点は頻出ポイントです。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目Allow + ロギングDeny + ロギング
ログの発生単位接続(コネクション)ごとに1回一意な5-tupleごとに、パケットが観測されるたびに再発生
継続時間中の追加ログ + 生成されない(ステートフルなため応答トラフィックは記録されない) + パケットが観測される限り約5秒ごとに繰り返し記録される
既存アクティブ接続へのロギング有効化 + 新規ログは即時生成されない。アイドル10分後、新しいパケットが来た時点で記録される + 該当なし(そもそも許可されていない接続)
長時間接続の可視性低い(1エントリのみ)高い(継続的に記録される)
+
+
+
+ ✅ベストプラクティス +
+

+ アイドル期間のない長時間ストリームを継続的に可視化したい場合はVPC Firewall + Rules LoggingではなくVPC Flow + Logsを使用してください。ファイアウォールログは「許可/拒否の判断根拠」を追うのに適し、Flow + Logsは「トラフィックの実体」を追うのに適しています。両者は補完関係にあり、片方だけでは不十分なケースが多くあります。 +

+
+

+ 階層型ファイアウォールポリシーおよびグローバル/リージョナルのネットワークファイアウォールポリシー(Cloud + NGFW)でも同様にロギングを有効化でき、ログはCloud + Loggingの同じ基盤に書き込まれます。Firewall + Insightsが生成するインサイトは、このロギングデータを土台にしています(詳細は3.5節)。 +

+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/firewall/docs/vpc-firewall-rules-logging-overview +

+
+

1.2.3 Cloud Routerのログ

+

+ Cloud RouterはBGPセッションの状態変化を3種類のイベントとしてCloud + Loggingに記録します。 +

+
+sequenceDiagram
+    participant CR as Cloud Router
+    participant Log as Cloud Logging
+    participant Peer as オンプレミス/ピアルータ
+
+    CR->>Log: Router event<br/>(Router task activated/de-activated)
+    CR->>Peer: BGPセッション確立を試行
+    Peer-->>CR: OPEN / KEEPALIVE
+    CR->>Log: BGP event<br/>(peering came up X seconds ago)
+    CR->>Log: Route event<br/>(Advertising prefix / prefix received)
+    Note over CR,Peer: 障害発生
+    Peer--xCR: セッション断
+    CR->>Log: BGP event<br/>(peering went down, reason: HOLD_TIMER_EXPIRED等)
+    CR->>Log: Route event<br/>(Withdrawing prefix)
+

+ ログには「Router event」(タスクの起動/非活性化)、「BGP + event」(ピアリングの確立・切断とその理由)、「Route + event」(経路の広告・撤回、学習した経路のネクストホップ)の3系統があり、それぞれ[Event Type]: [Log Text]という定型フォーマットで出力されます。障害調査では、まず「BGP + event」でセッション断の理由(HOLD_TIMER_EXPIRED、LINK_DOWNなど)を確認し、次に「Route + event」で経路の広告状況を突き合わせるのが定石です。 +

+
+
+ 🔗出典 +
+ + +
+

1.2.4 Cloud VPNのログ

+

+ Cloud + VPNのログは自動的に有効化されており、追加設定は不要です。IKEネゴシエーション、鍵の再交換(rekeying)、SA(セキュリティアソシエーション)の削除といったイベントが記録されます。トンネルが確立後すぐに切断を繰り返す場合、Received SA_DELETEログの直後に再接続している形跡があれば、オンプレミス側のゲートウェイがrekeyingではなく既存SAの削除後に新規SAをネゴシエートする実装になっている可能性を疑います。 +

+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/viewing-logs-metrics +

+
+

1.2.5 Cloud NATのログ

+

+ Cloud + NATのログエントリには、重大度・プロジェクトIDなど一般的なフィールドに加え、NAT固有の情報(変換前後のIPアドレス・ポート、NAT64の場合は宛先IPv4埋め込みIPv6アドレスなど)が含まれます。ログベースメトリクスへのエクスポートも構成可能です。加えて、Cloud + NATはcompute.googleapis.comを対象とした監査ログ(Admin Activity / + Data Access)も生成します。 +

+
+
+ 🔗出典 +
+ + +
+

1.2.6 Cloud DNSのログ

+

+ Cloud + DNSロギングは、VPCネットワーク内のVM、GKEコンテナ、ピアリング先のゾーン、オンプレミスからのインバウンドフォワーディングなど、さまざまな経路からのDNSクエリを記録します。パブリックゾーンに対する外部からの直接クエリも対象です。既定では無効で、ゾーン単位・ポリシー単位で有効化します。ログには重複するフィールドがあり、一部はモニタリングメトリクスとも共有されています。目安として1万クエリあたり約5MBのログが生成されます。 +

+
+
+ ⚠注意 +
+

+ SERVFAILを含むログでdestinationIP・egressIP・egressErrorなどのフィールドが欠落している場合は、Cloud + DNSのトラブルシューティングドキュメントの該当セクションを参照してください。 +

+
+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/dns/docs/monitoring +

+
+

+ 1.2.7 VPC Service Controlsの監査ログ +

+

+ VPC Service + Controlsは、セキュリティポリシー違反によって拒否されたすべてのアクセスを既定でCloud + Loggingに記録します。監査ログは「Audited + Resource」というログストリームに書き込まれ、protoPayload.metadata.@typeがtype.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadataのログとして特定できます。 +

+
+flowchart TB
+    A["サービスへのAPIリクエスト"] --> B{"サービス境界を<br/>越えるか"}
+    B -->|"No"| C["通常どおり処理"]
+    B -->|"Yes"| D{"アクセスレベル/<br/>Ingress-Egressルールで<br/>許可されているか"}
+    D -->|"Yes"| C
+    D -->|"No"| E["アクセス拒否<br/>(トラブルシューティングトークンを生成)"]
+    E --> F["Cloud Audit Logsに記録<br/>(VpcServiceControlAuditMetadata)"]
+    F --> G["Violation Dashboardで集約<br/>(組織レベルのログシンクが必要)"]
+    F --> H["Violation Analyzerで<br/>トラブルシューティングトークンから原因を診断"]
+

+ 組織全体の違反を俯瞰するにはViolation Dashboardの設定(組織レベルのログシンクとログバケットの構成)が必要で、設定前に発生した違反はバックフィルされません。個別の拒否イベントは、エラーメッセージ中の一意なID(トラブルシューティングトークン)を使い、Violation Analyzerまたはgcloud logging readでの直接検索によって原因を特定できます。 +

+
+
+ 🔗出典 +
+ + +
+

+ 1.2.8 Network Connectivity Centerのログ +

+

+ NCC(ハブ・スポーク・NCC Gatewayスポーク)およびRouter + applianceに関するログも、Cloud + Loggingに一般的なフィールド(重大度・プロジェクトID・タイムスタンプ)とログ種別ごとの詳細情報を伴って記録されます。なお、Router + applianceのログ自体はCloud + Routerのロギング機構に委譲されており、NCC固有のログとCloud + Routerのログを併読する必要がある点に注意してください。 +

+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/network-connectivity/docs/network-connectivity-center/how-to/viewing-logs-metrics +

+
+

+ 1.3 ネットワークメトリクスのモニタリング +

+

+ 試験ガイドが明示するモニタリング対象は、Cloud VPN、Cloud InterconnectとVLANアタッチメント、Cloud + Router、ロードバランサ、Google Cloud Armor、Cloud NATの6分野です。いずれも自動的にCloud + Monitoringへメトリクスが送信されるため、エージェントのインストールは不要です。 +

+
+flowchart TB
+    subgraph Metrics["自動収集されるメトリクス"]
+        M1["Cloud VPN<br/>トンネル単位のbytes/packets"]
+        M2["Cloud Interconnect<br/>物理接続 + VLANアタッチメント"]
+        M3["Cloud Router<br/>ルータ単位 + BGPセッション単位"]
+        M4["ロードバランサ<br/>request_count / latencies / response_code"]
+        M5["Cloud Armor<br/>ポリシー単位のリクエスト・ブロック数"]
+        M6["Cloud NAT<br/>ゲートウェイ単位の使用率・ドロップ数"]
+    end
+    Metrics --> CM["Cloud Monitoring"]
+    CM --> D1["事前定義ダッシュボード<br/>(各コンソール画面のMonitoringタブ)"]
+    CM --> D2["Metrics Explorer /<br/>カスタムダッシュボード"]
+    CM --> D3["アラートポリシー<br/>(通知チャネル経由)"]
+

1.3.1 Cloud VPNのメトリクス

+

+ 各トンネルの詳細ページの「Monitoring」タブで、bytes/packetsなどの主要メトリクスをリージョン・ゲートウェイ・トンネル単位でフィルタして確認できます。パケットがドロップされた場合、ゲートウェイはドロップ理由を提供します。 +

+

+ 1.3.2 Cloud InterconnectとVLANアタッチメントのメトリクス +

+

+ Google側でポートを割り当てた時点(接続がまだ使用可能になる前)からメトリクス収集が始まるため、開通前の物理接続の監視やテストにも活用できます。VLANアタッチメント・ワイヤグループについては作成直後からメトリクス収集が始まり、パケット数・バイト数が1分間隔でMonitoringに送信され、6週間保持されます。 +

+

監視の実務では、次の3層を意識すると原因の切り分けがしやすくなります。

+
+flowchart LR
+    A["物理層<br/>(Interconnect接続の稼働状態・光レベル)"] --> D["障害/劣化の切り分け"]
+    B["論理層<br/>(VLANアタッチメントの帯域・パケット数)"] --> D
+    C["ルーティング層<br/>(BGPセッションの状態・経路数)"] --> D
+    D --> E["対応: キャリア/コロケーション連携、<br/>帯域増強、BGP設定修正など"]
+
+
+ ⚠注意 +
+

+ VLANアタッチメントのメトリクスは60秒間隔でサンプリングされるため、瞬間的なトラフィックバーストがBANDWIDTH_THROTTLEによるドロップの原因であっても、Ingress/Egressの使用率グラフ上にはスパイクとして現れないことがあります。帯域超過が疑われる場合は、アタッチメントの利用率を下げる、容量を増やす、追加のVLANアタッチメントを使用するといった対応を検討してください。 +

+
+
+
+ 🔗出典 +
+ + +
+

1.3.3 Cloud Routerのメトリクス

+

+ Cloud + Routerのメトリクスは、ルータ単位のもの(router-name)とBGPセッション単位のもの(router-name(bgp-name))の2種類に分かれます。受信経路数・学習経路数を示すメトリクスは動的に学習された経路に関するものであり、Custom + learned routes機能とは無関係である点に注意してください。 +

+

1.3.4 ロードバランサのメトリクス

+

+ 代表的なメトリクスはloadbalancing.googleapis.com/https/request_count、.../total_latencies、.../backend_latencies、.../response_code_countなどです。Total latencyはクライアント〜ロードバランサ〜バックエンドを含む全体のレイテンシ、Backend latencyはバックエンドの応答時間のみを表すため、両者を切り分けて監視することでアプリケーション側の問題かネットワーク側の問題かを判断できます。SLOを設計する場合、DistributionCutを使ったリクエストベースのSLI(例:「1時間のローリングウィンドウで99%のリクエストが100ms以内」)として表現するのが一般的です。 +

+
+
+ 🔗出典 +
+ + +
+

+ 1.3.5 Google Cloud Armorのメトリクスと運用監視 +

+

+ Cloud + Armorのセキュリティポリシーのメトリクスは、ロードバランサのバックエンドサービスに紐づく形でCloud + Monitoringに送信され、ポリシー単位でリクエスト数・ブロック数・レート制限のヒット数などを確認できます。詳細なポリシー種別・WAFルール・DDoS防御・レート制限・bot管理の設計論点は、既刊のS5(Section + 6ネットワークセキュリティ)ガイドで扱っているため、本ガイドでは監視・運用の観点に絞って要点のみ再掲します。 +

+

1.3.6 Cloud NATのメトリクス

+

+ Cloud NATはゲートウェイのフリート全体の使用状況をCloud + Monitoringに自動送信します。コンソール上のNATゲートウェイ詳細ページの「Monitoring」タブから事前定義ダッシュボードを確認できるほか、Cloud NAT gatewayやVM Instanceをフィルタしてアラートポリシーを作成できます。Shared + VPCでVMとNATゲートウェイが異なるプロジェクトにある場合、VMレベルのメトリクスへのアクセスにはVMが属するプロジェクトのroles/monitoring.viewer、ゲートウェイリソースのメトリクスへのアクセスにはゲートウェイが属するプロジェクトのroles/monitoring.viewerが、それぞれ個別に必要です。 +

+
+
+ ✅ベストプラクティス +
+

+ Cloud + NATでは「割り当てポートの枯渇」がサイレント障害になりやすい落とし穴です。動的ポート割り当てを使用している場合でも、nat_allocation_failed系のメトリクスやログをアラート対象に含め、ポート使用率が閾値を超えたら通知されるようにしておくことを推奨します。 +

+
+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/nat/docs/monitoring +

+
+

+ 1.4 Task 5.1 設計・運用チェックリスト +

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+

+ Part 2: 接続性の維持とトラブルシューティング(Task 5.2) +

+

+ 2.1 Application Load Balancerでのトラフィックドレイン・リダイレクト +

+

+ バックエンドVMやNEGエンドポイントをローリングアップデート・スケールイン・メンテナンスのために安全に取り除くには、コネクションドレイニング(connection + draining)を使用します。ドレイニングタイムアウトを設定した状態でインスタンスグループからVMを削除、またはゾーンスコープのNEGからエンドポイントを削除すると、ロードバランサは新規接続を即座に停止しつつ、既存のリクエスト/接続には完了までの猶予を与えます。 +

+
+stateDiagram-v2
+    [*] --> ACTIVE
+    ACTIVE --> DRAINING: バックエンドグループから<br/>VM/エンドポイントを削除
+    DRAINING --> DRAINING: 既存リクエストは<br/>タイムアウトまで継続処理<br/>(新規接続は送られない)
+    DRAINING --> REMOVED: drainingTimeoutSecの経過、<br/>または全接続の完了
+    REMOVED --> [*]
+

ロードバランサの種類によって挙動に差異があります。

+
+ + + + + + + + + + + + + + + + + + + + + +
ロードバランサ種別ドレイニング中の挙動
Application Load Balancer(L7) + 指定したタイムアウトの間、既存リクエストの完了を待つ。新規リクエストは送られない +
Proxy Network Load Balancer既存のTCPコネクションはタイムアウト期間中も動作を継続する
内部パススルーNetwork Load Balancer(フェイルオーバー時) + disableConnectionDrainOnFailoverとdropTrafficIfUnhealthyで挙動を制御。既定のドレイニングタイムアウトは固定10分 +
+
+

+ トラフィックの計画的な移動には、バックエンドサービスのcapacity scalerも併用します。0(完全ドレイン、単一バックエンドの場合は設定不可)から1.0(100%)まで手動で目標キャパシティを調整でき、メンテナンス前にトラフィックを段階的に他のバックエンドへ寄せる、といった運用が可能です。GKE環境では、PodのpreStopフックの実行時間を「Backend + Service Drain Timeout + + ドレインレイテンシ(目安1分)」以上に設定し、terminationGracePeriodSecondsを十分に長く取ることで、NEGからのエンドポイント除去とPodの終了を同期させます。 +

+
+
+ ✅ベストプラクティス +
+

+ リージョナル外部パススルーNetwork Load + Balancerでは、フォワーディングルールのトラフィックステアリングを使い、特定の送信元IPレンジだけを別のバックエンドサービス(異なるヘルスチェックやドレイニング設定を持つ)へ振り向けることができます。これはカナリアリリースやトラブルシューティング目的のトラフィック分離に有効です。 +

+
+
+
+ 🔗出典 +
+ + +
+

+ 2.2 Cloud VPNの管理とトラブルシューティング +

+

+ Cloud + VPNのトラブルシューティングは、コンソールの「VPN」ページでトンネルステータスとBGPセッションステータスの両方を確認するところから始めます。HA + VPNではさらに、99.99% + SLAを満たすための高可用性ステータス(両インターフェースのトンネルが正しくオンプレミス側の冗長構成と対になっているか)も確認が必要です。 +

+
+flowchart TB
+    A["VPNトンネルが<br/>ESTABLISHEDにならない/<br/>不安定"] --> B{"トンネルステータスの<br/>アイコンにエラーメッセージは<br/>表示されているか"}
+    B -->|"Yes"| C["エラーメッセージから<br/>原因を特定<br/>(IKE/PSK/ピアIP不正など)"]
+    B -->|"No/不明"| D["Cloud VPNログを確認<br/>(自動収集済み)"]
+    D --> E{"SA_DELETE直後に<br/>再接続している形跡は<br/>あるか"}
+    E -->|"Yes"| F["オンプレミス側がrekeyingではなく<br/>SA削除後の再ネゴシエートに<br/>なっていないか確認"]
+    E -->|"No"| G["IKE暗号スイート/PSK/<br/>ピアIPアドレスの整合性を確認<br/>(RFC5735/5737の予約IPでないかも確認)"]
+    C --> H{"BGPセッションは<br/>ESTABLISHEDか"}
+    F --> H
+    G --> H
+    H -->|"No"| I["Cloud Router側の<br/>BGPトラブルシューティングへ<br/>(2.4節)"]
+    H -->|"Yes"| J["トンネル層は正常<br/>アプリケーション層/<br/>ルーティング層を調査"]
+

+ 代表的な作成失敗パターンとして、ピアIPアドレスがRFC + 5735/5737の予約アドレス範囲に該当しているケースが挙げられます。この場合、構成時のIPレンジを見直す必要があります。また、Cloud + VPNは既定でSAの有効期限が切れる前に自動的に再ネゴシエート(rekeying)しますが、オンプレミス側のゲートウェイがこれに対応しておらず、既存SAの削除後にのみ新規SAをネゴシエートする実装だと、接続が周期的に瞬断します。 +

+
+
+ 🔗出典 +
+ + +
+

+ 2.3 Cloud Interconnectの管理とトラブルシューティング +

+

+ Cloud Interconnectのトラブルシューティングは、1.3.2節で紹介した「物理層・論理層・ルーティング層」の3層モデルに沿って切り分けるのが基本です。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状疑うべき層確認方法
接続自体が確立しない/光レベル異常物理層 + gcloud compute interconnects get-diagnosticsでTx/Rx光レベルと稼働状態を確認 +
特定のVLANアタッチメントだけ帯域が頭打ち論理層 + VLANアタッチメントのMonitoringタブでingress/egress利用率、BANDWIDTH_THROTTLEドロップの有無を確認(60秒サンプリングのためバーストは見えにくい点に注意) +
経路が広告されない/学習されないルーティング層 + Cloud + RouterのBGPセッション状態、advertisedRoutesフィールドを確認 +
暗号化VLANアタッチメントの削除に失敗する論理層(MACsec) + MACsec構成済みのDedicated/Partner + Interconnectでは、削除前にMACsec設定の解除が必要な場合がある +
+
+

+ HA VPN over Cloud Interconnectのような複合構成では、「Cloud + Interconnect層(VLANアタッチメント間のBGP)」と「HA + VPN層(オンプレミスとVPCの間のBGP)」という2階層のBGPが存在するため、どちらの層で問題が起きているかを切り分けることが重要です。具体的には、まずCloud + Interconnect層のCloud Routerでgcloud compute routers get-statusを実行し、advertisedRoutesにHA + VPNゲートウェイのアドレスが含まれているかを確認し、次にHA + VPN層のBGPセッションを確認するという順序になります。 +

+
+
+ 🔗出典 +
+

+ https://docs.cloud.google.com/network-connectivity/docs/interconnect/support/troubleshooting +

+
+

+ 2.4 Cloud RouterのBGPピアリングのトラブルシューティング +

+

+ BGPセッションは、確立までに複数の状態を遷移します。試験では状態遷移の理解に加え、「どのログ/メトリクスでどの状態を確認するか」が問われます。 +

+
+stateDiagram-v2
+    [*] --> Idle
+    Idle --> Connect: セッション開始
+    Connect --> Active: TCP接続失敗
+    Connect --> OpenSent: TCP接続成功、OPEN送信
+    Active --> Connect: 再試行
+    OpenSent --> OpenConfirm: 相手からOPEN受信
+    OpenSent --> Idle: エラー/NOTIFICATION受信
+    OpenConfirm --> Established: KEEPALIVE受信
+    OpenConfirm --> Idle: エラー/NOTIFICATION受信
+    Established --> Idle: HOLD_TIMER_EXPIRED/<br/>LINK_DOWN/手動無効化など
+    Established --> [*]: 正常運用継続
+

代表的な障害パターンと対処の要点は次のとおりです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
障害パターン原因対処
ローカルASNとピアASNの重複 + 同一リージョン・同一ネットワーク内で同じASNを持つオンプレミスデバイスとセッションを試みている + Cloud RouterまたはオンプレミスルータのASN設計を見直す
MD5認証エラー(MD5_AUTH_INTERNAL_PROBLEM)Cloud Router内部でMD5認証設定に失敗(内部エラー)通常は自動復旧を待つ(1時間以上続く場合はサポートに連絡)
MD5認証エラー(鍵不一致)Cloud Routerとピアの事前共有鍵(認証キー)が一致していない認証キーを更新して再同期
最大経路数超過によるセッション遮断オンプレミスルータが5,000プレフィックスを超えて広告 + CEASE/MAX_PREFIXES_REACHEDログを確認し、広告プレフィックス数を削減するか、手動でBGPピアリングをリセット +
BGPフラップ(定期的な切断)Cloud Routerのソフトウェアメンテナンスイベント + オンプレミスルータでGraceful + Restartに対応し、ホールドタイマーを60秒以上に設定していれば通常は問題ない +
BFDの検知タイムアウト制御パケットが検知タイマー(既定5,000ms)以内に届かないBFDのMinRx/MinTx間隔・マルチプライヤの双方一致を確認
+
+

BFDを併用している場合は、BGPとは独立した状態機械としてBFDの状態を確認します。

+
+stateDiagram-v2
+    [*] --> AdminDown: BFD無効
+    [*] --> Down: BFD有効化直後
+    Down --> Init: ローカルが相手を検知
+    Init --> Up: 相手からの確認を受信
+    Up --> Down: 検知タイマーの<br/>タイムアウト
+    AdminDown --> Down: BFD再有効化
+

+ BFDの診断コード(NO_DIAGNOSTIC、CONTROL_DETECTION_TIME_EXPIRED、NEIGHBOR_SIGNALED_SESSION_DOWN、ADMINISTRATIVELY_DOWNなど)は、gcloud compute routers get-statusのbfdStatusフィールドで確認できます。BFDとBGP Graceful + Restartを併用している場合、Cloud + Routerの再起動時にはBFDへAdminDownを送信して意図的に停止し、その間BGPセッション自体はオンプレミス側でGraceful + Restartモードとして維持される、という設計になっている点も理解しておく必要があります。 +

+
+
+ ⚠注意 +
+

+ ICMPv6 pingはCloud + RouterのBGPアドレスに対してサポートされていません。レイヤー3疎通確認にはICMPv4 + pingを使用してください。 +

+
+
+
+ 🔗出典 +
+ + +
+

+ 2.5 VPC Flow Logs・ファイアウォールログ・Packet + Mirroringを使ったトラブルシューティング +

+

+ 3つのツールはそれぞれ異なる「見え方」を提供するため、組み合わせて使うことで根本原因への到達が早まります。 +

+
+flowchart TB
+    A["通信の疎通/性能に<br/>関する問い合わせ"] --> B{"通信自体が<br/>届いているか<br/>(Flow Logs)"}
+    B -->|"届いていない"| C{"ファイアウォールで<br/>拒否されているか<br/>(Firewall Logs)"}
+    C -->|"Deny hit あり"| D["該当ルールを特定し<br/>意図した挙動か確認<br/>(Firewall Insightsも活用)"]
+    C -->|"Deny hit なし"| E["ルーティング/<br/>Connectivity Testsで<br/>経路を確認"]
+    B -->|"届いているが<br/>アプリ層で問題"| F["Packet Mirroringで<br/>パケット内容を収集し<br/>アプリ/プロトコルを詳細分析"]
+    D --> G["是正"]
+    E --> G
+    F --> G
+

+ VPC Flow + Logsは「通信があったかどうか、どれだけの量か」を5-tuple単位で示しますが、パケットのペイロード自体は含みません。ペイロードレベルでの分析(アプリケーションプロトコルの異常、侵入検知システムとの連携など)が必要な場合はPacket Mirroringを使用します。 +

+
+flowchart LR
+    A["ミラーリング対象<br/>(VMインスタンスのNIC)"] -->|"ポリシーで指定<br/>(タグ/サブネット単位)"| B["Packet Mirroringポリシー"]
+    B --> C["コレクタ宛先<br/>(内部パススルーNLBの<br/>フォワーディングルール)"]
+    C --> D["コレクタインスタンス群<br/>(推奨: マネージドインスタンスグループ)"]
+    D --> E["収集・解析<br/>(IDS/IPS、パケットキャプチャ分析ツールなど)"]
+

Packet Mirroringの主な制約・特性は次のとおりです。

+
    +
  • + ミラー対象とコレクタ宛先は同一プロジェクト・同一リージョンである必要がある(コレクタは同一VPCまたはVPC + Network Peeringで接続されたVPCに配置可能) +
  • +
  • + 1つのミラーリングポリシーが参照できるコレクタ宛先は1つだが、1つのコレクタ宛先を複数のポリシーから参照することは可能 +
  • +
  • + ミラーリングとコレクションを同一VMの同一NICで行うと、ミラーリングループが発生するため不可 +
  • +
  • + GKEの同一ノード上のPod間通信をミラーリングするには、クラスタでIntranode + visibilityを有効化する必要がある +
  • +
  • + VPC Flow + Logsはミラーされたパケット自体をログしない。ただしコレクタインスタンスが属するサブネットでFlow + Logsが有効な場合、コレクタ宛の直接トラフィック(元の宛先IPがコレクタのIPと一致する場合)は通常どおり記録される +
  • +
  • + ミラーリングは元パケットとミラーパケットの両方を処理するため、処理レート(スループット)が低下する。低下幅はマシンタイプ・CPU使用率・パケットサイズに依存する +
  • +
+

+ Packet + Mirroringのモニタリングでは、ミラー対象VM側で「ミラーされたネットワークバイト数/パケット数」(成功・ドロップ両方)を確認できますが、コレクタ側のドロップパケット数は個別には提供されません(コレクタ側の監視は内部パススルーNLBのロギング・モニタリングに準じます)。 +

+
+
+ 🔗出典 +
+ + +
+

+ 2.6 Task 5.2 トラブルシューティングチェックリスト +

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+

+ Part 3: Network Intelligence Centerによる監視とトラブルシューティング(Task 5.3) +

+

+ 3.1 Network Intelligence Centerの全体像 +

+

+ Network Intelligence Center(NIC)は、Google + Cloudネットワークの可視化・監視・トラブルシューティングを1つのコンソールに統合したプラットフォームです。2019年の提供開始時点ではConnectivity + TestsとNetwork Topologyがベータ、Performance DashboardとFirewall Metrics & + Insightsがアルファでしたが、現在は6つのモジュールが揃っています。 +

+
+flowchart TB
+    NIC["Network Intelligence Center"] --> M1["Network Topology<br/>トポロジ可視化"]
+    NIC --> M2["Connectivity Tests<br/>疎通診断"]
+    NIC --> M3["Performance Dashboard<br/>パケットロス・レイテンシ"]
+    NIC --> M4["Firewall Insights<br/>ファイアウォールルール最適化"]
+    NIC --> M5["Network Analyzer<br/>構成の自動監視・誤設定検知"]
+    NIC --> M6["Flow Analyzer<br/>Flow Logsの高速分析"]
+
+    M1 -.->|"問題箇所の当たりをつける"| M2
+    M2 -.->|"設定起因の不通を特定"| M5
+    M5 -.->|"検知したインサイトを深掘り"| M6
+    M3 -.->|"性能劣化の切り分け"| M2
+    M4 -.->|"ルール最適化"| M5
+

各モジュールの役割を一言でまとめると次のようになります。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
モジュール主な問い分析の性質
Network Topology「今、どこにどれだけのトラフィックが流れているか」リアルタイムのテレメトリ + 構成情報の可視化
Connectivity Tests「AからBへ到達できるか、できないなら何が阻んでいるか」構成分析(+一部でデータプレーン検証)
Performance Dashboard + 「ゾーン/リージョン間のパケットロス・レイテンシはどの程度か」 + 実トラフィックに基づく能動的プロービング
Firewall Insights「このファイアウォールルールは安全に削除・厳格化できるか」ロギングデータ + 機械学習予測
Network Analyzer「構成に誤りや非効率はないか」構成の自動巡回監視(プッシュ型)
Flow Analyzer「Flow Logsから見える実際の通信パターンは何か」SQLレスなFlow Logs分析(BigQuery基盤)
+
+
+
+ 🔗出典 +
+ + +
+

3.2 Network Topology

+

+ Network + Topologyは、構成情報とリアルタイムの運用データを1つのグラフに統合して可視化するツールです。Infrastructure viewではVPCネットワーク、オンプレミスとのハイブリッド接続、Google管理サービスへの接続とそれらのメトリクスを表示し、GKE Enterprise viewではクラスタ・ネームスペース・ワークロード・Podとそのメトリクスを表示します。 +

+

+ 活用の典型例は、特定のCloud + VPNトンネルやVLANアタッチメントを流れるトラフィック量をエンティティ単位で確認し、Shared + VPCの他プロジェクトやリージョン間トラフィックへの影響を把握することです。エンティティをクリックすると、そのエンティティを通過するすべてのトラフィックパスがハイライトされます。 +

+
+
+ 🔗出典 +
+

+ https://cloud.google.com/network-intelligence-center/docs/network-topology/reference/metrics-reference +

+
+

3.3 Connectivity Tests

+

+ Connectivity + Testsは、送信元と宛先(VM、GKEクラスタ、ロードバランサのフォワーディングルール、インターネット上のIPアドレスなど)を指定し、その間のパケットが実際にどう転送されるかをシミュレーションするツールです。分析は2種類に分かれます。 +

+
+flowchart TB
+    A["Connectivity Testの作成<br/>(送信元・宛先・プロトコル・ポート)"] --> B["構成分析<br/>(configuration analysis)"]
+    B --> C{"複数の経路<br/>(トレース)が<br/>存在するか"}
+    C -->|"1本のみ"| D["トレースの最終状態が<br/>そのまま総合結果になる"]
+    C -->|"複数本<br/>(例: LBの背後に<br/>複数バックエンド)"| E["各トレースの最終状態の<br/>分布から総合結果を算出"]
+    D --> F["総合到達性(overall reachability result)"]
+    E --> F
+    F --> G{"対応シナリオでは<br/>データプレーン検証も<br/>実行可能"}
+    G -->|"Yes"| H["実際にプローブパケットを送信し、<br/>レイテンシ・パケットロスの<br/>ベースラインを取得"]
+    G -->|"No"| I["構成分析結果のみで判断"]
+

総合到達性の結果は4値のいずれかです。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
結果意味
Reachable現在の構成でトラフィックが送信元から宛先へ到達できる
Unreachable + 経路上のどこかでトラフィックが遮断されている(トレースにドロップ箇所が示される) +
Ambiguous + 複数トレースの最終状態が混在している(例: + 一部バックエンドは到達可能、一部は不可) +
Undeterminedエラー、非対応の入力、権限不足などにより判定不能
+
+
+
+ ⚠注意 +
+

+ Ambiguousの典型的な原因の1つは、閲覧権限のない階層型ファイアウォールポリシーをトレースが参照している場合です。ポリシー自体の閲覧権限がなくても、自分のVPCネットワークに適用される実効ルールは「Effective + firewall + rules」で確認できます。また、構成分析でReachableと判定されても、実際にはデータプレーンで100%パケットロスが発生している場合があります。これは構成分析とデータプレーン分析が別物であるためで、対応シナリオではデータプレーン検証も併用して裏取りすることが推奨されます。 +

+
+

+ Google管理サービス(Cloud + SQL、GKEなど)を宛先とするテストも作成できますが、Google所有プロジェクト内のリソースについては閲覧権限がないため、トレースの詳細(具体的にどのルール・ルートが適用されたか)は表示されず、総合到達性の結果のみが返されます。 +

+
+
+ 🔗出典 +
+ + +
+

3.4 Performance Dashboard

+

+ Performance Dashboardは、Google + Cloudネットワーク全体、および自分のプロジェクトのリソースに関するパケットロスとレイテンシ(RTT)を可視化します。セットアップは不要で、十分な数のVMがあればパケットロスメトリクスが、十分なトラフィック量があればレイテンシメトリクスが自動的に得られます。 +

+
+flowchart LR
+    A["Performance Dashboard"] --> B["プロジェクトパフォーマンスビュー"]
+    A --> C["Google Cloud全体パフォーマンスビュー"]
+    B --> B1["自分のVM間の<br/>パケットロス(能動プロービング)"]
+    B --> B2["実トラフィックに基づく<br/>レイテンシ(TCP SEQ/ACK計測)"]
+    C --> C1["全ゾーンペア間の<br/>パケットロス"]
+    C --> C2["リージョン⇔インターネット拠点間の<br/>レイテンシ中央値"]
+    B1 --> D["ヒートマップ/<br/>サマリチャートで表示<br/>(最大6週間の履歴)"]
+    B2 --> D
+    C1 --> D
+    C2 --> D
+

+ 代表的な活用パターンは、アプリケーションで性能問題が疑われたときに「まずPerformance + Dashboardでネットワーク側に異常がないかを確認し、異常がなければアプリケーション側を疑う」という切り分けです。自分のプロジェクトの値と、Google + Cloud全体の同一ゾーン/リージョンペアの平均値を並べて比較することで、自分の環境固有の問題か、Google + Cloud全体で起きている事象かを判断できます。パケットロスメトリクスは常に利用可能ですが、1分あたり400プローブ未満の場合はアスタリスク(*)が付き、データの信頼性が低いことを示します。 +

+
+
+ 🔗出典 +
+ + +
+

3.5 Firewall Insights

+

+ Firewall Insightsは、ファイアウォールルール(VPC firewall + rules、ファイアウォールポリシーに属するルールの両方)の構成と使用実態を分析し、最適化のためのインサイトを提供します。インサイトは大きく3種類です。 +

+
+flowchart TB
+    FI["Firewall Insights"] --> T1["シャドウルール<br/>(shadowed rule)"]
+    FI --> T2["過度に寛容なルール<br/>(overly permissive rule)"]
+    FI --> T3["拒否ルールのヒット<br/>(deny rule insight)"]
+
+    T1 --> T1a["構成情報のみから判定可能<br/>(ロギング不要)"]
+    T2 --> T2a["ヒットなしのAllowルール"]
+    T2 --> T2b["未使用の属性を持つルール"]
+    T2 --> T2c["過度に広いIP/ポートレンジ"]
+    T2 --> T2d["適応的分析による<br/>陳腐化予測(機械学習)"]
+    T3 --> T3a["観測期間中にヒットした<br/>Denyルールの詳細"]
+
+    T2a -.->|"Firewall Rules Logging<br/>のデータが必要"| Log["ロギング有効化"]
+    T2b -.-> Log
+    T2c -.-> Log
+    T3a -.-> Log
+

+ シャドウルールは、自分より優先度が高い(または同等の)ルールと属性(IPレンジなど)が重複しており、実質的に一度もマッチし得ないルールです。構成情報だけから機械的に判定できるため、Firewall + Rules + Loggingを有効化していなくても検知されます。一方、過度に寛容なルールと拒否ルールインサイトはログベースであり、Firewall + Rules + Loggingを有効化した状態でのトラフィック実績が必要です。シャドウルール・過度に寛容なルールのインサイトは、Firewall + Insightsのページで機能を有効化してから最大48時間で生成され始め、機械学習による陳腐化予測は新規/更新されたルールに対して最大10日ほどかかります。 +

+
+
+ ⚠注意 +
+

+ ロードバランサのヘルスチェック用IPレンジ(35.191.0.0/16など)を許可するルールは、ヒット数が少なくても「過度に寛容」や「未使用」と誤判定されて削除対象に挙げられることがあります。これらはGoogle + Cloudの機能上必要なルールであるため、インサイトを鵜呑みにせず、削除前に用途を確認してください。 +

+
+

+ Firewall Insightsが検出したインサイトは、Recommenderが提供するActive Assistダッシュボードからも確認できます(カード名がFirewall + Insights側とは異なる点に注意)。 +

+
+
+ 🔗出典 +
+ + +
+

3.6 Network Analyzer

+

+ Network + Analyzerは、VPCネットワークの構成を自動的に巡回監視し、誤設定や非効率な構成を検出するプッシュ型のツールです。設定変更後は約10分でその変更に関連する分析が実行され、それとは別に少なくとも1日1回の定期分析も行われます。インサイトは5つのグループに分類されます。 +

+
+flowchart TB
+    NA["Network Analyzer<br/>インサイトグループ"] --> G1["VPCネットワーク<br/>IPアドレス/ルート/ファイアウォール/<br/>VPC Peering/Shared VPC"]
+    NA --> G2["ネットワークサービス<br/>ロードバランサ/Cloud NAT"]
+    NA --> G3["ハイブリッド接続<br/>Cloud VPN/Interconnect/<br/>Cloud Router/BGP/NCC"]
+    NA --> G4["GKE<br/>ノード⇔コントロールプレーン疎通/<br/>Pod IP使用率/ベストプラクティス"]
+    NA --> G5["マネージドサービス<br/>Cloud SQLなどへの接続性"]
+

+ たとえばVPCネットワークグループでは「無効なネクストホップを持つルート」、ネットワークサービスグループでは「ヘルスチェックをブロックしているファイアウォールルール」「トラフィックとヘルスチェックで異なるポートを使っているバックエンドサービス」、GKEグループでは「ノードからコントロールプレーンへの双方向疎通の設定起因の問題」「PodのIPアドレス使用率」といった具体的なインサイトが提供されます。 +

+

+ Shared + VPCでは、ホストプロジェクト側でIPアドレス使用率などVPCネットワーク全体に関わるインサイトが提供され(サービスプロジェクトの情報も自動集約)、サービスプロジェクト側ではロードバランサやGKEなどそのプロジェクト固有のサービスに関するインサイトが提供されます。複数プロジェクトを横断して監視したい場合は、Cloud + Monitoringのメトリクススコープを構成し、対象プロジェクトを監視対象として追加します。 +

+

+ Network Analyzerが公開したインサイトはCloud + Loggingにも格納され、ログ名はprojects/{project-id}/logs/networkanalyzer.googleapis.com/analyzer_reportsの形式です。Network + Analyzer自体はCloud + Monitoringへメトリクスを送信しないため、リアルタイムアラートが必要な場合はこのログに対してログベースアラートを設定します。 +

+
+
+ 🔗出典 +
+ + +
+

3.7 Flow Analyzer

+

+ Flow Analyzerは、VPC Flow + Logsに対して複雑なSQLクエリを書かずにトラフィックパターンを分析できるツールです。Observability + Analytics(旧Log Analytics)が有効化されたログバケットに格納されたFlow + Logsのレコードを対象とし、BigQueryを基盤に5-tuple粒度でのオピニオンベース分析(意見の分かれない、定型化された分析軸)を提供します。 +

+
+flowchart TB
+    A["VPC Flow Logsを<br/>ログバケットに格納"] --> B["ログバケットを<br/>Observability Analytics用に<br/>アップグレード"]
+    B --> C["Flow Analyzerで<br/>集計方法・時間範囲を選択"]
+    C --> D["Organize Flows by<br/>(例: VPCサブネットワーク/IP/ポート)で<br/>グルーピング"]
+    D --> E["Highest data flowsチャート /<br/>All data flowsテーブルで<br/>結果を確認"]
+    E --> F["特定のフローを<br/>ドリルダウン<br/>(送信元/宛先/トラフィック量の詳細)"]
+    F --> G["さらに他のフィールドで<br/>分割してドリルダウン"]
+

+ 活用例として、「誰が接続を開始したか」を知りたい場合は送信元ペインで「VPCサブネットワーク」「IP」「ポート」を選択してグルーピングします。Cross-Cloud + Network環境では、VLANアタッチメントやVPNトンネルに対してもVPC Flow + Logsを有効化でき、reporter(トラフィックの方向)やgatewayオブジェクト(ゲートウェイの名前・タイプ・プロジェクトID・ロケーション)といった新しい注釈がFlow + Analyzerに統合されており、オンプレミス⇔クラウド間の「エレファントフロー」(高帯域フロー)の特定やShared + VPC環境でのサービスプロジェクト別のハイブリッド帯域使用量の監査に活用できます。 +

+
+
+ 🔗出典 +
+ + +
+

3.8 Task 5.3 活用チェックリスト

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+

+ 総合トラブルシューティングワークフロー +

+

+ 最後に、Task + 5.1〜5.3で紹介したツールを、実際のインシデント対応の流れに沿って統合したワークフローを示します。試験では個々のツールの仕様だけでなく、「この状況ではどのツールをどの順序で使うべきか」という統合的な判断力も問われます。 +

+
+flowchart TB
+    Start(["ネットワーク障害・性能劣化の<br/>アラートまたは問い合わせ"]) --> Q1{"影響範囲は<br/>特定エンドポイント間か、<br/>広範囲か"}
+
+    Q1 -->|"特定のA-B間"| CT["Connectivity Testsで<br/>構成分析を実行(3.3)"]
+    Q1 -->|"広範囲/不明"| NT["Network Topologyで<br/>トラフィック全体を俯瞰(3.2)"]
+
+    NT --> Q2{"特定のコンポーネントに<br/>異常が見えるか"}
+    Q2 -->|"Yes"| Route["該当コンポーネントの<br/>ログ・メトリクスへ(Part 1)"]
+    Q2 -->|"No"| PD["Performance Dashboardで<br/>パケットロス/レイテンシを確認(3.4)"]
+
+    CT --> Q3{"結果は<br/>Unreachable/Ambiguousか"}
+    Q3 -->|"Yes"| FL["VPC Flow Logs /<br/>Firewall Logsで実トラフィックを確認(2.5)"]
+    Q3 -->|"No(Reachable)"| PD
+
+    PD --> Q4{"パケットロス/レイテンシが<br/>Google Cloud平均から<br/>逸脱しているか"}
+    Q4 -->|"Yes"| Route
+    Q4 -->|"No"| App["アプリケーション側の<br/>問題を疑う"]
+
+    FL --> Q5{"ファイアウォールの<br/>Denyヒットが原因か"}
+    Q5 -->|"Yes"| FI["Firewall Insightsで<br/>ルールの妥当性を検証(3.5)"]
+    Q5 -->|"No"| PM["Packet Mirroringで<br/>ペイロードレベルの分析(2.5)"]
+
+    Route --> Q6{"VPN/Interconnect/<br/>Cloud RouterのBGPが<br/>関係するか"}
+    Q6 -->|"Yes"| BGP["2.2〜2.4節の手順で<br/>トンネル/物理層/BGPを切り分け"]
+    Q6 -->|"No"| Other["該当コンポーネントの<br/>ログ・メトリクスで直接調査"]
+
+    FI --> Fix["是正・ルール修正"]
+    PM --> Fix
+    BGP --> Fix
+    Other --> Fix
+    App --> Fix
+
+    Fix --> NA["Network Analyzerで<br/>再発防止の構成チェックを<br/>定期実行(3.6)"]
+    NA --> End(["恒久対応・<br/>ランブック更新"])
+
+
+ ✅ベストプラクティス +
+

+ このワークフローが機能する前提は、Part + 1で解説したロギング・モニタリングが平時から有効化されていることです。障害発生後にVPC + Flow LogsやFirewall Rules + Loggingを有効化しても、発生時点までのデータは遡って取得できません。試験対策としても実務としても、「まず何を有効化しておくべきか」という設計判断(Task + 5.1)が、トラブルシューティング(Task 5.2)とNetwork Intelligence + Centerの活用(Task + 5.3)の土台になっている、という関係を押さえておいてください。 +

+
+
+

参考文献

+
+ + + + + +
+

Network Intelligence Center

+ +
+
+
+
+ + + + diff --git a/S6-network-ops-monitoring.md b/S6-network-ops-monitoring.md new file mode 100644 index 000000000..fdfc8cedd --- /dev/null +++ b/S6-network-ops-monitoring.md @@ -0,0 +1,846 @@ +# PCNE試験対策ガイド S6: ネットワーク操作と監視 + +**Google Cloud Professional Cloud Network Engineer(PCNE)認定試験 — Section 5: Managing, monitoring, and troubleshooting network operations(出題比率 約14**%) + +--- + +## この記事について(スコープ対応表) + +本ガイドは、本シリーズで先行して公開したS1(Section 1 設計・計画)、S2(Section 4 ハイブリッド接続)、S3(Section 2 VPC実装 / Section 3 Task 3.1 ロードバランシング)、S4(Section 3 Task 3.2-3.3 CDN・DNS・IPAM)、S5(Section 6 ネットワークセキュリティ)の続編として、公式Exam Guide(PDF)の**Section 5「Managing, monitoring, and troubleshooting network operations**」(出題比率約14%)に厳密に対応する範囲を扱います。ユーザー呼称の「S6」は公式セクション番号とは一致しませんが、これまでのシリーズと同じ命名慣行を踏襲しています。 + +| 本ガイドの構成 | 公式Exam GuideのTask | 主な内容 | +|---|---|---| +| Part 1 | Task 5.1 Logging and monitoring with Google Cloud Observability | ネットワークコンポーネント別のCloud Loggingログ、Cloud Monitoringメトリクス | +| Part 2 | Task 5.2 Maintaining and troubleshooting connectivity issues | ALBのトラフィックドレイン、VPN/Interconnect/Cloud Router BGPのトラブルシューティング、Flow Logs・Firewall Logs・Packet Mirroringの活用 | +| Part 3 | Task 5.3 Using Network Intelligence Center to monitor and troubleshoot common networking issues | Network Topology、Connectivity Tests、Performance Dashboard、Firewall Insights、Network Analyzer、Flow Analyzer | + +> **出典** +> - https://cloud.google.com/learn/certification/cloud-network-engineer +> - https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf + +--- + +## 目次 + +- [全体像](#全体像) +- [Part 1: Google Cloud Observabilityによるロギングとモニタリング(Task 5.1)](#part-1-google-cloud-observabilityによるロギングとモニタリングtask-51) + - [1.1 Google Cloud Observabilityの基本構造](#11-google-cloud-observabilityの基本構造) + - [1.2 ネットワークコンポーネント別ロギング](#12-ネットワークコンポーネント別ロギング) + - [1.3 ネットワークメトリクスのモニタリング](#13-ネットワークメトリクスのモニタリング) + - [1.4 Task 5.1 設計・運用チェックリスト](#14-task-51-設計運用チェックリスト) +- [Part 2: 接続性の維持とトラブルシューティング(Task 5.2)](#part-2-接続性の維持とトラブルシューティングtask-52) + - [2.1 Application Load Balancerでのトラフィックドレイン・リダイレクト](#21-application-load-balancerでのトラフィックドレインリダイレクト) + - [2.2 Cloud VPNの管理とトラブルシューティング](#22-cloud-vpnの管理とトラブルシューティング) + - [2.3 Cloud Interconnectの管理とトラブルシューティング](#23-cloud-interconnectの管理とトラブルシューティング) + - [2.4 Cloud RouterのBGPピアリングのトラブルシューティング](#24-cloud-routerのbgpピアリングのトラブルシューティング) + - [2.5 VPC Flow Logs・ファイアウォールログ・Packet Mirroringを使ったトラブルシューティング](#25-vpc-flow-logsファイアウォールログpacket-mirroringを使ったトラブルシューティング) + - [2.6 Task 5.2 トラブルシューティングチェックリスト](#26-task-52-トラブルシューティングチェックリスト) +- [Part 3: Network Intelligence Centerによる監視とトラブルシューティング(Task 5.3)](#part-3-network-intelligence-centerによる監視とトラブルシューティングtask-53) + - [3.1 Network Intelligence Centerの全体像](#31-network-intelligence-centerの全体像) + - [3.2 Network Topology](#32-network-topology) + - [3.3 Connectivity Tests](#33-connectivity-tests) + - [3.4 Performance Dashboard](#34-performance-dashboard) + - [3.5 Firewall Insights](#35-firewall-insights) + - [3.6 Network Analyzer](#36-network-analyzer) + - [3.7 Flow Analyzer](#37-flow-analyzer) + - [3.8 Task 5.3 活用チェックリスト](#38-task-53-活用チェックリスト) +- [総合トラブルシューティングワークフロー](#総合トラブルシューティングワークフロー) +- [参考文献](#参考文献) + +--- + +## 全体像 + +Section 5は「作る」フェーズ(Section 1〜4)を終えたネットワークを、日々どう**観測し**、**維持し**、**壊れたときに直す**かを問う領域です。試験ガイドは3つのTaskに分かれていますが、実務的には次の1本の流れとして理解すると整理しやすくなります。 + +```mermaid +flowchart TB + A["ネットワークコンポーネント
VPC/Router/VPN/Interconnect/NAT/DNS/LB/Armor/NCC"] --> B["Task 5.1
Cloud Observabilityで収集する"] + B --> C{"異常や問い合わせが
発生した"} + C -->|"Yes"| D["Task 5.2
コンポーネント別に
トラブルシューティングする"] + C -->|"No(平常時)"| E["Task 5.3
Network Intelligence Centerで
可視化・予防診断する"] + D --> F["Flow Logs / Firewall Logs /
Packet Mirroringで
根本原因を特定する"] + E --> G["Connectivity Tests / Network Analyzer /
Firewall Insightsで
構成起因の問題を先回りで検知する"] + F --> H["是正・再発防止"] + G --> H + H --> B +``` + +この循環の中で、**Task 5.1(収集)** が土台であり、Flow Logs・Firewall Rules Logging・各種メトリクスが有効化されていなければ、Task 5.2の切り分けもTask 5.3のNetwork Analyzer/Firewall Insightsのログベースインサイトも機能しません。試験でも「まずロギングとモニタリングが有効化されているか」を問う設問が土台になっている点を意識してください。 + +--- + +## Part 1: Google Cloud Observabilityによるロギングとモニタリング(Task 5.1) + +### 1.1 Google Cloud Observabilityの基本構造 + +Google Cloud Observability(旧Stackdriver)は、**Cloud Logging**(ログ)と**Cloud Monitoring**(メトリクス)を中核としたスイートです。ネットワークコンポーネントの大半は、追加のエージェント導入なしにログとメトリクスを自動的に送信します。 + +```mermaid +flowchart LR + subgraph Sources["ネットワークコンポーネント"] + direction TB + S1["Cloud VPN"] + S2["Cloud Router"] + S3["VPC Service Controls"] + S4["Cloud NGFW / VPC Firewall"] + S5["VPC Flow Logs"] + S6["Cloud DNS"] + S7["Cloud NAT"] + S8["Network Connectivity Center"] + end + + Sources --> L["Cloud Logging
(Logs Explorer)"] + Sources --> M["Cloud Monitoring
(Metrics Explorer)"] + + L --> LS1["ログシンク経由でエクスポート
BigQuery / Cloud Storage / Pub/Sub"] + L --> LS2["ログベースメトリクス /
ログベースアラート"] + M --> MS1["事前定義ダッシュボード"] + M --> MS2["カスタムダッシュボード /
アラートポリシー"] + LS1 --> SIEM["Flow Analyzer / BigQuery分析 /
外部SIEM"] +``` + +試験ガイドが明示的に列挙しているロギング対象コンポーネントは、**Cloud VPN、Cloud Router、VPC Service Controls、Cloud NGFW、Firewall Insights、VPC Flow Logs、Cloud DNS、Cloud NAT、Network Connectivity Center**の8つです。これらはすべてCloud Loggingに自動で書き込まれ、追加課金なしで有効化できるものがほとんどですが、VPC Flow LogsとFirewall Rules Loggingは明示的な設定が必要です。 + +> **注意** +> Cloud LoggingとCloud Monitoringは無料枠を超えると、取り込み量(ログ)やサンプル数(メトリクス)に応じた課金が発生します。特にVPC Flow Logsはトラフィック量に比例して急増しやすいため、サンプリングレートやメタデータ注釈の絞り込みが設計上の重要な検討事項になります。 + +### 1.2 ネットワークコンポーネント別ロギング + +#### 1.2.1 VPC Flow Logs + +VPC Flow Logsは、VPCネットワーク内を流れるパケットをサンプリングし、5-tuple(送信元/宛先IP、ポート、プロトコル)単位で集約したフローログを生成します。対象となるトラフィックは次のとおりです。 + +- VMインスタンス(GKEノードを含む)が送受信するパケット +- Direct VPC Egressを構成したCloud Runリソースが送受信するパケット +- Cloud InterconnectのVLANアタッチメントやCloud VPNトンネルを通過するパケット + +```mermaid +flowchart LR + A["VPCネットワーク内の
パケット"] --> B["サンプリング
(primary sampling rate)"] + B --> C["5-tuple単位で集約
(aggregation interval)"] + C --> D["メタデータ注釈付与
(送信元/宛先の名前解決、地理情報など)"] + D --> E["フィルタリング
(任意)"] + E --> F["Cloud Logging
(vpc_flows)"] + F --> G["Logs Explorer /
ログシンクでエクスポート"] + F --> H["Flow Analyzer
(Log Analytics有効化時)"] +``` + +VPC Flow Logsは組織レベル・プロジェクトレベル・サブネット/VLANアタッチメント/VPNトンネル単位で個別に構成でき、組織レベルの構成を持つ場合はShared VPC・VPC Network Peering・Network Connectivity Center経由のフローに「クロスプロジェクト注釈」が付与されます。GKEのPodからインターネット向けの通信は、IP masqueradeによって送信元IPがノードIPに変換されるため、既定ではPodの注釈が付きません(Podの注釈を取得したい場合はCloud NATと組み合わせる必要があります)。 + +用途としては、ネットワークフォレンジック(侵害されたIPの特定)、キャパシティプランニング、コスト最適化(トップトーカーの特定)が代表的です。 + +> **出典** +> - https://cloud.google.com/vpc/docs/flow-logs +> - https://cloud.google.com/vpc/docs/using-flow-logs +> - https://cloud.google.com/vpc/docs/about-flow-logs-records +> - https://cloud.google.com/vpc/docs/access-flow-logs + +#### 1.2.2 ファイアウォールルールロギング(VPC Firewall Rules / 階層型ポリシー / Cloud NGFW) + +VPC Firewall Rules LoggingはCompute Engine VM(GKEノードを含む)への/からのトラフィックを対象とし、ルールが許可または拒否した通信のたびに「接続レコード」というログエントリを生成します。**Allow系ルール**と**Deny系ルール**でログの挙動が大きく異なる点は頻出ポイントです。 + +| 項目 | Allow + ロギング | Deny + ロギング | +|---|---|---| +| ログの発生単位 | 接続(コネクション)ごとに1回 | 一意な5-tupleごとに、パケットが観測されるたびに再発生 | +| 継続時間中の追加ログ | 生成されない(ステートフルなため応答トラフィックは記録されない) | パケットが観測される限り約5秒ごとに繰り返し記録される | +| 既存アクティブ接続へのロギング有効化 | 新規ログは即時生成されない。アイドル10分後、新しいパケットが来た時点で記録される | 該当なし(そもそも許可されていない接続) | +| 長時間接続の可視性 | 低い(1エントリのみ) | 高い(継続的に記録される) | + +> **ベストプラクティス** +> アイドル期間のない長時間ストリームを継続的に可視化したい場合はVPC Firewall Rules LoggingではなくVPC Flow Logsを使用してください。ファイアウォールログは「許可/拒否の判断根拠」を追うのに適し、Flow Logsは「トラフィックの実体」を追うのに適しています。両者は補完関係にあり、片方だけでは不十分なケースが多くあります。 + +階層型ファイアウォールポリシーおよびグローバル/リージョナルのネットワークファイアウォールポリシー(Cloud NGFW)でも同様にロギングを有効化でき、ログはCloud Loggingの同じ基盤に書き込まれます。Firewall Insightsが生成するインサイトは、このロギングデータを土台にしています(詳細は[3.5節](#35-firewall-insights))。 + +> **出典**: https://docs.cloud.google.com/firewall/docs/vpc-firewall-rules-logging-overview + +#### 1.2.3 Cloud Routerのログ + +Cloud RouterはBGPセッションの状態変化を3種類のイベントとしてCloud Loggingに記録します。 + +```mermaid +sequenceDiagram + participant CR as Cloud Router + participant Log as Cloud Logging + participant Peer as オンプレミス/ピアルータ + + CR->>Log: Router event
(Router task activated/de-activated) + CR->>Peer: BGPセッション確立を試行 + Peer-->>CR: OPEN / KEEPALIVE + CR->>Log: BGP event
(peering came up X seconds ago) + CR->>Log: Route event
(Advertising prefix / prefix received) + Note over CR,Peer: 障害発生 + Peer--xCR: セッション断 + CR->>Log: BGP event
(peering went down, reason: HOLD_TIMER_EXPIRED等) + CR->>Log: Route event
(Withdrawing prefix) +``` + +ログには「Router event」(タスクの起動/非活性化)、「BGP event」(ピアリングの確立・切断とその理由)、「Route event」(経路の広告・撤回、学習した経路のネクストホップ)の3系統があり、それぞれ`[Event Type]: [Log Text]`という定型フォーマットで出力されます。障害調査では、まず「BGP event」でセッション断の理由(`HOLD_TIMER_EXPIRED`、`LINK_DOWN`など)を確認し、次に「Route event」で経路の広告状況を突き合わせるのが定石です。 + +> **出典** +> - https://cloud.google.com/network-connectivity/docs/router/how-to/viewing-logs-metrics +> - https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-log-messages + +#### 1.2.4 Cloud VPNのログ + +Cloud VPNのログは自動的に有効化されており、追加設定は不要です。IKEネゴシエーション、鍵の再交換(rekeying)、SA(セキュリティアソシエーション)の削除といったイベントが記録されます。トンネルが確立後すぐに切断を繰り返す場合、`Received SA_DELETE`ログの直後に再接続している形跡があれば、オンプレミス側のゲートウェイがrekeyingではなく既存SAの削除後に新規SAをネゴシエートする実装になっている可能性を疑います。 + +> **出典**: https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/viewing-logs-metrics + +#### 1.2.5 Cloud NATのログ + +Cloud NATのログエントリには、重大度・プロジェクトIDなど一般的なフィールドに加え、NAT固有の情報(変換前後のIPアドレス・ポート、NAT64の場合は宛先IPv4埋め込みIPv6アドレスなど)が含まれます。ログベースメトリクスへのエクスポートも構成可能です。加えて、Cloud NATは`compute.googleapis.com`を対象とした監査ログ(Admin Activity / Data Access)も生成します。 + +> **出典** +> - https://cloud.google.com/nat/docs/monitoring +> - https://cloud.google.com/nat/docs/audit-logging + +#### 1.2.6 Cloud DNSのログ + +Cloud DNSロギングは、VPCネットワーク内のVM、GKEコンテナ、ピアリング先のゾーン、オンプレミスからのインバウンドフォワーディングなど、さまざまな経路からのDNSクエリを記録します。パブリックゾーンに対する外部からの直接クエリも対象です。既定では無効で、ゾーン単位・ポリシー単位で有効化します。ログには重複するフィールドがあり、一部はモニタリングメトリクスとも共有されています。目安として1万クエリあたり約5MBのログが生成されます。 + +> **注意** +> `SERVFAIL`を含むログで`destinationIP`・`egressIP`・`egressError`などのフィールドが欠落している場合は、Cloud DNSのトラブルシューティングドキュメントの該当セクションを参照してください。 + +> **出典**: https://docs.cloud.google.com/dns/docs/monitoring + +#### 1.2.7 VPC Service Controlsの監査ログ + +VPC Service Controlsは、セキュリティポリシー違反によって拒否されたすべてのアクセスを既定でCloud Loggingに記録します。監査ログは「Audited Resource」というログストリームに書き込まれ、`protoPayload.metadata.@type`が`type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata`のログとして特定できます。 + +```mermaid +flowchart TB + A["サービスへのAPIリクエスト"] --> B{"サービス境界を
越えるか"} + B -->|"No"| C["通常どおり処理"] + B -->|"Yes"| D{"アクセスレベル/
Ingress-Egressルールで
許可されているか"} + D -->|"Yes"| C + D -->|"No"| E["アクセス拒否
(トラブルシューティングトークンを生成)"] + E --> F["Cloud Audit Logsに記録
(VpcServiceControlAuditMetadata)"] + F --> G["Violation Dashboardで集約
(組織レベルのログシンクが必要)"] + F --> H["Violation Analyzerで
トラブルシューティングトークンから原因を診断"] +``` + +組織全体の違反を俯瞰するには**Violation Dashboard**の設定(組織レベルのログシンクとログバケットの構成)が必要で、設定前に発生した違反はバックフィルされません。個別の拒否イベントは、エラーメッセージ中の一意なID(トラブルシューティングトークン)を使い、**Violation Analyzer**または`gcloud logging read`での直接検索によって原因を特定できます。 + +> **出典** +> - https://docs.cloud.google.com/vpc-service-controls/docs/audit-logging +> - https://docs.cloud.google.com/vpc-service-controls/docs/violation-dashboard +> - https://docs.cloud.google.com/vpc-service-controls/docs/retrieve-troubleshoot-errors +> - https://cloud.google.com/vpc-service-controls/docs/violation-analyzer + +#### 1.2.8 Network Connectivity Centerのログ + +NCC(ハブ・スポーク・NCC Gatewayスポーク)およびRouter applianceに関するログも、Cloud Loggingに一般的なフィールド(重大度・プロジェクトID・タイムスタンプ)とログ種別ごとの詳細情報を伴って記録されます。なお、Router applianceの**ログ**自体はCloud Routerのロギング機構に委譲されており、NCC固有のログとCloud Routerのログを併読する必要がある点に注意してください。 + +> **出典**: https://docs.cloud.google.com/network-connectivity/docs/network-connectivity-center/how-to/viewing-logs-metrics + + +### 1.3 ネットワークメトリクスのモニタリング + +試験ガイドが明示するモニタリング対象は、**Cloud VPN、Cloud InterconnectとVLANアタッチメント、Cloud Router、ロードバランサ、Google Cloud Armor、Cloud NAT**の6分野です。いずれも自動的にCloud Monitoringへメトリクスが送信されるため、エージェントのインストールは不要です。 + +```mermaid +flowchart TB + subgraph Metrics["自動収集されるメトリクス"] + M1["Cloud VPN
トンネル単位のbytes/packets"] + M2["Cloud Interconnect
物理接続 + VLANアタッチメント"] + M3["Cloud Router
ルータ単位 + BGPセッション単位"] + M4["ロードバランサ
request_count / latencies / response_code"] + M5["Cloud Armor
ポリシー単位のリクエスト・ブロック数"] + M6["Cloud NAT
ゲートウェイ単位の使用率・ドロップ数"] + end + Metrics --> CM["Cloud Monitoring"] + CM --> D1["事前定義ダッシュボード
(各コンソール画面のMonitoringタブ)"] + CM --> D2["Metrics Explorer /
カスタムダッシュボード"] + CM --> D3["アラートポリシー
(通知チャネル経由)"] +``` + +#### 1.3.1 Cloud VPNのメトリクス + +各トンネルの詳細ページの「Monitoring」タブで、bytes/packetsなどの主要メトリクスをリージョン・ゲートウェイ・トンネル単位でフィルタして確認できます。パケットがドロップされた場合、ゲートウェイはドロップ理由を提供します。 + +#### 1.3.2 Cloud InterconnectとVLANアタッチメントのメトリクス + +Google側でポートを割り当てた時点(接続がまだ使用可能になる前)からメトリクス収集が始まるため、開通前の物理接続の監視やテストにも活用できます。VLANアタッチメント・ワイヤグループについては作成直後からメトリクス収集が始まり、パケット数・バイト数が1分間隔でMonitoringに送信され、6週間保持されます。 + +監視の実務では、次の3層を意識すると原因の切り分けがしやすくなります。 + +```mermaid +flowchart LR + A["物理層
(Interconnect接続の稼働状態・光レベル)"] --> D["障害/劣化の切り分け"] + B["論理層
(VLANアタッチメントの帯域・パケット数)"] --> D + C["ルーティング層
(BGPセッションの状態・経路数)"] --> D + D --> E["対応: キャリア/コロケーション連携、
帯域増強、BGP設定修正など"] +``` + +> **注意** +> VLANアタッチメントのメトリクスは60秒間隔でサンプリングされるため、瞬間的なトラフィックバーストが`BANDWIDTH_THROTTLE`によるドロップの原因であっても、Ingress/Egressの使用率グラフ上にはスパイクとして現れないことがあります。帯域超過が疑われる場合は、アタッチメントの利用率を下げる、容量を増やす、追加のVLANアタッチメントを使用するといった対応を検討してください。 + +> **出典** +> - https://cloud.google.com/network-connectivity/docs/interconnect/how-to/monitoring +> - https://docs.cloud.google.com/network-connectivity/docs/interconnect/support/troubleshooting + +#### 1.3.3 Cloud Routerのメトリクス + +Cloud Routerのメトリクスは、ルータ単位のもの(`router-name`)とBGPセッション単位のもの(`router-name(bgp-name)`)の2種類に分かれます。受信経路数・学習経路数を示すメトリクスは動的に学習された経路に関するものであり、Custom learned routes機能とは無関係である点に注意してください。 + +#### 1.3.4 ロードバランサのメトリクス + +代表的なメトリクスは`loadbalancing.googleapis.com/https/request_count`、`.../total_latencies`、`.../backend_latencies`、`.../response_code_count`などです。**Total latency**はクライアント〜ロードバランサ〜バックエンドを含む全体のレイテンシ、**Backend latency**はバックエンドの応答時間のみを表すため、両者を切り分けて監視することでアプリケーション側の問題かネットワーク側の問題かを判断できます。SLOを設計する場合、`DistributionCut`を使ったリクエストベースのSLI(例:「1時間のローリングウィンドウで99%のリクエストが100ms以内」)として表現するのが一般的です。 + +> **出典** +> - https://docs.cloud.google.com/load-balancing/docs/https/https-logging-monitoring +> - https://docs.cloud.google.com/stackdriver/docs/solutions/slo-monitoring/sli-metrics/lb-metrics + +#### 1.3.5 Google Cloud Armorのメトリクスと運用監視 + +Cloud Armorのセキュリティポリシーのメトリクスは、ロードバランサのバックエンドサービスに紐づく形でCloud Monitoringに送信され、ポリシー単位でリクエスト数・ブロック数・レート制限のヒット数などを確認できます。詳細なポリシー種別・WAFルール・DDoS防御・レート制限・bot管理の設計論点は、既刊のS5(Section 6ネットワークセキュリティ)ガイドで扱っているため、本ガイドでは監視・運用の観点に絞って要点のみ再掲します。 + +#### 1.3.6 Cloud NATのメトリクス + +Cloud NATはゲートウェイのフリート全体の使用状況をCloud Monitoringに自動送信します。コンソール上のNATゲートウェイ詳細ページの「Monitoring」タブから事前定義ダッシュボードを確認できるほか、`Cloud NAT gateway`や`VM Instance`をフィルタしてアラートポリシーを作成できます。Shared VPCでVMとNATゲートウェイが異なるプロジェクトにある場合、VMレベルのメトリクスへのアクセスにはVMが属するプロジェクトの`roles/monitoring.viewer`、ゲートウェイリソースのメトリクスへのアクセスにはゲートウェイが属するプロジェクトの`roles/monitoring.viewer`が、それぞれ個別に必要です。 + +> **ベストプラクティス** +> Cloud NATでは「割り当てポートの枯渇」がサイレント障害になりやすい落とし穴です。動的ポート割り当てを使用している場合でも、`nat_allocation_failed`系のメトリクスやログをアラート対象に含め、ポート使用率が閾値を超えたら通知されるようにしておくことを推奨します。 + +> **出典**: https://docs.cloud.google.com/nat/docs/monitoring + +### 1.4 Task 5.1 設計・運用チェックリスト + +- [ ] VPC Flow Logsを、少なくとも本番トラフィックが通過するサブネット・VLANアタッチメント・VPNトンネルで有効化しているか(組織レベル/プロジェクトレベルの設定範囲を意図通りに設計しているか) +- [ ] VPC Firewall Rules Loggingを、Allow/Denyそれぞれの目的(可視性 vs. セキュリティ監査)に応じて必要なルールにのみ有効化しているか(全ルールでの一律有効化はログ量爆発のリスクがある) +- [ ] Cloud Router・Cloud VPN・Cloud Interconnectのログとメトリクスを組み合わせ、BGPセッション断・トンネル断・帯域劣化を横断的に追跡できるダッシュボードを用意しているか +- [ ] Cloud DNSロギングを、少なくとも障害調査が必要になり得るゾーン・ポリシーで有効化しているか +- [ ] VPC Service Controlsを利用している場合、組織レベルのViolation Dashboardを事前に設定し、拒否イベントの発生時に遡って調査できる状態にしているか +- [ ] ロードバランサのTotal latency/Backend latencyを分離して監視し、SLOのアラート閾値を設定しているか +- [ ] Cloud NATのポート割り当て失敗・使用率メトリクスにアラートを設定しているか +- [ ] ログの保持期間・エクスポート先(BigQuery/Cloud Storage/Pub/Sub)をコンプライアンス要件・コスト要件に照らして設計しているか + +--- + +## Part 2: 接続性の維持とトラブルシューティング(Task 5.2) + +### 2.1 Application Load Balancerでのトラフィックドレイン・リダイレクト + +バックエンドVMやNEGエンドポイントをローリングアップデート・スケールイン・メンテナンスのために安全に取り除くには、**コネクションドレイニング**(connection draining)を使用します。ドレイニングタイムアウトを設定した状態でインスタンスグループからVMを削除、またはゾーンスコープのNEGからエンドポイントを削除すると、ロードバランサは新規接続を即座に停止しつつ、既存のリクエスト/接続には完了までの猶予を与えます。 + +```mermaid +stateDiagram-v2 + [*] --> ACTIVE + ACTIVE --> DRAINING: バックエンドグループから
VM/エンドポイントを削除 + DRAINING --> DRAINING: 既存リクエストは
タイムアウトまで継続処理
(新規接続は送られない) + DRAINING --> REMOVED: drainingTimeoutSecの経過、
または全接続の完了 + REMOVED --> [*] +``` + +ロードバランサの種類によって挙動に差異があります。 + +| ロードバランサ種別 | ドレイニング中の挙動 | +|---|---| +| Application Load Balancer(L7) | 指定したタイムアウトの間、既存リクエストの完了を待つ。新規リクエストは送られない | +| Proxy Network Load Balancer | 既存のTCPコネクションはタイムアウト期間中も動作を継続する | +| 内部パススルーNetwork Load Balancer(フェイルオーバー時) | `disableConnectionDrainOnFailover`と`dropTrafficIfUnhealthy`で挙動を制御。既定のドレイニングタイムアウトは固定10分 | + +トラフィックの計画的な移動には、バックエンドサービスの**capacity scaler**も併用します。`0`(完全ドレイン、単一バックエンドの場合は設定不可)から`1.0`(100%)まで手動で目標キャパシティを調整でき、メンテナンス前にトラフィックを段階的に他のバックエンドへ寄せる、といった運用が可能です。GKE環境では、Podの`preStop`フックの実行時間を「Backend Service Drain Timeout + ドレインレイテンシ(目安1分)」以上に設定し、`terminationGracePeriodSeconds`を十分に長く取ることで、NEGからのエンドポイント除去とPodの終了を同期させます。 + +> **ベストプラクティス** +> リージョナル外部パススルーNetwork Load Balancerでは、フォワーディングルールの**トラフィックステアリング**を使い、特定の送信元IPレンジだけを別のバックエンドサービス(異なるヘルスチェックやドレイニング設定を持つ)へ振り向けることができます。これはカナリアリリースやトラブルシューティング目的のトラフィック分離に有効です。 + +> **出典** +> - https://docs.cloud.google.com/load-balancing/docs/enabling-connection-draining +> - https://docs.cloud.google.com/kubernetes-engine/docs/troubleshooting/load-balancing +> - https://cloud.google.com/load-balancing/docs/backend-service +> - https://docs.cloud.google.com/load-balancing/docs/network/networklb-backend-service +> - https://docs.cloud.google.com/load-balancing/docs/internal/failover-overview + +### 2.2 Cloud VPNの管理とトラブルシューティング + +Cloud VPNのトラブルシューティングは、コンソールの「VPN」ページでトンネルステータスとBGPセッションステータスの両方を確認するところから始めます。HA VPNではさらに、99.99% SLAを満たすための**高可用性ステータス**(両インターフェースのトンネルが正しくオンプレミス側の冗長構成と対になっているか)も確認が必要です。 + +```mermaid +flowchart TB + A["VPNトンネルが
ESTABLISHEDにならない/
不安定"] --> B{"トンネルステータスの
アイコンにエラーメッセージは
表示されているか"} + B -->|"Yes"| C["エラーメッセージから
原因を特定
(IKE/PSK/ピアIP不正など)"] + B -->|"No/不明"| D["Cloud VPNログを確認
(自動収集済み)"] + D --> E{"SA_DELETE直後に
再接続している形跡は
あるか"} + E -->|"Yes"| F["オンプレミス側がrekeyingではなく
SA削除後の再ネゴシエートに
なっていないか確認"] + E -->|"No"| G["IKE暗号スイート/PSK/
ピアIPアドレスの整合性を確認
(RFC5735/5737の予約IPでないかも確認)"] + C --> H{"BGPセッションは
ESTABLISHEDか"} + F --> H + G --> H + H -->|"No"| I["Cloud Router側の
BGPトラブルシューティングへ
(2.4節)"] + H -->|"Yes"| J["トンネル層は正常
アプリケーション層/
ルーティング層を調査"] +``` + +代表的な作成失敗パターンとして、ピアIPアドレスがRFC 5735/5737の予約アドレス範囲に該当しているケースが挙げられます。この場合、構成時のIPレンジを見直す必要があります。また、Cloud VPNは既定でSAの有効期限が切れる前に自動的に再ネゴシエート(rekeying)しますが、オンプレミス側のゲートウェイがこれに対応しておらず、既存SAの削除後にのみ新規SAをネゴシエートする実装だと、接続が周期的に瞬断します。 + +> **出典** +> - https://docs.cloud.google.com/network-connectivity/docs/vpn/support/troubleshooting +> - https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/checking-vpn-status +> - https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/viewing-logs-metrics + +### 2.3 Cloud Interconnectの管理とトラブルシューティング + +Cloud Interconnectのトラブルシューティングは、[1.3.2節](#132-cloud-interconnectとvlanアタッチメントのメトリクス)で紹介した「物理層・論理層・ルーティング層」の3層モデルに沿って切り分けるのが基本です。 + +| 症状 | 疑うべき層 | 確認方法 | +|---|---|---| +| 接続自体が確立しない/光レベル異常 | 物理層 | `gcloud compute interconnects get-diagnostics`でTx/Rx光レベルと稼働状態を確認 | +| 特定のVLANアタッチメントだけ帯域が頭打ち | 論理層 | VLANアタッチメントのMonitoringタブでingress/egress利用率、`BANDWIDTH_THROTTLE`ドロップの有無を確認(60秒サンプリングのためバーストは見えにくい点に注意) | +| 経路が広告されない/学習されない | ルーティング層 | Cloud RouterのBGPセッション状態、`advertisedRoutes`フィールドを確認 | +| 暗号化VLANアタッチメントの削除に失敗する | 論理層(MACsec) | MACsec構成済みのDedicated/Partner Interconnectでは、削除前にMACsec設定の解除が必要な場合がある | + +HA VPN over Cloud Interconnectのような複合構成では、「Cloud Interconnect層(VLANアタッチメント間のBGP)」と「HA VPN層(オンプレミスとVPCの間のBGP)」という2階層のBGPが存在するため、どちらの層で問題が起きているかを切り分けることが重要です。具体的には、まずCloud Interconnect層のCloud Routerで`gcloud compute routers get-status`を実行し、`advertisedRoutes`にHA VPNゲートウェイのアドレスが含まれているかを確認し、次にHA VPN層のBGPセッションを確認するという順序になります。 + +> **出典**: https://docs.cloud.google.com/network-connectivity/docs/interconnect/support/troubleshooting + +### 2.4 Cloud RouterのBGPピアリングのトラブルシューティング + +BGPセッションは、確立までに複数の状態を遷移します。試験では状態遷移の理解に加え、「どのログ/メトリクスでどの状態を確認するか」が問われます。 + +```mermaid +stateDiagram-v2 + [*] --> Idle + Idle --> Connect: セッション開始 + Connect --> Active: TCP接続失敗 + Connect --> OpenSent: TCP接続成功、OPEN送信 + Active --> Connect: 再試行 + OpenSent --> OpenConfirm: 相手からOPEN受信 + OpenSent --> Idle: エラー/NOTIFICATION受信 + OpenConfirm --> Established: KEEPALIVE受信 + OpenConfirm --> Idle: エラー/NOTIFICATION受信 + Established --> Idle: HOLD_TIMER_EXPIRED/
LINK_DOWN/手動無効化など + Established --> [*]: 正常運用継続 +``` + +代表的な障害パターンと対処の要点は次のとおりです。 + +| 障害パターン | 原因 | 対処 | +|---|---|---| +| ローカルASNとピアASNの重複 | 同一リージョン・同一ネットワーク内で同じASNを持つオンプレミスデバイスとセッションを試みている | Cloud RouterまたはオンプレミスルータのASN設計を見直す | +| MD5認証エラー(`MD5_AUTH_INTERNAL_PROBLEM`) | Cloud Router内部でMD5認証設定に失敗(内部エラー) | 通常は自動復旧を待つ(1時間以上続く場合はサポートに連絡) | +| MD5認証エラー(鍵不一致) | Cloud Routerとピアの事前共有鍵(認証キー)が一致していない | 認証キーを更新して再同期 | +| 最大経路数超過によるセッション遮断 | オンプレミスルータが5,000プレフィックスを超えて広告 | `CEASE/MAX_PREFIXES_REACHED`ログを確認し、広告プレフィックス数を削減するか、手動でBGPピアリングをリセット | +| BGPフラップ(定期的な切断) | Cloud Routerのソフトウェアメンテナンスイベント | オンプレミスルータでGraceful Restartに対応し、ホールドタイマーを60秒以上に設定していれば通常は問題ない | +| BFDの検知タイムアウト | 制御パケットが検知タイマー(既定5,000ms)以内に届かない | BFDのMinRx/MinTx間隔・マルチプライヤの双方一致を確認 | + +BFDを併用している場合は、BGPとは独立した状態機械としてBFDの状態を確認します。 + +```mermaid +stateDiagram-v2 + [*] --> AdminDown: BFD無効 + [*] --> Down: BFD有効化直後 + Down --> Init: ローカルが相手を検知 + Init --> Up: 相手からの確認を受信 + Up --> Down: 検知タイマーの
タイムアウト + AdminDown --> Down: BFD再有効化 +``` + +BFDの診断コード(`NO_DIAGNOSTIC`、`CONTROL_DETECTION_TIME_EXPIRED`、`NEIGHBOR_SIGNALED_SESSION_DOWN`、`ADMINISTRATIVELY_DOWN`など)は、`gcloud compute routers get-status`の`bfdStatus`フィールドで確認できます。BFDとBGP Graceful Restartを併用している場合、Cloud Routerの再起動時にはBFDへ`AdminDown`を送信して意図的に停止し、その間BGPセッション自体はオンプレミス側でGraceful Restartモードとして維持される、という設計になっている点も理解しておく必要があります。 + +> **注意** +> ICMPv6 pingはCloud RouterのBGPアドレスに対してサポートされていません。レイヤー3疎通確認にはICMPv4 pingを使用してください。 + +> **出典** +> - https://cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-sessions +> - https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-peering +> - https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bgp-states +> - https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-log-messages +> - https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bfd +> - https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bfd-states +> - https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-routes + +### 2.5 VPC Flow Logs・ファイアウォールログ・Packet Mirroringを使ったトラブルシューティング + +3つのツールはそれぞれ異なる「見え方」を提供するため、組み合わせて使うことで根本原因への到達が早まります。 + +```mermaid +flowchart TB + A["通信の疎通/性能に
関する問い合わせ"] --> B{"通信自体が
届いているか
(Flow Logs)"} + B -->|"届いていない"| C{"ファイアウォールで
拒否されているか
(Firewall Logs)"} + C -->|"Deny hit あり"| D["該当ルールを特定し
意図した挙動か確認
(Firewall Insightsも活用)"] + C -->|"Deny hit なし"| E["ルーティング/
Connectivity Testsで
経路を確認"] + B -->|"届いているが
アプリ層で問題"| F["Packet Mirroringで
パケット内容を収集し
アプリ/プロトコルを詳細分析"] + D --> G["是正"] + E --> G + F --> G +``` + +VPC Flow Logsは「通信があったかどうか、どれだけの量か」を5-tuple単位で示しますが、パケットのペイロード自体は含みません。ペイロードレベルでの分析(アプリケーションプロトコルの異常、侵入検知システムとの連携など)が必要な場合は**Packet Mirroring**を使用します。 + +```mermaid +flowchart LR + A["ミラーリング対象
(VMインスタンスのNIC)"] -->|"ポリシーで指定
(タグ/サブネット単位)"| B["Packet Mirroringポリシー"] + B --> C["コレクタ宛先
(内部パススルーNLBの
フォワーディングルール)"] + C --> D["コレクタインスタンス群
(推奨: マネージドインスタンスグループ)"] + D --> E["収集・解析
(IDS/IPS、パケットキャプチャ分析ツールなど)"] +``` + +Packet Mirroringの主な制約・特性は次のとおりです。 + +- ミラー対象とコレクタ宛先は同一プロジェクト・同一リージョンである必要がある(コレクタは同一VPCまたはVPC Network Peeringで接続されたVPCに配置可能) +- 1つのミラーリングポリシーが参照できるコレクタ宛先は1つだが、1つのコレクタ宛先を複数のポリシーから参照することは可能 +- ミラーリングとコレクションを同一VMの同一NICで行うと、ミラーリングループが発生するため不可 +- GKEの同一ノード上のPod間通信をミラーリングするには、クラスタでIntranode visibilityを有効化する必要がある +- VPC Flow Logsはミラーされたパケット自体をログしない。ただしコレクタインスタンスが属するサブネットでFlow Logsが有効な場合、コレクタ宛の直接トラフィック(元の宛先IPがコレクタのIPと一致する場合)は通常どおり記録される +- ミラーリングは元パケットとミラーパケットの両方を処理するため、処理レート(スループット)が低下する。低下幅はマシンタイプ・CPU使用率・パケットサイズに依存する + +Packet Mirroringのモニタリングでは、ミラー対象VM側で「ミラーされたネットワークバイト数/パケット数」(成功・ドロップ両方)を確認できますが、コレクタ側のドロップパケット数は個別には提供されません(コレクタ側の監視は内部パススルーNLBのロギング・モニタリングに準じます)。 + +> **出典** +> - https://cloud.google.com/vpc/docs/packet-mirroring +> - https://cloud.google.com/vpc/docs/using-packet-mirroring +> - https://cloud.google.com/vpc/docs/monitoring-packet-mirroring + +### 2.6 Task 5.2 トラブルシューティングチェックリスト + +- [ ] ロードバランサのバックエンド入れ替え手順に、コネクションドレイニングのタイムアウトとGKEの`preStop`/`terminationGracePeriodSeconds`の整合を組み込んでいるか +- [ ] Cloud VPNトラブル時に、まずトンネルステータス→BGPセッションステータス→高可用性ステータスの順で確認する手順が周知されているか +- [ ] Cloud Interconnect障害時に、物理層・論理層・ルーティング層のどこで問題が起きているかを`get-diagnostics`やVLANアタッチメントのメトリクスで切り分けられるか +- [ ] HA VPN over Cloud Interconnectのような複合構成で、どちらの層のBGPセッションが問題かを区別する手順があるか +- [ ] BGPフラップの許容範囲(ホールドタイマー、Graceful Restart対応)をオンプレミス側と合意しているか +- [ ] BFDのMinRx/MinTx/マルチプライヤの設定値がCloud Router側とオンプレミス側で一致しているか +- [ ] 「疎通しない」問い合わせに対し、Flow Logs→Firewall Logs→Connectivity Tests→Packet Mirroringの順で切り分ける標準手順があるか +- [ ] Packet Mirroringのコレクタにマネージドインスタンスグループ(オートスケーリング/オートヒーリング)を使用しているか + +--- + +## Part 3: Network Intelligence Centerによる監視とトラブルシューティング(Task 5.3) + +### 3.1 Network Intelligence Centerの全体像 + +Network Intelligence Center(NIC)は、Google Cloudネットワークの可視化・監視・トラブルシューティングを1つのコンソールに統合したプラットフォームです。2019年の提供開始時点ではConnectivity TestsとNetwork Topologyがベータ、Performance DashboardとFirewall Metrics & Insightsがアルファでしたが、現在は6つのモジュールが揃っています。 + +```mermaid +flowchart TB + NIC["Network Intelligence Center"] --> M1["Network Topology
トポロジ可視化"] + NIC --> M2["Connectivity Tests
疎通診断"] + NIC --> M3["Performance Dashboard
パケットロス・レイテンシ"] + NIC --> M4["Firewall Insights
ファイアウォールルール最適化"] + NIC --> M5["Network Analyzer
構成の自動監視・誤設定検知"] + NIC --> M6["Flow Analyzer
Flow Logsの高速分析"] + + M1 -.->|"問題箇所の当たりをつける"| M2 + M2 -.->|"設定起因の不通を特定"| M5 + M5 -.->|"検知したインサイトを深掘り"| M6 + M3 -.->|"性能劣化の切り分け"| M2 + M4 -.->|"ルール最適化"| M5 +``` + +各モジュールの役割を一言でまとめると次のようになります。 + +| モジュール | 主な問い | 分析の性質 | +|---|---|---| +| Network Topology | 「今、どこにどれだけのトラフィックが流れているか」 | リアルタイムのテレメトリ + 構成情報の可視化 | +| Connectivity Tests | 「AからBへ到達できるか、できないなら何が阻んでいるか」 | 構成分析(+一部でデータプレーン検証) | +| Performance Dashboard | 「ゾーン/リージョン間のパケットロス・レイテンシはどの程度か」 | 実トラフィックに基づく能動的プロービング | +| Firewall Insights | 「このファイアウォールルールは安全に削除・厳格化できるか」 | ロギングデータ + 機械学習予測 | +| Network Analyzer | 「構成に誤りや非効率はないか」 | 構成の自動巡回監視(プッシュ型) | +| Flow Analyzer | 「Flow Logsから見える実際の通信パターンは何か」 | SQLレスなFlow Logs分析(BigQuery基盤) | + +> **出典** +> - https://cloud.google.com/blog/products/networking/announcing-network-intelligence-center +> - https://docs.cloud.google.com/network-intelligence-center/docs/overview +> - https://docs.cloud.google.com/network-intelligence-center/docs + +### 3.2 Network Topology + +Network Topologyは、構成情報とリアルタイムの運用データを1つのグラフに統合して可視化するツールです。**Infrastructure view**ではVPCネットワーク、オンプレミスとのハイブリッド接続、Google管理サービスへの接続とそれらのメトリクスを表示し、**GKE Enterprise view**ではクラスタ・ネームスペース・ワークロード・Podとそのメトリクスを表示します。 + +活用の典型例は、特定のCloud VPNトンネルやVLANアタッチメントを流れるトラフィック量をエンティティ単位で確認し、Shared VPCの他プロジェクトやリージョン間トラフィックへの影響を把握することです。エンティティをクリックすると、そのエンティティを通過するすべてのトラフィックパスがハイライトされます。 + +> **出典**: https://cloud.google.com/network-intelligence-center/docs/network-topology/reference/metrics-reference + +### 3.3 Connectivity Tests + +Connectivity Testsは、送信元と宛先(VM、GKEクラスタ、ロードバランサのフォワーディングルール、インターネット上のIPアドレスなど)を指定し、その間のパケットが実際にどう転送されるかを**シミュレーション**するツールです。分析は2種類に分かれます。 + +```mermaid +flowchart TB + A["Connectivity Testの作成
(送信元・宛先・プロトコル・ポート)"] --> B["構成分析
(configuration analysis)"] + B --> C{"複数の経路
(トレース)が
存在するか"} + C -->|"1本のみ"| D["トレースの最終状態が
そのまま総合結果になる"] + C -->|"複数本
(例: LBの背後に
複数バックエンド)"| E["各トレースの最終状態の
分布から総合結果を算出"] + D --> F["総合到達性(overall reachability result)"] + E --> F + F --> G{"対応シナリオでは
データプレーン検証も
実行可能"} + G -->|"Yes"| H["実際にプローブパケットを送信し、
レイテンシ・パケットロスの
ベースラインを取得"] + G -->|"No"| I["構成分析結果のみで判断"] +``` + +総合到達性の結果は4値のいずれかです。 + +| 結果 | 意味 | +|---|---| +| Reachable | 現在の構成でトラフィックが送信元から宛先へ到達できる | +| Unreachable | 経路上のどこかでトラフィックが遮断されている(トレースにドロップ箇所が示される) | +| Ambiguous | 複数トレースの最終状態が混在している(例: 一部バックエンドは到達可能、一部は不可) | +| Undetermined | エラー、非対応の入力、権限不足などにより判定不能 | + +> **注意** +> Ambiguousの典型的な原因の1つは、閲覧権限のない階層型ファイアウォールポリシーをトレースが参照している場合です。ポリシー自体の閲覧権限がなくても、自分のVPCネットワークに適用される実効ルールは「Effective firewall rules」で確認できます。また、構成分析でReachableと判定されても、実際にはデータプレーンで100%パケットロスが発生している場合があります。これは構成分析とデータプレーン分析が別物であるためで、対応シナリオではデータプレーン検証も併用して裏取りすることが推奨されます。 + +Google管理サービス(Cloud SQL、GKEなど)を宛先とするテストも作成できますが、Google所有プロジェクト内のリソースについては閲覧権限がないため、トレースの詳細(具体的にどのルール・ルートが適用されたか)は表示されず、総合到達性の結果のみが返されます。 + +> **出典** +> - https://cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/state-tables +> - https://cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/reachability +> - https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/support/troubleshooting +> - https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/test-google-managed-services +> - https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/overview + +### 3.4 Performance Dashboard + +Performance Dashboardは、Google Cloudネットワーク全体、および自分のプロジェクトのリソースに関するパケットロスとレイテンシ(RTT)を可視化します。セットアップは不要で、十分な数のVMがあればパケットロスメトリクスが、十分なトラフィック量があればレイテンシメトリクスが自動的に得られます。 + +```mermaid +flowchart LR + A["Performance Dashboard"] --> B["プロジェクトパフォーマンスビュー"] + A --> C["Google Cloud全体パフォーマンスビュー"] + B --> B1["自分のVM間の
パケットロス(能動プロービング)"] + B --> B2["実トラフィックに基づく
レイテンシ(TCP SEQ/ACK計測)"] + C --> C1["全ゾーンペア間の
パケットロス"] + C --> C2["リージョン⇔インターネット拠点間の
レイテンシ中央値"] + B1 --> D["ヒートマップ/
サマリチャートで表示
(最大6週間の履歴)"] + B2 --> D + C1 --> D + C2 --> D +``` + +代表的な活用パターンは、アプリケーションで性能問題が疑われたときに「まずPerformance Dashboardでネットワーク側に異常がないかを確認し、異常がなければアプリケーション側を疑う」という切り分けです。自分のプロジェクトの値と、Google Cloud全体の同一ゾーン/リージョンペアの平均値を並べて比較することで、自分の環境固有の問題か、Google Cloud全体で起きている事象かを判断できます。パケットロスメトリクスは常に利用可能ですが、1分あたり400プローブ未満の場合はアスタリスク(*)が付き、データの信頼性が低いことを示します。 + +> **出典** +> - https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/overview +> - https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/metrics-views +> - https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/use-cases-project +> - https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/use-cases-google-cloud +> - https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/how-to/viewing-perf-dash-metrics + +### 3.5 Firewall Insights + +Firewall Insightsは、ファイアウォールルール(VPC firewall rules、ファイアウォールポリシーに属するルールの両方)の構成と使用実態を分析し、最適化のためのインサイトを提供します。インサイトは大きく3種類です。 + +```mermaid +flowchart TB + FI["Firewall Insights"] --> T1["シャドウルール
(shadowed rule)"] + FI --> T2["過度に寛容なルール
(overly permissive rule)"] + FI --> T3["拒否ルールのヒット
(deny rule insight)"] + + T1 --> T1a["構成情報のみから判定可能
(ロギング不要)"] + T2 --> T2a["ヒットなしのAllowルール"] + T2 --> T2b["未使用の属性を持つルール"] + T2 --> T2c["過度に広いIP/ポートレンジ"] + T2 --> T2d["適応的分析による
陳腐化予測(機械学習)"] + T3 --> T3a["観測期間中にヒットした
Denyルールの詳細"] + + T2a -.->|"Firewall Rules Logging
のデータが必要"| Log["ロギング有効化"] + T2b -.-> Log + T2c -.-> Log + T3a -.-> Log +``` + +**シャドウルール**は、自分より優先度が高い(または同等の)ルールと属性(IPレンジなど)が重複しており、実質的に一度もマッチし得ないルールです。構成情報だけから機械的に判定できるため、Firewall Rules Loggingを有効化していなくても検知されます。一方、**過度に寛容なルール**と**拒否ルールインサイト**はログベースであり、Firewall Rules Loggingを有効化した状態でのトラフィック実績が必要です。シャドウルール・過度に寛容なルールのインサイトは、Firewall Insightsのページで機能を有効化してから最大48時間で生成され始め、機械学習による陳腐化予測は新規/更新されたルールに対して最大10日ほどかかります。 + +> **注意** +> ロードバランサのヘルスチェック用IPレンジ(`35.191.0.0/16`など)を許可するルールは、ヒット数が少なくても「過度に寛容」や「未使用」と誤判定されて削除対象に挙げられることがあります。これらはGoogle Cloudの機能上必要なルールであるため、インサイトを鵜呑みにせず、削除前に用途を確認してください。 + +Firewall Insightsが検出したインサイトは、Recommenderが提供する**Active Assist**ダッシュボードからも確認できます(カード名がFirewall Insights側とは異なる点に注意)。 + +> **出典** +> - https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/concepts/overview +> - https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/view-understand-insights +> - https://cloud.google.com/network-intelligence-center/docs/firewall-insights/concepts/insights-categories-states +> - https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/enable-api-features +> - https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/view-insights-recommendation-hub + +### 3.6 Network Analyzer + +Network Analyzerは、VPCネットワークの構成を自動的に巡回監視し、誤設定や非効率な構成を検出するプッシュ型のツールです。設定変更後は約10分でその変更に関連する分析が実行され、それとは別に少なくとも1日1回の定期分析も行われます。インサイトは5つのグループに分類されます。 + +```mermaid +flowchart TB + NA["Network Analyzer
インサイトグループ"] --> G1["VPCネットワーク
IPアドレス/ルート/ファイアウォール/
VPC Peering/Shared VPC"] + NA --> G2["ネットワークサービス
ロードバランサ/Cloud NAT"] + NA --> G3["ハイブリッド接続
Cloud VPN/Interconnect/
Cloud Router/BGP/NCC"] + NA --> G4["GKE
ノード⇔コントロールプレーン疎通/
Pod IP使用率/ベストプラクティス"] + NA --> G5["マネージドサービス
Cloud SQLなどへの接続性"] +``` + +たとえばVPCネットワークグループでは「無効なネクストホップを持つルート」、ネットワークサービスグループでは「ヘルスチェックをブロックしているファイアウォールルール」「トラフィックとヘルスチェックで異なるポートを使っているバックエンドサービス」、GKEグループでは「ノードからコントロールプレーンへの双方向疎通の設定起因の問題」「PodのIPアドレス使用率」といった具体的なインサイトが提供されます。 + +Shared VPCでは、ホストプロジェクト側でIPアドレス使用率などVPCネットワーク全体に関わるインサイトが提供され(サービスプロジェクトの情報も自動集約)、サービスプロジェクト側ではロードバランサやGKEなどそのプロジェクト固有のサービスに関するインサイトが提供されます。複数プロジェクトを横断して監視したい場合は、Cloud Monitoringの**メトリクススコープ**を構成し、対象プロジェクトを監視対象として追加します。 + +Network Analyzerが公開したインサイトはCloud Loggingにも格納され、ログ名は`projects/{project-id}/logs/networkanalyzer.googleapis.com/analyzer_reports`の形式です。Network Analyzer自体はCloud Monitoringへメトリクスを送信しないため、リアルタイムアラートが必要な場合はこのログに対してログベースアラートを設定します。 + +> **出典** +> - https://cloud.google.com/network-intelligence-center/docs/network-analyzer/insight-groups-types +> - https://docs.cloud.google.com/network-intelligence-center/docs/network-analyzer/overview +> - https://www.doit.com/blog/proactively-detect-network-misconfigurations-in-google-cloud-with-network-analyzer +> - https://cloud.google.com/network-intelligence-center/docs/network-analyzer/insights/kubernetes-engine/gke-node-to-control-plane + +### 3.7 Flow Analyzer + +Flow Analyzerは、VPC Flow Logsに対して複雑なSQLクエリを書かずにトラフィックパターンを分析できるツールです。Observability Analytics(旧Log Analytics)が有効化されたログバケットに格納されたFlow Logsのレコードを対象とし、BigQueryを基盤に5-tuple粒度でのオピニオンベース分析(意見の分かれない、定型化された分析軸)を提供します。 + +```mermaid +flowchart TB + A["VPC Flow Logsを
ログバケットに格納"] --> B["ログバケットを
Observability Analytics用に
アップグレード"] + B --> C["Flow Analyzerで
集計方法・時間範囲を選択"] + C --> D["Organize Flows by
(例: VPCサブネットワーク/IP/ポート)で
グルーピング"] + D --> E["Highest data flowsチャート /
All data flowsテーブルで
結果を確認"] + E --> F["特定のフローを
ドリルダウン
(送信元/宛先/トラフィック量の詳細)"] + F --> G["さらに他のフィールドで
分割してドリルダウン"] +``` + +活用例として、「誰が接続を開始したか」を知りたい場合は送信元ペインで「VPCサブネットワーク」「IP」「ポート」を選択してグルーピングします。Cross-Cloud Network環境では、VLANアタッチメントやVPNトンネルに対してもVPC Flow Logsを有効化でき、`reporter`(トラフィックの方向)や`gateway`オブジェクト(ゲートウェイの名前・タイプ・プロジェクトID・ロケーション)といった新しい注釈がFlow Analyzerに統合されており、オンプレミス⇔クラウド間の「エレファントフロー」(高帯域フロー)の特定やShared VPC環境でのサービスプロジェクト別のハイブリッド帯域使用量の監査に活用できます。 + +> **出典** +> - https://cloud.google.com/network-intelligence-center/docs/flow-analyzer/overview +> - https://docs.cloud.google.com/network-intelligence-center/docs/flow-analyzer/monitor-traffic-flows +> - https://cloud.google.com/network-intelligence-center/docs/flow-analyzer/enable-log-analytics +> - https://cloud.google.com/blog/products/networking/vpc-flow-logs-for-cross-cloud-network +> - https://cloud.google.com/blog/products/networking/using-vpc-flow-logs-to-de-risk-network-migration + +### 3.8 Task 5.3 活用チェックリスト + +- [ ] 障害調査の初手として、Network Topologyでトラフィックの全体像を把握する運用が定着しているか +- [ ] 「AからBへ到達できるか」という問い合わせに対し、Connectivity Testsを標準ツールとして使っているか(構成分析とデータプレーン検証の違いを理解した上で) +- [ ] アプリケーション性能問題の切り分けにPerformance Dashboardを使い、ネットワーク起因かアプリケーション起因かを最初に判断しているか +- [ ] Firewall Insightsのシャドウルール・過度に寛容なルールを定期的にレビューし、ヘルスチェック用ルールなど誤判定されやすいものを除外するプロセスがあるか +- [ ] Network Analyzerの5つのインサイトグループ(VPCネットワーク/ネットワークサービス/ハイブリッド接続/GKE/マネージドサービス)を横断して定期確認しているか、複数プロジェクトの場合はメトリクススコープを構成しているか +- [ ] Flow AnalyzerでVPC Flow Logsを分析するために、対象ログバケットのObservability Analyticsを有効化しているか +- [ ] Cross-Cloud Network構成の場合、VLANアタッチメント・VPNトンネルでもVPC Flow Logsを有効化し、Flow Analyzerでハイブリッド帯域を可視化しているか + +--- + +## 総合トラブルシューティングワークフロー + +最後に、Task 5.1〜5.3で紹介したツールを、実際のインシデント対応の流れに沿って統合したワークフローを示します。試験では個々のツールの仕様だけでなく、「この状況ではどのツールをどの順序で使うべきか」という統合的な判断力も問われます。 + +```mermaid +flowchart TB + Start(["ネットワーク障害・性能劣化の
アラートまたは問い合わせ"]) --> Q1{"影響範囲は
特定エンドポイント間か、
広範囲か"} + + Q1 -->|"特定のA-B間"| CT["Connectivity Testsで
構成分析を実行(3.3)"] + Q1 -->|"広範囲/不明"| NT["Network Topologyで
トラフィック全体を俯瞰(3.2)"] + + NT --> Q2{"特定のコンポーネントに
異常が見えるか"} + Q2 -->|"Yes"| Route["該当コンポーネントの
ログ・メトリクスへ(Part 1)"] + Q2 -->|"No"| PD["Performance Dashboardで
パケットロス/レイテンシを確認(3.4)"] + + CT --> Q3{"結果は
Unreachable/Ambiguousか"} + Q3 -->|"Yes"| FL["VPC Flow Logs /
Firewall Logsで実トラフィックを確認(2.5)"] + Q3 -->|"No(Reachable)"| PD + + PD --> Q4{"パケットロス/レイテンシが
Google Cloud平均から
逸脱しているか"} + Q4 -->|"Yes"| Route + Q4 -->|"No"| App["アプリケーション側の
問題を疑う"] + + FL --> Q5{"ファイアウォールの
Denyヒットが原因か"} + Q5 -->|"Yes"| FI["Firewall Insightsで
ルールの妥当性を検証(3.5)"] + Q5 -->|"No"| PM["Packet Mirroringで
ペイロードレベルの分析(2.5)"] + + Route --> Q6{"VPN/Interconnect/
Cloud RouterのBGPが
関係するか"} + Q6 -->|"Yes"| BGP["2.2〜2.4節の手順で
トンネル/物理層/BGPを切り分け"] + Q6 -->|"No"| Other["該当コンポーネントの
ログ・メトリクスで直接調査"] + + FI --> Fix["是正・ルール修正"] + PM --> Fix + BGP --> Fix + Other --> Fix + App --> Fix + + Fix --> NA["Network Analyzerで
再発防止の構成チェックを
定期実行(3.6)"] + NA --> End(["恒久対応・
ランブック更新"]) +``` + +> **ベストプラクティス** +> このワークフローが機能する前提は、Part 1で解説したロギング・モニタリングが**平時から有効化されていること**です。障害発生後にVPC Flow LogsやFirewall Rules Loggingを有効化しても、発生時点までのデータは遡って取得できません。試験対策としても実務としても、「まず何を有効化しておくべきか」という設計判断(Task 5.1)が、トラブルシューティング(Task 5.2)とNetwork Intelligence Centerの活用(Task 5.3)の土台になっている、という関係を押さえておいてください。 + +--- + +## 参考文献 + +### 公式認定・試験ガイド + +- https://cloud.google.com/learn/certification/cloud-network-engineer +- https://services.google.com/fh/files/misc/professional_cloud_network_engineer_exam_guide_english.pdf + +### Cloud Logging / Cloud Monitoring基盤・コンポーネント別ロギング + +- https://docs.cloud.google.com/firewall/docs/vpc-firewall-rules-logging-overview +- https://cloud.google.com/vpc/docs/flow-logs +- https://cloud.google.com/vpc/docs/using-flow-logs +- https://cloud.google.com/vpc/docs/about-flow-logs-records +- https://cloud.google.com/vpc/docs/access-flow-logs +- https://cloud.google.com/nat/docs/monitoring +- https://cloud.google.com/nat/docs/audit-logging +- https://docs.cloud.google.com/dns/docs/monitoring +- https://docs.cloud.google.com/vpc-service-controls/docs/audit-logging +- https://docs.cloud.google.com/vpc-service-controls/docs/violation-dashboard +- https://docs.cloud.google.com/vpc-service-controls/docs/retrieve-troubleshoot-errors +- https://cloud.google.com/vpc-service-controls/docs/violation-analyzer +- https://docs.cloud.google.com/network-connectivity/docs/network-connectivity-center/how-to/viewing-logs-metrics + +### ハイブリッド接続(Cloud VPN / Cloud Interconnect / Cloud Router)のモニタリングとトラブルシューティング + +- https://docs.cloud.google.com/network-connectivity/docs/vpn/support/troubleshooting +- https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/checking-vpn-status +- https://docs.cloud.google.com/network-connectivity/docs/vpn/how-to/viewing-logs-metrics +- https://cloud.google.com/network-connectivity/docs/interconnect/how-to/monitoring +- https://docs.cloud.google.com/network-connectivity/docs/interconnect/support/troubleshooting +- https://cloud.google.com/network-connectivity/docs/router/how-to/viewing-logs-metrics +- https://cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-sessions +- https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-peering +- https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-bgp-routes +- https://docs.cloud.google.com/network-connectivity/docs/router/support/troubleshoot-log-messages +- https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bgp-states +- https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bfd +- https://docs.cloud.google.com/network-connectivity/docs/router/concepts/bfd-states + +### ロードバランシング・トラフィック管理 + +- https://docs.cloud.google.com/load-balancing/docs/enabling-connection-draining +- https://docs.cloud.google.com/kubernetes-engine/docs/troubleshooting/load-balancing +- https://cloud.google.com/load-balancing/docs/backend-service +- https://docs.cloud.google.com/load-balancing/docs/network/networklb-backend-service +- https://docs.cloud.google.com/load-balancing/docs/internal/failover-overview +- https://docs.cloud.google.com/load-balancing/docs/https/https-logging-monitoring +- https://docs.cloud.google.com/stackdriver/docs/solutions/slo-monitoring/sli-metrics/lb-metrics + +### VPC Flow Logs・Packet Mirroringによるトラブルシューティング + +- https://cloud.google.com/vpc/docs/packet-mirroring +- https://cloud.google.com/vpc/docs/using-packet-mirroring +- https://cloud.google.com/vpc/docs/monitoring-packet-mirroring + +### Network Intelligence Center + +- https://cloud.google.com/blog/products/networking/announcing-network-intelligence-center +- https://docs.cloud.google.com/network-intelligence-center/docs/overview +- https://docs.cloud.google.com/network-intelligence-center/docs +- https://cloud.google.com/network-intelligence-center/docs/network-topology/reference/metrics-reference +- https://cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/state-tables +- https://cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/reachability +- https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/support/troubleshooting +- https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/test-google-managed-services +- https://docs.cloud.google.com/network-intelligence-center/docs/connectivity-tests/concepts/overview +- https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/overview +- https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/metrics-views +- https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/use-cases-project +- https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/concepts/use-cases-google-cloud +- https://docs.cloud.google.com/network-intelligence-center/docs/performance-dashboard/how-to/viewing-perf-dash-metrics +- https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/concepts/overview +- https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/view-understand-insights +- https://cloud.google.com/network-intelligence-center/docs/firewall-insights/concepts/insights-categories-states +- https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/enable-api-features +- https://docs.cloud.google.com/network-intelligence-center/docs/firewall-insights/how-to/view-insights-recommendation-hub +- https://cloud.google.com/network-intelligence-center/docs/network-analyzer/insight-groups-types +- https://docs.cloud.google.com/network-intelligence-center/docs/network-analyzer/overview +- https://cloud.google.com/network-intelligence-center/docs/network-analyzer/insights/kubernetes-engine/gke-node-to-control-plane +- https://www.doit.com/blog/proactively-detect-network-misconfigurations-in-google-cloud-with-network-analyzer +- https://cloud.google.com/network-intelligence-center/docs/flow-analyzer/overview +- https://docs.cloud.google.com/network-intelligence-center/docs/flow-analyzer/monitor-traffic-flows +- https://cloud.google.com/network-intelligence-center/docs/flow-analyzer/enable-log-analytics +- https://cloud.google.com/blog/products/networking/vpc-flow-logs-for-cross-cloud-network +- https://cloud.google.com/blog/products/networking/using-vpc-flow-logs-to-de-risk-network-migration From d2970e144b436039b0149347dab1d01a78f215c2 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 22:25:05 +0900 Subject: [PATCH 007/123] fix(docs): align Apps Script task instructions --- ...uery-appsscript-connectedsheets-guide.html | 20 +++++++++++++++---- Bigquery-appsscript-connectedsheets-guide.md | 20 +++++++++++++++---- Ml-api-challenge-lab-guide.md | 3 ++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Bigquery-appsscript-connectedsheets-guide.html b/Bigquery-appsscript-connectedsheets-guide.html index 6595d04f6..b82faaf6d 100644 --- a/Bigquery-appsscript-connectedsheets-guide.html +++ b/Bigquery-appsscript-connectedsheets-guide.html @@ -520,7 +520,7 @@

このラボの全体像

- 4タスクの全体アーキテクチャ(クリックで拡大されません。スクロールしてご覧ください) + 4タスクの全体アーキテクチャ

@@ -1013,11 +1013,23 @@

タスク4: Apps Scriptで新規ワークシートを作成する

手順の流れ

    -
  1. Google Sheetsを開き、新しい空白のスプレッドシートを作成する
  2. -
  3. 左上のセルA1(1行目・A列)をクリックする
  4. -
  5. 76 9th Ave, New York という住所文字列を入力する
  6. +
  7. Apps Scriptで新しいプロジェクトを開く
  8. +
  9. + エディタに次のコードを貼り付け、createAddressSheet + を実行して権限を承認する +
  10. +
  11. 実行ログに出力されたURLを開き、新規ワークシートのセルA1に住所が入力されていることを確認する
+
function createAddressSheet() {
+  const spreadsheet = SpreadsheetApp.create('Address Sheet');
+  const sheet = spreadsheet.getSheets()[0];
+
+  sheet.setName('Address');
+  sheet.getRange('A1').setValue('76 9th Ave, New York');
+  console.log(spreadsheet.getUrl());
+}
+

なぜここでApps Scriptの組み込みサービスが登場するのか

タスク1では「BigQuery diff --git a/Bigquery-appsscript-connectedsheets-guide.md b/Bigquery-appsscript-connectedsheets-guide.md index 136e988ac..6d8457e9e 100644 --- a/Bigquery-appsscript-connectedsheets-guide.md +++ b/Bigquery-appsscript-connectedsheets-guide.md @@ -1,5 +1,6 @@ # BigQuery × Apps Script × Connected Sheets 実践ガイド -### 〜 Google Cloud Skills Boost チャレンジラボ攻略のためのベストプラクティス解説 〜 + +**〜 Google Cloud Skills Boost チャレンジラボ攻略のためのベストプラクティス解説 〜** > 対象ラボ: [Google Cloud Skills Boost(元ラボページ)](https://www.skills.google/course_templates/737/labs/607137) [1] > 対象読者: BigQuery・Apps Script・Google Sheetsの連携を初めて行うインフラ/アプリケーションエンジニア @@ -267,9 +268,20 @@ Google Sheets上でグラフを作成した後は、元データが更新され ### 手順の流れ -1. Google Sheetsを開き、新しい空白のスプレッドシートを作成する -2. 左上のセルA1(1行目・A列)をクリックする -3. `76 9th Ave, New York` という住所文字列を入力する +1. Apps Scriptで新しいプロジェクトを開く +2. エディタに次のコードを貼り付け、`createAddressSheet` を実行して権限を承認する +3. 実行ログに出力されたURLを開き、新規ワークシートのセルA1に住所が入力されていることを確認する + +```javascript +function createAddressSheet() { + const spreadsheet = SpreadsheetApp.create('Address Sheet'); + const sheet = spreadsheet.getSheets()[0]; + + sheet.setName('Address'); + sheet.getRange('A1').setValue('76 9th Ave, New York'); + console.log(spreadsheet.getUrl()); +} +``` ### なぜここでApps Scriptの組み込みサービスが登場するのか diff --git a/Ml-api-challenge-lab-guide.md b/Ml-api-challenge-lab-guide.md index aa47d3b17..3f8633d41 100644 --- a/Ml-api-challenge-lab-guide.md +++ b/Ml-api-challenge-lab-guide.md @@ -1,5 +1,6 @@ # Machine Learning APIs チャレンジラボ 攻略ガイド -### 〜 Vision API × Translation API × BigQuery によるサイン画像テキスト抽出パイプライン 〜 + +**〜 Vision API × Translation API × BigQuery によるサイン画像テキスト抽出パイプライン 〜** 対象ラボ: [Integrate with Machine Learning APIs: Challenge Lab](https://www.skills.google/course_templates/630/labs/612231) From 60450598a4a4eb037ecba8a75d58d66ea3a79f96 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 22:26:00 +0900 Subject: [PATCH 008/123] fix(docs): correct COVID query semantics --- Bigquery-covid19-challenge-lab-guide.md | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Bigquery-covid19-challenge-lab-guide.md b/Bigquery-covid19-challenge-lab-guide.md index 87b48be11..bbf18bb68 100644 --- a/Bigquery-covid19-challenge-lab-guide.md +++ b/Bigquery-covid19-challenge-lab-guide.md @@ -67,7 +67,7 @@ flowchart TD この挙動は、データセット提供元の公式リポジトリでも明記されています。`subregion1_code` が `NULL` であれば国レベルの集計、値が入っていれば州レベルの集計であるとされ、集計レベルの判定には `aggregation_level` を使う方法もあると案内されています([GoogleCloudPlatform/covid-19-open-data README](https://github.com/GoogleCloudPlatform/covid-19-open-data))。 -✅ 実務での回避策:地域別に集計したいときは、必ず `subregion1_name IS NOT NULL`(州レベルだけを見る)や `subregion1_name IS NULL AND subregion2_name IS NULL`(国レベルだけを見る)のように、対象の粒度を明示的にWHERE句で絞り込みます。 +✅ 実務での回避策:地域別に集計したいときは、必ず `subregion1_name IS NOT NULL AND subregion2_name IS NULL`(州レベルだけを見る)や `subregion1_name IS NULL AND subregion2_name IS NULL`(国レベルだけを見る)のように、対象の粒度を明示的にWHERE句で絞り込みます。 ⚠️ ただし1点注意:本ラボのタスク1(世界全体の確定症例数)のように「日付だけで単純に `SUM` する」ことが公式の想定解になっているタスクもあります。これは採点システムの期待値がその単純な合計に合わせて作られているためです。本ガイドでは、ラボへの提出クエリはラボの想定解パターンに沿えつつ、実務で同じデータセットを使う際に注意すべき点は都度コラムとして補足します。 @@ -167,7 +167,8 @@ FROM ( WHERE country_name = "United States of America" AND date = "" - AND subregion1_name IS NOT NULL -- 国全体の1行(州レベルではない行)を除外する + AND subregion1_name IS NOT NULL -- 国レベルの行を除外する + AND subregion2_name IS NULL -- 郡レベルの行を除外する GROUP BY subregion1_name ) @@ -177,7 +178,7 @@ WHERE 処理の流れ: 1. 内側のサブクエリで、州ごとに `cumulative_deceased` を集計し `death_count` を作る -2. `subregion1_name IS NOT NULL` によって、国レベルの合計行(州の情報を持たない1行)を弾く。これを忘れると「51件目の州」として国全体の行が混ざり、件数がずれる +2. `subregion1_name IS NOT NULL` によって国レベルの行を除外する。ただし、この条件だけでは `subregion1_name` と `subregion2_name` の両方を持つ郡レベルの行も残り、州の値との `SUM(cumulative_deceased)` で二重計上される。州だけを集計するには `subregion2_name IS NULL` も必要になる 3. 外側の `WHERE death_count > ` で、しきい値を超えた州だけを残す 4. `COUNT(*)` で、残った州の件数を数える @@ -204,6 +205,7 @@ WHERE country_code = "US" AND date = "" AND subregion1_name IS NOT NULL + AND subregion2_name IS NULL GROUP BY subregion1_name HAVING @@ -213,7 +215,7 @@ ORDER BY ``` 処理の流れ: -1. `WHERE` で対象日・対象国・州レベルの行だけに絞り込む +1. `WHERE` で対象日・対象国に絞り、`subregion1_name IS NOT NULL` で国レベルの行を除外する。この条件だけでは郡レベルの行も残って州の値と二重計上されるため、`subregion2_name IS NULL` を併用して州レベルだけに絞り込む 2. `GROUP BY subregion1_name` で州ごとに集計する 3. `HAVING total_confirmed_cases > ` で、Step 2-2のパターンどおり「集計後の値」をしきい値で絞り込む 4. `ORDER BY total_confirmed_cases DESC` で症例数が多い順に並べ替える @@ -242,8 +244,8 @@ WHERE ``` 処理の流れ: -- `date BETWEEN 初日 AND 末日` で対象月の行だけに絞り込む -- `SUM` でその月の(=月末時点の)累積確定症例数・累積死亡者数をそのまま取得する +- `date BETWEEN 初日 AND 末日` で、対象月の初日から末日までの各日の行をすべて選択する +- `SUM` で各日の日次累積値を合計する。このため `total_confirmed_cases` と `total_deaths` は「日次累積値の月間合計」であり、月末時点の値ではない - `SAFE_DIVIDE` で割り算し、100倍してパーセント表記にする ⚠️ 月末日を手で数える手間を減らしたい場合:閏年の2月など、月末日を間違えやすいケースがあります。次のように `EXTRACT` を使うと、月末日を意識せずに書けます。 @@ -369,7 +371,7 @@ WITH us_cases_by_date AS ( `bigquery-public-data.covid19_open_data.covid19_open_data` WHERE country_name = "United States of America" - AND date BETWEEN "2020-03-22" AND "2020-04-20" + AND date BETWEEN "" AND "" GROUP BY date ORDER BY @@ -411,7 +413,7 @@ WHERE ## Task 8. 回復率(Recovery Rate)ランキングを作る -💡 一言で言うと:「指定した日付時点で、確定症例数が5万件を超える国だけを対象に、回復率が高い順に上位いくつかを表示するクエリ」です。 +💡 一言で言うと:「指定した日付時点で、指定された確定症例数を超える国だけを対象に、回復率が高い順に上位いくつかを表示するクエリ」です。 ```sql WITH cases_by_country AS ( @@ -422,7 +424,7 @@ WITH cases_by_country AS ( FROM `bigquery-public-data.covid19_open_data.covid19_open_data` WHERE - date = "2020-05-10" + date = "" GROUP BY country_name ) @@ -435,7 +437,7 @@ SELECT FROM cases_by_country WHERE - confirmed_cases > 50000 + confirmed_cases > ORDER BY recovery_rate DESC LIMIT @@ -443,10 +445,10 @@ LIMIT 処理の流れ: 1. CTEで国ごとに確定症例数・回復者数を集計する -2. 外側の `WHERE confirmed_cases > 50000` で、感染規模がある程度大きい国だけに絞り込む(Step 2-2と同じ「集計後の値で絞り込む」パターン) +2. 外側の `WHERE confirmed_cases > ` で、指定された感染規模を超える国だけに絞り込む(Step 2-2と同じ「集計後の値で絞り込む」パターン) 3. `recovery_rate` を計算し、降順に並べ替えて `LIMIT` で件数を絞る -💡 補足:`ORDER BY` と `LIMIT` を組み合わせる際は、`WHERE confirmed_cases > 50000` を先に適用してから並べ替えることで、「症例数が少ないのに回復率だけ100%に近い」ような小規模な国がランキング上位に紛れ込むのを防いでいます。この順序(先に絞り込み、後で並べ替え)は、集計を伴うランキングクエリで繰り返し使えるパターンです。 +💡 補足:`ORDER BY` と `LIMIT` を組み合わせる際は、`WHERE confirmed_cases > ` を先に適用してから並べ替えることで、「症例数が少ないのに回復率だけ100%に近い」ような小規模な国がランキング上位に紛れ込むのを防いでいます。この順序(先に絞り込み、後で並べ替え)は、集計を伴うランキングクエリで繰り返し使えるパターンです。 📖 このセクションで登場した用語 - (新出用語なし) @@ -481,7 +483,7 @@ flowchart TD 各不具合の解説: - **不具合1(構文エラー)**:`LEAD(total_cases)` はウィンドウ関数ですが、`OVER` 句が付いていません。Step 2-3で説明した通り、ウィンドウ関数は必ず `OVER` 句とセットで書く必要があるため、このままではエラーになります([ナビゲーション関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions))。`LEAD(total_cases) OVER (ORDER BY date)` のように修正します。 -- **不具合2(未入力の値)**:`date IN ('2020-01-24', '')` の2番目が空文字列のままです。CDGRを計算したい最終日(例では2020年5月10日)の日付リテラルに置き換えます。 +- **不具合2(未入力の値)**:`date IN ('', '')` の2番目が空文字列のままです。ラボで指定された初日と、CDGRを計算したい最終日の各日付リテラルに置き換えます。 - **不具合3(関数の選び間違い)**:最後の `SELECT` で `SQRT((last_day_cases/first_day_cases),(1/days_diff))-1` という書き方をしていますが、`SQRT`(平方根)は引数を1つしか取らない関数です。ここで本当にやりたいのは「累乗(べき乗)」の計算なので、2つの引数(底と指数)を取る `POWER(底, 指数)` 関数に置き換える必要があります([数学関数リファレンス](https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions))。 修正後のクエリ: @@ -495,7 +497,7 @@ WITH france_cases AS ( `bigquery-public-data.covid19_open_data.covid19_open_data` WHERE country_name = "France" - AND date IN ("2020-01-24", "<最終日, 例: 2020-05-10>") + AND date IN ("", "") GROUP BY date ORDER BY @@ -509,7 +511,8 @@ WITH france_cases AS ( DATE_DIFF(LEAD(date) OVER (ORDER BY date), date, DAY) AS days_diff FROM france_cases - LIMIT 1 + QUALIFY + last_day_cases IS NOT NULL ) SELECT @@ -524,7 +527,7 @@ FROM 処理の流れ: 1. `france_cases` CTEで、初日と最終日、2行だけを取り出す 2. `summary` CTEで、`LEAD` を使って「1行目に初日、2行目に最終日」という2行を1行にまとめ、`DATE_DIFF` で経過日数も同じ行に並べる -3. `LIMIT 1` で、まとめ終わった1行だけを残す +3. `QUALIFY last_day_cases IS NOT NULL` で、最終日の値を取得できた初日の行だけを残す 4. 最後の `SELECT` で `POWER` を使ってCDGRを計算する 📖 このセクションで登場した用語 @@ -578,7 +581,7 @@ ORDER BY - [ ] クエリ中のすべての `<プレースホルダー>` を、自分のラボ画面に表示された実際の値に置き換えたか - [ ] `country_name` と `country_code` のどちらで絞り込むべきタスクか、混同していないか -- [ ] 州・郡レベルの集計をするタスクで `subregion1_name IS NOT NULL` の絞り込みを入れたか +- [ ] 州レベルの集計をするタスクで `subregion1_name IS NOT NULL AND subregion2_name IS NULL` の絞り込みを入れたか - [ ] 集計後の値(`SUM` や `COUNT` の結果)を条件にするときは `WHERE` ではなく `HAVING` かサブクエリ/CTEを使っているか - [ ] `LAG` / `LEAD` に `OVER (ORDER BY ...)` を付け忘れていないか - [ ] 割り算を含むタスクで、ゼロ除算対策(`SAFE_DIVIDE`)を検討したか From e1e9adc27f6bb07747de9ae10b9d2b4b4e87ffb3 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 22:27:30 +0900 Subject: [PATCH 009/123] fix(docs): harden BigQuery sharing guide --- ...uery-data-sharing-challenge-lab-guide.html | 76 ++++++++++++------- Bigquery-data-sharing-challenge-lab-guide.md | 8 +- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/Bigquery-data-sharing-challenge-lab-guide.html b/Bigquery-data-sharing-challenge-lab-guide.html index a6c15ed2d..8cd713bf1 100644 --- a/Bigquery-data-sharing-challenge-lab-guide.html +++ b/Bigquery-data-sharing-challenge-lab-guide.html @@ -799,9 +799,14 @@

4.1 手順

SET cust.county=vw.county FROM -`Partner Project ID.demo_dataset.Partner authorized view` vw +`<PARTNER_PROJECT_ID>.demo_dataset.<PARTNER_AUTHORIZED_VIEW>` vw WHERE vw.zip_code=cust.postal_code; +

+ <PARTNER_PROJECT_ID> と + <PARTNER_AUTHORIZED_VIEW> + はプレースホルダーです。ラボで指定された実際のパートナープロジェクトIDとビュー名に置き換えてください。 +

実行後、This statement modified 14 rows in customer_info.9. 参考文献 / ソース一覧 - + + + + +

+ + +
+
+
CCNA Automation Certification ガイド
+

+ 200-901 CCNAAUTO ドメイン4.0
Application Deployment and Security + 完全解説 +

+

+ Cisco公式サイトおよび公式出題範囲PDFをもとに、CCNA + Automation認定試験「Automating Networks Using Cisco Platforms v1.1(200-901 + CCNAAUTO)」の6ドメインのうち、ドメイン4.0「Application Deployment and + Security(アプリケーションの展開とセキュリティ)」(配点15%)を、初学者でも理解できるようにステップバイステップで解説します。 +

+
+ 配点 15% + サブトピック 4.1〜4.12 + 試験時間 120分 + 対応言語:英語・日本語 +
+
+ +
+
このガイドの使い方
+

+ 各章は試験ガイドのサブトピック番号(4.1、4.2…)に対応しています。図解はすべてMermaidによるフローチャート、比較情報はすべて表で示しており、ASCIIアートは使用していません。前提知識としてPythonの基礎、Linux/Bashの基本操作、Dockerの概念に軽く触れたことがあると理解がスムーズですが、未経験でも読み進められるように用語から説明しています。各章末の「この章のポイント」と、最後の第12章の早見表を試験直前の見直しに活用してください。 +

+
+ + +
+

第1章 出題範囲の全体像

+

+ CCNA Automation認定を取得するには、120分の試験「200-901 + CCNAAUTO」に合格する必要があります。この試験は6つのドメインで構成されており、それぞれに配点比率が設定されています。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ドメイン番号ドメイン名配点比率
1.0Software Development and Design(ソフトウェア開発と設計)15%
2.0Understanding and Using APIs(APIの理解と利用)20%
3.0 + Cisco Platforms and Development(Ciscoプラットフォームと開発) + 15%
4.0 + Application Deployment and + Security(アプリケーションの展開とセキュリティ) + 15%
5.0 + Infrastructure and Automation(インフラストラクチャと自動化) + 20%
6.0Network Fundamentals(ネットワークの基礎)15%
+ +

+ 本ガイドが扱うドメイン4.0は、「作ったアプリケーションをどこに・どうやって・安全に動かすか」という、開発から運用への橋渡しにあたる領域です。具体的には以下の12個のサブトピックで構成されます。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
サブトピック内容
4.1エッジコンピューティングの利点を説明する
4.2 + 異なるアプリケーション展開モデル(プライベートクラウド、パブリッククラウド、ハイブリッドクラウド、エッジ)の属性を説明する +
4.3 + 展開タイプ(仮想マシン/ベアメタル/コンテナ)の属性を説明する +
4.4 + アプリケーション展開におけるCI/CDパイプラインの構成要素を説明する +
4.5Pythonのユニットテストを構築する
4.6Dockerfileの内容を解釈する
4.7ローカル開発環境でDockerイメージを利用する
4.8 + シークレット保護、暗号化(保存時・転送時)、データ取り扱いに関するアプリケーションセキュリティの課題を説明する +
4.9 + ファイアウォール、DNS、ロードバランサー、リバースプロキシのアプリケーション展開における役割を説明する +
4.10 + OWASPのトップ脅威(XSS、SQLインジェクション、CSRFなど)を説明する +
4.11 + Bashコマンド(ファイル管理、ディレクトリ操作、環境変数)を利用する +
4.12DevOpsプラクティスの原則を説明する
+ +

このドメイン全体の位置づけを図にすると次のとおりです。

+
+ +
+ +
+
この章のポイント
+

+ ドメイン4.0は試験全体の15%を占め、「開発したアプリをどこでどう動かし、どう守るか」を扱う。12個のサブトピックは、大きく「展開モデル・展開先の選定(4.1〜4.3)」「CI/CDと自動テスト(4.4・4.5)」「コンテナ実務(4.6・4.7)」「セキュリティ(4.8〜4.10)」「運用スキル(4.11・4.12)」の5グループに整理できる。 +

+
+
+ + +
+

第2章 エッジコンピューティングとアプリケーション展開モデル(4.1・4.2)

+ +

4.1 エッジコンピューティングの利点

+

+ エッジコンピューティングとは、データを中央のクラウドやデータセンターまで送らず、データが発生する場所(ネットワークの「端=エッジ」)に近い場所で処理するという考え方です。工場のセンサー、店舗のPOSレジ、IoTデバイスなどが典型例です。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
利点説明
低遅延(レイテンシ削減) + クラウドまでの往復通信が不要になり、リアルタイム性が向上する +
帯域幅の節約 + 現地で処理・要約してから必要なデータだけをクラウドに送るため、通信量を削減できる +
オフライン耐性クラウドとの接続が一時的に切れても現地で処理を継続できる
データローカリティ/プライバシー + 機密データを現地にとどめたまま処理でき、規制対応がしやすい場合がある +
+ +

4.2 アプリケーション展開モデルの比較

+

+ アプリケーションをどこで動かすかという「展開モデル」には、主に次の4種類があります。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
展開モデル管理主体主な特徴典型的な用途
プライベートクラウド自社(または委託先が自社専用に構築) + 自由度が高く、既存のセキュリティ・コンプライアンス要件に合わせやすいが、初期投資と運用負荷が大きい + 金融・医療など規制が厳しい業界の基幹システム
パブリッククラウドクラウド事業者(AWS、Azure、Google Cloudなど) + 従量課金で始めやすく、拡張性が高いが、事業者のインフラに依存する + Webサービス、需要変動の大きいアプリケーション
ハイブリッドクラウド自社とクラウド事業者の組み合わせ + 機密データはプライベート側、負荷変動の大きい処理はパブリック側、というように使い分けられる + 既存の社内システムとクラウドサービスを連携させたい企業
エッジ現地(店舗・工場・デバイス側)クラウドに比べて計算資源は限られるが、低遅延処理が可能IoT、産業オートメーション、リアルタイム映像解析
+ +

これらの関係をフローで示すと次のようになります。

+
+ +
+ +
+
この章のポイント
+

+ エッジコンピューティングの利点は「低遅延・帯域節約・オフライン耐性・データローカリティ」の4つに集約できる。展開モデルは「誰が管理するか」「どこにデータ・計算資源があるか」で分類され、要件(コスト・拡張性・規制・遅延)に応じて選択する。 +

+
+
+ + +
+

第3章 アプリケーション実行環境の比較:VM・ベアメタル・コンテナ(4.3)

+

+ 展開モデル(どこで動かすか)が決まったら、次は「どの単位でアプリケーションをパッケージ化して動かすか」を選びます。試験ガイドでは以下の3タイプが挙げられています。 +

+
    +
  • 4.3.a 仮想マシン(Virtual Machine)
  • +
  • 4.3.b ベアメタル(Bare Metal)
  • +
  • 4.3.c コンテナ(Container)
  • +
+ +

3つの実行環境の構造比較

+
+ +
+ +

属性比較表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目ベアメタル仮想マシン(VM)コンテナ
分離レベル物理サーバー単位(最も高い)ハイパーバイザーによるハードウェアレベルの分離OSカーネルを共有するプロセスレベルの分離
起動速度サーバーの起動時間に依存(遅い)数十秒〜数分数百ミリ秒〜数秒(非常に速い)
リソースオーバーヘッドなし(すべての資源を占有)大きい(ゲストOSごとに必要)小さい(OSを共有するため軽量)
移植性(ポータビリティ)低い(ハードウェアに強く依存)中程度(イメージ化して移動可能)高い(同じイメージがどこでも同じ動作)
主なユースケース高いパフォーマンスが必須の基幹システム、レガシーアプリ複数OSの混在環境、既存VM資産の活用マイクロサービス、CI/CDでの高速な使い捨て環境
+ +
+
この章のポイント
+

+ 分離の強さと起動の速さはトレードオフの関係にある:ベアメタル・VMは分離が強いが重く、コンテナは軽量だがOSカーネルを共有する。コンテナはCI/CDやマイクロサービスとの相性が良く、次章のCI/CDパイプラインとも密接に関係する。 +

+
+
+ + +
+

第4章 CI/CDパイプラインの基礎(4.4)

+

+ CI/CD(Continuous Integration / Continuous Delivery(or + Deployment)=継続的インテグレーション/継続的デリバリー(デプロイ))は、コードの変更を自動的にビルド・テスト・展開するための仕組みです。 +

+ +

CI/CDパイプラインの主な構成要素

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ステージ目的
ソース管理(Git等) + コードの変更履歴を管理し、変更をトリガーにパイプラインを起動する +
ビルド依存関係の解決、コンパイル、静的解析などを行う
テストユニットテスト・統合テストなどを自動実行し、品質を検証する
パッケージング/アーティファクト作成コンテナイメージなど、展開可能な成果物を作成する
レジストリへの登録作成した成果物をイメージレジストリなどに格納する
ステージング展開本番相当の環境に自動展開し、追加検証を行う
本番展開承認を経て本番環境に展開する
監視・フィードバック稼働状況を監視し、問題や改善点を開発側にフィードバックする
+ +

パイプライン全体の流れ

+
+ +
+ +
+
この章のポイント
+

+ CI(継続的インテグレーション)は「頻繁に統合し、自動テストで早期に問題を検出する」こと、CD(継続的デリバリー/デプロイ)は「いつでも展開できる状態を保ち、実際に自動展開まで行う」ことを指す。テスト失敗・承認却下時にフローが開発者へ戻る「フィードバックループ」がある点が重要。 +

+
+
+ + +
+

第5章 Pythonユニットテストの構築(4.5)

+

+ ユニットテストとは、プログラムの中の「最小単位(関数やメソッド)」が期待どおりに動くかを自動で検証するテストです。Pythonでは標準ライブラリのunittestモジュールがよく使われます。 +

+ +

テストの基本的な流れ(Arrange-Act-Assertパターン)

+
+ +
+ +

コード例

+
+
Python — unittest の基本形
+
+ +
+ +

+ assertEqualのほかにも、assertTrue(真偽値の検証)、assertRaises(例外発生の検証)などがよく使われます。ユニットテストは第1章の「テスト駆動開発(TDD)」の考え方や、第4章のCI/CDパイプラインの「テストステージ」と直結しています。 +

+ +
+
この章のポイント
+

+ ユニットテストは「準備(Arrange)→実行(Act)→検証(Assert)」の3ステップで考えると書きやすい。CI/CDパイプラインでは、このユニットテストが自動テストステージの中核を担う。 +

+
+
+ + +
+

第6章 Dockerfileの読み方とDockerイメージの活用(4.6・4.7)

+ +

4.6 Dockerfileの内容を解釈する

+

+ Dockerfileは、Dockerイメージをどのように構築するかを記述したテキストファイルです。代表的な命令は以下のとおりです。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
命令役割
FROM + ベースとなるイメージを指定する(例:FROM python:3.12-slim) +
WORKDIRコンテナ内の作業ディレクトリを設定する
COPY / ADDホスト側のファイルをイメージ内にコピーする
RUN + イメージ構築時にコマンドを実行する(パッケージのインストール等) +
ENV環境変数を設定する
EXPOSE + コンテナが待ち受けるポート番号を明示する(実際の公開はdocker run -pで行う) +
CMDコンテナ起動時のデフォルトの実行コマンドを指定する
ENTRYPOINT + コンテナを実行ファイルのように振る舞わせる際のエントリポイントを指定する +
+ +

サンプルDockerfile

+
+
Dockerfile
+
+ +
+ +

4.7 ローカル開発環境でのDockerイメージの利用

+

よく使うDockerコマンドは以下のとおりです。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
コマンド用途
docker build -t イメージ名:タグ .カレントディレクトリのDockerfileからイメージをビルドする
docker imagesローカルに存在するイメージの一覧を表示する
docker run -d -p 8080:8080 イメージ名:タグ + イメージからコンテナをバックグラウンドで起動し、ポートを公開する +
docker ps / docker ps -a稼働中(または全て)のコンテナ一覧を表示する
docker logs コンテナIDコンテナのログを表示する
docker exec -it コンテナID bash稼働中のコンテナ内でシェルを起動する
docker stop / docker rmコンテナを停止・削除する
docker push / docker pullイメージをレジストリへ送信/取得する
+ +

イメージのライフサイクル

+
+ +
+ +
+
この章のポイント
+

+ Dockerfileは「上から順に1行ずつ実行される構築手順書」として読むと理解しやすい。COPY requirements.txtをCOPY . .より先に行うのは、依存関係が変わらない限りビルドキャッシュを再利用して高速化するための定石。 +

+
+
+ + +
+

+ 第7章 + アプリケーションセキュリティの基礎:シークレット保護・暗号化・データ取り扱い(4.8) +

+ +

シークレット保護

+

+ 「シークレット」とは、パスワード、APIキー、証明書の秘密鍵など、漏洩すると重大な影響が出る機密情報を指します。基本原則は次のとおりです。 +

+
    +
  • + コードやDockerイメージにシークレットを直接書き込まない(ハードコードしない) +
  • +
  • + 環境変数、シークレット管理サービス(Vaultなど)、クラウドのシークレットマネージャーを利用する +
  • +
  • + Gitリポジトリに誤ってコミットしないよう.gitignoreや事前スキャンツールを活用する +
  • +
+ +

暗号化(保存時・転送時)

+ + + + + + + + + + + + + + + + + + + + +
種類説明代表例
転送時の暗号化(Encryption in Transit)ネットワークを流れるデータを暗号化し、盗聴・改ざんを防ぐTLS/HTTPS、SSH
保存時の暗号化(Encryption at Rest)ディスクやデータベースに保存されているデータを暗号化するディスク暗号化、データベースの列暗号化
+ +

データ取り扱いの考え方

+

+ 個人情報や機密データを扱う際は、「必要最小限のデータのみ収集・保持する」「アクセス権を最小限にする」「保持期間を定めて不要になったら削除する」といった原則が重要です。 +

+ +

セキュアなデータフローのイメージ

+
+ +
+ +
+
この章のポイント
+

+ 「シークレットはコードに書かない」「転送時と保存時の両方を暗号化する」の2点は特に頻出の観点。暗号化は「守る対象がどこにあるか(通信中か、保存中か)」で使う技術が異なる。 +

+
+
+ + +
+

+ 第8章 + ネットワーク境界のセキュリティ要素:ファイアウォール・DNS・ロードバランサー・リバースプロキシ(4.9) +

+

+ アプリケーションを展開する際、ユーザーからのリクエストは複数のネットワーク要素を経由します。それぞれの役割を理解することが重要です。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
要素役割
DNS(Domain Name System) + ドメイン名(例:example.com)をIPアドレスに変換する名前解決を行う +
ファイアウォール + 事前に定義したルールに基づき、許可された通信のみを通過させる +
ロードバランサー + 複数のサーバーにリクエストを振り分け、負荷分散と可用性向上を実現する +
リバースプロキシ + クライアントとサーバーの間に立ち、TLS終端・キャッシュ・経路制御・ヘッダー書き換えなどを行う +
+ +

リクエストが辿る経路

+
+ +
+ +
+
この章のポイント
+

+ 「名前解決 → アクセス制御 → 負荷分散 → プロキシによる終端・制御 → + アプリ本体」という順序でリクエストが処理される、という全体像を押さえる。ロードバランサーとリバースプロキシは役割が重なることも多いが、試験では「負荷分散」と「TLS終端・経路制御」という観点の違いで整理すると覚えやすい。 +

+
+
+ + +
+

第9章 OWASPトップの脅威(4.10)

+

+ OWASP(Open Worldwide Application Security + Project)は、Webアプリケーションセキュリティに関する非営利のコミュニティで、代表的な脅威をまとめた「OWASP + Top 10」を定期的に公開しています。 +

+

+ 試験ガイドでは代表例としてXSS(クロスサイトスクリプティング)、SQLインジェクション、CSRF(クロスサイトリクエストフォージェリ)が挙げられています。 +

+ +

代表的な脅威の説明

+ + + + + + + + + + + + + + + + + + + + + + + + + +
脅威概要典型的な対策
XSS(クロスサイトスクリプティング) + ユーザーの入力値がそのままページに出力され、悪意あるスクリプトがブラウザ上で実行されてしまう + + 出力時のエスケープ処理、入力値の検証、Content Security + Policyの設定 +
SQLインジェクション + ユーザーの入力値がSQL文にそのまま埋め込まれ、意図しないSQLが実行されてしまう + + プレースホルダ/パラメータ化クエリの利用、入力値の検証、最小権限のDBアカウント +
CSRF(クロスサイトリクエストフォージェリ) + 認証済みユーザーのブラウザを利用して、本人の意図しないリクエストを別サイトから送信させられる + + CSRFトークンの検証、SameSite属性付きCookie、重要操作での再認証 +
+ +

+ XSSとSQLインジェクションは「入力値や出力値の不十分な検証・処理」が主因となるのに対し、CSRFは「認証済みブラウザからのクロスサイト状態変更リクエストの信頼」に起因します。そのため、XSSには文脈に応じた出力エンコーディング、SQLインジェクションにはパラメータ化クエリ、CSRFにはCSRFトークン検証・Origin/Referer検証・SameSite Cookie属性による対策がそれぞれ不可欠です。 +

+ +
+ +
+ +

最新の公式リストとの関係

+

+ OWASP Top + 10は数年ごとに改訂されており、2025年版(2021年版の後継、8回目の改訂版)が現時点の最新版です。参考として、2025年版の10カテゴリを紹介します。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
順位カテゴリ名(英語)
A01Broken Access Control(アクセス制御の不備)
A02Security Misconfiguration(セキュリティ設定の不備)
A03 + Software Supply Chain + Failures(ソフトウェアサプライチェーンの問題) +
A04Cryptographic Failures(暗号化の不備)
A05Injection(インジェクション)
A06Insecure Design(安全でない設計)
A07Authentication Failures(認証の不備)
A08 + Software or Data Integrity + Failures(ソフトウェア/データ整合性の不備) +
A09 + Security Logging & Alerting + Failures(セキュリティログ・アラートの不備) +
A10 + Mishandling of Exceptional Conditions(例外的な状態の処理不備) +
+ +

+ 試験ガイドが例示するSQLインジェクションとXSSは、現行の2025年版では主に「A05 + Injection」に含まれる代表的な攻撃パターンです。CSRFは2017年版以降、単独の最上位カテゴリとしては掲載されなくなりましたが、現在も広く知られた古典的な攻撃パターンであり、アクセス制御や認証まわりの対策(トークン検証・Cookie属性の設定など)と合わせて理解しておく価値があります。試験対策としては、まず試験ガイドが明示するXSS・SQLインジェクション・CSRFの3つの仕組みと対策を確実に押さえるのがよいでしょう。 +

+ +
+
この章のポイント
+

+ XSS・SQLインジェクション・CSRFはいずれも「入力・リクエストを検証せず信頼する」ことが根本原因。OWASP + Top + 10は定期的に改訂されるため、学習時は「攻撃の仕組みと対策の考え方」を理解することを優先し、順位や名称は最新の一次情報で確認する。 +

+
+
+ + +
+

第10章 Bashコマンドの活用(4.11)

+

+ Linux環境での自動化スクリプトや運用作業では、Bashコマンドの基本操作が欠かせません。 +

+ +

ファイル管理

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
コマンド用途
ls -lファイル・ディレクトリの一覧を詳細表示する
cp src dstファイルをコピーする
mv src dstファイルを移動・リネームする
rm fileファイルを削除する(-rでディレクトリごと削除)
chmod 755 fileファイルの権限を変更する
cat fileファイルの内容を表示する
grep "keyword" fileファイル内から文字列を検索する
+ +

ディレクトリ操作

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
コマンド用途
pwd現在のディレクトリを表示する
cd pathディレクトリを移動する
mkdir dirディレクトリを作成する
rmdir dir空のディレクトリを削除する
find . -name "*.py"条件に合うファイルを再帰的に検索する
+ +

環境変数

+ + + + + + + + + + + + + + + + + + + + + + + + + +
コマンド用途
echo $HOME環境変数の値を表示する
export VAR=value環境変数を設定する(現在のシェルとその子プロセスに反映)
env / printenv現在の環境変数一覧を表示する
unset VAR環境変数を削除する
+ +
+
この章のポイント
+

+ 「ファイル管理」「ディレクトリ操作」「環境変数」の3分類で整理すると覚えやすい。自動化スクリプト(第4章のCI/CDや第11章のDevOps運用)では、これらのコマンドが組み合わさって使われる。 +

+
+
+ + +
+

第11章 DevOpsの原則(4.12)

+

+ DevOpsとは、開発(Development)と運用(Operations)の壁をなくし、ソフトウェアを継続的に、かつ安全・迅速にリリースし続けるための文化・プラクティスです。 +

+ +

代表的な原則は次のように整理できます。

+ + + + + + + + + + + + + + + + + + + + + + + + + +
原則説明
文化(Culture) + 開発チームと運用チームが責任を分断せず、協力してリリースに責任を持つ +
自動化(Automation) + ビルド・テスト・展開・監視といった繰り返し作業を自動化し、人的ミスと作業時間を減らす +
計測(Measurement / Lean) + リリース頻度、障害復旧時間、変更失敗率などの指標を計測し、継続的に改善する +
共有(Sharing)ツール・知見・障害対応のノウハウをチーム間で共有する
+ +

+ これらは、第4章で扱ったCI/CDパイプラインを支える文化的・組織的な土台にあたります。DevOpsのプラクティスは、しばしば「計画→コーディング→ビルド→テスト→リリース→展開→運用→監視」という循環(ループ)として表現されます。 +

+ +
+ +
+ +
+
この章のポイント
+

+ DevOpsは単なるツールの導入ではなく、「文化・自動化・計測・共有」という考え方の集合体である。このループが途切れず回り続けることこそが、継続的デリバリー(CD)の本質。 +

+
+
+ + +
+

第12章 まとめ:ドメイン4.0 早見表

+

試験直前の見直し用に、サブトピックごとの要点を1行にまとめました。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.サブトピック一言でいうと
4.1エッジコンピューティングの利点低遅延・帯域節約・オフライン耐性・データローカリティ
4.2展開モデルの比較 + プライベート/パブリック/ハイブリッド/エッジを、管理主体とコスト・拡張性で選ぶ +
4.3VM・ベアメタル・コンテナ + 分離の強さと起動の速さはトレードオフ。コンテナは軽量・高速・高移植性 +
4.4CI/CDパイプライン + コミット→ビルド→テスト→パッケージ→展開→監視を自動化する一連の流れ +
4.5PythonユニットテストArrange(準備)→Act(実行)→Assert(検証)の3ステップ
4.6Dockerfileの解釈 + FROM/COPY/RUN/CMDなど、上から順に実行される構築手順書 +
4.7Dockerイメージの活用 + build→images→run→ps→push/pullのライフサイクル +
4.8アプリケーションセキュリティ + シークレットはコードに書かない/転送時と保存時の両方を暗号化する +
4.9ネットワーク境界の要素 + DNS→ファイアウォール→ロードバランサー→リバースプロキシの順で経由 +
4.10OWASPトップの脅威 + XSS・SQLi・CSRFはいずれも「入力・リクエストを検証しない」ことが原因 +
4.11Bashコマンドファイル管理/ディレクトリ操作/環境変数の3分類
4.12DevOpsの原則文化・自動化・計測・共有、そして途切れないリリースループ
+
+ + + +
+
+ + + + + + + + + diff --git a/archive/Cisco/md/ccna/Ccna-automation-application-deployment-security.md b/archive/Cisco/md/ccna/Ccna-automation-application-deployment-security.md new file mode 100644 index 000000000..c4ae9467b --- /dev/null +++ b/archive/Cisco/md/ccna/Ccna-automation-application-deployment-security.md @@ -0,0 +1,614 @@ +# CCNA Automation(200-901 CCNAAUTO) +# 「Application Deployment and Security」ドメイン 完全解説ガイド + +> 本ガイドは、Cisco公式サイトおよび公式試験ガイド(出典は末尾に記載)をもとに、CCNA Automation認定の試験「Automating Networks Using Cisco Platforms v1.1(200-901 CCNAAUTO)」の6つの出題ドメインのうち、**ドメイン4.0「Application Deployment and Security(アプリケーションの展開とセキュリティ)」**(配点15%)を、初学者でも理解できるようにステップバイステップで解説したものです。 + +--- + +## このガイドの使い方 + +- 各章は試験ガイドのサブトピック番号(例:4.1、4.2…)に対応しています。 +- ASCIIアートではなく、**Mermaid記法によるフローチャート**と**Markdownの表**のみで図解しています。GitHubやVS Code、多くのMarkdownビューアでそのままレンダリングされます。 +- 前提知識:Pythonの基礎、Linux/Bashの基本操作、Dockerの概念に軽く触れたことがあると理解がスムーズです(未経験でも読み進められるように用語から説明しています)。 +- 章末に「この章のポイント」を置き、最後に全体の早見表(第12章)を用意しています。 + +--- + +## 目次 + +1. [出題範囲の全体像](#chapter1) +2. [エッジコンピューティングとアプリケーション展開モデル(4.1・4.2)](#chapter2) +3. [アプリケーション実行環境の比較:VM・ベアメタル・コンテナ(4.3)](#chapter3) +4. [CI/CDパイプラインの基礎(4.4)](#chapter4) +5. [Pythonユニットテストの構築(4.5)](#chapter5) +6. [Dockerfileの読み方とDockerイメージの活用(4.6・4.7)](#chapter6) +7. [アプリケーションセキュリティの基礎:シークレット保護・暗号化・データ取り扱い(4.8)](#chapter7) +8. [ネットワーク境界のセキュリティ要素:ファイアウォール・DNS・ロードバランサー・リバースプロキシ(4.9)](#chapter8) +9. [OWASPトップの脅威(4.10)](#chapter9) +10. [Bashコマンドの活用(4.11)](#chapter10) +11. [DevOpsの原則(4.12)](#chapter11) +12. [まとめ:ドメイン4.0 早見表](#chapter12) +13. [参考文献・出典](#references) + +--- + + +## 第1章 出題範囲の全体像 + +CCNA Automation認定を取得するには、120分の試験「200-901 CCNAAUTO」に合格する必要があります。この試験は6つのドメインで構成されており、それぞれに配点比率が設定されています。 + +| ドメイン番号 | ドメイン名 | 配点比率 | +|---|---|---| +| 1.0 | Software Development and Design(ソフトウェア開発と設計) | 15% | +| 2.0 | Understanding and Using APIs(APIの理解と利用) | 20% | +| 3.0 | Cisco Platforms and Development(Ciscoプラットフォームと開発) | 15% | +| **4.0** | **Application Deployment and Security(アプリケーションの展開とセキュリティ)** | **15%** | +| 5.0 | Infrastructure and Automation(インフラストラクチャと自動化) | 20% | +| 6.0 | Network Fundamentals(ネットワークの基礎) | 15% | + +本ガイドが扱うドメイン4.0は、**「作ったアプリケーションをどこに・どうやって・安全に動かすか」**という、開発から運用への橋渡しにあたる領域です。具体的には以下の12個のサブトピックで構成されます。 + +| サブトピック | 内容 | +|---|---| +| 4.1 | エッジコンピューティングの利点を説明する | +| 4.2 | 異なるアプリケーション展開モデル(プライベートクラウド、パブリッククラウド、ハイブリッドクラウド、エッジ)の属性を説明する | +| 4.3 | 展開タイプ(仮想マシン/ベアメタル/コンテナ)の属性を説明する | +| 4.4 | アプリケーション展開におけるCI/CDパイプラインの構成要素を説明する | +| 4.5 | Pythonのユニットテストを構築する | +| 4.6 | Dockerfileの内容を解釈する | +| 4.7 | ローカル開発環境でDockerイメージを利用する | +| 4.8 | シークレット保護、暗号化(保存時・転送時)、データ取り扱いに関するアプリケーションセキュリティの課題を説明する | +| 4.9 | ファイアウォール、DNS、ロードバランサー、リバースプロキシのアプリケーション展開における役割を説明する | +| 4.10 | OWASPのトップ脅威(XSS、SQLインジェクション、CSRFなど)を説明する | +| 4.11 | Bashコマンド(ファイル管理、ディレクトリ操作、環境変数)を利用する | +| 4.12 | DevOpsプラクティスの原則を説明する | + +このドメイン全体の位置づけを図にすると次のとおりです。 + +```mermaid +flowchart TB + EXAM["200-901 CCNAAUTO 試験(120分)"] + D1["1.0 Software Development and Design (15%)"] + D2["2.0 Understanding and Using APIs (20%)"] + D3["3.0 Cisco Platforms and Development (15%)"] + D4["4.0 Application Deployment and Security (15%)"] + D5["5.0 Infrastructure and Automation (20%)"] + D6["6.0 Network Fundamentals (15%)"] + EXAM --> D1 + EXAM --> D2 + EXAM --> D3 + EXAM --> D4 + EXAM --> D5 + EXAM --> D6 + D1 ~~~ D2 ~~~ D3 ~~~ D4 ~~~ D5 ~~~ D6 + style D4 fill:#ffe08a,stroke:#d99a00,stroke-width:2px,color:#241a00 +``` + +**この章のポイント** +- ドメイン4.0は試験全体の15%を占め、「開発したアプリをどこでどう動かし、どう守るか」を扱う。 +- 12個のサブトピックは、大きく「展開モデル・展開先の選定(4.1〜4.3)」「CI/CDと自動テスト(4.4・4.5)」「コンテナ実務(4.6・4.7)」「セキュリティ(4.8〜4.10)」「運用スキル(4.11・4.12)」の5グループに整理できる。 + +--- + + +## 第2章 エッジコンピューティングとアプリケーション展開モデル(4.1・4.2) + +### 4.1 エッジコンピューティングの利点 + +エッジコンピューティングとは、データを中央のクラウドやデータセンターまで送らず、**データが発生する場所(ネットワークの「端=エッジ」)に近い場所で処理する**という考え方です。工場のセンサー、店舗のPOSレジ、IoTデバイスなどが典型例です。 + +主な利点は次のとおりです。 + +| 利点 | 説明 | +|---|---| +| 低遅延(レイテンシ削減) | クラウドまでの往復通信が不要になり、リアルタイム性が向上する | +| 帯域幅の節約 | 現地で処理・要約してから必要なデータだけをクラウドに送るため、通信量を削減できる | +| オフライン耐性 | クラウドとの接続が一時的に切れても現地で処理を継続できる | +| データローカリティ/プライバシー | 機密データを現地にとどめたまま処理でき、規制対応がしやすい場合がある | + +### 4.2 アプリケーション展開モデルの比較 + +アプリケーションをどこで動かすかという「展開モデル」には、主に次の4種類があります。 + +| 展開モデル | 管理主体 | 主な特徴 | 典型的な用途 | +|---|---|---|---| +| プライベートクラウド | 自社(または委託先が自社専用に構築) | 自由度が高く、既存のセキュリティ・コンプライアンス要件に合わせやすいが、初期投資と運用負荷が大きい | 金融・医療など規制が厳しい業界の基幹システム | +| パブリッククラウド | クラウド事業者(AWS、Azure、Google Cloudなど) | 従量課金で始めやすく、拡張性が高いが、事業者のインフラに依存する | Webサービス、需要変動の大きいアプリケーション | +| ハイブリッドクラウド | 自社とクラウド事業者の組み合わせ | 機密データはプライベート側、負荷変動の大きい処理はパブリック側、というように使い分けられる | 既存の社内システムとクラウドサービスを連携させたい企業 | +| エッジ | 現地(店舗・工場・デバイス側) | クラウドに比べて計算資源は限られるが、低遅延処理が可能 | IoT、産業オートメーション、リアルタイム映像解析 | + +これらの関係をフローで示すと次のようになります。 + +```mermaid +flowchart TB + IoT["IoTデバイス / 店舗POS / センサー"] + EdgeNode["エッジサーバー(現地で低遅延処理)"] + Pub["パブリッククラウド"] + Priv["プライベートクラウド"] + Hyb["ハイブリッドクラウド構成"] + + IoT --> EdgeNode + EdgeNode -->|"集約データのみ送信"| Pub + EdgeNode -.->|"機密データは社内で保持"| Priv + Priv <--> Pub + Priv --- Hyb + Pub --- Hyb +``` + +**この章のポイント** +- エッジコンピューティングの利点は「低遅延・帯域節約・オフライン耐性・データローカリティ」の4つに集約できる。 +- 展開モデルは「誰が管理するか」「どこにデータ・計算資源があるか」で分類され、要件(コスト・拡張性・規制・遅延)に応じて選択する。 + +--- + + +## 第3章 アプリケーション実行環境の比較:VM・ベアメタル・コンテナ(4.3) + +展開モデル(どこで動かすか)が決まったら、次は「どの単位でアプリケーションをパッケージ化して動かすか」を選びます。試験ガイドでは以下の3タイプが挙げられています。 + +- 4.3.a 仮想マシン(Virtual Machine) +- 4.3.b ベアメタル(Bare Metal) +- 4.3.c コンテナ(Container) + +### 3つの実行環境の構造比較 + +```mermaid +flowchart TB + subgraph BareMetal["ベアメタル"] + direction TB + BM_App["アプリケーション"] + BM_OS["OS(ホストに直接インストール)"] + BM_HW["物理ハードウェア"] + BM_App --> BM_OS --> BM_HW + end + + subgraph VM["仮想マシン"] + direction TB + VM_App["アプリケーション"] + VM_GuestOS["ゲストOS(複数台分)"] + VM_Hyper["ハイパーバイザー"] + VM_HW["物理ハードウェア"] + VM_App --> VM_GuestOS --> VM_Hyper --> VM_HW + end + + subgraph Container["コンテナ"] + direction TB + C_App["アプリケーション"] + C_Engine["コンテナエンジン(Dockerなど)"] + C_OS["ホストOS(共有)"] + C_HW["物理ハードウェア"] + C_App --> C_Engine --> C_OS --> C_HW + end + + BareMetal ~~~ VM ~~~ Container +``` + +### 属性比較表 + +| 項目 | ベアメタル | 仮想マシン(VM) | コンテナ | +|---|---|---|---| +| 分離レベル | 物理サーバー単位(最も高い) | ハイパーバイザーによるハードウェアレベルの分離 | OSカーネルを共有するプロセスレベルの分離 | +| 起動速度 | サーバーの起動時間に依存(遅い) | 数十秒〜数分 | 数百ミリ秒〜数秒(非常に速い) | +| リソースオーバーヘッド | なし(すべての資源を占有) | 大きい(ゲストOSごとに必要) | 小さい(OSを共有するため軽量) | +| 移植性(ポータビリティ) | 低い(ハードウェアに強く依存) | 中程度(イメージ化して移動可能) | 高い(同じイメージがどこでも同じ動作) | +| 主なユースケース | 高いパフォーマンスが必須の基幹システム、レガシーアプリ | 複数OSの混在環境、既存VM資産の活用 | マイクロサービス、CI/CDでの高速な使い捨て環境 | + +**この章のポイント** +- 分離の強さと起動の速さはトレードオフの関係にある:ベアメタル・VMは分離が強いが重く、コンテナは軽量だがOSカーネルを共有する。 +- コンテナはCI/CDやマイクロサービスとの相性が良く、次章のCI/CDパイプラインとも密接に関係する。 + +--- + + +## 第4章 CI/CDパイプラインの基礎(4.4) + +CI/CD(Continuous Integration / Continuous Delivery(or Deployment)=継続的インテグレーション/継続的デリバリー(デプロイ))は、コードの変更を自動的にビルド・テスト・展開するための仕組みです。 + +### CI/CDパイプラインの主な構成要素 + +| ステージ | 目的 | +|---|---| +| ソース管理(Git等) | コードの変更履歴を管理し、変更をトリガーにパイプラインを起動する | +| ビルド | 依存関係の解決、コンパイル、静的解析などを行う | +| テスト | ユニットテスト・統合テストなどを自動実行し、品質を検証する | +| パッケージング/アーティファクト作成 | コンテナイメージなど、展開可能な成果物を作成する | +| レジストリへの登録 | 作成した成果物をイメージレジストリなどに格納する | +| ステージング展開 | 本番相当の環境に自動展開し、追加検証を行う | +| 本番展開 | 承認を経て本番環境に展開する | +| 監視・フィードバック | 稼働状況を監視し、問題や改善点を開発側にフィードバックする | + +### パイプライン全体の流れ + +```mermaid +flowchart TB + Dev["開発者がコードをコミット"] --> VCS["バージョン管理システム(Git)"] + VCS --> Build["ビルドステージ(依存関係解決・コンパイル)"] + Build --> Test["自動テストステージ(ユニット・統合テスト)"] + Test -->|"成功"| Package["アーティファクト作成(コンテナイメージ等)"] + Test -->|"失敗"| Dev + Package --> Registry["イメージ / パッケージレジストリ"] + Registry --> DeployStg["ステージング環境へ自動展開"] + DeployStg --> Approve["承認 / 追加テスト"] + Approve -->|"承認"| DeployProd["本番環境へ展開"] + Approve -->|"却下"| Dev + DeployProd --> Monitor["監視・フィードバック収集"] + Monitor -.->|"改善点を反映"| Dev +``` + +**この章のポイント** +- CI(継続的インテグレーション)は「頻繁に統合し、自動テストで早期に問題を検出する」こと、CD(継続的デリバリー/デプロイ)は「いつでも展開できる状態を保ち、実際に自動展開まで行う」ことを指す。 +- テスト失敗・承認却下時にフローが開発者へ戻る「フィードバックループ」がある点が重要。 + +--- + + +## 第5章 Pythonユニットテストの構築(4.5) + +ユニットテストとは、プログラムの中の「最小単位(関数やメソッド)」が期待どおりに動くかを自動で検証するテストです。Pythonでは標準ライブラリの`unittest`モジュールがよく使われます。 + +### テストの基本的な流れ(Arrange-Act-Assertパターン) + +```mermaid +flowchart TB + Start["テスト対象の関数を用意"] --> Arrange["Arrange: テストデータ・前提条件を準備"] + Arrange --> Act["Act: テスト対象の関数を実行"] + Act --> Assert["Assert: 期待した結果と実際の結果を比較"] + Assert -->|"一致"| Pass["テスト成功(PASS)"] + Assert -->|"不一致"| Fail["テスト失敗(FAIL) 原因を調査"] +``` + +### コード例 + +```python +import unittest + +def add(a, b): + """2つの数値を加算する簡単な関数""" + return a + b + +class TestAddFunction(unittest.TestCase): + + def test_add_positive_numbers(self): + # Arrange + a, b = 2, 3 + # Act + result = add(a, b) + # Assert + self.assertEqual(result, 5) + + def test_add_negative_numbers(self): + result = add(-1, -1) + self.assertEqual(result, -2) + +if __name__ == "__main__": + unittest.main() +``` + +`assertEqual`のほかにも、`assertTrue`(真偽値の検証)、`assertRaises`(例外発生の検証)などがよく使われます。ユニットテストは第1章の「テスト駆動開発(TDD)」の考え方や、第4章のCI/CDパイプラインの「テストステージ」と直結しています。 + +**この章のポイント** +- ユニットテストは「準備(Arrange)→実行(Act)→検証(Assert)」の3ステップで考えると書きやすい。 +- CI/CDパイプラインでは、このユニットテストが自動テストステージの中核を担う。 + +--- + + +## 第6章 Dockerfileの読み方とDockerイメージの活用(4.6・4.7) + +### 4.6 Dockerfileの内容を解釈する + +Dockerfileは、Dockerイメージをどのように構築するかを記述したテキストファイルです。代表的な命令は以下のとおりです。 + +| 命令 | 役割 | +|---|---| +| `FROM` | ベースとなるイメージを指定する(例:`FROM python:3.12-slim`) | +| `WORKDIR` | コンテナ内の作業ディレクトリを設定する | +| `COPY` / `ADD` | ホスト側のファイルをイメージ内にコピーする | +| `RUN` | イメージ構築時にコマンドを実行する(パッケージのインストール等) | +| `ENV` | 環境変数を設定する | +| `EXPOSE` | コンテナが待ち受けるポート番号を明示する(実際の公開は`docker run -p`で行う) | +| `CMD` | コンテナ起動時のデフォルトの実行コマンドを指定する | +| `ENTRYPOINT` | コンテナを実行ファイルのように振る舞わせる際のエントリポイントを指定する | + +### サンプルDockerfile + +```dockerfile +# ベースイメージを指定 +FROM python:3.12-slim + +# 作業ディレクトリを作成・移動 +WORKDIR /app + +# 依存関係定義ファイルを先にコピーしてキャッシュを効かせる +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# アプリ本体をコピー +COPY . . + +# コンテナが待ち受けるポートを明示 +EXPOSE 8080 + +# コンテナ起動時に実行するコマンド +CMD ["python", "app.py"] +``` + +### 4.7 ローカル開発環境でのDockerイメージの利用 + +よく使うDockerコマンドは以下のとおりです。 + +| コマンド | 用途 | +|---|---| +| `docker build -t : .` | カレントディレクトリのDockerfileからイメージをビルドする | +| `docker images` | ローカルに存在するイメージの一覧を表示する | +| `docker run -d -p 8080:8080 :` | イメージからコンテナをバックグラウンドで起動し、ポートを公開する | +| `docker ps` / `docker ps -a` | 稼働中(または全て)のコンテナ一覧を表示する | +| `docker logs ` | コンテナのログを表示する | +| `docker exec -it bash` | 稼働中のコンテナ内でシェルを起動する | +| `docker stop` / `docker rm` | コンテナを停止・削除する | +| `docker push` / `docker pull` | イメージをレジストリへ送信/取得する | + +### イメージのライフサイクル + +```mermaid +flowchart TB + Dockerfile["Dockerfileを作成"] --> Build["docker buildでイメージを生成"] + Build --> LocalImage["ローカルイメージ(docker imagesで確認)"] + LocalImage --> Run["docker runでコンテナ起動"] + Run --> Container["稼働中のコンテナ(docker psで確認)"] + Container -->|"docker stop"| Stopped["停止したコンテナ"] + LocalImage -->|"docker push"| Registry["イメージレジストリ(Docker Hub等)"] + Registry -->|"docker pull"| LocalImage +``` + +**この章のポイント** +- Dockerfileは「上から順に1行ずつ実行される構築手順書」として読むと理解しやすい。 +- `COPY requirements.txt`を`COPY . .`より先に行うのは、依存関係が変わらない限りビルドキャッシュを再利用して高速化するための定石。 + +--- + + +## 第7章 アプリケーションセキュリティの基礎:シークレット保護・暗号化・データ取り扱い(4.8) + +### シークレット保護 + +「シークレット」とは、パスワード、APIキー、証明書の秘密鍵など、漏洩すると重大な影響が出る機密情報を指します。基本原則は次のとおりです。 + +- **コードやDockerイメージにシークレットを直接書き込まない**(ハードコードしない) +- 環境変数、シークレット管理サービス(Vaultなど)、クラウドのシークレットマネージャーを利用する +- Gitリポジトリに誤ってコミットしないよう`.gitignore`や事前スキャンツールを活用する + +### 暗号化(保存時・転送時) + +| 種類 | 説明 | 代表例 | +|---|---|---| +| 転送時の暗号化(Encryption in Transit) | ネットワークを流れるデータを暗号化し、盗聴・改ざんを防ぐ | TLS/HTTPS、SSH | +| 保存時の暗号化(Encryption at Rest) | ディスクやデータベースに保存されているデータを暗号化する | ディスク暗号化、データベースの列暗号化 | + +### データ取り扱いの考え方 + +個人情報や機密データを扱う際は、「必要最小限のデータのみ収集・保持する」「アクセス権を最小限にする」「保持期間を定めて不要になったら削除する」といった原則が重要です。 + +### セキュアなデータフローのイメージ + +```mermaid +flowchart TB + Client["クライアント(ブラウザ / アプリ)"] -->|"HTTPS(TLSで暗号化)"| LB["ロードバランサー"] + LB -->|"内部ネットワーク"| App["アプリケーションサーバー"] + App -->|"シークレットを取得(コードに直書きしない)"| Vault["シークレット管理(Vault等)"] + App -->|"暗号化して書き込み"| DB["データベース(保存時暗号化)"] + DB -->|"復号して返却"| App +``` + +**この章のポイント** +- 「シークレットはコードに書かない」「転送時と保存時の両方を暗号化する」の2点は特に頻出の観点。 +- 暗号化は「守る対象がどこにあるか(通信中か、保存中か)」で使う技術が異なる。 + +--- + + +## 第8章 ネットワーク境界のセキュリティ要素:ファイアウォール・DNS・ロードバランサー・リバースプロキシ(4.9) + +アプリケーションを展開する際、ユーザーからのリクエストは複数のネットワーク要素を経由します。それぞれの役割を理解することが重要です。 + +| 要素 | 役割 | +|---|---| +| DNS(Domain Name System) | ドメイン名(例:`example.com`)をIPアドレスに変換する名前解決を行う | +| ファイアウォール | 事前に定義したルールに基づき、許可された通信のみを通過させる | +| ロードバランサー | 複数のサーバーにリクエストを振り分け、負荷分散と可用性向上を実現する | +| リバースプロキシ | クライアントとサーバーの間に立ち、TLS終端・キャッシュ・経路制御・ヘッダー書き換えなどを行う | + +### リクエストが辿る経路 + +```mermaid +flowchart TB + User["ユーザー"] --> DNS["DNSで名前解決(ドメイン→IPアドレス)"] + DNS --> FW["ファイアウォール(不要な通信を遮断)"] + FW --> LB["ロードバランサー(複数サーバーへ振り分け)"] + LB --> RP["リバースプロキシ(TLS終端・キャッシュ・経路制御)"] + RP --> Srv1["アプリケーションサーバー1"] + RP --> Srv2["アプリケーションサーバー2"] +``` + +**この章のポイント** +- 「名前解決 → アクセス制御 → 負荷分散 → プロキシによる終端・制御 → アプリ本体」という順序でリクエストが処理される、という全体像を押さえる。 +- ロードバランサーとリバースプロキシは役割が重なることも多いが、試験では「負荷分散」と「TLS終端・経路制御」という観点の違いで整理すると覚えやすい。 + +--- + + +## 第9章 OWASPトップの脅威(4.10) + +OWASP(Open Worldwide Application Security Project)は、Webアプリケーションセキュリティに関する非営利のコミュニティで、代表的な脅威をまとめた「OWASP Top 10」を定期的に公開しています。 + +試験ガイドでは代表例として**XSS(クロスサイトスクリプティング)**、**SQLインジェクション**、**CSRF(クロスサイトリクエストフォージェリ)**が挙げられています。 + +### 代表的な脅威の説明 + +| 脅威 | 概要 | 典型的な対策 | +|---|---|---| +| XSS(クロスサイトスクリプティング) | ユーザーの入力値がそのままページに出力され、悪意あるスクリプトがブラウザ上で実行されてしまう | 出力時のエスケープ処理、入力値の検証、Content Security Policyの設定 | +| SQLインジェクション | ユーザーの入力値がSQL文にそのまま埋め込まれ、意図しないSQLが実行されてしまう | プレースホルダ/パラメータ化クエリの利用、入力値の検証、最小権限のDBアカウント | +| CSRF(クロスサイトリクエストフォージェリ) | 認証済みユーザーのブラウザを利用して、本人の意図しないリクエストを別サイトから送信させられる | CSRFトークンの検証、SameSite属性付きCookie、重要操作での再認証 | + +XSSとSQLインジェクションは「入力値や出力値の不十分な検証・処理」が主因となるのに対し、CSRFは「認証済みブラウザからのクロスサイト状態変更リクエストの信頼」に起因します。そのため、XSSには文脈に応じた出力エンコーディング、SQLインジェクションにはパラメータ化クエリ、CSRFにはCSRFトークン検証・Origin/Referer検証・SameSite Cookie属性による対策がそれぞれ不可欠です。 + +```mermaid +flowchart TB + Input["ユーザー入力(フォーム・URLパラメータ等)"] --> Validate{"入力を検証・サニタイズしているか?"} + Validate -->|"していない"| Risk1["SQLインジェクションのリスク(意図しないSQL文が実行される)"] + Validate -->|"していない"| Risk2["XSSのリスク(悪意あるスクリプトが実行される)"] + Validate -->|"している"| Safe["安全な処理へ"] + Session["認証済みユーザーのセッション"] --> CSRFCheck{"CSRFトークンを検証しているか?"} + CSRFCheck -->|"していない"| Risk3["CSRFのリスク(意図しないリクエストが実行される)"] + CSRFCheck -->|"している"| Safe +``` + +### 最新の公式リストとの関係 + +OWASP Top 10は数年ごとに改訂されており、2025年版(2021年版の後継、8回目の改訂版)が現時点の最新版です。参考として、2025年版の10カテゴリを紹介します。 + +| 順位 | カテゴリ名(英語) | +|---|---| +| A01 | Broken Access Control(アクセス制御の不備) | +| A02 | Security Misconfiguration(セキュリティ設定の不備) | +| A03 | Software Supply Chain Failures(ソフトウェアサプライチェーンの問題) | +| A04 | Cryptographic Failures(暗号化の不備) | +| A05 | Injection(インジェクション) | +| A06 | Insecure Design(安全でない設計) | +| A07 | Authentication Failures(認証の不備) | +| A08 | Software or Data Integrity Failures(ソフトウェア/データ整合性の不備) | +| A09 | Security Logging & Alerting Failures(セキュリティログ・アラートの不備) | +| A10 | Mishandling of Exceptional Conditions(例外的な状態の処理不備) | + +試験ガイドが例示するSQLインジェクションとXSSは、現行の2025年版では主に「A05 Injection」に含まれる代表的な攻撃パターンです。CSRFは2017年版以降、単独の最上位カテゴリとしては掲載されなくなりましたが、現在も広く知られた古典的な攻撃パターンであり、アクセス制御や認証まわりの対策(トークン検証・Cookie属性の設定など)と合わせて理解しておく価値があります。試験対策としては、まず試験ガイドが明示するXSS・SQLインジェクション・CSRFの3つの仕組みと対策を確実に押さえるのがよいでしょう。 + +**この章のポイント** +- XSSとSQLインジェクションは「入力値/出力値の不十分な検証・処理」が根本原因となるのに対し、CSRFは「認証済みブラウザからのリクエストの信頼」が根本原因となるため、CSRF対策はCSRFトークン検証やOrigin/Referer検証、SameSite Cookie属性による対策として明確に分けて理解する。 +- OWASP Top 10は定期的に改訂されるため、学習時は「攻撃の仕組みと対策の考え方」を理解することを優先し、順位や名称は最新の一次情報で確認する。 + +--- + + +## 第10章 Bashコマンドの活用(4.11) + +Linux環境での自動化スクリプトや運用作業では、Bashコマンドの基本操作が欠かせません。 + +### ファイル管理 + +| コマンド | 用途 | +|---|---| +| `ls -l` | ファイル・ディレクトリの一覧を詳細表示する | +| `cp src dst` | ファイルをコピーする | +| `mv src dst` | ファイルを移動・リネームする | +| `rm file` | ファイルを削除する(`-r`でディレクトリごと削除) | +| `chmod 755 file` | ファイルの権限を変更する | +| `cat file` | ファイルの内容を表示する | +| `grep "keyword" file` | ファイル内から文字列を検索する | + +### ディレクトリ操作 + +| コマンド | 用途 | +|---|---| +| `pwd` | 現在のディレクトリを表示する | +| `cd path` | ディレクトリを移動する | +| `mkdir dir` | ディレクトリを作成する | +| `rmdir dir` | 空のディレクトリを削除する | +| `find . -name "*.py"` | 条件に合うファイルを再帰的に検索する | + +### 環境変数 + +| コマンド | 用途 | +|---|---| +| `echo $HOME` | 環境変数の値を表示する | +| `export VAR=value` | 環境変数を設定する(現在のシェルとその子プロセスに反映) | +| `env` / `printenv` | 現在の環境変数一覧を表示する | +| `unset VAR` | 環境変数を削除する | + +**この章のポイント** +- 「ファイル管理」「ディレクトリ操作」「環境変数」の3分類で整理すると覚えやすい。 +- 自動化スクリプト(第5章のCI/CDや第11章のDevOps運用)では、これらのコマンドが組み合わさって使われる。 + +--- + + +## 第11章 DevOpsの原則(4.12) + +DevOpsとは、開発(Development)と運用(Operations)の壁をなくし、ソフトウェアを継続的に、かつ安全・迅速にリリースし続けるための文化・プラクティスです。 + +代表的な原則は次のように整理できます。 + +| 原則 | 説明 | +|---|---| +| 文化(Culture) | 開発チームと運用チームが責任を分断せず、協力してリリースに責任を持つ | +| 自動化(Automation) | ビルド・テスト・展開・監視といった繰り返し作業を自動化し、人的ミスと作業時間を減らす | +| 計測(Measurement / Lean) | リリース頻度、障害復旧時間、変更失敗率などの指標を計測し、継続的に改善する | +| 共有(Sharing) | ツール・知見・障害対応のノウハウをチーム間で共有する | + +これらは、第4章で扱ったCI/CDパイプラインを支える文化的・組織的な土台にあたります。DevOpsのプラクティスは、しばしば「計画→コーディング→ビルド→テスト→リリース→展開→運用→監視」という循環(ループ)として表現されます。 + +```mermaid +flowchart TB + Plan["Plan: 計画"] --> Code["Code: コーディング"] + Code --> Build["Build: ビルド"] + Build --> Test["Test: テスト"] + Test --> Release["Release: リリース準備"] + Release --> Deploy["Deploy: 展開"] + Deploy --> Operate["Operate: 運用"] + Operate --> Monitor["Monitor: 監視"] + Monitor -.->|"フィードバック"| Plan +``` + +**この章のポイント** +- DevOpsは単なるツールの導入ではなく、「文化・自動化・計測・共有」という考え方の集合体である。 +- このループが途切れず回り続けることこそが、継続的デリバリー(CD)の本質。 + +--- + + +## 第12章 まとめ:ドメイン4.0 早見表 + +試験直前の見直し用に、サブトピックごとの要点を1行にまとめました。 + +| No. | サブトピック | 一言でいうと | +|---|---|---| +| 4.1 | エッジコンピューティングの利点 | 低遅延・帯域節約・オフライン耐性・データローカリティ | +| 4.2 | 展開モデルの比較 | プライベート/パブリック/ハイブリッド/エッジを、管理主体とコスト・拡張性で選ぶ | +| 4.3 | VM・ベアメタル・コンテナ | 分離の強さと起動の速さはトレードオフ。コンテナは軽量・高速・高移植性 | +| 4.4 | CI/CDパイプライン | コミット→ビルド→テスト→パッケージ→展開→監視を自動化する一連の流れ | +| 4.5 | Pythonユニットテスト | Arrange(準備)→Act(実行)→Assert(検証)の3ステップ | +| 4.6 | Dockerfileの解釈 | `FROM`/`COPY`/`RUN`/`CMD`など、上から順に実行される構築手順書 | +| 4.7 | Dockerイメージの活用 | `build`→`images`→`run`→`ps`→`push`/`pull`のライフサイクル | +| 4.8 | アプリケーションセキュリティ | シークレットはコードに書かない/転送時と保存時の両方を暗号化する | +| 4.9 | ネットワーク境界の要素 | DNS→ファイアウォール→ロードバランサー→リバースプロキシの順で経由 | +| 4.10 | OWASPトップの脅威 | XSS・SQLi・CSRFはいずれも「入力・リクエストを検証しない」ことが原因 | +| 4.11 | Bashコマンド | ファイル管理/ディレクトリ操作/環境変数の3分類 | +| 4.12 | DevOpsの原則 | 文化・自動化・計測・共有、そして途切れないリリースループ | + +--- + + +## 参考文献・出典 + +本ガイドの内容は、以下の一次情報(Cisco公式・OWASP公式・各技術の公式ドキュメント)を根拠としています。最新情報は必ず一次情報でご確認ください。 + +- CCNA Automation Certification(Cisco公式・認定概要ページ) + https://www.cisco.com/site/us/en/learn/training-certifications/certifications/automation/ccna-automation/index.html +- CCNA Automation Exam and Training(Cisco公式・試験と学習ページ) + https://www.cisco.com/site/us/en/learn/training-certifications/certifications/automation/ccna-automation/exams-and-training.html +- 200-901 CCNAAUTO(Cisco公式・試験詳細ページ) + https://www.cisco.com/site/us/en/learn/training-certifications/exams/ccnaauto.html +- Automating Networks Using Cisco Platforms v1.1(200-901)Exam Topics(Cisco公式・出題範囲PDF、本ガイドのドメイン4.0の記載内容の一次ソース) + https://learningcontent.cisco.com/documents/marketing/exam-topics/200-901-CCNAAUTO_v.1.1.pdf +- CCNAAUTO Exam Topics and Study Guide(Cisco Learning Network) + https://learningnetwork.cisco.com/s/ccnaauto-exam-topics +- OWASP Top 10:2025(OWASP公式、第9章のOWASP脅威一覧の一次ソース) + https://owasp.org/Top10/2025/ +- Dockerfile reference(Docker公式ドキュメント、第6章のDockerfile命令一覧の一次ソース) + https://docs.docker.com/reference/dockerfile/ +- unittest — ユニットテストフレームワーク(Python公式ドキュメント、第5章のユニットテストの一次ソース) + https://docs.python.org/3/library/unittest.html + +--- + +*本ガイドは学習支援を目的とした非公式の解説資料です。試験の出題範囲・配点・内容は変更される可能性があるため、受験前に必ずCisco公式サイトの最新情報をご確認ください。* From 534f16c7586d405becfb60a6027eff4076e442f5 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 22:50:44 +0900 Subject: [PATCH 021/123] chore(docs): update MIGRATION_PROGRESS.md for CCNA automation app deployment security guide --- MIGRATION_PROGRESS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION_PROGRESS.md b/MIGRATION_PROGRESS.md index 068fc736e..bec32b4b4 100644 --- a/MIGRATION_PROGRESS.md +++ b/MIGRATION_PROGRESS.md @@ -29,8 +29,8 @@ HTMLファイルから Next.js / React コンポーネントへの移行作業 - [app/cisco/ccna/automation-application-deployment-security/constants.ts](app/cisco/ccna/automation-application-deployment-security/constants.ts) - [app/cisco/ccna/automation-application-deployment-security/page.css](app/cisco/ccna/automation-application-deployment-security/page.css) - [__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx](__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx) -- [archive/Cisco/html/Ccna-automation-application-deployment-security.html](archive/Cisco/html/Ccna-automation-application-deployment-security.html) -- [archive/Cisco/md/Ccna-automation-application-deployment-security.md](archive/Cisco/md/Ccna-automation-application-deployment-security.md) +- [archive/Cisco/html/ccna/Ccna-automation-application-deployment-security.html](archive/Cisco/html/ccna/Ccna-automation-application-deployment-security.html) +- [archive/Cisco/md/ccna/Ccna-automation-application-deployment-security.md](archive/Cisco/md/ccna/Ccna-automation-application-deployment-security.md) ## 2026-08-05: Cisco「CCIE Enterprise Infrastructure 認定 完全ガイド」移行 (完了) From 4b859e03e9956f28361bedcb41d7571007b91f31 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 23:56:23 +0900 Subject: [PATCH 022/123] fix(mermaid): memoize diagram components and fix natural scale sizing --- .../GriffinWordPressGkeGuide.tsx | 6 ++--- components/MermaidDiagram.tsx | 25 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/gcl/hands-on/griffin-wordpress-gke-guide/GriffinWordPressGkeGuide.tsx b/app/gcl/hands-on/griffin-wordpress-gke-guide/GriffinWordPressGkeGuide.tsx index 8a890d8a3..18741e972 100644 --- a/app/gcl/hands-on/griffin-wordpress-gke-guide/GriffinWordPressGkeGuide.tsx +++ b/app/gcl/hands-on/griffin-wordpress-gke-guide/GriffinWordPressGkeGuide.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { memo, useState, useEffect, useRef, useCallback } from 'react'; import { MermaidDiagram } from '@/components/MermaidDiagram'; import { NavBar } from './NavBar'; import { DIAGRAMS } from './constants'; @@ -12,7 +12,7 @@ import { DIAGRAMS } from './constants'; * @param label - The accessible label for the rendered diagram * @returns The rendered diagram, or `null` when the identifier is unknown */ -function Diagram({ id, label }: { id: string; label: string }) { +const Diagram = memo(function Diagram({ id, label }: { id: string; label: string }) { const chart = DIAGRAMS[id]; if (!chart) return null; return ( @@ -20,7 +20,7 @@ function Diagram({ id, label }: { id: string; label: string }) { ); -} +}); /** * Renders the Team Griffin Google Cloud infrastructure challenge-lab guide, including navigation and task instructions for configuring VPCs, Cloud SQL, GKE, WordPress, monitoring, and IAM. diff --git a/components/MermaidDiagram.tsx b/components/MermaidDiagram.tsx index 0f3642789..1ac76c916 100644 --- a/components/MermaidDiagram.tsx +++ b/components/MermaidDiagram.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useId, useRef, useState } from 'react'; +import { memo, useEffect, useId, useRef, useState } from 'react'; import { cn } from '@/lib/utils'; import styles from './MermaidDiagram.module.css'; import mermaid from 'mermaid'; @@ -105,12 +105,15 @@ export const applySvgFixups = ( svgEl.removeAttribute('width'); svgEl.removeAttribute('height'); svgEl.style.height = 'auto'; - svgEl.style.maxWidth = '100%'; svgEl.style.overflow = 'visible'; svgEl.style.marginBottom = '10px'; const viewBox = svgEl.getAttribute('viewBox'); - if (!viewBox) return; + if (!viewBox) { + // viewBox がない場合はコンテナに収まるよう max-width のフォールバックを設定して終了 + svgEl.style.maxWidth = '100%'; + return; + } const parts = viewBox.split(/\s+/).map(Number); if (parts.length !== 4 || !parts.every((n) => Number.isFinite(n))) return; @@ -122,12 +125,18 @@ export const applySvgFixups = ( const [x, y, w, h] = parts as [number, number, number, number]; // ⚠️ SKILL.md「SVG 幅の鉄則」: viewBox 由来の自然 px 幅 + maxWidth:100%。 // preserveNaturalScale は文字を1rem相当の自然倍率で見せたい図に個別指定する。 + // preserveNaturalScale=true のとき: mermaid の fontSize(16px=1rem) が実寸で見えるよう + // viewBox 幅が小さすぎる場合は最低 600px を確保してスケールアップする。 let targetWidth = w; - if (!preserveNaturalScale && w > 0 && w < 550) { + if (preserveNaturalScale && w > 0) { + targetWidth = Math.max(w, 600); + } else if (!preserveNaturalScale && w > 0 && w < 550) { targetWidth = Math.min(650, Math.max(Math.round(w * 1.35), 480)); } svgEl.style.width = `${targetWidth}px`; - svgEl.style.maxWidth = '100%'; + // preserveNaturalScale=true のとき max-width:none でコンテナ縮小に追従させない。 + // ラッパー(.mermaid-wrap)の overflow-x:auto が横スクロールを担う。 + svgEl.style.maxWidth = preserveNaturalScale ? 'none' : '100%'; svgEl.style.maxHeight = preserveNaturalScale ? 'none' : h > 550 ? '580px' : 'none'; svgEl.setAttribute('viewBox', `${x} ${y} ${w} ${h + extraHeight}`); }; @@ -138,7 +147,7 @@ export const applySvgFixups = ( * - SSR / 初回マウント前は DSL を `
` として見せてハイドレーションエラーを防ぐ。
  * - jsdom 等 `getBBox` が無い環境ではフォールバック表示(DSL の `
`)のまま描画しない。
  */
-export const MermaidDiagram: React.FC = ({
+export const MermaidDiagram: React.FC = memo(({
     chart,
     ariaLabel,
     className,
@@ -238,4 +247,6 @@ export const MermaidDiagram: React.FC = ({
             )}
         
     );
-};
+});
+
+MermaidDiagram.displayName = 'MermaidDiagram';

From f9b5d3d6da8503266db1df105d71a77e1ef035d1 Mon Sep 17 00:00:00 2001
From: myoshizumi 
Date: Sat, 8 Aug 2026 23:56:27 +0900
Subject: [PATCH 023/123] test(mermaid): update MermaidDiagram tests for
 minimum width and max-width none

---
 __tests__/components/MermaidDiagram.test.tsx | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/__tests__/components/MermaidDiagram.test.tsx b/__tests__/components/MermaidDiagram.test.tsx
index 179cbc0e5..397c617b2 100644
--- a/__tests__/components/MermaidDiagram.test.tsx
+++ b/__tests__/components/MermaidDiagram.test.tsx
@@ -108,13 +108,27 @@ describe('MermaidDiagram', () => {
             expect(svg.getAttribute('viewBox')).toBe('0 0 250 615');
         });
 
-        it('個別指定された図は自然倍率を維持して文字を拡大縮小しないこと', () => {
+        it('個別指定された図は自然倍率を維持し、viewBox幅が小さい場合は最低600pxに拡大すること', () => {
             const svg = makeSvg('0 0 250 600');
 
             applySvgFixups(svg, 'flowchart TD\nA-->B', true);
 
-            expect(svg.style.width).toBe('250px');
-            expect(svg.style.maxWidth).toBe('100%');
+            // viewBox 幅 250 < 600 のため、最低幅 600px に拡大される(文字が1rem=16px相当で見えるよう)
+            expect(svg.style.width).toBe('600px');
+            // preserveNaturalScale=true: コンテナ幅で縮小させないよう max-width:none を設定
+            expect(svg.style.maxWidth).toBe('none');
+            expect(svg.style.maxHeight).toBe('none');
+        });
+
+        it('個別指定された図でviewBox幅が十分大きい場合はそのままの幅を使用すること', () => {
+            const svg = makeSvg('0 0 800 600');
+
+            applySvgFixups(svg, 'flowchart TD\nA-->B', true);
+
+            // viewBox 幅 800 > 600 のため、そのまま 800px
+            expect(svg.style.width).toBe('800px');
+            // preserveNaturalScale=true: max-width:none でスクロール時縮小を防止
+            expect(svg.style.maxWidth).toBe('none');
             expect(svg.style.maxHeight).toBe('none');
         });
 

From 75204a504d08baf3587fe97c074e4bd71332f9ee Mon Sep 17 00:00:00 2001
From: myoshizumi 
Date: Sat, 8 Aug 2026 23:56:32 +0900
Subject: [PATCH 024/123] docs(skills): update fix-mermaid skill with
 memoization and natural scale rules

---
 .claude/skills/fix-mermaid/SKILL.md | 52 ++++++++++++++++++++++++-----
 1 file changed, 43 insertions(+), 9 deletions(-)

diff --git a/.claude/skills/fix-mermaid/SKILL.md b/.claude/skills/fix-mermaid/SKILL.md
index bfed90d8a..bf0632425 100644
--- a/.claude/skills/fix-mermaid/SKILL.md
+++ b/.claude/skills/fix-mermaid/SKILL.md
@@ -317,7 +317,28 @@ const DISPLAY = {
 
 ```
 
-自然倍率propのテストでは、`width === viewBox幅` かつ `maxHeight === 'none'` を検証する。既定動作のテストも残し、他ページへの波及を防ぐ。
+自然倍率propのテストでは、`width === viewBox幅` かつ `maxHeight === 'none'` かつ `maxWidth === 'none'` を検証する。既定動作のテストも残し、他ページへの波及を防ぐ。
+
+#### ⚠️ スクロール時の図解縮小・チカチカバグの防止(React.memo メモ化)
+
+`IntersectionObserver` 等によるスクロール位置の監視(`setActiveSection` 等)により、親コンポーネントがスクロールするたびに高頻度で再レンダリングされる。
+ダイアグラムラッパー(`Diagram` コンポーネント等)および `MermaidDiagram` がメモ化されていない場合、親の再レンダリングのたびに `dangerouslySetInnerHTML` や `useEffect` がトリガーされ、DOM に適用された `style.width` などのインラインスタイルがリセットされて「上下スクロール時に図が豆粒に縮小される」不具合が発生する。
+
+**【対策】**:
+1. `MermaidDiagram` および各ガイドページの `Diagram` コンポーネントを必ず `React.memo` でラップする。
+2. `preserveNaturalScale=true` が指定されている場合、`applySvgFixups` で `svgEl.style.maxWidth = 'none'` を設定し、コンテナ幅の変化に追従した自動縮小を防止する。 viewbox 幅が 600px 未満等の小さい図は `targetWidth = Math.max(w, 600)` で最低幅 600px を確保して文字の1rem表示を保証する。
+
+```tsx
+const Diagram = memo(function Diagram({ id, label }: { id: string; label: string }) {
+    const chart = DIAGRAMS[id];
+    if (!chart) return null;
+    return (
+        
+ +
+ ); +}); +``` #### 2. テスト環境(Vitest)での MermaidDiagram のモック化 @@ -378,7 +399,11 @@ mermaid.initialize({ **`innerHTML` 注入後の実 DOM 要素を直接操作**する(`apply_render_pipeline.mjs` も同方式)。React では `ref` + `svgStr` 依存の `useEffect` で、注入済み `` に対して後処理を適用する。 ```ts -const applySvgFixups = (svgEl: SVGSVGElement, chart: string): void => { +const applySvgFixups = ( + svgEl: SVGSVGElement, + chart: string, + preserveNaturalScale = false +): void => { svgEl.removeAttribute('width'); svgEl.removeAttribute('height'); svgEl.style.height = 'auto'; @@ -386,7 +411,10 @@ const applySvgFixups = (svgEl: SVGSVGElement, chart: string): void => { svgEl.style.marginBottom = '10px'; const viewBox = svgEl.getAttribute('viewBox'); - if (!viewBox) return; + if (!viewBox) { + svgEl.style.maxWidth = '100%'; + return; + } const parts = viewBox.split(/\s+/).map(Number); if (parts.length !== 4 || !parts.every((n) => Number.isFinite(n))) return; const trimmed = chart.trim(); @@ -394,12 +422,18 @@ const applySvgFixups = (svgEl: SVGSVGElement, chart: string): void => { trimmed.startsWith('sequenceDiagram') || trimmed.startsWith('stateDiagram'); const extraHeight = isSequenceOrState ? 110 : 15; const [x, y, w, h] = parts as [number, number, number, number]; - // ⚠️ SVG 幅の鉄則: viewBox 由来の自然 px 幅 + maxWidth:100% を使う。 - // width:'100%' は viewBox のみで intrinsic サイズを持たない SVG をコンテナ全幅へ - // 伸ばし、小さい flowchart LR 図を異常拡大させるため使わない。 - // width:${w}px + maxWidth:100% なら「親より広い図のみ縮小、小さい図は自然サイズ」となる。 - svgEl.style.width = `${w}px`; - svgEl.style.maxWidth = '100%'; + + let targetWidth = w; + if (preserveNaturalScale && w > 0) { + // preserveNaturalScale=true: 1rem(16px)文字が実寸で見えるよう、小さい図は最低600pxに拡大 + targetWidth = Math.max(w, 600); + } else if (!preserveNaturalScale && w > 0 && w < 550) { + targetWidth = Math.min(650, Math.max(Math.round(w * 1.35), 480)); + } + svgEl.style.width = `${targetWidth}px`; + // preserveNaturalScale=true のときは max-width:none でスクロール時・コンテナ幅変更時の縮小を防止 + svgEl.style.maxWidth = preserveNaturalScale ? 'none' : '100%'; + svgEl.style.maxHeight = preserveNaturalScale ? 'none' : h > 550 ? '580px' : 'none'; svgEl.setAttribute('viewBox', `${x} ${y} ${w} ${h + extraHeight}`); }; ``` From 341e76e2ff66eb1ed433a5e6403624d3afd1cee3 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 23:58:38 +0900 Subject: [PATCH 025/123] fix(mermaid): use justify-content safe center to prevent left clipping on overflow --- app/gcl/hands-on/griffin-wordpress-gke-guide/page.css | 2 +- components/MermaidDiagram.module.css | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css b/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css index a3d07ba3c..5603bcea4 100644 --- a/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css +++ b/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css @@ -443,7 +443,7 @@ padding: 20px; margin: 16px 0 24px 0; display: flex; - justify-content: center; + justify-content: safe center; overflow-x: auto; } diff --git a/components/MermaidDiagram.module.css b/components/MermaidDiagram.module.css index 9f2afcb47..dc76321cd 100644 --- a/components/MermaidDiagram.module.css +++ b/components/MermaidDiagram.module.css @@ -4,15 +4,13 @@ border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; - /* スクロールコンテナは外側の .diagram-wrap が担う。 - ここで overflow:auto にすると二重スクロールになりSVGが縮小される。 */ - overflow: visible; + overflow-x: auto; } .mermaidTarget { width: 100%; display: flex; - justify-content: center; + justify-content: safe center; } .mermaidTarget :global(svg) { From d410b3222f76334cc9ecd8b089d6c5326ed30f1e Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 8 Aug 2026 23:58:43 +0900 Subject: [PATCH 026/123] docs(skills): sync safe center rules in fix-mermaid skill --- .claude/skills/fix-mermaid/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.claude/skills/fix-mermaid/SKILL.md b/.claude/skills/fix-mermaid/SKILL.md index bf0632425..153deafea 100644 --- a/.claude/skills/fix-mermaid/SKILL.md +++ b/.claude/skills/fix-mermaid/SKILL.md @@ -225,11 +225,12 @@ mermaid.initialize({ ```css .mermaid-wrap { display: flex; - justify-content: center; + justify-content: safe center; /* 親幅を超える場合は flex-start(左詰め)として扱い左見切れを防ぐ */ + overflow-x: auto; } .mermaid { display: flex; - justify-content: center; + justify-content: safe center; width: 100%; } .mermaid svg { From 6ec7aa8181d2604b96f5f3d47a1c56a083aebe1e Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:01:35 +0900 Subject: [PATCH 027/123] fix(ui): expand main container to full width and ensure 1rem font size for diagrams --- .../GkePrivateClusterSecurityGuide.tsx | 6 +++--- .../gke-private-cluster-security-guide/page.css | 13 ++++++------- .../hands-on/griffin-wordpress-gke-guide/page.css | 4 +++- components/MermaidDiagram.module.css | 5 ++++- components/MermaidDiagram.tsx | 4 +++- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/gcl/hands-on/gke-private-cluster-security-guide/GkePrivateClusterSecurityGuide.tsx b/app/gcl/hands-on/gke-private-cluster-security-guide/GkePrivateClusterSecurityGuide.tsx index b887d7149..15441821e 100644 --- a/app/gcl/hands-on/gke-private-cluster-security-guide/GkePrivateClusterSecurityGuide.tsx +++ b/app/gcl/hands-on/gke-private-cluster-security-guide/GkePrivateClusterSecurityGuide.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, memo } from 'react'; import { MermaidDiagram } from '@/components/MermaidDiagram'; import { DIAGRAMS, type DiagramId } from './constants'; import { NavBar } from './NavBar'; @@ -12,7 +12,7 @@ import { NavBar } from './NavBar'; * @param label - Accessibility label for the diagram * @returns The rendered diagram container */ -function Diagram({ id, label }: { id: DiagramId; label: string }) { +const Diagram = memo(function Diagram({ id, label }: { id: DiagramId; label: string }) { const chart = DIAGRAMS[id]; return (
@@ -21,7 +21,7 @@ function Diagram({ id, label }: { id: DiagramId; label: string }) {
); -} +}); /** * Presents a walkthrough for securing and validating a GKE private cluster. diff --git a/app/gcl/hands-on/gke-private-cluster-security-guide/page.css b/app/gcl/hands-on/gke-private-cluster-security-guide/page.css index a86985d86..9fb92a095 100644 --- a/app/gcl/hands-on/gke-private-cluster-security-guide/page.css +++ b/app/gcl/hands-on/gke-private-cluster-security-guide/page.css @@ -90,11 +90,12 @@ color: #7c9eff; } -/* メインコンテンツ: サイドバーを除いた領域で読みやすい幅に配置 */ +/* メインコンテンツ: 横幅いっぱいに配置 */ .gke-security-guide-page .main { - margin: 0 auto; - padding: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 32px) 48px 120px; - max-width: 1120px; + margin: 0; + padding: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 32px) 32px 120px; + width: 100%; + max-width: 100%; box-sizing: border-box; } @@ -327,7 +328,7 @@ .gke-security-guide-page .mermaid-wrap { width: 100%; display: flex; - justify-content: center; + justify-content: safe center; background: var(--color-card, #0d1b2e); border: 1px solid #1c2d42; border-radius: var(--radius-lg, 12px); @@ -336,9 +337,7 @@ } .gke-security-guide-page .mermaid-wrap svg { - max-width: 100% !important; height: auto !important; - min-width: 600px; /* 文字サイズ 1rem (16px) を維持し極小潰れを防止 */ } .gke-security-guide-page ol, diff --git a/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css b/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css index 5603bcea4..deebc437f 100644 --- a/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css +++ b/app/gcl/hands-on/griffin-wordpress-gke-guide/page.css @@ -150,7 +150,9 @@ .griffin-wordpress-gke-guide-page .main { margin-left: 272px; - padding: 56px 64px 96px 64px; + padding: 56px 32px 96px 32px; + width: calc(100% - 272px); + max-width: 100%; } .griffin-wordpress-gke-guide-page .hero { diff --git a/components/MermaidDiagram.module.css b/components/MermaidDiagram.module.css index dc76321cd..6dfd3cb49 100644 --- a/components/MermaidDiagram.module.css +++ b/components/MermaidDiagram.module.css @@ -36,8 +36,11 @@ */ .mermaidTarget :global(foreignObject > div), .mermaidTarget :global(.nodeLabel), -.mermaidTarget :global(.edgeLabel) { +.mermaidTarget :global(.edgeLabel), +.mermaidTarget :global(text), +.mermaidTarget :global(tspan) { overflow: visible; + font-size: 1rem !important; } /* diff --git a/components/MermaidDiagram.tsx b/components/MermaidDiagram.tsx index 1ac76c916..c488d126e 100644 --- a/components/MermaidDiagram.tsx +++ b/components/MermaidDiagram.tsx @@ -129,7 +129,9 @@ export const applySvgFixups = ( // viewBox 幅が小さすぎる場合は最低 600px を確保してスケールアップする。 let targetWidth = w; if (preserveNaturalScale && w > 0) { - targetWidth = Math.max(w, 600); + // preserveNaturalScale=true: viewBox 由来の自然 px 幅 (1.0倍) をそのまま使い、 + // 拡大・縮小せず文字を 1rem (16px) 実寸サイズで正確に描画する + targetWidth = w; } else if (!preserveNaturalScale && w > 0 && w < 550) { targetWidth = Math.min(650, Math.max(Math.round(w * 1.35), 480)); } From 05a6cfc9317c9b8fd67c0e517400ec3e59868500 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:01:38 +0900 Subject: [PATCH 028/123] test(mermaid): update test assertion to maintain 1.0x natural width for 1rem text --- __tests__/components/MermaidDiagram.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/components/MermaidDiagram.test.tsx b/__tests__/components/MermaidDiagram.test.tsx index 397c617b2..45b0134ad 100644 --- a/__tests__/components/MermaidDiagram.test.tsx +++ b/__tests__/components/MermaidDiagram.test.tsx @@ -108,13 +108,13 @@ describe('MermaidDiagram', () => { expect(svg.getAttribute('viewBox')).toBe('0 0 250 615'); }); - it('個別指定された図は自然倍率を維持し、viewBox幅が小さい場合は最低600pxに拡大すること', () => { + it('個別指定された図は自然倍率(1.0倍)を維持し、文字が巨大化しないよう自然px幅を使用すること', () => { const svg = makeSvg('0 0 250 600'); applySvgFixups(svg, 'flowchart TD\nA-->B', true); - // viewBox 幅 250 < 600 のため、最低幅 600px に拡大される(文字が1rem=16px相当で見えるよう) - expect(svg.style.width).toBe('600px'); + // preserveNaturalScale=true: viewBox 由来の自然幅 250px をそのまま維持し 1rem 実寸で表示 + expect(svg.style.width).toBe('250px'); // preserveNaturalScale=true: コンテナ幅で縮小させないよう max-width:none を設定 expect(svg.style.maxWidth).toBe('none'); expect(svg.style.maxHeight).toBe('none'); From f6664b0a1a4f231d8917974ad2b535eab1a08564 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:01:41 +0900 Subject: [PATCH 029/123] docs(skills): update fix-mermaid skill with 1rem font size guarantee and full width rules --- .claude/skills/fix-mermaid/SKILL.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.claude/skills/fix-mermaid/SKILL.md b/.claude/skills/fix-mermaid/SKILL.md index 153deafea..885a779b3 100644 --- a/.claude/skills/fix-mermaid/SKILL.md +++ b/.claude/skills/fix-mermaid/SKILL.md @@ -426,8 +426,8 @@ const applySvgFixups = ( let targetWidth = w; if (preserveNaturalScale && w > 0) { - // preserveNaturalScale=true: 1rem(16px)文字が実寸で見えるよう、小さい図は最低600pxに拡大 - targetWidth = Math.max(w, 600); + // preserveNaturalScale=true: 1rem (16px) 文字サイズが実寸で見えるよう viewBox 由来の自然 px 幅 (1.0倍) を維持する + targetWidth = w; } else if (!preserveNaturalScale && w > 0 && w < 550) { targetWidth = Math.min(650, Math.max(Math.round(w * 1.35), 480)); } @@ -439,6 +439,21 @@ const applySvgFixups = ( }; ``` +### 文字サイズ 1rem (16px) の絶対担保 + +図解内のすべての文字要素(ノードラベル、エッジラベル、テキスト等)が確実に `1rem` で表示されるよう、CSS(`MermaidDiagram.module.css`)でスタイルを強制します。 + +```css +.mermaidTarget :global(foreignObject > div), +.mermaidTarget :global(.nodeLabel), +.mermaidTarget :global(.edgeLabel), +.mermaidTarget :global(text), +.mermaidTarget :global(tspan) { + overflow: visible; + font-size: 1rem !important; +} +``` + ### 文字色は「ノードラベル限定」で当てる(明背景×明文字の再発防止) 過去、全 SVG テキストへ `color/fill:#e6e9ee !important` を当てた結果、**エッジラベル・subgraph 見出し・シーケンス図 Note(明色背景)まで明色文字になり読めなくなる**「もぐら叩き」を繰り返した。`theme:'dark'` で背景色は適正化されるため、CSS で色を当てるのは**ノードラベル(`.node .nodeLabel`)に限定**する。エッジラベル / Note はテーマ任せ(暗背景+明文字)にする。 From 3e775b51258025ae9bdc1c336af5337126ec97a7 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:03:42 +0900 Subject: [PATCH 030/123] fix(ui): expand ccna automation software dev design page to full width --- .../CcnaSoftwareDevDesignGuide.tsx | 23 ++++--------------- .../page.css | 10 ++++---- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/app/cisco/ccna/automation-software-development-design/CcnaSoftwareDevDesignGuide.tsx b/app/cisco/ccna/automation-software-development-design/CcnaSoftwareDevDesignGuide.tsx index 2a4fbfcc2..7e661619b 100644 --- a/app/cisco/ccna/automation-software-development-design/CcnaSoftwareDevDesignGuide.tsx +++ b/app/cisco/ccna/automation-software-development-design/CcnaSoftwareDevDesignGuide.tsx @@ -1,24 +1,10 @@ 'use client'; +import React, { memo } from 'react'; import { MermaidDiagram } from '@/components/MermaidDiagram'; import { DIAGRAMS } from './constants'; import { NavBar } from './NavBar'; -const DIAGRAM_DISPLAY: Record = { - 'diag-0': { frameWidth: 760 }, - 'diag-1': { frameWidth: 1200 }, - 'diag-2': { frameWidth: 760 }, - 'diag-3': { frameWidth: 760 }, - 'diag-4': { frameWidth: 940 }, - 'diag-5': { frameWidth: 940 }, - 'diag-6': { frameWidth: 760 }, - 'diag-7': { frameWidth: 900 }, - 'diag-8': { frameWidth: 860 }, - 'diag-9': { frameWidth: 900 }, - 'diag-10': { frameWidth: 760 }, - 'diag-11': { frameWidth: 760 }, -}; - /** * Renders a Mermaid diagram with its accessibility label and configured display width. * @@ -26,20 +12,19 @@ const DIAGRAM_DISPLAY: Record = { * @param label - The accessible label for the diagram. * @returns The diagram element, or `null` when no diagram matches `id`. */ -function Diagram({ id, label }: { id: string; label: string }) { +const Diagram = memo(function Diagram({ id, label }: { id: string; label: string }) { const chart = DIAGRAMS[id]; if (!chart) return null; - const display = DIAGRAM_DISPLAY[id] ?? { frameWidth: 760 }; return (
); -} +}); /** * Renders the complete CCNA Automation guide to software development and design. diff --git a/app/cisco/ccna/automation-software-development-design/page.css b/app/cisco/ccna/automation-software-development-design/page.css index b63b53e0b..5be82b3d4 100644 --- a/app/cisco/ccna/automation-software-development-design/page.css +++ b/app/cisco/ccna/automation-software-development-design/page.css @@ -170,8 +170,8 @@ .ccna-software-dev-design-page .hero > *, .ccna-software-dev-design-page .section > * { - max-width: 1000px; - margin-inline: auto; + width: 100%; + max-width: 100%; } .ccna-software-dev-design-page .section { @@ -367,14 +367,16 @@ .ccna-software-dev-design-page .diagram-frame { margin: 24px auto 16px; display: flex; - justify-content: center; + justify-content: safe center; + overflow-x: auto; } .ccna-software-dev-design-page .mermaid-wrap { display: flex; - justify-content: center; + justify-content: safe center; width: 100%; margin: 0 auto; + overflow-x: auto; } .ccna-software-dev-design-page .mermaid-wrap > div { From 99fb861bdb5b7f8f44e27be5d7ce2975445d3abb Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:03:46 +0900 Subject: [PATCH 031/123] test(cisco): update test assertions for full width diagram containers --- .../page.test.tsx | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/__tests__/cisco/ccna/automation-software-development-design/page.test.tsx b/__tests__/cisco/ccna/automation-software-development-design/page.test.tsx index 49ca6c2ce..8d8c5bacf 100644 --- a/__tests__/cisco/ccna/automation-software-development-design/page.test.tsx +++ b/__tests__/cisco/ccna/automation-software-development-design/page.test.tsx @@ -76,22 +76,9 @@ describe('CcnaSoftwareDevDesignPage', () => { expect(diagrams).toHaveLength(12); diagrams.forEach((diagram) => expect(diagram).toHaveAttribute('data-natural-scale', 'true')); - const widths: Record = { - 'diag-0': '760px', - 'diag-1': '1200px', - 'diag-2': '760px', - 'diag-3': '760px', - 'diag-4': '940px', - 'diag-5': '940px', - 'diag-6': '760px', - 'diag-7': '900px', - 'diag-8': '860px', - 'diag-9': '900px', - 'diag-10': '760px', - 'diag-11': '760px', - }; - Object.entries(widths).forEach(([id, width]) => { - expect(container.querySelector(`[data-diagram-id="${id}"]`)).toHaveStyle({ maxWidth: width }); + const diagramIds = ['diag-0', 'diag-1', 'diag-2', 'diag-3', 'diag-4', 'diag-5', 'diag-6', 'diag-7', 'diag-8', 'diag-9', 'diag-10', 'diag-11']; + diagramIds.forEach((id) => { + expect(container.querySelector(`[data-diagram-id="${id}"]`)).toHaveStyle({ maxWidth: '100%' }); }); }); From 6957ad7ce9db8cce0bf26e67c86a91fbe09d322a Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:08:09 +0900 Subject: [PATCH 032/123] test(cisco): add failing tests for CCNA automation cisco platforms and development guide migration --- .../page.test.tsx | 130 ++++++++++++++++++ ...ation-cisco-platforms-and-development.html | 0 ...omation-cisco-platforms-and-development.md | 0 3 files changed, 130 insertions(+) create mode 100644 __tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx rename Ccna-automation-cisco-platforms-and-development.html => archive/Cisco/html/ccna/Ccna-automation-cisco-platforms-and-development.html (100%) rename Ccna-automation-cisco-platforms-and-development.md => archive/Cisco/md/ccna/Ccna-automation-cisco-platforms-and-development.md (100%) diff --git a/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx new file mode 100644 index 000000000..0eb97984b --- /dev/null +++ b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx @@ -0,0 +1,130 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import CcnaCiscoPlatformsDevelopmentPage from '@/app/cisco/ccna/automation-cisco-platforms-and-development/page'; + +// Mock MermaidDiagram to avoid dynamic import / browser execution issues in Vitest +vi.mock('@/components/MermaidDiagram', () => ({ + MermaidDiagram: ({ chart, ariaLabel, preserveNaturalScale }: { chart: string; ariaLabel?: string; preserveNaturalScale?: boolean }) => ( +
+ Mermaid Diagram Mock +
+ ), +})); + +describe('CcnaCiscoPlatformsDevelopmentPage', () => { + it('renders main heading and hero kicker correctly', () => { + render(); + + const mainHeading = screen.getByRole('heading', { + level: 1, + name: /Cisco Platforms and Development/i, + }); + expect(mainHeading).toBeInTheDocument(); + + expect(screen.getByText(/CCNA Automation 200-901 CCNAAUTO v1.1/i)).toBeInTheDocument(); + }); + + it('renders all 13 section headings correctly', () => { + render(); + + const sectionTitles = [ + 'はじめに', + 'CCNA Automation試験の全体像とこのドメインの位置づけ', + 'Cisco Platforms and Developmentドメインの全体マップ', + '3.1 Cisco SDKを使ったPythonスクリプトの構築', + '3.2〜3.5 Cisco製品プラットフォームとAPIの全体像', + '3.6 IOS XE / NX-OSのデバイスレベルAPIと動的インターフェース', + '3.7 シナリオに応じたDevNetリソースの選択', + '3.8 モデル駆動型プログラマビリティ(YANG / NETCONF / RESTCONF)', + '3.9 実践:APIドキュメントを基にしたコード構築', + '学習ロードマップ:ハンズオンの進め方', + '試験対策のポイントとよくある誤解', + 'まとめ', + '参考文献・出典一覧', + ]; + + sectionTitles.forEach((title) => { + expect( + screen.getByRole('heading', { level: 2, name: new RegExp(title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') }), + ).toBeInTheDocument(); + }); + }); + + it('renders subheadings correctly', () => { + render(); + + const subHeadings = [ + 'SDKとは何か、なぜ使うのか', + '基本的なワークフロー', + 'コード例:Meraki SDKで組織一覧を取得する', + '3.2 ネットワーク管理プラットフォームとAPI', + '3.3 コンピュート管理プラットフォームとAPI', + '3.4 コラボレーションプラットフォームとAPI', + '3.5 セキュリティプラットフォームとAPI', + 'なぜ「モデル駆動」なのか', + 'NETCONFとRESTCONFの比較', + '3.9.a:Meraki APIでネットワークデバイス一覧を取得する', + '3.9.b:Webex APIでスペース・参加者・メッセージを管理する', + ]; + + subHeadings.forEach((subTitle) => { + expect( + screen.getByRole('heading', { level: 3, name: new RegExp(subTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') }), + ).toBeInTheDocument(); + }); + }); + + it('renders structured tables content correctly', () => { + render(); + + // Domain table + expect(screen.getByText('Software Development and Design')).toBeInTheDocument(); + expect(screen.getByText('Understanding and Using APIs')).toBeInTheDocument(); + + // Platform overview table + expect(screen.getByText(/3.2 ネットワーク管理/i)).toBeInTheDocument(); + expect(screen.getByText(/Meraki, Cisco DNA Center, ACI, Cisco SD-WAN, NSO/i)).toBeInTheDocument(); + + // Security table + expect(screen.getByText('Secure Endpoint')).toBeInTheDocument(); + expect(screen.getByText('Secure Malware Analytics')).toBeInTheDocument(); + + // NETCONF vs RESTCONF table + expect(screen.getByText('SSH(既定ポート830)')).toBeInTheDocument(); + expect(screen.getByText('HTTPS(既定ポート443)')).toBeInTheDocument(); + }); + + it('renders sidebar navigation links correctly', () => { + const { container } = render(); + + const tocNav = screen.getByRole('navigation', { name: /目次ナビゲーション/i }); + expect(tocNav).toBeInTheDocument(); + + const aside = container.querySelector('aside'); + expect(aside).toHaveClass('sidebar'); + }); + + it('renders all 10 Mermaid diagrams with ariaLabels and preserveNaturalScale', () => { + render(); + + const diagrams = screen.getAllByTestId('mermaid-diagram'); + expect(diagrams).toHaveLength(10); + + diagrams.forEach((diagram) => { + expect(diagram.getAttribute('aria-label')).toBeTruthy(); + expect(diagram.getAttribute('data-natural-scale')).toBe('true'); + }); + }); + + it('renders reference links correctly', () => { + render(); + + const refLinks = screen.getAllByRole('link', { name: /^https:\/\//i }); + expect(refLinks.length).toBeGreaterThanOrEqual(16); + }); +}); diff --git a/Ccna-automation-cisco-platforms-and-development.html b/archive/Cisco/html/ccna/Ccna-automation-cisco-platforms-and-development.html similarity index 100% rename from Ccna-automation-cisco-platforms-and-development.html rename to archive/Cisco/html/ccna/Ccna-automation-cisco-platforms-and-development.html diff --git a/Ccna-automation-cisco-platforms-and-development.md b/archive/Cisco/md/ccna/Ccna-automation-cisco-platforms-and-development.md similarity index 100% rename from Ccna-automation-cisco-platforms-and-development.md rename to archive/Cisco/md/ccna/Ccna-automation-cisco-platforms-and-development.md From b0c54c168da7ef844a3915b89961aafa02af8137 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:10:03 +0900 Subject: [PATCH 033/123] feat(cisco): implement CCNA automation cisco platforms and development guide to pass tests --- .../page.test.tsx | 4 +- .../CcnaCiscoPlatformsDevelopmentGuide.tsx | 840 ++++++++++++++++++ .../NavBar.tsx | 65 ++ .../constants.ts | 118 +++ .../page.css | 315 +++++++ .../page.tsx | 12 + 6 files changed, 1352 insertions(+), 2 deletions(-) create mode 100644 app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx create mode 100644 app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx create mode 100644 app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts create mode 100644 app/cisco/ccna/automation-cisco-platforms-and-development/page.css create mode 100644 app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx diff --git a/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx index 0eb97984b..f53847437 100644 --- a/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx +++ b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx @@ -87,8 +87,8 @@ describe('CcnaCiscoPlatformsDevelopmentPage', () => { expect(screen.getByText('Understanding and Using APIs')).toBeInTheDocument(); // Platform overview table - expect(screen.getByText(/3.2 ネットワーク管理/i)).toBeInTheDocument(); - expect(screen.getByText(/Meraki, Cisco DNA Center, ACI, Cisco SD-WAN, NSO/i)).toBeInTheDocument(); + expect(screen.getAllByText(/3.2 ネットワーク管理/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/Meraki, Cisco DNA Center, ACI, Cisco SD-WAN, NSO/i)[0]).toBeInTheDocument(); // Security table expect(screen.getByText('Secure Endpoint')).toBeInTheDocument(); diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx b/app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx new file mode 100644 index 000000000..defc4aab3 --- /dev/null +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx @@ -0,0 +1,840 @@ +'use client'; + +import { memo } from 'react'; +import { MermaidDiagram } from '@/components/MermaidDiagram'; +import { NavBar } from './NavBar'; +import { DIAGRAMS } from './constants'; + +const Diagram = memo(function Diagram({ id, label }: { id: string; label: string }) { + const chart = DIAGRAMS[id]; + if (!chart) return null; + return ( +
+
+ +
+
+ ); +}); + +export function CcnaCiscoPlatformsDevelopmentGuide() { + return ( +
+
+ + +
+
+ CCNA Automation 200-901 CCNAAUTO v1.1 +

Cisco Platforms and Development
徹底解説ガイド

+

+ CCNA Automation認定試験(旧称:Cisco Certified DevNet Associate)の6ドメインのうち、「3.0 Cisco Platforms and Development」(配点15%)を初学者向けにステップバイステップで解説します。 + 図解はすべてMermaid、表はすべてMarkdown相当の構造化テーブルで構成し、ASCIIアートは一切使用していません。 +

+
+
試験コード200-901 CCNAAUTO v1.1
+
試験時間120分
+
受験言語英語 / 日本語
+
受験料US$300(Cisco Learning Credits可)
+
+
+ 本ガイドについて: 2026年2月3日より、本試験は旧称「DevNet Associate(DEVASC)」から「CCNA Automation」へ名称変更されましたが、試験内容そのものは変更されていません。本ガイドは公式ブループリントに準拠した最新の製品名称(Secure Endpoint、XDRなど)を使用しつつ、旧名称(AMP、ThreatGridなど)にも適宜言及します。 +
+
+ + {/* ============ はじめに ============ */} +
+

はじめに

+
+

+ CCNA Automation認定は、Ciscoが提供する自動化・プログラマビリティ分野の入門レベル認定です。 + ネットワークエンジニアがソフトウェア開発のスキルを身につけ、逆にソフトウェア開発者がネットワークの基礎を理解するための「橋渡し」となる資格として位置づけられています。 +

+

+ このガイドで扱う「3.0 Cisco Platforms and Development」ドメインは、平たく言えば「Ciscoの各種製品(ネットワーク管理・コンピュート・コラボレーション・セキュリティ)を、それぞれのAPIを使ってプログラムから操作する方法を理解しているか」を問う分野です。 + 個々の製品の細かい操作方法を丸暗記するのではなく、「どの製品が何のためにあり、どんな種類のAPIを持っているか」を体系的に把握することが合格への近道になります。 +

+
+
+ + {/* ============ 試験全体像 ============ */} +
+

CCNA Automation試験の全体像とこのドメインの位置づけ

+
+

+ CCNA Automation認定を取得するには、単一の試験「Automating Networks Using Cisco Platforms(200-901 CCNAAUTO)v1.1」に合格する必要があります。 + 試験時間は120分、受験言語は英語と日本語に対応しています。試験は以下の6つのドメインで構成されており、それぞれに配点比率が定められています。 +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ドメイン番号ドメイン名(英語)配点比率
1.0Software Development and Design15%
2.0Understanding and Using APIs20%
3.0Cisco Platforms and Development15%
4.0Application Deployment and Security15%
5.0Infrastructure and Automation20%
6.0Network Fundamentals15%
+
+ + + +
+

+ 「2.0 Understanding and Using APIs」がAPIの一般的な仕組み(REST、HTTPメソッド、認証方式など)を扱うのに対し、 + 「3.0 Cisco Platforms and Development」は、その知識をCisco固有の製品に適用する力を問う点が違いです。両ドメインはセットで学習すると理解が深まります。 +

+
+ +
+ 💡 ポイント + Ciscoは認定試験のブループリントを定期的に見直しており、DevNet Associate(v1.0)からCCNA Automation(v1.1)への移行時にも、 + セキュリティ製品の名称更新(AMP→Secure Endpoint、ThreatGrid→Secure Malware Analytics、XDRの追加)など細かな改訂が行われています。 + 学習の際は必ず最新の公式ブループリントを確認してください。 +
+
+ + {/* ============ ドメイン全体マップ ============ */} +
+

Cisco Platforms and Developmentドメインの全体マップ

+
+

+ このドメインは、公式ブループリント上で以下の9つの学習項目(3.1〜3.9)に細分化されています。 +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
番号学習項目(要約)
3.1Cisco SDKのドキュメントを基にPythonスクリプトを構築する
3.2ネットワーク管理プラットフォームとAPIの機能を説明する(Meraki, Cisco DNA Center, ACI, Cisco SD-WAN, NSO)
3.3コンピュート管理プラットフォームとAPIの機能を説明する(UCS Manager, UCS Director, Intersight)
3.4コラボレーションプラットフォームとAPIの機能を説明する(Webex, Webex Devices, CUCM(AXL/UDS), Finesse)
3.5セキュリティプラットフォームとAPIの機能を説明する(XDR, Firepower, Umbrella, Secure Endpoint, ISE, Secure Malware Analytics)
3.6IOS XEおよびNX-OSのデバイスレベルAPIと動的インターフェースを説明する
3.7シナリオに応じて適切なDevNetリソースを特定する(Sandbox, Code Exchange, サポート, フォーラム, Learning Labs, APIドキュメント)
3.8モデル駆動型プログラマビリティの概念を適用する(YANG, RESTCONF, NETCONF)
3.9要件とAPIリファレンスドキュメントに基づき、特定の操作を行うコードを構築する
+
+ + + +
+

+ 前半(3.1〜3.6)が「各プラットフォームを知る」フェーズ、後半(3.7〜3.9)が「学んだ知識を使う」フェーズだとイメージすると理解しやすくなります。 +

+
+
+ + {/* ============ 3.1 ============ */} +
+

3.1 Cisco SDKを使ったPythonスクリプトの構築

+ +

SDKとは何か、なぜ使うのか

+
+

+ SDK(Software Development Kit)とは、APIを直接叩く(HTTPリクエストを自分で組み立てる)代わりに、 + あらかじめ用意された関数やクラスを呼び出すだけでAPI操作ができるようにしたライブラリです。 + 生のREST APIをrequestsライブラリで叩く場合と比較すると、次のようなメリットがあります。 +

+
    +
  • 認証ヘッダーの付与やURLの組み立てを自動化してくれる
  • +
  • レスポンスのJSONを、扱いやすいPythonオブジェクトとして受け取れる
  • +
  • エラー発生時に、わかりやすい例外(Exception)として通知してくれる
  • +
+
+ +

基本的なワークフロー

+ + +

コード例:Meraki SDKで組織一覧を取得する

+
+
import meraki
+
 
+
# APIキーはMerakiダッシュボードの
+
# Organization > Configure > API & Webhooks から発行する
+
API_KEY = "YOUR_API_KEY"
+
 
+
# SDKクライアントを初期化する
+
dashboard = meraki.DashboardAPI(API_KEY, suppress_logging=True)
+
 
+
# 所属する組織(Organization)の一覧を取得する
+
organizations = dashboard.organizations.getOrganizations()
+
 
+
for org in organizations:
+
    print(f"組織名: {org['name']} / ID: {org['id']}")
+
+ +
+

+ このように、Meraki公式のPython SDK(merakiパッケージ)を使うと、認証ヘッダーの組み立てやページネーション処理をSDKが肩代わりしてくれるため、 + 開発者は「何を取得したいか」に集中できます。試験では、SDKのドキュメント(メソッド名・引数・戻り値)を読んで、空欄になったコードを補完する形式の設問が出題される傾向があります。 +

+
+ +
+ 💡 試験対策 + SDKの内部実装を暗記する必要はありません。「このSDKはどの製品向けか」「認証にはどんな情報が必要か」「戻り値はどんな形(リスト/辞書)か」を、公式ドキュメントを見ながら読み解く練習をしておきましょう。 +
+
+ + {/* ============ 3.2-3.5 ============ */} +
+

3.2〜3.5 Cisco製品プラットフォームとAPIの全体像

+
+

+ 3.2から3.5までは、いずれも「特定のCisco製品群が、どんなAPIを持ち、何ができるかを説明する」という共通のパターンを持つ学習項目です。 + まずは全体像を俯瞰してから、各カテゴリーの詳細を見ていきましょう。 +

+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ドメイン代表的なCisco製品主なAPIの形式
3.2 ネットワーク管理Meraki, Cisco DNA Center, ACI, Cisco SD-WAN, NSOREST(Meraki Dashboard API、DNA Center Intent APIなど)
3.3 コンピュート管理UCS Manager, UCS Director, IntersightXML API(UCS Manager)、REST(UCS Director / Intersight)
3.4 コラボレーションWebex, Webex Devices, CUCM, FinesseREST(Webex API)、SOAP(CUCM AXL)、REST(CUCM UDS)
3.5 セキュリティXDR, Firepower, Umbrella, Secure Endpoint, ISE, Secure Malware AnalyticsREST(各製品ともにREST APIを提供)
+
+ +

3.2 ネットワーク管理プラットフォームとAPI

+
+

+ このカテゴリーは、複数のネットワークデバイスを一元的に管理する「コントローラー」製品群です。 + 試験対策として、まず押さえておきたいのはそれぞれの製品がどの領域を管理するかという役割分担です。 +

+
    +
  • Meraki:クラウド管理型のスイッチ・アクセスポイント・セキュリティアプライアンスを、Meraki Dashboard(クラウドUI)とそのREST APIから一元管理する。組織(Organization)> ネットワーク(Network)> デバイス(Device)という階層構造を持つ点が特徴。
  • +
  • Cisco DNA Center(現Catalyst Center):エンタープライズキャンパスネットワークのインテントベース管理コントローラー。「Intent API」と呼ばれるノースバウンドREST APIを通じて、ビジネス意図(例:新しいSSIDを展開したい)を宣言的に指定できる。
  • +
  • ACI(Application Centric Infrastructure):データセンターネットワークをポリシーベースで自動化するSDN基盤。APIC(Application Policy Infrastructure Controller)が中心的なコントローラーとなる。
  • +
  • Cisco SD-WAN:拠点間WANをソフトウェア定義で管理する製品群。vManageコントローラーがAPIの窓口となる。
  • +
  • NSO(Network Services Orchestrator):マルチベンダー環境でのサービス単位のオーケストレーションを担う製品。YANGモデルを核としたサービスパッケージという概念を持つ。
  • +
+
+ + + +

コード例:Cisco DNA Center(Catalyst Center)のIntent APIでデバイス一覧を取得する

+
+
import requests
+
 
+
DNAC = "https://sandboxdnac.cisco.com"
+
AUTH = ("devnetuser", "Cisco123!")
+
 
+
# 1. 認証トークンを取得する(Intent API)
+
resp = requests.post(f"{DNAC}/dna/system/api/v1/auth/token", auth=AUTH)
+
token = resp.json()["Token"]
+
 
+
# 2. 取得したトークンをヘッダーに設定してデバイス一覧を取得する
+
headers = {"X-Auth-Token": token}
+
devices = requests.get(
+
    f"{DNAC}/dna/intent/api/v1/network-device",
+
    headers=headers,
+
)
+
 
+
for device in devices.json()["response"]:
+
    print(device["hostname"], device["managementIpAddress"])
+
+ +

3.3 コンピュート管理プラットフォームとAPI

+
+

サーバー(コンピュート)リソースを管理するための製品群です。

+
    +
  • UCS Manager:シャーシに組み込まれたブレードサーバー群を管理する組み込み型の管理ソフトウェア。XMLベースのAPIを提供する。
  • +
  • UCS Director:データセンター全体のインフラ(コンピュート・ストレージ・ネットワーク)を横断的にオーケストレーションするプラットフォーム。REST APIを提供する。
  • +
  • Intersight:クラウドベースのインフラ管理プラットフォーム。API認証にAPIキー+秘密鍵によるHTTPリクエスト署名(signature)方式を採用しており、単純なAPIキー方式のMerakiやDNA Centerとは認証方式が異なる点が試験でも問われやすいポイントです。
  • +
+
+ +

3.4 コラボレーションプラットフォームとAPI

+
+
    +
  • Webex:チャット・ビデオ会議・通話などを提供するコラボレーションプラットフォーム。ブループリント上は歴史的経緯から「Webex Teams」と表記されることがありますが、製品としては「Webex」に統合されています。REST APIで、スペース(部屋)・メンバーシップ(参加者)・メッセージなどのリソースを操作します。
  • +
  • Webex Devices:Webex Room KitやDeskシリーズなどの物理デバイスを制御するxAPI(デバイス側のAPI)。
  • +
  • CUCM(Cisco Unified Communication Manager):企業向けのIP電話交換基盤。AXL(SOAPベースの管理系API、ユーザーや電話機の設定操作向け)と、UDS(RESTベースのユーザー向けAPI、ディレクトリ検索など)という2種類のインターフェースを持つ点が試験の頻出ポイントです。
  • +
  • Finesse:コンタクトセンター向けのエージェントデスクトップアプリケーションで、REST APIおよびJavaScript向けのAPIを提供する。
  • +
+
+ +

コード例:Webex APIでメッセージを送信する(webexpythonsdk/旧webexteamssdk)

+
+
from webexteamssdk import WebexTeamsAPI
+
 
+
# 環境変数 WEBEX_TEAMS_ACCESS_TOKEN からアクセストークンを自動読込
+
api = WebexTeamsAPI()
+
 
+
# 指定したスペース(部屋)にメッセージを送信する
+
api.messages.create(roomId="ROOM_ID_HERE", text="自動化スクリプトからの通知です")
+
+ +
+ 💡 豆知識 + かつてciscosparkapiという名前だったこのライブラリは、Webex Teamsへのブランド変更に伴いwebexteamssdkに改称され、 + 現在はさらにwebexpythonsdk(Python 3.10以降向け)へと移行が進んでいます。 試験のブループリント上の表記(Webex Teams)と、実際の開発現場での最新の呼称(Webex/webexpythonsdk)にはズレがあることを理解しておきましょう。 +
+ +

3.5 セキュリティプラットフォームとAPI

+
+

+ セキュリティ製品群です。v1.1のブループリントでは、下記のように一部の製品名が更新されています。 +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
旧名称(DEVASC v1.0)現行名称(CCNAAUTO v1.1)役割
-XDR(新規追加)複数のセキュリティ製品からのテレメトリを統合し、脅威検知・対応を自動化するプラットフォーム
FirepowerFirepower(変更なし)次世代ファイアウォール(NGFW)。FMC(管理センター)やFDM(デバイスマネージャー)経由でREST APIを提供
UmbrellaUmbrella(変更なし)クラウドベースのDNS/Webセキュリティサービス。Investigate APIやEnforcement APIを提供
AMPSecure Endpointエンドポイント(PCやサーバー)向けのマルウェア対策・EDR製品
ISEISE(変更なし)Identity Services Engine。ネットワークアクセス制御(NAC)を担う。ERS APIやpxGrid連携が代表的
ThreatGridSecure Malware Analyticsサンドボックス型のマルウェア解析プラットフォーム
+
+ +
+

+ このカテゴリーは「Describe(説明する)」レベルの知識が中心のため、コードを暗記するよりも、 + それぞれの製品が守る対象(ネットワーク境界/エンドポイント/DNS/ID)と役割の違いを整理しておくことが得点につながります。 +

+
+
+ + {/* ============ 3.6 ============ */} +
+

3.6 IOS XE / NX-OSのデバイスレベルAPIと動的インターフェース

+
+

+ 3.2〜3.5がコントローラー経由の管理であったのに対し、3.6ではデバイス単体(IOS XEを搭載したルーター/スイッチ、NX-OSを搭載したデータセンタースイッチ)に直接組み込まれたプログラマビリティ機能を扱います。 +

+
    +
  • NETCONF / RESTCONF:デバイス上で直接有効化できる、標準化されたモデル駆動型のプロトコル(詳細は次章で解説)。
  • +
  • gRPC / gNMI:ストリーミングテレメトリなど、より高頻度・低遅延なデータ収集に向く比較的新しいインターフェース。
  • +
  • NX-API:NX-OSデバイス向けのREST風API。CLIコマンドをそのままJSON/XML経由で実行できる点が特徴。
  • +
  • Guest Shell / On-box Python:デバイスのOS内に隔離されたLinuxコンテナ環境(Guest Shell)を用意し、その中でPythonスクリプトを直接実行できる「動的インターフェース」。デバイス自身にちょっとした自動化ロジックを持たせたい場合に使う。
  • +
  • EEM(Embedded Event Manager):デバイス内で発生したイベント(インターフェースダウンなど)をトリガーに、あらかじめ登録したアクション(Tclスクリプトなど)を自動実行する仕組み。
  • +
+
+ +

コード例:NETCONFでIOS XEデバイスの設定を取得する(ncclientライブラリ)

+
+
from ncclient import manager
+
 
+
# DevNet常設Sandbox(IOS XE on CSR)に接続する
+
with manager.connect(
+
    host="ios-xe-mgmt.cisco.com",
+
    port=10000,
+
    username="developer",
+
    password="C1sco12345",
+
) as m:
+
    # YANGフィルタを使ってinterface設定のみを取得する
+
    filter_xml = """
+
    <filter>
+
      <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
+
        <interface/>
+
      </native>
+
    </filter>
+
    """
+
    result = m.get_config(source="running", filter=filter_xml)
+
    print(result)
+
+ +
+ 💡 試験対策 + 「コントローラー経由(3.2〜3.5)」と「デバイス直接(3.6)」の違いを混同しないようにしましょう。同じNETCONF/RESTCONFという技術要素でも、3.6ではデバイス単体への適用、3.8ではその背景にあるモデル駆動の考え方そのものが問われます。 +
+
+ + {/* ============ 3.7 ============ */} +
+

3.7 シナリオに応じたDevNetリソースの選択

+
+

+ Cisco DevNetは、開発者向けに複数の学習・検証リソースを無償で提供しています。 + 試験では「このような状況で、あなたはどのリソースを使うべきか」という選択式の設問が出題されます。 +

+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
リソース主な用途
DevNet Sandbox実機相当の仮想環境を無料で予約・常設利用し、APIコールやコードの動作確認を行う(Always-On型/Reservation型の2種類がある)
Code ExchangeCiscoおよびコミュニティが公開するサンプルコード・SDK・Ansible/Terraformコンテンツを検索・参照する
API Documentation各プラットフォームのエンドポイント仕様・パラメータ・認証方式を確認する一次情報源
Support / Community Forums技術的な質問や不具合の相談、他の開発者との情報交換を行う場
Learning Labsステップバイステップで体系的に学べる学習コンテンツ(試験対策にも活用しやすい)
+
+ +
+ 💡 豆知識 + DevNet SandboxにはAlways-On(予約不要・常時稼働・共有環境)とReservation(事前予約制・自分専用の隔離環境)の2種類があります。管理者権限が必要な検証を行いたい場合はReservation型を選ぶ必要がある、という違いも押さえておきましょう。 +
+
+ + {/* ============ 3.8 ============ */} +
+

3.8 モデル駆動型プログラマビリティ(YANG / NETCONF / RESTCONF)

+ +

なぜ「モデル駆動」なのか

+
+

+ 従来、ネットワーク機器の設定はCLI(コマンドラインインターフェース)を通じて行われてきました。しかしCLIの出力形式はベンダーやOSバージョンによってまちまちで、プログラムから解析するのは非常に手間がかかります。 + そこで登場したのが、設定項目の構造とデータ型をあらかじめ「モデル」として定義しておき、そのモデルに沿ってプログラムから安全に設定を読み書きするという考え方です。この中核を担うのがYANGというデータモデリング言語です。 +

+
+ + + +

NETCONFとRESTCONFの比較

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目NETCONFRESTCONF
トランスポートSSH(既定ポート830)HTTPS(既定ポート443)
データ形式XMLJSONまたはXML
操作モデルRPCベース(get-config、edit-configなど)HTTP動詞ベース(GET/POST/PUT/PATCH/DELETE)
データストアの扱いrunning/candidate/startupを明確に区別できる基本的にrunning相当のデータストアのみを対象とする
ベースとなるモデルYANGYANG
向いている用途トランザクション性を要する一括設定変更REST APIに慣れた開発者による単純なCRUD操作
+
+ +

コード例:RESTCONFでIOS XEデバイスのインターフェース一覧を取得する

+
+
import requests
+
 
+
# DevNet常設Sandbox(IOS XE on CSR)のRESTCONFエンドポイント
+
url = "https://ios-xe-mgmt.cisco.com:9443/restconf/data/ietf-interfaces:interfaces"
+
headers = {"Accept": "application/yang-data+json"}
+
auth = ("developer", "C1sco12345")
+
 
+
resp = requests.get(url, headers=headers, auth=auth)
+
print(resp.json())
+
+ +
+ 💡 試験対策 + NETCONF・RESTCONFの「既定ポート」と「DevNet Sandboxで実際に使われるポート」は異なる場合があります(例:常設Sandboxでは踏み台の都合上、NETCONFが10000番、RESTCONFが9443番になっていることがある)。 + 試験では一般的な既定値(NETCONF=830、RESTCONF=443)で問われることが多いため、まずは標準ポートを正確に覚え、その上で実機演習時の差異は「環境固有の設定」として区別して理解しましょう。 +
+
+ + {/* ============ 3.9 ============ */} +
+

3.9 実践:APIドキュメントを基にしたコード構築

+
+

+ 3.9は、これまで学んだ知識を統合し、「与えられたAPIリファレンスドキュメントを読んで、具体的な操作を行うコードを完成させる」実践的な項目です。 + 公式に例示されている代表的なシナリオは次の2つです。 +

+
    +
  • 3.9.a:Meraki、Cisco DNA Center、ACI、Cisco SD-WAN、NSOのいずれかを使って、ネットワークデバイスの一覧を取得する
  • +
  • 3.9.b:Webex(Webex Teams)でスペース・参加者・メッセージを管理する
  • +
+
+ +

3.9.a:Meraki APIでネットワークデバイス一覧を取得する

+
+

+ Merakiの管理構造は「組織(Organization)→ ネットワーク(Network)→ デバイス(Device)」という階層になっており、 + デバイス一覧を得るには、まず組織を取得し、その中のネットワークをたどってデバイスを収集するという流れになります。 +

+
+ + + +
+
import meraki
+
 
+
dashboard = meraki.DashboardAPI("YOUR_API_KEY", suppress_logging=True)
+
 
+
# 1. 組織一覧を取得する
+
orgs = dashboard.organizations.getOrganizations()
+
 
+
# 2. 各組織のネットワークをたどり、デバイスを収集する
+
for org in orgs:
+
    networks = dashboard.organizations.getOrganizationNetworks(org["id"])
+
    for net in networks:
+
        devices = dashboard.networks.getNetworkDevices(net["id"])
+
        for device in devices:
+
            print(f"{org['name']} / {net['name']} / {device.get('name', device['serial'])}")
+
+ +

3.9.b:Webex APIでスペース・参加者・メッセージを管理する

+ + + +
+
from webexteamssdk import WebexTeamsAPI
+
 
+
api = WebexTeamsAPI()
+
 
+
# 1. 新しいスペース(部屋)を作成する
+
room = api.rooms.create(title="自動化通知スペース")
+
 
+
# 2. 参加者(メンバー)を追加する
+
api.memberships.create(room.id, personEmail="teammate@example.com")
+
 
+
# 3. メッセージを送信する
+
api.messages.create(room.id, text="スペースの準備が整いました")
+
+ +
+

+ このように、3.9は単体の知識ではなく、「どのAPIをどの順番で呼び出せば目的を達成できるか」という設計力を問う項目です。 + 試験ではコードの一部が空欄になった穴埋め形式(ドラッグ&ドロップ)で出題される傾向があるため、単純な暗記ではなく「このAPI呼び出しの後には、論理的に何をする必要があるか」を考える練習をしておくとよいでしょう。 +

+
+
+ + {/* ============ 学習ロードマップ ============ */} +
+

学習ロードマップ:ハンズオンの進め方

+
+

+ 知識のインプットだけでなく、実際に手を動かすことがこのドメインの理解を大きく助けます。以下の順序でDevNet Sandboxを活用した学習を進めることをおすすめします。 +

+
+ + + +
    +
  1. Step 1:developer.cisco.comでDevNetアカウントを作成し、Sandboxカタログにアクセスできる状態にする。
  2. +
  3. Step 2:Meraki Always-On Sandboxを使い、組織・ネットワーク・デバイスの階層構造を実際のAPIレスポンスで確認する。
  4. +
  5. Step 3:Cisco DNA Center(Catalyst Center)Sandboxで認証トークンを取得し、Intent APIでデバイス一覧を取得する。
  6. +
  7. Step 4:自分のWebexアカウントでアクセストークンを発行し、スペース作成からメッセージ送信までを自動化するスクリプトを書く。
  8. +
  9. Step 5:IOS XE Always-On Sandboxに対して、ncclient(NETCONF)とrequests(RESTCONF)の両方でアクセスし、挙動の違いを体感する。
  10. +
  11. Step 6:Meraki+Webexなど複数のAPIを組み合わせ、「特定の条件を満たしたらWebexへ通知する」といった3.9形式の複合シナリオを自作してみる。
  12. +
+
+ + {/* ============ 試験対策のポイント ============ */} +
+

試験対策のポイントとよくある誤解

+
+
    +
  • 製品名の変更に注意する:AMP→Secure Endpoint、ThreatGrid→Secure Malware Analytics、DNA Center→Catalyst Centerなど、Ciscoは製品ブランドを頻繁に見直しています。ブループリント上の表記と、実際の開発現場での最新名称の両方を把握しておきましょう。
  • +
  • NETCONFとRESTCONFのポート番号を混同しない:一般的な既定値はNETCONF=830(SSH)、RESTCONF=443(HTTPS)です。DevNet Sandbox特有の代替ポート(例:10000番、9443番)は環境固有の設定であり、試験の一般知識としては標準ポートを優先して覚えましょう。
  • +
  • Meraki APIの階層構造を理解する:Organization(組織)> Network(ネットワーク)> Device(デバイス)という3階層をたどらないとデバイス情報にたどり着けない点は頻出のポイントです。
  • +
  • CUCMのAXLとUDSを混同しない:AXLは管理者向けのSOAP API(設定変更を伴う操作向け)、UDSはエンドユーザー向けのREST API(ディレクトリ検索など、比較的軽量な参照操作向け)という役割の違いを押さえましょう。
  • +
  • 「コントローラー経由」と「デバイス直接」を区別する:3.2〜3.5で学ぶプラットフォームは基本的に複数デバイスをまとめて管理するコントローラー層であり、3.6・3.8で学ぶNETCONF/RESTCONF/YANGはデバイス単体(またはコントローラーの背後にある技術基盤)に関する知識です。
  • +
  • DevNetリソースの使い分け:「試したい」ならSandbox、「コードが欲しい」ならCode Exchange、「仕様を調べたい」ならAPI Documentation、「相談したい」ならSupport/Forums、「体系的に学びたい」ならLearning Labs、という対応関係を整理しておくと選択式問題で迷いません。
  • +
+
+
+ + {/* ============ まとめ ============ */} +
+

まとめ

+
+

+ 「3.0 Cisco Platforms and Development」ドメインは、Cisco製品群を横断的に俯瞰する力を問う分野です。個々のAPIエンドポイントを丸暗記するのではなく、 +

+
    +
  1. その製品が何を管理するものか(ネットワーク/コンピュート/コラボレーション/セキュリティ/デバイス単体)
  2. +
  3. どんな種類のAPIを提供しているか(REST/SOAP/XML/NETCONF/RESTCONF)
  4. +
  5. どのDevNetリソースを使えば効率よく学べるか
  6. +
+

+ という3つの軸で整理しながら学習を進めることで、着実に得点力を伸ばすことができます。ぜひDevNet Sandboxを積極的に活用し、実際にAPIを呼び出しながら知識を定着させてください。 +

+
+
+ + {/* ============ 参考文献 ============ */} + +
+
+
+ ); +} diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx b/app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx new file mode 100644 index 000000000..225bbcd26 --- /dev/null +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +const NAV_ITEMS = [ + { id: 'intro', label: 'はじめに' }, + { id: 'overview', label: '試験全体像とドメインの位置づけ' }, + { id: 'map', label: 'ドメイン全体マップ' }, + { id: 's31', label: '3.1 SDKとPythonスクリプト' }, + { id: 'platforms', label: '3.2〜3.5 プラットフォーム全体像' }, + { id: 's36', label: '3.6 デバイスレベルAPI' }, + { id: 's37', label: '3.7 DevNetリソースの選択' }, + { id: 's38', label: '3.8 モデル駆動型プログラマビリティ' }, + { id: 's39', label: '3.9 実践コード構築' }, + { id: 'roadmap', label: '学習ロードマップ' }, + { id: 'tips', label: '試験対策のポイント' }, + { id: 'summary', label: 'まとめ' }, + { id: 'references', label: '参考文献・出典一覧' }, +]; + +export function NavBar() { + const [activeId, setActiveId] = useState('intro'); + + useEffect(() => { + if (typeof IntersectionObserver === 'undefined') return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }, + { rootMargin: '-20% 0px -70% 0px' } + ); + + const sections = document.querySelectorAll('.ccna-platforms-dev-page section[id], .ccna-platforms-dev-page footer[id]'); + sections.forEach((sec) => observer.observe(sec)); + + return () => observer.disconnect(); + }, []); + + return ( + + ); +} diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts b/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts new file mode 100644 index 000000000..5e6832ef4 --- /dev/null +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts @@ -0,0 +1,118 @@ +export const DIAGRAMS: Record = { + 'diag-overview': `flowchart TB +A["CCNA Automation
200-901 CCNAAUTO v1.1"] --> D1["1.0 Software Development
and Design (15%)"] +A --> D2["2.0 Understanding and
Using APIs (20%)"] +A --> D3["3.0 Cisco Platforms
and Development (15%)"] +A --> D4["4.0 Application Deployment
and Security (15%)"] +A --> D5["5.0 Infrastructure and
Automation (20%)"] +A --> D6["6.0 Network
Fundamentals (15%)"] +D1 ~~~ D2 ~~~ D3 ~~~ D4 ~~~ D5 ~~~ D6 +style D3 fill:#ffe08a,stroke:#d99a00,stroke-width:2px,color:#241a00`, + + 'diag-map': `flowchart TB +Start(["3.0 Cisco Platforms
and Development"]) --> T1["3.1 SDKで
Pythonスクリプトを構築"] +T1 --> T2["3.2 ネットワーク管理
プラットフォームAPI"] +T2 --> T3["3.3 コンピュート管理
プラットフォームAPI"] +T3 --> T4["3.4 コラボレーション
プラットフォームAPI"] +T4 --> T5["3.5 セキュリティ
プラットフォームAPI"] +T5 --> T6["3.6 IOS XE/NX-OS
デバイスレベルAPI"] +T6 --> T7["3.7 DevNetリソースの
活用"] +T7 --> T8["3.8 モデル駆動型
プログラマビリティ"] +T8 --> T9["3.9 実践:APIドキュメント
からコードを構築"]`, + + 'diag-workflow': `flowchart TB +S1["SDKをインストールする
例: pip install meraki"] --> S2["認証情報を準備する
APIキー / アクセストークン"] +S2 --> S3["SDKクライアントを初期化する"] +S3 --> S4["SDKのメソッドを呼び出す
例: dashboard.organizations.getOrganizations()"] +S4 --> S5{"エラーは
発生したか"} +S5 -->|"Yes"| S6["例外をキャッチして
ログに出力する"] +S5 -->|"No"| S7["取得したデータを
業務ロジックで利用する"]`, + + 'diag-platforms': `flowchart TB +subgraph NW["ネットワーク管理プラットフォーム 3.2"] +direction TB +N1["Meraki"] +N2["Cisco DNA Center
(現Catalyst Center)"] +N3["ACI"] +N4["Cisco SD-WAN"] +N5["NSO"] +N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5 +end + +subgraph CP["コンピュート管理プラットフォーム 3.3"] +direction TB +C1["UCS Manager"] +C2["UCS Director"] +C3["Intersight"] +C1 ~~~ C2 ~~~ C3 +end + +subgraph CL["コラボレーションプラットフォーム 3.4"] +direction TB +L1["Webex"] +L2["Webex Devices"] +L3["CUCM (AXL / UDS)"] +L4["Finesse"] +L1 ~~~ L2 ~~~ L3 ~~~ L4 +end + +subgraph SEC["セキュリティプラットフォーム 3.5"] +direction TB +S1["XDR"] +S2["Firepower"] +S3["Umbrella"] +S4["Secure Endpoint"] +S5["ISE"] +S6["Secure Malware Analytics"] +S1 ~~~ S2 ~~~ S3 ~~~ S4 ~~~ S5 ~~~ S6 +end + +NW ~~~ CP ~~~ CL ~~~ SEC`, + + 'diag-north-south': `flowchart TB +App["外部アプリケーション
自動化スクリプト"] -->|"Northbound API: REST"| Ctrl["コントローラー層
DNA Center / Meraki Dashboard
APIC / vManage / NSO"] +Ctrl -->|"Southbound: NETCONF・RESTCONF・CLIなど"| Dev["ネットワークデバイス群
スイッチ・ルーター・WLC"]`, + + 'diag-devnet': `flowchart TB +Q["利用シーン別
DevNetリソースの選び方"] --> R1["実機相当の環境で
動作を試したい
→ DevNet Sandbox"] +R1 --> R2["すぐに使える
サンプルコードが欲しい
→ Code Exchange"] +R2 --> R3["APIのエンドポイントや
認証方式を確認したい
→ API Documentation"] +R3 --> R4["技術的な疑問や
不具合を相談したい
→ Support / Community Forums"] +R4 --> R5["体系立てて
基礎から学びたい
→ Learning Labs"]`, + + 'diag-yang': `flowchart TB +Y["YANGモデル
設定項目の構造とデータ型を定義"] --> P{"どちらのプロトコルで
アクセスするか"} +P -->|"NETCONF"| N["XMLベース
SSH (既定ポート830) を利用
candidate/running設定を明確に区別"] +P -->|"RESTCONF"| R["HTTP(S) ベース
JSONまたはXML
GET/POST/PUT/PATCH/DELETEで操作"] +N --> D["ネットワークデバイスの
設定へ反映"] +R --> D`, + + 'diag-seq-meraki': `sequenceDiagram +participant Dev as 自動化スクリプト +participant API as Meraki Dashboard API + +Dev->>API: GET /organizations +API-->>Dev: 200 OK (organizations一覧) +Dev->>API: GET /organizations/{orgId}/networks +API-->>Dev: 200 OK (networks一覧) +Dev->>API: GET /networks/{networkId}/devices +API-->>Dev: 200 OK (devices一覧)`, + + 'diag-seq-webex': `sequenceDiagram +participant Bot as Webexボット/スクリプト +participant WebexAPI as Webex API + +Bot->>WebexAPI: POST /v1/rooms (スペースを作成) +WebexAPI-->>Bot: 200 OK (roomId) +Bot->>WebexAPI: POST /v1/memberships (参加者を追加) +WebexAPI-->>Bot: 200 OK +Bot->>WebexAPI: POST /v1/messages (メッセージを送信) +WebexAPI-->>Bot: 200 OK (messageId)`, + + 'diag-roadmap': `flowchart TB +L1["Step 1
DevNet Sandboxアカウントを準備する"] --> L2["Step 2
Meraki SandboxでREST APIの基本を体験する"] +L2 --> L3["Step 3
Cisco DNA Center (Catalyst Center)
SandboxでIntent APIを試す"] +L3 --> L4["Step 4
Webex APIでメッセージ送信を自動化する"] +L4 --> L5["Step 5
IOS XE SandboxでNETCONF/RESTCONFを試す"] +L5 --> L6["Step 6
3.9形式の複合シナリオに挑戦する"]`, +}; diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css new file mode 100644 index 000000000..27c1f3df4 --- /dev/null +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css @@ -0,0 +1,315 @@ +.ccna-platforms-dev-page { + min-height: 100vh; + background-color: var(--color-background); + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .layout { + display: flex; + min-height: 100vh; + position: relative; +} + +.ccna-platforms-dev-page .sidebar { + position: fixed; + left: 0; + top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px)); + bottom: 0; + width: 280px; + background-color: var(--color-card); + border-right: 1px solid var(--color-border, rgba(255, 255, 255, 0.1)); + padding: 1.5rem 1rem; + overflow-y: auto; + z-index: 40; + scrollbar-width: thin; +} + +.ccna-platforms-dev-page .brand { + display: block; + font-size: 1rem; + font-weight: 700; + color: var(--color-foreground); + margin-bottom: 0.25rem; +} + +.ccna-platforms-dev-page .brand-sub { + display: block; + font-size: 0.8rem; + color: var(--color-muted-foreground); + margin-bottom: 1.5rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.ccna-platforms-dev-page .sidebar nav ul { + list-style: none; + padding: 0; + margin: 0; +} + +.ccna-platforms-dev-page .sidebar nav li { + margin-bottom: 0.35rem; +} + +.ccna-platforms-dev-page .sidebar nav a { + display: block; + padding: 0.45rem 0.75rem; + color: var(--color-muted-foreground); + text-decoration: none; + font-size: 0.875rem; + border-radius: var(--radius-md, 6px); + transition: all 0.2s ease; +} + +.ccna-platforms-dev-page .sidebar nav a:hover, +.ccna-platforms-dev-page .sidebar nav a.active { + color: var(--color-foreground); + background-color: rgba(66, 133, 244, 0.15); + border-left: 3px solid var(--color-google-blue, #4285f4); +} + +.ccna-platforms-dev-page .content { + flex: 1; + margin-left: 280px; + padding: 2rem 2.5rem 4rem; + max-width: 1000px; +} + +.ccna-platforms-dev-page .hero { + background: linear-gradient(135deg, rgba(20, 35, 60, 0.8) 0%, rgba(10, 20, 35, 0.9) 100%); + border: 1px solid rgba(66, 133, 244, 0.25); + border-radius: var(--radius-lg, 12px); + padding: 2.5rem; + margin-bottom: 3rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.ccna-platforms-dev-page .kicker { + display: inline-block; + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-google-blue, #4285f4); + background: rgba(66, 133, 244, 0.12); + padding: 0.25rem 0.75rem; + border-radius: var(--radius-sm, 4px); + margin-bottom: 1rem; +} + +.ccna-platforms-dev-page .hero h1 { + font-size: 2.2rem; + font-weight: 800; + line-height: 1.3; + margin-bottom: 1rem; + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .lead { + font-size: 1.05rem; + line-height: 1.7; + color: var(--color-muted-foreground); + margin-bottom: 1.5rem; +} + +.ccna-platforms-dev-page .meta-bar { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.ccna-platforms-dev-page .meta-chip { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.35rem 0.75rem; + border-radius: var(--radius-md, 6px); + font-size: 0.85rem; + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .meta-chip b { + color: var(--color-google-blue, #4285f4); + margin-right: 0.4rem; +} + +.ccna-platforms-dev-page .notice-box { + background: rgba(255, 186, 0, 0.08); + border-left: 4px solid #ffba00; + padding: 1rem 1.25rem; + border-radius: 0 var(--radius-md, 6px) var(--radius-md, 6px) 0; + font-size: 0.9rem; + line-height: 1.6; + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .section { + margin-bottom: 3.5rem; + scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 16px); +} + +.ccna-platforms-dev-page .section h2 { + font-size: 1.6rem; + font-weight: 700; + margin-bottom: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid rgba(66, 133, 244, 0.3); + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .section h2 .num { + color: var(--color-google-blue, #4285f4); + margin-right: 0.5rem; +} + +.ccna-platforms-dev-page .section h3 { + font-size: 1.25rem; + font-weight: 600; + margin-top: 1.75rem; + margin-bottom: 0.85rem; + color: var(--color-foreground); +} + +.ccna-platforms-dev-page .section h4 { + font-size: 1.05rem; + font-weight: 600; + margin-top: 1.25rem; + margin-bottom: 0.75rem; + color: var(--color-google-blue, #4285f4); +} + +.ccna-platforms-dev-page .prose { + font-size: 1rem; + line-height: 1.8; + color: var(--color-foreground); + margin-bottom: 1.25rem; +} + +.ccna-platforms-dev-page .prose p { + margin-bottom: 1rem; +} + +.ccna-platforms-dev-page .prose ul, +.ccna-platforms-dev-page .prose ol { + padding-left: 1.5rem; + margin-bottom: 1.25rem; +} + +.ccna-platforms-dev-page .prose li { + margin-bottom: 0.5rem; +} + +.ccna-platforms-dev-page .callout { + background: rgba(66, 133, 244, 0.08); + border: 1px solid rgba(66, 133, 244, 0.2); + border-radius: var(--radius-md, 8px); + padding: 1.25rem; + margin: 1.5rem 0; + font-size: 0.95rem; + line-height: 1.6; +} + +.ccna-platforms-dev-page .callout-label { + display: block; + font-weight: 700; + color: var(--color-google-blue, #4285f4); + margin-bottom: 0.4rem; +} + +.ccna-platforms-dev-page .table-wrapper { + overflow-x: auto; + margin: 1.5rem 0; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-md, 8px); +} + +.ccna-platforms-dev-page table { + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; + text-align: left; +} + +.ccna-platforms-dev-page th, +.ccna-platforms-dev-page td { + padding: 0.75rem 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.ccna-platforms-dev-page th { + background: rgba(255, 255, 255, 0.05); + font-weight: 700; + color: var(--color-foreground); +} + +.ccna-platforms-dev-page tr.highlight { + background: rgba(255, 224, 138, 0.1); +} + +.ccna-platforms-dev-page .diagram-wrapper { + margin: 1.5rem 0; + padding: 1rem; + background: rgba(10, 18, 30, 0.6); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--radius-md, 8px); + overflow-x: auto; +} + +.ccna-platforms-dev-page .mermaid-wrap { + display: flex; + justify-content: center; + width: 100%; +} + +.ccna-platforms-dev-page .step-list { + padding-left: 1.5rem; + margin: 1.5rem 0; +} + +.ccna-platforms-dev-page .step-list li { + margin-bottom: 1rem; + line-height: 1.7; +} + +.ccna-platforms-dev-page .footer { + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 16px); +} + +.ccna-platforms-dev-page .refs-list { + list-style: none; + padding: 0; + +} + +.ccna-platforms-dev-page .refs-list li { + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.ccna-platforms-dev-page .ref-desc { + display: block; + font-weight: 600; + margin-bottom: 0.2rem; +} + +.ccna-platforms-dev-page .refs-list a { + color: var(--color-google-blue, #4285f4); + text-decoration: underline; + word-break: break-all; + font-size: 0.9rem; +} + +@media (max-width: 900px) { + .ccna-platforms-dev-page .sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease; + } + .ccna-platforms-dev-page .content { + margin-left: 0; + padding: 1.5rem 1rem; + } +} diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx b/app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx new file mode 100644 index 000000000..ed2ed3a67 --- /dev/null +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from 'next'; +import { CcnaCiscoPlatformsDevelopmentGuide } from './CcnaCiscoPlatformsDevelopmentGuide'; +import './page.css'; + +export const metadata: Metadata = { + title: 'Cisco Platforms and Development 徹底解説ガイド | CCNA Automation', + description: 'CCNA Automation (200-901 CCNAAUTO v1.1) ドメイン3.0 Cisco Platforms and Developmentの徹底解説ガイド。SDK、ネットワーク/コンピュート/コラボレーション/セキュリティAPI、NETCONF/RESTCONF、YANGなどを網羅。', +}; + +export default function CcnaCiscoPlatformsDevelopmentPage() { + return ; +} From ba8a088cc270fa2c1b66a8452e41b6aac30e0b31 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:12:33 +0900 Subject: [PATCH 034/123] refactor(cisco): integrate CCNA automation cisco platforms and development guide into routing and update docs --- CLAUDE.md | 6 ++++++ GEMINI.md | 1 + MIGRATION_PROGRESS.md | 25 ++++++++++++++++++++++++- app/constants.ts | 5 +++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8ae9c859c..e75da3ef0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -236,6 +236,12 @@ app/ NavBar.tsx # サイドバーナビ(IntersectionObserver) constants.ts # Mermaid 図定義(12図) page.css # ページ固有スタイル + automation-cisco-platforms-and-development/ + page.tsx # CCNA Automation Cisco Platforms and Development 徹底解説ガイド(Server。メタデータ定義) + CcnaCiscoPlatformsDevelopmentGuide.tsx # 本文+インタラクション(client。全13セクション、Mermaid等) + NavBar.tsx # サイドバーナビ(IntersectionObserver) + constants.ts # Mermaid 図定義(10図) + page.css # ページ固有スタイル ip-connectivity-guide/ page.tsx # CCNA 200-301 IP Connectivity 完全ガイド(Server。メタデータ定義) CcnaIpConnectivityGuide.tsx # 本文+インタラクション(client。全6章+まとめ、Mermaid等) diff --git a/GEMINI.md b/GEMINI.md index 3514895ad..5ea2b524d 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -37,6 +37,7 @@ - `/app/cisco/ccna/beginner-guide`: Cisco CCNA試験 完全ガイド。 - `/app/cisco/ccna/automation-software-development-design`: CCNA Automation ソフトウェア開発と設計 完全ガイド。 - `/app/cisco/ccna/automation-application-deployment-security`: CCNA Automation アプリケーションの展開とセキュリティ 完全ガイド。 + - `/app/cisco/ccna/automation-cisco-platforms-and-development`: CCNA Automation Cisco Platforms and Development 徹底解説ガイド。 - `/app/cisco/ccna/ip-connectivity-guide`: CCNA 200-301 IP Connectivity 完全ガイド。 - `/app/cisco/ccna/ip-services-guide`: CCNA 200-301 IP Services 完全ガイド。 - `/app/aws/solutions-architect-associate`: AWS Certified Solutions Architect – Associate (SAA-C03) 完全対策ガイド(`domain1` を含む)。 diff --git a/MIGRATION_PROGRESS.md b/MIGRATION_PROGRESS.md index bec32b4b4..c188a40d6 100644 --- a/MIGRATION_PROGRESS.md +++ b/MIGRATION_PROGRESS.md @@ -7,7 +7,30 @@ HTMLファイルから Next.js / React コンポーネントへの移行作業 - **ブランチ:** dev - **進行中タスク:** (なし) - **次の作業:** (なし) -- **最終更新日時(UTC):** 2026-08-08T22:49:00.000Z +- **最終更新日時(UTC):** 2026-08-08T15:10:00.000Z + +## 2026-08-08: Cisco「CCNA Automation ドメイン3.0 Cisco Platforms and Development 徹底解説ガイド」移行 (完了) + +### 目的 + +`Ccna-automation-cisco-platforms-and-development.html` (および `.md`)(静的HTML・1864行・10個のMermaid図・6個の表)を、正準の設計パターン(NavBar + Server page.tsx + Client CcnaCiscoPlatformsDevelopmentGuide.tsx + constants.ts + page.css + 共有 MermaidDiagram)で `app/cisco/ccna/automation-cisco-platforms-and-development` ルートへ完全移行する。文章・表・10個のMermaid図・7個のコードブロック・16個の参考文献の一切の省略・要約なしで完全移植。 + +### 完了済みステップ + +- [x] **Step 1 (Red)**: `test(cisco): add failing tests for CCNA automation cisco platforms and development guide migration` (`__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx` 失敗テストの作成、原本ファイルの `archive/Cisco/` への `git mv` 移動) +- [x] **Step 2 (Green)**: `feat(cisco): implement CCNA automation cisco platforms and development guide to pass tests` (`page.tsx`, `CcnaCiscoPlatformsDevelopmentGuide.tsx`, `NavBar.tsx`, `constants.ts`, `page.css` 実装) +- [x] **Step 3 (Refactor / Integration & Nav & Archive)**: `refactor(cisco): integrate CCNA automation cisco platforms and development guide into routing and update docs` (`app/constants.ts` EXAMSへの統合、ドキュメント更新) + +### 関連ファイル + +- [app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx](app/cisco/ccna/automation-cisco-platforms-and-development/page.tsx) +- [app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx](app/cisco/ccna/automation-cisco-platforms-and-development/CcnaCiscoPlatformsDevelopmentGuide.tsx) +- [app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx](app/cisco/ccna/automation-cisco-platforms-and-development/NavBar.tsx) +- [app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts](app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts) +- [app/cisco/ccna/automation-cisco-platforms-and-development/page.css](app/cisco/ccna/automation-cisco-platforms-and-development/page.css) +- [__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx](__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx) +- [archive/Cisco/html/ccna/Ccna-automation-cisco-platforms-and-development.html](archive/Cisco/html/ccna/Ccna-automation-cisco-platforms-and-development.html) +- [archive/Cisco/md/ccna/Ccna-automation-cisco-platforms-and-development.md](archive/Cisco/md/ccna/Ccna-automation-cisco-platforms-and-development.md) ## 2026-08-08: Cisco「CCNA Automation ドメイン4.0 Application Deployment and Security 完全ガイド」移行 (完了) diff --git a/app/constants.ts b/app/constants.ts index 2efa2454e..b2ba5ca20 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -336,6 +336,11 @@ const ALL_EXAMS: Exam[] = [ href: '/cisco/ccna/automation-application-deployment-security', pct: '15%', }, + { + label: '3.0 Cisco Platforms and Development', + href: '/cisco/ccna/automation-cisco-platforms-and-development', + pct: '15%', + }, ], badge: 'ネットワーク基礎', icon: '🌐', From 1361937efdd15a57a4a14760514cc4096d4a25c4 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:16:06 +0900 Subject: [PATCH 035/123] refactor(cisco): align CSS styles and variables with original HTML design --- .../page.css | 464 +++++++++++------- 1 file changed, 300 insertions(+), 164 deletions(-) diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css index 27c1f3df4..3b5c21c4f 100644 --- a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css @@ -1,258 +1,336 @@ .ccna-platforms-dev-page { + --bg: #07111e; + --bg-panel: #0d1b2c; + --bg-panel-2: #101f34; + --bg-code: #0a1424; + --border: rgba(124, 158, 255, 0.18); + --border-soft: rgba(124, 158, 255, 0.1); + --accent: #7c9eff; + --accent-2: #ffd479; + --accent-2-soft: rgba(255, 212, 121, 0.14); + --text: #e8edf7; + --text-muted: #9aa8c4; + --text-faint: #6b7a99; + --radius: 14px; + --sidebar-width: 300px; + --font-sans: 'Noto Sans JP', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace; + min-height: 100vh; - background-color: var(--color-background); - color: var(--color-foreground); + background: var(--bg); + color: var(--text); + font-family: var(--font-sans); + line-height: 1.9; + -webkit-font-smoothing: antialiased; +} + +.ccna-platforms-dev-page a { + color: var(--accent); + text-decoration: none; +} + +.ccna-platforms-dev-page a:hover { + text-decoration: underline; } +/* ---------- Layout ---------- */ .ccna-platforms-dev-page .layout { - display: flex; + display: block; min-height: 100vh; position: relative; } .ccna-platforms-dev-page .sidebar { position: fixed; - left: 0; top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px)); - bottom: 0; - width: 280px; - background-color: var(--color-card); - border-right: 1px solid var(--color-border, rgba(255, 255, 255, 0.1)); - padding: 1.5rem 1rem; + left: 0; + width: var(--sidebar-width); + height: calc(100vh - var(--header-h, 60px) - var(--disclaimer-height, 0px)); overflow-y: auto; - z-index: 40; + background: var(--bg-panel); + border-right: 1px solid var(--border); + padding: 32px 22px 48px; + z-index: 20; scrollbar-width: thin; } -.ccna-platforms-dev-page .brand { - display: block; - font-size: 1rem; +.ccna-platforms-dev-page .sidebar .brand { font-weight: 700; - color: var(--color-foreground); - margin-bottom: 0.25rem; + font-size: 1.05rem; + letter-spacing: 0.02em; + color: var(--text); + margin-bottom: 4px; + display: block; } -.ccna-platforms-dev-page .brand-sub { +.ccna-platforms-dev-page .sidebar .brand-sub { + font-size: 1rem; + color: var(--text-faint); + margin-bottom: 28px; display: block; - font-size: 0.8rem; - color: var(--color-muted-foreground); - margin-bottom: 1.5rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .ccna-platforms-dev-page .sidebar nav ul { list-style: none; - padding: 0; margin: 0; + padding: 0; } .ccna-platforms-dev-page .sidebar nav li { - margin-bottom: 0.35rem; + margin-bottom: 2px; } .ccna-platforms-dev-page .sidebar nav a { display: block; - padding: 0.45rem 0.75rem; - color: var(--color-muted-foreground); + padding: 9px 12px; + font-size: 1rem; + color: var(--text-muted); + border-left: 2px solid transparent; + border-radius: 6px; + transition: all 0.15s ease; +} + +.ccna-platforms-dev-page .sidebar nav a:hover { + color: var(--text); + background: rgba(124, 158, 255, 0.06); text-decoration: none; - font-size: 0.875rem; - border-radius: var(--radius-md, 6px); - transition: all 0.2s ease; } -.ccna-platforms-dev-page .sidebar nav a:hover, .ccna-platforms-dev-page .sidebar nav a.active { - color: var(--color-foreground); - background-color: rgba(66, 133, 244, 0.15); - border-left: 3px solid var(--color-google-blue, #4285f4); + color: var(--accent); + border-left: 2px solid var(--accent); + background: rgba(124, 158, 255, 0.09); + font-weight: 600; } +/* ---------- Main Content ---------- */ .ccna-platforms-dev-page .content { - flex: 1; - margin-left: 280px; - padding: 2rem 2.5rem 4rem; - max-width: 1000px; + margin-left: var(--sidebar-width); + width: auto; + max-width: none; + padding: 0 64px 100px; +} + +.ccna-platforms-dev-page .hero, +.ccna-platforms-dev-page .section, +.ccna-platforms-dev-page .prose, +.ccna-platforms-dev-page .footer { + max-width: none; } .ccna-platforms-dev-page .hero { - background: linear-gradient(135deg, rgba(20, 35, 60, 0.8) 0%, rgba(10, 20, 35, 0.9) 100%); - border: 1px solid rgba(66, 133, 244, 0.25); - border-radius: var(--radius-lg, 12px); - padding: 2.5rem; - margin-bottom: 3rem; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + padding: 72px 0 40px; + border-bottom: 1px solid var(--border-soft); + margin-bottom: 48px; } -.ccna-platforms-dev-page .kicker { +.ccna-platforms-dev-page .hero .kicker { display: inline-block; - font-size: 0.85rem; - font-weight: 700; - text-transform: uppercase; + font-size: 1rem; + font-weight: 600; letter-spacing: 0.08em; - color: var(--color-google-blue, #4285f4); - background: rgba(66, 133, 244, 0.12); - padding: 0.25rem 0.75rem; - border-radius: var(--radius-sm, 4px); - margin-bottom: 1rem; + color: var(--accent); + background: rgba(124, 158, 255, 0.1); + border: 1px solid var(--border); + padding: 6px 14px; + border-radius: 999px; + margin-bottom: 22px; } .ccna-platforms-dev-page .hero h1 { - font-size: 2.2rem; - font-weight: 800; - line-height: 1.3; - margin-bottom: 1rem; - color: var(--color-foreground); -} - -.ccna-platforms-dev-page .lead { - font-size: 1.05rem; - line-height: 1.7; - color: var(--color-muted-foreground); - margin-bottom: 1.5rem; + font-size: clamp(2rem, 4vw, 3.1rem); + line-height: 1.35; + margin: 0 0 20px; + font-weight: 900; + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; +} + +.ccna-platforms-dev-page .hero .lead { + font-size: 1.15rem; + color: var(--text-muted); + max-width: none; + margin: 0 0 28px; + line-height: 1.8; } .ccna-platforms-dev-page .meta-bar { display: flex; flex-wrap: wrap; - gap: 0.75rem; - margin-bottom: 1.5rem; + gap: 14px; + margin-top: 8px; } .ccna-platforms-dev-page .meta-chip { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - padding: 0.35rem 0.75rem; - border-radius: var(--radius-md, 6px); - font-size: 0.85rem; - color: var(--color-foreground); + background: var(--bg-panel-2); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px 18px; + font-size: 1rem; + color: var(--text-muted); } .ccna-platforms-dev-page .meta-chip b { - color: var(--color-google-blue, #4285f4); - margin-right: 0.4rem; + color: var(--text); + display: block; + font-size: 1.05rem; + margin-bottom: 2px; } .ccna-platforms-dev-page .notice-box { - background: rgba(255, 186, 0, 0.08); - border-left: 4px solid #ffba00; - padding: 1rem 1.25rem; - border-radius: 0 var(--radius-md, 6px) var(--radius-md, 6px) 0; - font-size: 0.9rem; - line-height: 1.6; - color: var(--color-foreground); + background: var(--bg-panel-2); + border: 1px solid var(--border); + border-left: 3px solid var(--accent); + border-radius: 10px; + padding: 20px 24px; + margin: 28px 0; + color: var(--text-muted); + font-size: 1rem; + line-height: 1.8; +} + +.ccna-platforms-dev-page .notice-box strong { + color: var(--text); } +/* ---------- Sections ---------- */ .ccna-platforms-dev-page .section { - margin-bottom: 3.5rem; - scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 16px); + padding-top: 12px; + margin-bottom: 64px; + scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 24px); } .ccna-platforms-dev-page .section h2 { - font-size: 1.6rem; - font-weight: 700; - margin-bottom: 1.25rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid rgba(66, 133, 244, 0.3); - color: var(--color-foreground); + font-size: 1.75rem; + color: var(--text); + margin: 0 0 16px; + display: flex; + align-items: center; + gap: 12px; + border-bottom: none; + padding-bottom: 0; } .ccna-platforms-dev-page .section h2 .num { - color: var(--color-google-blue, #4285f4); - margin-right: 0.5rem; + color: var(--accent-2); + font-family: var(--font-mono); + font-size: 1.1rem; } .ccna-platforms-dev-page .section h3 { - font-size: 1.25rem; - font-weight: 600; - margin-top: 1.75rem; - margin-bottom: 0.85rem; - color: var(--color-foreground); + font-size: 1.3rem; + color: var(--accent); + margin: 40px 0 14px; } .ccna-platforms-dev-page .section h4 { - font-size: 1.05rem; - font-weight: 600; - margin-top: 1.25rem; - margin-bottom: 0.75rem; - color: var(--color-google-blue, #4285f4); -} - -.ccna-platforms-dev-page .prose { - font-size: 1rem; - line-height: 1.8; - color: var(--color-foreground); - margin-bottom: 1.25rem; + font-size: 1.08rem; + color: var(--text); + margin: 26px 0 10px; } .ccna-platforms-dev-page .prose p { - margin-bottom: 1rem; + margin: 0 0 18px; + color: var(--text); + font-size: 1rem; + line-height: 1.9; } .ccna-platforms-dev-page .prose ul, .ccna-platforms-dev-page .prose ol { - padding-left: 1.5rem; - margin-bottom: 1.25rem; + margin: 0 0 20px; + padding-left: 1.4em; + color: var(--text); } .ccna-platforms-dev-page .prose li { - margin-bottom: 0.5rem; + margin-bottom: 8px; +} + +.ccna-platforms-dev-page .prose strong { + color: var(--accent-2); } +/* ---------- Callouts ---------- */ .ccna-platforms-dev-page .callout { - background: rgba(66, 133, 244, 0.08); - border: 1px solid rgba(66, 133, 244, 0.2); - border-radius: var(--radius-md, 8px); - padding: 1.25rem; - margin: 1.5rem 0; - font-size: 0.95rem; - line-height: 1.6; + background: var(--accent-2-soft); + border: 1px solid rgba(255, 212, 121, 0.35); + border-left: 3px solid var(--accent-2); + border-radius: 10px; + padding: 18px 22px; + margin: 26px 0; + font-size: 1rem; + line-height: 1.8; } -.ccna-platforms-dev-page .callout-label { - display: block; +.ccna-platforms-dev-page .callout .callout-label { font-weight: 700; - color: var(--color-google-blue, #4285f4); - margin-bottom: 0.4rem; + color: var(--accent-2); + display: block; + margin-bottom: 6px; } +/* ---------- Tables ---------- */ .ccna-platforms-dev-page .table-wrapper { overflow-x: auto; - margin: 1.5rem 0; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: var(--radius-md, 8px); + margin: 26px 0; + border: none; + border-radius: 0; } .ccna-platforms-dev-page table { width: 100%; border-collapse: collapse; - font-size: 0.9rem; + font-size: 1rem; + background: var(--bg-panel-2); + border-radius: var(--radius); + overflow: hidden; +} + +.ccna-platforms-dev-page thead th { text-align: left; + padding: 14px 18px; + background: rgba(124, 158, 255, 0.12); + color: var(--accent); + font-weight: 700; + border-bottom: 1px solid var(--border); + white-space: nowrap; } -.ccna-platforms-dev-page th, -.ccna-platforms-dev-page td { - padding: 0.75rem 1rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); +.ccna-platforms-dev-page tbody td { + padding: 13px 18px; + border-bottom: 1px solid var(--border-soft); + color: var(--text); + vertical-align: top; } -.ccna-platforms-dev-page th { - background: rgba(255, 255, 255, 0.05); - font-weight: 700; - color: var(--color-foreground); +.ccna-platforms-dev-page tbody tr:last-child td { + border-bottom: none; } -.ccna-platforms-dev-page tr.highlight { - background: rgba(255, 224, 138, 0.1); +.ccna-platforms-dev-page tbody tr:hover td { + background: rgba(124, 158, 255, 0.04); } +.ccna-platforms-dev-page tr.highlight td { + background: rgba(255, 212, 121, 0.08); +} + +.ccna-platforms-dev-page tr.highlight td:first-child { + border-left: 3px solid var(--accent-2); +} + +/* ---------- Diagrams ---------- */ .ccna-platforms-dev-page .diagram-wrapper { - margin: 1.5rem 0; - padding: 1rem; - background: rgba(10, 18, 30, 0.6); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: var(--radius-md, 8px); overflow-x: auto; + margin: 30px 0; + background: var(--bg-panel-2); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 28px 12px; } .ccna-platforms-dev-page .mermaid-wrap { @@ -261,55 +339,113 @@ width: 100%; } +/* ---------- Code Blocks ---------- */ +.ccna-platforms-dev-page .code-block { + background: var(--bg-code); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 22px 24px; + overflow-x: auto; + margin: 14px 0 26px; + font-size: 1rem; + line-height: 1.7; + font-family: var(--font-mono); +} + +.ccna-platforms-dev-page .code-keyword { color: #ff7b72; font-weight: 600; } +.ccna-platforms-dev-page .code-string { color: #a5d6ff; } +.ccna-platforms-dev-page .code-comment { color: #8b949e; font-style: italic; } + +/* ---------- Roadmap Steps ---------- */ .ccna-platforms-dev-page .step-list { - padding-left: 1.5rem; - margin: 1.5rem 0; + counter-reset: step; + list-style: none; + padding: 0; + margin: 24px 0; } .ccna-platforms-dev-page .step-list li { - margin-bottom: 1rem; - line-height: 1.7; + counter-increment: step; + position: relative; + padding: 6px 0 6px 46px; + margin-bottom: 16px; + color: var(--text); + line-height: 1.8; +} + +.ccna-platforms-dev-page .step-list li::before { + content: counter(step); + position: absolute; + left: 0; + top: 2px; + width: 32px; + height: 32px; + background: rgba(124, 158, 255, 0.14); + border: 1px solid var(--border); + color: var(--accent); + font-weight: 700; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; } +/* ---------- Footer ---------- */ .ccna-platforms-dev-page .footer { - margin-top: 4rem; - padding-top: 2rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); - scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 16px); + border-top: 1px solid var(--border-soft); + padding-top: 32px; + color: var(--text-faint); + font-size: 1rem; + scroll-margin-top: calc(var(--header-h, 60px) + var(--disclaimer-height, 0px) + 24px); } .ccna-platforms-dev-page .refs-list { list-style: none; padding: 0; - + margin: 0; + counter-reset: ref; } .ccna-platforms-dev-page .refs-list li { - margin-bottom: 1rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + counter-increment: ref; + padding: 12px 0 12px 40px; + position: relative; + border-bottom: 1px solid var(--border-soft); + font-size: 1rem; } -.ccna-platforms-dev-page .ref-desc { - display: block; - font-weight: 600; - margin-bottom: 0.2rem; +.ccna-platforms-dev-page .refs-list li::before { + content: counter(ref); + position: absolute; + left: 0; + top: 12px; + color: var(--accent-2); + font-family: var(--font-mono); + font-weight: 700; } .ccna-platforms-dev-page .refs-list a { - color: var(--color-google-blue, #4285f4); - text-decoration: underline; + color: var(--accent); word-break: break-all; - font-size: 0.9rem; } -@media (max-width: 900px) { +.ccna-platforms-dev-page .refs-list .ref-desc { + color: var(--text-muted); + display: block; + margin-bottom: 4px; +} + +/* ---------- Responsive ---------- */ +@media (max-width: 960px) { .ccna-platforms-dev-page .sidebar { - transform: translateX(-100%); - transition: transform 0.3s ease; + display: none; } .ccna-platforms-dev-page .content { margin-left: 0; - padding: 1.5rem 1rem; + padding: 0 24px 80px; + } + .ccna-platforms-dev-page .hero { + padding: 48px 0 32px; } } From 22360a2d6340cecdef85fe882cc53ce3a3eeb488 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:16:58 +0900 Subject: [PATCH 036/123] refactor(cisco): ensure exact Mermaid diagram color styling and yellow node label readability --- .../page.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css index 3b5c21c4f..026a725cb 100644 --- a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css @@ -335,10 +335,19 @@ .ccna-platforms-dev-page .mermaid-wrap { display: flex; - justify-content: center; + justify-content: safe center; width: 100%; } +.ccna-platforms-dev-page :global(.node[style*="ffe08a" i] .nodeLabel), +.ccna-platforms-dev-page :global(.node[style*="ffe08a" i] .nodeLabel *), +.ccna-platforms-dev-page :global(.node:has([style*="ffe08a" i]) .nodeLabel), +.ccna-platforms-dev-page :global(.node:has([style*="ffe08a" i]) .nodeLabel *), +.ccna-platforms-dev-page :global(.node:has([fill*="ffe08a" i]) .nodeLabel), +.ccna-platforms-dev-page :global(.node:has([fill*="ffe08a" i]) .nodeLabel *) { + color: #241a00 !important; +} + /* ---------- Code Blocks ---------- */ .ccna-platforms-dev-page .code-block { background: var(--bg-code); From 122ad235e55e41ea73b8a923f48db49425cd4975 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:18:34 +0900 Subject: [PATCH 037/123] fix(cisco): fix yellow node text contrast and SVG layout boundaries in CCNA platforms guide --- .../constants.ts | 2 +- .../page.css | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts b/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts index 5e6832ef4..7498085bd 100644 --- a/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/constants.ts @@ -2,7 +2,7 @@ export const DIAGRAMS: Record = { 'diag-overview': `flowchart TB A["CCNA Automation
200-901 CCNAAUTO v1.1"] --> D1["1.0 Software Development
and Design (15%)"] A --> D2["2.0 Understanding and
Using APIs (20%)"] -A --> D3["3.0 Cisco Platforms
and Development (15%)"] +A --> D3["3.0 Cisco Platforms
and Development (15%)
"] A --> D4["4.0 Application Deployment
and Security (15%)"] A --> D5["5.0 Infrastructure and
Automation (20%)"] A --> D6["6.0 Network
Fundamentals (15%)"] diff --git a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css index 026a725cb..93ec576a6 100644 --- a/app/cisco/ccna/automation-cisco-platforms-and-development/page.css +++ b/app/cisco/ccna/automation-cisco-platforms-and-development/page.css @@ -331,21 +331,45 @@ border: 1px solid var(--border); border-radius: var(--radius); padding: 28px 12px; + max-width: 100%; } .ccna-platforms-dev-page .mermaid-wrap { display: flex; justify-content: safe center; width: 100%; + overflow-x: auto; +} + +.ccna-platforms-dev-page .mermaid-wrap :global(svg) { + max-width: 100% !important; + height: auto !important; } +/* Ensure yellow node text is dark and high-contrast (#241a00) */ +.ccna-platforms-dev-page :global(#D3 .nodeLabel), +.ccna-platforms-dev-page :global(#D3 .nodeLabel *), +.ccna-platforms-dev-page :global(#D3 span), +.ccna-platforms-dev-page :global(#D3 div), +.ccna-platforms-dev-page :global(#D3 p), +.ccna-platforms-dev-page :global([id*="D3"] .nodeLabel), +.ccna-platforms-dev-page :global([id*="D3"] .nodeLabel *), .ccna-platforms-dev-page :global(.node[style*="ffe08a" i] .nodeLabel), .ccna-platforms-dev-page :global(.node[style*="ffe08a" i] .nodeLabel *), +.ccna-platforms-dev-page :global(.node[style*="FFE08A" i] .nodeLabel), +.ccna-platforms-dev-page :global(.node[style*="FFE08A" i] .nodeLabel *), +.ccna-platforms-dev-page :global(.node[style*="255, 224, 138" i] .nodeLabel), +.ccna-platforms-dev-page :global(.node[style*="255, 224, 138" i] .nodeLabel *), .ccna-platforms-dev-page :global(.node:has([style*="ffe08a" i]) .nodeLabel), .ccna-platforms-dev-page :global(.node:has([style*="ffe08a" i]) .nodeLabel *), .ccna-platforms-dev-page :global(.node:has([fill*="ffe08a" i]) .nodeLabel), -.ccna-platforms-dev-page :global(.node:has([fill*="ffe08a" i]) .nodeLabel *) { +.ccna-platforms-dev-page :global(.node:has([fill*="ffe08a" i]) .nodeLabel *), +.ccna-platforms-dev-page :global(.node:has([fill*="FFE08A" i]) .nodeLabel), +.ccna-platforms-dev-page :global(.node:has([fill*="FFE08A" i]) .nodeLabel *), +.ccna-platforms-dev-page :global(.node:has([fill*="#ffe08a" i]) .nodeLabel), +.ccna-platforms-dev-page :global(.node:has([fill*="#ffe08a" i]) .nodeLabel *) { color: #241a00 !important; + fill: #241a00 !important; } /* ---------- Code Blocks ---------- */ From a61e586d47c622b477e246fe28a065e3273be343 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:19:29 +0900 Subject: [PATCH 038/123] fix(components): add #ffe08a and yellow shade variations to MermaidDiagram black text color rule --- components/MermaidDiagram.module.css | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/components/MermaidDiagram.module.css b/components/MermaidDiagram.module.css index 6dfd3cb49..807c9b704 100644 --- a/components/MermaidDiagram.module.css +++ b/components/MermaidDiagram.module.css @@ -53,13 +53,29 @@ color: #ffffff !important; } -/* 黄色ノード(#fbbc04)は白×黄で同化するため、ラベルのみ黒に戻す */ +/* 黄色系ノード(#fbbc04, #ffe08a, #ffd479, #ffba00)は白×黄で同化するため、ラベルのみ黒に戻す */ .mermaidTarget :global(.node[style*="fbbc04" i] .nodeLabel), .mermaidTarget :global(.node[style*="fbbc04" i] .nodeLabel *), +.mermaidTarget :global(.node[style*="ffe08a" i] .nodeLabel), +.mermaidTarget :global(.node[style*="ffe08a" i] .nodeLabel *), +.mermaidTarget :global(.node[style*="ffd479" i] .nodeLabel), +.mermaidTarget :global(.node[style*="ffd479" i] .nodeLabel *), +.mermaidTarget :global(.node[style*="ffba00" i] .nodeLabel), +.mermaidTarget :global(.node[style*="ffba00" i] .nodeLabel *), .mermaidTarget :global(.node:has([style*="fbbc04" i]) .nodeLabel), .mermaidTarget :global(.node:has([style*="fbbc04" i]) .nodeLabel *), -.mermaidTarget :global(.node:has([fill="#fbbc04" i]) .nodeLabel), -.mermaidTarget :global(.node:has([fill="#fbbc04" i]) .nodeLabel *) { +.mermaidTarget :global(.node:has([style*="ffe08a" i]) .nodeLabel), +.mermaidTarget :global(.node:has([style*="ffe08a" i]) .nodeLabel *), +.mermaidTarget :global(.node:has([style*="ffd479" i]) .nodeLabel), +.mermaidTarget :global(.node:has([style*="ffd479" i]) .nodeLabel *), +.mermaidTarget :global(.node:has([fill*="fbbc04" i]) .nodeLabel), +.mermaidTarget :global(.node:has([fill*="fbbc04" i]) .nodeLabel *), +.mermaidTarget :global(.node:has([fill*="ffe08a" i]) .nodeLabel), +.mermaidTarget :global(.node:has([fill*="ffe08a" i]) .nodeLabel *), +.mermaidTarget :global(.node:has([fill*="ffd479" i]) .nodeLabel), +.mermaidTarget :global(.node:has([fill*="ffd479" i]) .nodeLabel *), +.mermaidTarget :global(.node:has([fill*="ffba00" i]) .nodeLabel), +.mermaidTarget :global(.node:has([fill*="ffba00" i]) .nodeLabel *) { color: #000000 !important; } From 04aa8ac958f3ad7bf055a2c81f9cc1d20f55325a Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 00:21:03 +0900 Subject: [PATCH 039/123] docs(rules): document strict design migration and yellow Mermaid node contrast rules --- .agents/AGENTS.md | 23 +++++++++++++++++++ .claude/skills/fix-mermaid/SKILL.md | 22 +++++++++++++++--- .../skills/html-to-nextjs-migration/SKILL.md | 2 ++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 .agents/AGENTS.md diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md new file mode 100644 index 000000000..ebaadb463 --- /dev/null +++ b/.agents/AGENTS.md @@ -0,0 +1,23 @@ +# Project Rules & Quality Mandates: Cloud Infrastructure Studies + +本ファイルは、本プロジェクトにおけるコード実装・HTML移行・スタイリング・テスト駆動開発(TDD)の厳格な品質基準と運用ルールを規定する。 + +## 1. デザイン完全移転原則(厳守) + +- **元HTMLの `:root` スタイル変数の100%全量移植**: + - 静的HTMLから Next.js への移行時、元HTMLの ` 直前に注入する。既に注入済みなら不変。 + * が無い場合は 直前に '); + if (styleCloseIdx !== -1) { + return html.slice(0, styleCloseIdx) + CENTERING_CSS + html.slice(styleCloseIdx); + } + const headCloseIdx = html.indexOf(''); + if (headCloseIdx !== -1) { + const block = ` \n`; + return html.slice(0, headCloseIdx) + block + html.slice(headCloseIdx); + } + throw new Error(' も も見つからず中央寄せ CSS を注入できません。'); +} + +/** + * 全ステップを冪等に適用する。 + * @returns {{ html: string, report: string[] }} + */ +export function applyPipeline(html) { + const report = []; + + const ids = injectIds(html); + let out = ids.html; + report.push( + ids.count > 0 + ? `div→id 置換: ${ids.count} 件` + : 'div→id 置換: 対象なし (適用済みか div.mermaid 不在)', + ); + + if (!/const\s+DIAGRAMS\s*=/.test(out)) { + // DIAGRAMS 未定義: 空スタブを初期化ブロック直前に挿入して警告 + const initIdx = out.indexOf('mermaid.initialize('); + if (initIdx !== -1) { + out = out.slice(0, initIdx) + 'const DIAGRAMS = {};\n ' + out.slice(initIdx); + } + report.push('⚠️ DIAGRAMS 未定義: 空スタブを挿入。各図のソースを手動で定義してください。'); + } + + const beforeFlags = out; + out = ensureInitFlags(out); + report.push(out !== beforeFlags ? 'init フラグ: 更新' : 'init フラグ: 変更なし'); + + const beforeLoop = out; + out = injectRenderLoop(out); + report.push(out !== beforeLoop ? 'render ループ: 注入' : 'render ループ: 適用済み'); + + const beforeCss = out; + out = injectCenteringCss(out); + report.push(out !== beforeCss ? '中央寄せ CSS: 注入' : '中央寄せ CSS: 適用済み'); + + return { html: out, report }; +} + +// --- CLI エントリポイント ---------------------------------------------------- + +if (import.meta.main) { + const file = process.argv[2]; + if (!file) { + console.error('Usage: bun run apply_render_pipeline.mjs '); + process.exit(1); + } + if (!fs.existsSync(file)) { + console.error(`❌ File not found: ${file}`); + process.exit(1); + } + const input = fs.readFileSync(file, 'utf8'); + let result; + try { + result = applyPipeline(input); + } catch (err) { + console.error(`❌ ${err instanceof Error ? err.message : String(err)}`); + process.exit(1); + } + result.report.forEach((line) => console.log(' - ' + line)); + if (result.html !== input) { + fs.writeFileSync(file, result.html, 'utf8'); + console.log(`\n✅ Applied: ${file}`); + } else { + console.log(`\n✅ No changes (already applied): ${file}`); + } +} diff --git a/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts b/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts new file mode 100644 index 000000000..9576cae56 --- /dev/null +++ b/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts @@ -0,0 +1,130 @@ +import { expect, test, describe } from "vitest"; +import { + injectIds, + ensureInitFlags, + injectRenderLoop, + injectCenteringCss, + applyPipeline, +} from "./apply_render_pipeline.mjs"; + +// 最小フィクスチャ: div.mermaid 1 つ + startOnLoad:true の初期化 + DIAGRAMS 定義 +const FIXTURE = ` + + + + + + +
+
flowchart TD +A --> B
+
+ + +`; + +describe("injectIds", () => { + test("各 div.mermaid を連番 id 付きの空 div に置換する", () => { + const { html, count } = injectIds(FIXTURE); + expect(count).toBe(1); + expect(html).toContain('
'); + // 旧来のインライン Mermaid ソースは div から除去される + expect(html).not.toContain('
flowchart TD'); + }); + + test("既に id 付きの div は再変換しない(冪等)", () => { + const { html } = injectIds(FIXTURE); + const second = injectIds(html); + expect(second.count).toBe(0); + expect(second.html).toBe(html); + }); +}); + +describe("ensureInitFlags", () => { + test("startOnLoad:true を false にし securityLevel:'loose' を付与する", () => { + const out = ensureInitFlags(FIXTURE); + expect(out).toContain("startOnLoad: false"); + expect(out).toContain("securityLevel: 'loose'"); + expect(out).not.toContain("startOnLoad: true"); + }); + + test("カンマ無しの startOnLoad:true でも false 化し securityLevel を付与する", () => { + const input = "mermaid.initialize({ startOnLoad: true });"; + const out = ensureInitFlags(input); + expect(out).toContain("startOnLoad: false"); + expect(out).toContain("securityLevel: 'loose'"); + expect(out).not.toContain("startOnLoad: true"); + }); + + test("startOnLoad 不在でも securityLevel:'loose' を注入する", () => { + const input = "mermaid.initialize({ theme: 'dark' });"; + const out = ensureInitFlags(input); + expect(out).toContain("securityLevel: 'loose'"); + }); + + test("securityLevel 既存なら重複注入しない(冪等)", () => { + const input = "mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });"; + const out = ensureInitFlags(input); + expect(out.match(/securityLevel/g)?.length).toBe(1); + }); +}); + +describe("injectRenderLoop", () => { + test("applySvgFixups と render ループを注入する", () => { + const out = injectRenderLoop(FIXTURE); + expect(out).toContain("function applySvgFixups"); + expect(out).toContain("mermaid.render('svg-' + id"); + }); + + test("SVG 幅は viewBox 由来の自然 px + maxWidth:100% を使う(width:'100%'/'auto' は使わない)", () => { + const out = injectRenderLoop(FIXTURE); + // 異常拡大の原因になる固定値は使わない + expect(out).not.toContain("style.width = '100%'"); + expect(out).not.toContain("style.width = 'auto'"); + // 自然幅(px)と maxWidth:100% を使う + expect(out).toContain("maxWidth = '100%'"); + expect(out).toMatch(/style\.width\s*=\s*w\s*\+\s*'px'/); + // viewBox 高さ拡張(下端見切れ対策) + expect(out).toContain("setAttribute('viewBox'"); + }); + + test("既に注入済みなら再注入しない(冪等)", () => { + const once = injectRenderLoop(FIXTURE); + const twice = injectRenderLoop(once); + expect(twice).toBe(once); + }); +}); + +describe("injectCenteringCss", () => { + test("中央寄せ CSS を注入する", () => { + const out = injectCenteringCss(FIXTURE); + expect(out).toContain("justify-content: center"); + }); + + test("既に注入済みなら再注入しない(冪等)", () => { + const once = injectCenteringCss(FIXTURE); + const twice = injectCenteringCss(once); + expect(twice).toBe(once); + }); +}); + +describe("applyPipeline (統合・冪等性)", () => { + test("全ステップを適用し、再適用で不変(冪等)", () => { + const first = applyPipeline(FIXTURE); + expect(first.html).toContain('id="diag-1"'); + expect(first.html).toContain("startOnLoad: false"); + expect(first.html).toContain("function applySvgFixups"); + expect(first.html).toContain("justify-content: center"); + + const second = applyPipeline(first.html); + expect(second.html).toBe(first.html); + }); +}); diff --git a/.agents/skills/fix-mermaid/scripts/fix_mermaid.test.ts b/.agents/skills/fix-mermaid/scripts/fix_mermaid.test.ts new file mode 100644 index 000000000..7a9326fb3 --- /dev/null +++ b/.agents/skills/fix-mermaid/scripts/fix_mermaid.test.ts @@ -0,0 +1,101 @@ +import { expect, test, describe } from "bun:test"; +import { fixHtmlMermaid, fixMarkdownMermaid, fixTsxMermaid } from "./fix_mermaid"; + +describe("fixHtmlMermaid", () => { + test("HTML フォーマッターで分割された sequenceDiagram 行が結合される", () => { + const html = `
+ sequenceDiagram + participant A + Note over A,B: + some message + A->B: hello +
`; + const { fixed, report } = fixHtmlMermaid(html); + expect(fixed).toContain("Note over A,B: some message"); + expect(fixed).not.toContain(" sequenceDiagram"); + expect(fixed).toContain("sequenceDiagram"); + expect(report.length).toBe(1); + expect(report[0]).toContain("modified"); + expect(report[0]).toContain("sequenceDiagram"); + }); + + test("mindmap のインデントが保持され、不正な結合が起きない", () => { + const html = `
+ mindmap + root((Title)) + Child1 + Grandchild1 + Child2 +
`; + const { fixed, report } = fixHtmlMermaid(html); + expect(fixed).toMatch(/^mindmap$/m); + expect(fixed).toContain(" root((Title))"); + expect(fixed).toContain(" Child1"); + expect(fixed).toContain(" Grandchild1"); + expect(fixed).toContain(" Child2"); + expect(fixed).not.toContain("root((Title))Child1"); + expect(report).toEqual([]); + }); + + test("class 属性に追加トークンがあってもブロックが検出・処理される", () => { + const html = `
+ graph TD + A --> B +
`; + const { fixed, report } = fixHtmlMermaid(html); + expect(fixed).toContain("graph TD"); + expect(fixed).not.toContain(" graph TD"); + expect(fixed).toContain("A --> B"); + expect(fixed).not.toContain(" A --> B"); + expect(report.length).toBe(1); + }); +}); + +describe("fixMarkdownMermaid", () => { + test("Markdown 内の ```mermaid ブロックのインデントが正規化される", () => { + const md = `Some text here. +\`\`\`mermaid + graph TD + A --> B +\`\`\` +Other text here.`; + const { fixed, report } = fixMarkdownMermaid(md); + expect(fixed).toContain("graph TD\nA --> B"); + expect(fixed).not.toContain(" graph TD"); + expect(report.length).toBe(1); + }); +}); + +describe("fixTsxMermaid", () => { + test("TSX 内のテンプレートリテラルの Mermaid コードのインデントが正規化される", () => { + const tsx = `import Mermaid from '../../components/Mermaid'; +export default function Page() { + return ( + B + B --> C + \`} /> + ); +}`; + const { fixed, report } = fixTsxMermaid(tsx); + expect(fixed).toContain("graph TD\nA --> B\nB --> C"); + expect(fixed).not.toContain(" A --> B"); + expect(report.length).toBe(1); + }); + + test("TSX 内のテンプレートリテラルでバッククォートの直後に改行がある場合も Mermaid コードが検出されて修正される", () => { + const tsx = `import Mermaid from '../../components/Mermaid'; +export default function Page() { + return ( + B + B --> C + \`} /> + ); +}`; + const { fixed, report } = fixTsxMermaid(tsx); + expect(fixed).toContain("graph TD\nA --> B\nB --> C"); + expect(report.length).toBe(1); + }); +}); diff --git a/.agents/skills/fix-mermaid/scripts/fix_mermaid.ts b/.agents/skills/fix-mermaid/scripts/fix_mermaid.ts new file mode 100644 index 000000000..07c933ab9 --- /dev/null +++ b/.agents/skills/fix-mermaid/scripts/fix_mermaid.ts @@ -0,0 +1,227 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +const newStmtRe = /^(?:\w+\s*-[->.>]|Note\b|participant\b|actor\b|alt\b|else\b|opt\b|loop\b|rect\b|par\b|end\b|%%|activate\b|deactivate\b|subgraph\b|style\b|classDef\b|linkStyle\b)/i; +const seqFragRe = /^(?:Note\s+(?:over|left\s+of|right\s+of)\b|participant\b|actor\b|alt\b|loop\b|rect\b)/i; + +/** + * Determine the diagram type from a block of Mermaid content. + * + * Scans the content for the first non-empty line that does not start with `%%` and returns its first whitespace-delimited token. + * + * @param inner - Mermaid diagram content + * @returns The diagram type token (e.g., `graph`, `sequenceDiagram`, `mindmap`), or `"unknown"` if no suitable line is found + */ +function getDiagramType(inner: string): string { + const rawLines = inner.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); + const diagramTypeLine = rawLines.find(line => line.trim() && !line.trim().startsWith('%%')) || ''; + return diagramTypeLine.trim().split(/\s+/)[0] || 'unknown'; +} + +/** + * Fixes indentation and broken statement lines inside a Mermaid diagram block. + * + * Normalizes newlines, repairs mindmap indentation or merges incorrectly broken lines + * for other diagram types, and returns the corrected content along with a count + * of modified lines. + * + * @param inner - The raw content of a Mermaid diagram block + * @param report - Optional array that will be appended with a summary line when changes are made. + * Each appended entry has the form `[]: line(s) modified`. + * @returns An object containing `fixedContent` (the corrected diagram text) and `fixedCount` (the number of lines modified) + */ +export function fixMermaidContent(inner: string, report?: string[]): { fixedContent: string; fixedCount: number } { + const rawLines = inner.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); + + // 最初の非空・非ディレクティブ行でダイアグラム種別を判定 + const diagramTypeLine = rawLines.find(line => line.trim() && !line.trim().startsWith('%%')) || ''; + const diagramType = diagramTypeLine.trim(); + const isMindmap = diagramType.toLowerCase().startsWith('mindmap'); + + const fixed: string[] = []; + let fixedCount = 0; + + let i = 0; + while (i < rawLines.length) { + const ln = rawLines[i]; + const stripped = ln.trimStart(); + const leading = ln.length - stripped.length; + + if (isMindmap) { + let commonIndent = Infinity; + for (let j = i; j < rawLines.length; j++) { + const line = rawLines[j]; + if (line.trim()) { + const indent = line.length - line.trimStart().length; + if (indent < commonIndent) { + commonIndent = indent; + } + } + } + if (commonIndent === Infinity) { + commonIndent = 0; + } + + let localFixedCount = 0; + for (let j = i; j < rawLines.length; j++) { + const line = rawLines[j]; + const sliced = line.slice(commonIndent); + if (sliced !== line) { + localFixedCount++; + } + fixed.push(sliced); + } + if (localFixedCount > 0) { + fixedCount += localFixedCount; + if (report) { + const diagramType = getDiagramType(inner); + report.push(`[${diagramType}]: ${localFixedCount} line(s) modified`); + } + } + i = rawLines.length; + break; + } + + if (leading > 0 && stripped) { + const prev = fixed.length > 0 ? fixed[fixed.length - 1].trimEnd() : ''; + const fragMatch = seqFragRe.test(prev); + const isIncompleteFrag = fragMatch && !/:\s*\S/.test(prev); + const isCont = (prev.endsWith(':') || isIncompleteFrag) && !newStmtRe.test(stripped); + + let changed = false; + if (isCont && fixed.length > 0) { + fixed[fixed.length - 1] = prev + ' ' + stripped; + changed = true; + } else { + fixed.push(stripped); + changed = true; + } + if (changed) { + fixedCount++; + } + } else { + fixed.push(ln); + } + i++; + } + + if (fixedCount > 0 && report && !isMindmap) { + const diagramType = getDiagramType(inner); + report.push(`[${diagramType}]: ${fixedCount} line(s) modified`); + } + + return { + fixedContent: fixed.join('\n'), + fixedCount, + }; +} + +/** + * Fixes Mermaid diagram blocks found inside an HTML string. + * + * Searches for
elements whose class attribute contains "mermaid", repairs + * the inner Mermaid content, and returns the updated HTML along with a report + * of modifications performed. + * + * @param html - The HTML document text to scan and fix + * @returns An object with `fixed` containing the HTML with corrected Mermaid blocks, and `report` containing per-diagram modification messages + */ +export function fixHtmlMermaid(html: string): { fixed: string; report: string[] } { + const report: string[] = []; + const pattern = /(]*\bclass\s*=\s*(?:"[^"]*\bmermaid\b[^"]*"|'[^']*\bmermaid\b[^']*'|[^\s>]*\bmermaid\b[^\s>]*)[^>]*>)([\s\S]*?)(<\/div>)/gi; + + const fixed = html.replace(pattern, (match, openTag, inner, closeTag) => { + const { fixedContent } = fixMermaidContent(inner, report); + return openTag + fixedContent + closeTag; + }); + + return { fixed, report }; +} + +/** + * Fixes Mermaid code blocks inside a Markdown string. + * + * @param markdown - The Markdown source to scan for fenced ```mermaid blocks. + * @returns An object with `fixed` containing the Markdown where each Mermaid block has been corrected, and `report` listing short messages for each diagram that was modified. + */ +export function fixMarkdownMermaid(markdown: string): { fixed: string; report: string[] } { + const report: string[] = []; + const pattern = /(```mermaid\r?\n)([\s\S]*?)(\r?\n```)/gi; + + const fixed = markdown.replace(pattern, (match, openTag, inner, closeTag) => { + const { fixedContent } = fixMermaidContent(inner, report); + return openTag + fixedContent + closeTag; + }); + + return { fixed, report }; +} + +/** + * Fixes Mermaid diagram code contained in template literals within TS/TSX source text. + * + * Scans the provided file content for backtick-delimited template literals whose inner text begins with + * `graph `, `flowchart `, `sequenceDiagram`, or `mindmap`, repairs malformed Mermaid blocks, + * and returns the updated source and a list of modification summaries. + * + * @param content - The TS/TSX source text to scan and fix + * @returns An object with `fixed` containing the updated source text and `report` containing per-diagram + * modification messages (e.g. "[sequenceDiagram]: 3 line(s) modified") + */ +export function fixTsxMermaid(content: string): { fixed: string; report: string[] } { + const report: string[] = []; + // バッククォート ` で囲まれたテンプレートリテラルで、 + // 内部が graph/flowchart/sequenceDiagram/mindmap 等で始まるものを検出 + const pattern = /`(\s*(?:graph\s+\w+|flowchart\s+\w+|sequenceDiagram|mindmap|(?:\w+Diagram)|gantt\b|pie\b|journey\b)[\s\S]*?)`/gi; + + const fixed = content.replace(pattern, (match, inner) => { + const { fixedContent } = fixMermaidContent(inner, report); + return '`' + fixedContent + '`'; + }); + + return { fixed, report }; +} + +// Bun/Node 環境での直接実行エントリポイント +if (typeof require !== 'undefined' && require.main === module) { + const args = process.argv.slice(2); + if (args.length < 1) { + console.log("Usage: bun run fix_mermaid.ts "); + process.exit(1); + } + + const filePath = args[0]; + try { + const absolutePath = path.resolve(filePath); + if (!fs.existsSync(absolutePath)) { + console.error(`❌ File not found: ${filePath}`); + process.exit(1); + } + + const content = fs.readFileSync(absolutePath, 'utf8'); + const ext = path.extname(absolutePath).toLowerCase(); + + let result: { fixed: string; report: string[] }; + + if (ext === '.html' || ext === '.htm') { + result = fixHtmlMermaid(content); + } else if (ext === '.md' || ext === '.markdown') { + result = fixMarkdownMermaid(content); + } else if (['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs'].includes(ext)) { + result = fixTsxMermaid(content); + } else { + console.error(`❌ Unsupported file type: ${ext}`); + process.exit(1); + } + + if (result.report.length > 0) { + result.report.forEach(line => { console.log(line); }); + fs.writeFileSync(absolutePath, result.fixed, 'utf8'); + console.log(`\n✅ Fixed and saved: ${filePath}`); + } else { + console.log("✅ No Mermaid formatting issues found."); + } + } catch (error) { + console.error(`❌ Error processing ${filePath}:`, error); + process.exit(1); + } +} diff --git a/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs b/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs new file mode 100644 index 000000000..d62f0c0f5 --- /dev/null +++ b/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs @@ -0,0 +1,120 @@ +/** + * restore_diagrams.mjs — 壊れた HTML 内の `DIAGRAMS` を、正本となる Markdown の + * ```mermaid ブロックからキーワード一致で復元する。 + * + * フォーマッタ等で HTML 側の図ソースが破壊された場合に、対応する .md(正本)から + * 各図の正しいソースを引き当てて差し替える。 + * + * 使い方: + * bun run .claude/skills/fix-mermaid/scripts/restore_diagrams.mjs + */ +import fs from 'fs'; + +/** + * Markdown 内の ```mermaid ブロックを抽出する。 + * @returns {string[]} 各ブロックの中身(trim 済み) + */ +export function extractMdMermaidBlocks(md) { + const blocks = []; + // CRLF / マーカー直後の余分な空白を許容する寛容な正規表現 + const regex = /```mermaid[ \t]*\r?\n([\s\S]*?)\r?\n```/g; + for (const match of md.matchAll(regex)) { + blocks.push(match[1].trim()); + } + return blocks; +} + +/** + * 壊れた図ソースから検索キーワードを抽出する。 + * クォート内の文字列を優先し、無ければ英字 5 文字以上の語を使う。 + */ +function extractKeywords(brokenCode) { + const keywords = []; + for (const match of brokenCode.matchAll(/"(.*?)"/g)) { + if (match[1].length > 3) keywords.push(match[1]); + } + if (keywords.length === 0) { + for (const match of brokenCode.matchAll(/[a-zA-Z]{5,}/g)) { + keywords.push(match[0]); + } + } + return keywords; +} + +/** + * 壊れた DIAGRAMS と MD ブロック群から、各図に最も一致するブロックを選び復元する。 + * @returns {{ diagrams: Record, warnings: string[] }} + */ +export function restoreDiagrams(diagrams, mdBlocks) { + const restored = {}; + const warnings = []; + for (const [id, brokenCode] of Object.entries(diagrams)) { + const keywords = extractKeywords(brokenCode); + let bestMatch = null; + let maxScore = -1; + for (const block of mdBlocks) { + let score = 0; + const normalizedBlock = block.replace(/\s+/g, ''); + for (const kw of keywords) { + if (normalizedBlock.includes(kw.replace(/\s+/g, ''))) score++; + } + if (score > maxScore) { + maxScore = score; + bestMatch = block; + } + } + if (bestMatch && maxScore > 0) { + restored[id] = bestMatch; + } else { + warnings.push(`一致なし: ${id} (keywords: ${keywords.slice(0, 3).join(', ')})`); + restored[id] = brokenCode; // フォールバック: 元のまま残す + } + } + return { diagrams: restored, warnings }; +} + +// --- CLI エントリポイント ---------------------------------------------------- + +if (import.meta.main) { + const [htmlPath, mdPath] = process.argv.slice(2); + if (!htmlPath || !mdPath) { + console.error('Usage: bun run restore_diagrams.mjs '); + process.exit(1); + } + for (const p of [htmlPath, mdPath]) { + if (!fs.existsSync(p)) { + console.error(`❌ File not found: ${p}`); + process.exit(1); + } + } + + let html = fs.readFileSync(htmlPath, 'utf8'); + const md = fs.readFileSync(mdPath, 'utf8'); + + const mdBlocks = extractMdMermaidBlocks(md); + if (mdBlocks.length === 0) { + console.error(`❌ No mermaid blocks found in ${mdPath}`); + process.exit(1); + } + + const diagramsMatch = html.match(/const DIAGRAMS = (\{[\s\S]*?\});/); + if (!diagramsMatch) { + console.error(`❌ Could not find "const DIAGRAMS = {...};" in ${htmlPath}`); + process.exit(1); + } + + let diagrams; + try { + diagrams = JSON.parse(diagramsMatch[1]); + } catch { + console.error('❌ DIAGRAMS が JSON.parse できません。JSON 形式の DIAGRAMS のみ対応します。'); + process.exit(1); + } + + const { diagrams: restored, warnings } = restoreDiagrams(diagrams, mdBlocks); + warnings.forEach((w) => console.warn(' ⚠️ ' + w)); + + html = html.replace(diagramsMatch[1], JSON.stringify(restored, null, 2)); + fs.writeFileSync(htmlPath, html, 'utf8'); + console.log(`\n✅ Restored ${Object.keys(restored).length} diagrams into ${htmlPath}`); +} diff --git a/.agents/skills/html-to-nextjs-migration/SKILL.md b/.agents/skills/html-to-nextjs-migration/SKILL.md new file mode 100644 index 000000000..088499132 --- /dev/null +++ b/.agents/skills/html-to-nextjs-migration/SKILL.md @@ -0,0 +1,400 @@ +--- +name: infra-html-to-nextjs-migration +description: > + Complete workflow for migrating static HTML pages to Next.js App Router page.tsx + in this repository (GCP/AWS 資格試験対策 Next.js 学習アプリ). Covers CSS variable + mapping (HTML vars to Tailwind v4 @theme tokens), page-specific CSS extraction, + EXAMS-driven Header navigation, MermaidDiagram reuse, and CLAUDE.md documentation. + Extends the global html-to-nextjs-migration skill with project-specific knowledge: + the canonical guide-page structure, GCP design-token map, sidebar layout recipe, + and a token-efficient reading protocol. + Trigger: HTMLマイグレーション, ページ移行, HTML変換, 静的HTML移行, CSS変数マッピング, + new page creation from HTML, HTMLからpage.tsx, ガイドページ移行, Mermaid 図移行. +--- + +# HTML → Next.js Migration Workflow(本リポジトリ専用) + +## Goal + +Provide the complete, ordered workflow for converting a standalone HTML page (with embedded `|` ブロック・本文・末尾 ` + + + + + From cadb47e58d3346a3aba79b481818c6876b48a75b Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 13:03:34 +0900 Subject: [PATCH 066/123] test: strengthen guide regression coverage --- .../domain4/page.test.tsx | 81 +++++++++++++++++-- .../page.test.tsx | 30 ++++--- .../page.test.tsx | 2 +- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/__tests__/aws/solutions-architect-associate/domain4/page.test.tsx b/__tests__/aws/solutions-architect-associate/domain4/page.test.tsx index e567aa280..1b2426067 100644 --- a/__tests__/aws/solutions-architect-associate/domain4/page.test.tsx +++ b/__tests__/aws/solutions-architect-associate/domain4/page.test.tsx @@ -94,13 +94,78 @@ describe('AWS SAA Domain 4 Guide Page', () => { it('renders source and reference links with correct href attributes', () => { const { container } = render(); - const references = container.querySelector('#references')?.parentElement; - expect(references).not.toBeNull(); - - const links = references!.querySelectorAll('a[target="_blank"]'); - expect(links.length).toBeGreaterThanOrEqual(10); - - const hrefs = Array.from(links).map((a) => a.getAttribute('href')); - expect(hrefs.some((h) => h?.includes('wellarchitected/latest/cost-optimization-pillar'))).toBe(true); + const references = container.querySelector('#references + .ref-grid'); + expect(references).toBeInTheDocument(); + + const expectedReferences = [ + ['AWS Cost Explorer とは', 'https://docs.aws.amazon.com/cost-management/latest/userguide/ce-what-is.html'], + ['AWS Budgets を使用したコスト管理', 'https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html'], + ['AWS Cost and Usage Report とは', 'https://docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html'], + ['コスト配分タグの使用', 'https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-alloc-tags.html'], + ['AWS Organizations の連結請求', 'https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts_consolidated-billing.html'], + ['AWS Trusted Advisor', 'https://docs.aws.amazon.com/awssupport/latest/user/trusted-advisor.html'], + ['AWS Compute Optimizer とは', 'https://docs.aws.amazon.com/compute-optimizer/latest/ug/what-is.html'], + ['AWS Well-Architected Framework - コスト最適化の柱', 'https://docs.aws.amazon.com/wellarchitected/latest/cost-optimization-pillar/welcome.html'], + ['Amazon S3 とは', 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html'], + ['Amazon S3 ストレージクラス', 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html'], + ['S3 オブジェクトライフサイクル管理', 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html'], + ['Requester Pays バケットの使用', 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html'], + ['Amazon EBS ボリュームタイプ', 'https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html'], + ['Amazon EFS とは', 'https://docs.aws.amazon.com/efs/latest/ug/whatisefs.html'], + ['Amazon FSx', 'https://aws.amazon.com/fsx/'], + ['AWS DataSync とは', 'https://docs.aws.amazon.com/datasync/latest/userguide/what-is-datasync.html'], + ['AWS Transfer Family とは', 'https://docs.aws.amazon.com/transfer/latest/userguide/what-is-aws-transfer-family.html'], + ['AWS Storage Gateway とは', 'https://docs.aws.amazon.com/storagegateway/latest/userguide/WhatIsStorageGateway.html'], + ['AWS Snow Family', 'https://aws.amazon.com/snow/'], + ['AWS Backup とは', 'https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html'], + ['S3 Glacier Vault Lock', 'https://docs.aws.amazon.com/amazonglacier/latest/dev/vault-lock.html'], + ['Amazon EC2 インスタンス購入オプション', 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-purchasing-options.html'], + ['Spotインスタンスの使用', 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html'], + ['Savings Plans とは', 'https://docs.aws.amazon.com/savingsplans/latest/userguide/what-is-savings-plans.html'], + ['リザーブドインスタンス', 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-reserved-instances.html'], + ['Amazon EC2 インスタンスタイプ', 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html'], + ['Amazon EC2 Auto Scaling とは', 'https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html'], + ['EC2 インスタンスの休止(Hibernate)', 'https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html'], + ['AWS Lambda とは', 'https://docs.aws.amazon.com/lambda/latest/dg/welcome.html'], + ['AWS Fargate とは', 'https://docs.aws.amazon.com/AmazonECS/latest/userguide/what-is-fargate.html'], + ['AWS Batch とは', 'https://docs.aws.amazon.com/batch/latest/userguide/what-is-batch.html'], + ['Elastic Load Balancing とは', 'https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/elastic-load-balancing.html'], + ['Application Load Balancer', 'https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html'], + ['Network Load Balancer', 'https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html'], + ['Gateway Load Balancer', 'https://docs.aws.amazon.com/elasticloadbalancing/latest/gateway/introduction.html'], + ['AWS Outposts とは', 'https://docs.aws.amazon.com/outposts/latest/userguide/what-is-outposts.html'], + ['AWS Local Zones とは', 'https://docs.aws.amazon.com/local-zones/latest/ug/what-is-aws-local-zones.html'], + ['AWS Wavelength とは', 'https://docs.aws.amazon.com/wavelength/latest/developerguide/what-is-wavelength.html'], + ['Amazon RDS とは', 'https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html'], + ['Amazon Aurora の概要', 'https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html'], + ['Amazon DynamoDB とは', 'https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html'], + ['DynamoDB の読み込み/書き込みキャパシティモード', 'https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html'], + ['Amazon RDS Proxy', 'https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html'], + ['Amazon RDS の読み取りレプリカの使用', 'https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html'], + ['Amazon ElastiCache とは', 'https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/WhatIs.html'], + ['DynamoDB Accelerator (DAX)', 'https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.html'], + ['Amazon RDS の自動バックアップの使用', 'https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html'], + ['AWS Database Migration Service とは', 'https://docs.aws.amazon.com/dms/latest/userguide/Welcome.html'], + ['AWS Schema Conversion Tool', 'https://docs.aws.amazon.com/SchemaConversionTool/latest/userguide/CHAP_Welcome.html'], + ['NATゲートウェイ', 'https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html'], + ['AWS Direct Connect とは', 'https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html'], + ['AWS Site-to-Site VPN', 'https://docs.aws.amazon.com/vpn/latest/s2svpn/VPC_VPN.html'], + ['AWS Transit Gateway とは', 'https://docs.aws.amazon.com/vpc/latest/tgw/what-is-transit-gateway.html'], + ['VPCピアリングとは', 'https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html'], + ['VPCエンドポイント', 'https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints.html'], + ['Amazon EC2 オンデマンド料金(データ転送)', 'https://aws.amazon.com/ec2/pricing/on-demand/'], + ['AWSの料金の仕組み: データ転送', 'https://docs.aws.amazon.com/whitepapers/latest/how-aws-pricing-works/data-transfer.html'], + ['Amazon CloudFront とは', 'https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html'], + ['AWS Global Accelerator とは', 'https://docs.aws.amazon.com/global-accelerator/latest/dg/what-is-global-accelerator.html'], + ['Amazon API Gateway でのリクエストスロットリング', 'https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html'], + ['AWS Certified Solutions Architect - Associate (SAA-C03) Exam Guide', 'https://docs.aws.amazon.com/aws-certification/latest/solutions-architect-associate-03/solutions-architect-associate-03.html'], + ['Content Domain 4: Design Cost-Optimized Architectures', 'https://docs.aws.amazon.com/aws-certification/latest/solutions-architect-associate-03/solutions-architect-associate-03-domain4.html'], + ] as const; + + for (const [name, href] of expectedReferences) { + const link = references!.querySelector(`a[href="${href}"]`); + expect(link, name).not.toBeNull(); + expect(link!.closest('li'), name).toHaveTextContent(name); + } }); }); diff --git a/__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx b/__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx index 4c02160de..cbaf37639 100644 --- a/__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx +++ b/__tests__/cisco/ccna/automation-application-deployment-security/page.test.tsx @@ -152,17 +152,29 @@ describe('CcnaAppDeploymentSecurityPage', () => { }); it('目次クリック時にURLフラグメントとアクティブ項目を更新する', () => { + const originalScrollIntoView = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollIntoView'); + const originalHash = window.location.hash; const scrollIntoView = vi.fn(); - Element.prototype.scrollIntoView = scrollIntoView; - const { container } = render(); - const link = container.querySelector('a[href="#chapter4"]'); - - expect(link).not.toBeNull(); - fireEvent.click(link!); - expect(window.location.hash).toBe('#chapter4'); - expect(link).toHaveClass('active'); - expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' }); + try { + Element.prototype.scrollIntoView = scrollIntoView; + const { container } = render(); + const link = container.querySelector('a[href="#chapter4"]'); + + expect(link).not.toBeNull(); + fireEvent.click(link!); + + expect(window.location.hash).toBe('#chapter4'); + expect(link).toHaveClass('active'); + expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' }); + } finally { + if (originalScrollIntoView) { + Object.defineProperty(Element.prototype, 'scrollIntoView', originalScrollIntoView); + } else { + Reflect.deleteProperty(Element.prototype, 'scrollIntoView'); + } + window.location.hash = originalHash; + } }); it('Docker CMDの説明で「最後」を正しく表示する', () => { diff --git a/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx index 5da3e3ebd..746628633 100644 --- a/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx +++ b/__tests__/cisco/ccna/automation-cisco-platforms-and-development/page.test.tsx @@ -154,7 +154,7 @@ describe('CcnaCiscoPlatformsDevelopmentPage', () => { const moduleStyles = existsSync(modulePath) ? readFileSync(modulePath, 'utf8') : ''; const { container } = render(); - expect(moduleStyles).not.toMatch(/^\s*--(?:bg|border|accent|text|radius|sidebar-width|font-sans|font-mono)\s*:/m); + expect(moduleStyles).not.toMatch(/^\s*--[\w-]+\s*:/m); expect(moduleStyles).toContain('margin-inline: auto'); expect(container.querySelector('.article-body')).toBeInTheDocument(); }); From 98d271b6ce23d5eff4abb00fab3eaa6229675b26 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 13:06:16 +0900 Subject: [PATCH 067/123] docs(rules): clarify validation and sync workflows --- .agents/AGENTS.md | 3 +++ .agents/rules/css-cache-reset.md | 27 +++++++++++++++++++----- .agents/rules/migration-progress-sync.md | 19 +++++++++++++---- .agents/rules/no-absolute-paths.md | 16 ++++++++------ .agents/rules/tdd-commit-workflow.md | 6 ++++-- .agents/skills/spec-sync/SKILL.md | 23 +++++++++++--------- 6 files changed, 67 insertions(+), 27 deletions(-) diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 6db8483db..8ac01219a 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -1,5 +1,7 @@ # Project Rules & Quality Mandates: Cloud Infrastructure Studies +(最終更新日: 2026-08-09) + 本ファイルは、本プロジェクトにおけるコード実装・HTML移行・スタイリング・テスト駆動開発(TDD)の厳格な品質基準と運用ルールを規定する。 ## 1. デザイン完全移転原則(厳守) @@ -15,6 +17,7 @@ - 新しい黄色系カラーコードを使用する際は、必ず [`components/MermaidDiagram.module.css`](../components/MermaidDiagram.module.css) の黒文字転換セレクタ(`.mermaidTarget :global(.node[style*="..."] .nodeLabel)` 等)にカラーコードを追加し、黒文字 (`#000000 !important`) で高コントラスト表示されることを確認すること。 - **図解の拡大・枠外はみ出し防止**: - `diagram-wrapper` および `mermaid-wrap` 内の SVG 要素には `max-width: 100% !important; height: auto !important;` を指定し、ヘッダーや画面枠をはみ出さないよう収めること。 + - **例外**: `MermaidDiagram.tsx` で `preserveNaturalScale={true}` を指定した図は、SVG の自然倍率維持を優先して `max-width: none` とし、親ラッパーの `overflow-x: auto` で横スクロールを提供する。この場合、`diagram-wrapper` / `mermaid-wrap` から `max-width: 100% !important` を SVG に適用してはならない。 ## 3. TDD & コミットワークフローの鉄則 diff --git a/.agents/rules/css-cache-reset.md b/.agents/rules/css-cache-reset.md index 2be1577fd..ceb5f1827 100644 --- a/.agents/rules/css-cache-reset.md +++ b/.agents/rules/css-cache-reset.md @@ -1,5 +1,7 @@ # globals.css 変更後のキャッシュリセットルール +(最終更新日: 2026-08-09) + ## 問題 `globals.css`(特に `@theme` ブロック)を変更した後、`.next` に古い CSS チャンクが残ると、CSS カスタムプロパティ(`--color-background` 等)が空文字に解決されてページのダークモードが消える。 @@ -8,10 +10,18 @@ ```js getComputedStyle(document.documentElement).getPropertyValue('--color-background') -// "" が返る → キャッシュ汚染 +// "" が返る → CSS変数が未適用の症状(キャッシュ汚染とは未確定) // "#08090f" が返る → 正常 ``` +空文字の場合は `.next` を削除する前に、次を順に確認する: + +1. ブラウザの Network パネルで対象ページの CSS が 200 応答で読み込まれていること。 +2. 読み込まれた生成 CSS に `--color-background` の定義が含まれていること。 +3. ルートレイアウトから `app/globals.css` が import されていること。 + +これらが正常でも古い CSS が配信される場合に、キャッシュ不整合として以下の削除・再起動を行う。 + ## ルール ### 必須トリガー @@ -25,13 +35,20 @@ getComputedStyle(document.documentElement).getPropertyValue('--color-background' ### 手順 ```bash -# 1. dev サーバーを停止(ポート 3000 または起動中のポートを使用中の場合) -kill $(lsof -ti:3000) 2>/dev/null +# 1. 設定済み、または実際に LISTEN 中の dev サーバーポートを確認 +lsof -nP -iTCP -sTCP:LISTEN | rg 'node|next' + +# 2. 対象 PID のコマンドと作業対象がこのプロジェクトの dev サーバーであることを確認 +dev_pid=$(lsof -tiTCP: -sTCP:LISTEN) +ps -p "$dev_pid" -o pid=,command= + +# 3. 確認済みの dev サーバーだけを停止 +kill "$dev_pid" -# 2. キャッシュ削除 +# 4. キャッシュ削除 rm -rf .next -# 3. dev サーバー再起動 +# 5. dev サーバー再起動(プロジェクトで設定されたポートを使用) bun run dev ``` diff --git a/.agents/rules/migration-progress-sync.md b/.agents/rules/migration-progress-sync.md index 27fbb8885..32b9ba85d 100644 --- a/.agents/rules/migration-progress-sync.md +++ b/.agents/rules/migration-progress-sync.md @@ -7,13 +7,15 @@ paths: # MIGRATION_PROGRESS.md セッション終了前同期ルール +(最終更新日: 2026-08-09) + HTML → Next.js 移行セッションでは、**コンテキストが逼迫する前に**必ず以下を実施してセッションを終えること。 ## 実行タイミング **AI エージェントへの厳格な指示**: このプロトコルは提案ではなく絶対的な**ゲート条件(Gate Condition)**です。 -ユーザーへ作業完了を報告する前に、以下の手続き(コード変更のコミット -> 進捗ファイルの更新 -> 進捗ファイルのコミット)を**ユーザーの許可を待たずに自律的に、必ずステップバイステップで**実行してください。ステップバイステップのコミット分割ルールを無視して一括コミットを行ったり、コミットせずにユーザーに判断を委ねたりすることは重大な規約違反です。 +コミットはリポジトリの単一コミット認可方針と `.agents/rules/tdd-commit-workflow.md` に従い、ユーザーの依頼がコミットを明示または許可している場合にのみ実行してください。コミット前に `git status --short` と `git diff` を確認し、実装コミットには関連コードだけ、進捗同期コミットには関連する進捗ファイルだけを含めます。追加の認可が必要な場合に自律コミットしてはなりません。 ### 必須(毎ページ・例外なし) @@ -36,16 +38,19 @@ HTML → Next.js 移行セッションでは、**コンテキストが逼迫す ```bash bun run build # ビルド成功を確認 bun run lint # ESLint エラーなし -git rev-parse --short HEAD +implementation_head=$(git rev-parse --short HEAD) ``` +`implementation_head` は進捗ファイルを編集する前の最新実装コミットであり、後続の進捗同期コミットとは区別する。 + ### 2. `MIGRATION_PROGRESS.md` を更新 更新対象フィールド: | フィールド | 更新内容 | |---|---| -| `最新 HEAD` | `git rev-parse --short HEAD` の実値 + コミットメッセージ要約 | +| `最新実装 HEAD` | 進捗ファイル編集前に保存した `implementation_head` + 実装コミットメッセージ要約 | +| `最新進捗同期コミット` | 直前に完了した進捗同期コミット。新しい同期コミット作成後に `git rev-parse --short HEAD` で別途取得し、次回同期時の監査基準として扱う | | `次の作業` | 次セッションで **最初に** 取り掛かるページ(例: `Gcp-ace-complete-advanced-guide.html 移行`) | | `ビルド状態` | `bun run build` / `bun run lint` の最新状態 | @@ -53,17 +58,23 @@ git rev-parse --short HEAD `現在地` の値と一致するように再開プロンプト内の以下を書き換える: -- `最新 HEAD: ` の値 +- `最新実装 HEAD: ` の値(`implementation_head` と一致) +- `最新進捗同期コミット: ` の値(前回の進捗同期コミットと一致) - `次の作業:` の説明(ページ粒度で具体的に) - 未移行 HTML の残数 ### 4. コミット ```bash +git status --short git add MIGRATION_PROGRESS.md +git diff --cached -- MIGRATION_PROGRESS.md git commit -m "chore(docs): update MIGRATION_PROGRESS.md — <作業内容の1行要約>" +progress_sync_commit=$(git rev-parse --short HEAD) ``` +`progress_sync_commit` は作成した進捗同期コミットの識別子として実行結果・引き継ぎに記録し、`最新実装 HEAD` を上書きしない。 + ## 禁止 - HEAD 値をコミットせず新セッションに引き継ぐ(ズレが発生する) diff --git a/.agents/rules/no-absolute-paths.md b/.agents/rules/no-absolute-paths.md index cb4f1529a..d6f1ed4db 100644 --- a/.agents/rules/no-absolute-paths.md +++ b/.agents/rules/no-absolute-paths.md @@ -1,5 +1,7 @@ # リポジトリ内ファイルへの絶対パス記載禁止ルール +(最終更新日: 2026-08-09) + ## ルール コミット対象のファイル(ドキュメント、設定ファイル、コードのコメント等)に @@ -8,9 +10,9 @@ **禁止例**: ```text -/Users/johndoe/.claude/plans/my-plan.md -/home/johndoe/workspace/project/... -C:\Users\johndoe\... +/Users//.claude/plans/my-plan.md +/home//workspace/project/... +C:\Users\\... ``` **許可例**: @@ -24,7 +26,6 @@ C:\Users\johndoe\... `~/.claude/plans/my-plan.md` などのチルダ(`~`)を使用したパス表記は、ローカルの個人環境におけるホームディレクトリを示します。これらは共有・コミットされるドキュメントやコード内に含めると動作しないか、ユーザー名が含まれる恐れがあるため、コミット対象のファイルへの記載は禁止されています。 外部ファイルを参照したい場合は、後述の [外部ファイルを参照したい場合](#外部ファイルを参照したい場合) を確認し、リポジトリ配下にファイルをコピーした上で相対パスで参照してください。 - ## 適用対象 - `MIGRATION_PROGRESS.md` などのドキュメント @@ -56,8 +57,11 @@ AI エージェントは、`git commit` などのコミットを行う前に、 ```bash # コミット対象の差分にローカル絶対パス(Users/ や home/)が含まれていないかチェック -# プレースホルダー(johndoe)を除く絶対パスが検出された場合はコミットを中止する -git diff --cached | grep -E '^\+[^+]' | grep -E '(/Users/|/home/|C:\\Users\\)' | grep -vE 'johndoe' +# 例示用プレースホルダー文字列だけを除去した後、絶対パスが検出された場合はコミットを中止する +git diff --cached \ + | grep -E '^\+[^+]' \ + | sed -E 's#/Users//##g; s#/home//##g; s#C:\\Users\\\\##g' \ + | grep -E '(/Users/|/home/|C:\\Users\\)' ``` このチェックで結果(追加行)が出力された場合は、該当箇所を削除または相対パスに変更し、クリーンであることを確認した上でコミットを実行してください。 diff --git a/.agents/rules/tdd-commit-workflow.md b/.agents/rules/tdd-commit-workflow.md index c1430a2a8..30e51d762 100644 --- a/.agents/rules/tdd-commit-workflow.md +++ b/.agents/rules/tdd-commit-workflow.md @@ -11,6 +11,8 @@ paths: # TDD & Step-by-Step Commit Workflow Rules +(最終更新日: 2026-08-09) + ## 目的 (Objective) LLMエージェントがコードを実装する際、要件漏れや意図しない破壊的変更を防ぐため、**テスト駆動開発(TDD)**と**ステップバイステップの細かなコミット**を**絶対の義務(マスト)**として規定する。 @@ -56,13 +58,13 @@ LLMエージェントがコードを実装する際、要件漏れや意図し - [ ] Green 完了時に元資料と TSX の文字数・行数・内容を照合し、大幅な乖離がないか? - [ ] 意図的な要約や一部省略コードを実装してもテストが Pass してしまうような抜け穴がないか? -- **実行**: `bun test` または `bun run test` で失敗(またはコンパイルエラー)を確認する(リファクタリングはパスを確認)。 +- **実行**: `bun run test` で失敗(またはコンパイルエラー)を確認する(リファクタリングはパスを確認)。 - **コミット**: `test(): add failing tests for [機能名/バグID]` (または `test: add failing tests for ...`) — **テスト作成直後に必ずコミットすること。Step 2への繰り越しは禁止。** ### Step 2: Green(最小実装と成功) - テストをパスさせるための最小限のプロダクションコードを `app/` または `components/` 等に実装する。 -- **実行**: `bun test` でテストがPassすることを確認する。 +- **実行**: `bun run test` でテストがPassすることを確認する。 - **コミット**: `feat(): implement [機能名] to pass tests` (または `feat/fix: implement ...`) ### Step 3: Refactor(リファクタリング・最適化・統合) diff --git a/.agents/skills/spec-sync/SKILL.md b/.agents/skills/spec-sync/SKILL.md index 70da2107f..b4b3cf19d 100644 --- a/.agents/skills/spec-sync/SKILL.md +++ b/.agents/skills/spec-sync/SKILL.md @@ -5,6 +5,8 @@ description: Audit and update all repository specifications (CLAUDE.md, GEMINI.m # 仕様書・テスト進捗同期スキル (spec-sync) +(最終更新日: 2026-08-09) + **🚨 開発時の必須ルール(TDD & Step-by-step Commit) 🚨** 仕様書の更新やテスト進捗の更新作業においても、対応するコード修正(実装やテスト修正)を伴う場合は必ず `.claude/rules/tdd-commit-workflow.md` のステップバイステップ・コミットルールに従うこと。 @@ -30,7 +32,7 @@ description: Audit and update all repository specifications (CLAUDE.md, GEMINI.m | `MIGRATION_PROGRESS.md` | `Updated YYYY-MM-DD`(現在地テーブル内) | 現在地テーブル内、または「最終 HEAD」欄 | | `docs/TEST_COVERAGE_PROGRESS.md` | `最終更新日: YYYY-MM-DD` | ファイル冒頭付近 | | `docs/coverage-dashboard.html` | `` | ヘッダーのメタ情報エリア(`Updated`)およびフッター | -| 各個別 `SKILL.md` / `*.md` | `(最終更新日: YYYY-MM-DD)` または未移行HTMLリスト等の日付 | タイトル下、または進捗管理の日付欄 | +| `.agents/AGENTS.md`、`.agents/rules/*.md`、各個別 `.agents/skills/*/SKILL.md` / `*.md` | `(最終更新日: YYYY-MM-DD)` または未移行HTMLリスト等の日付 | タイトル下、または進捗管理の日付欄。新規作成時から必須とし、既存ファイルは次回編集時に追記する | --- @@ -144,7 +146,7 @@ find app -name "page.tsx" 2>/dev/null | sed 's|app/||' | sed 's|/page.tsx||' find __tests__/ -name "*.test.ts" -o -name "*.test.tsx" 2>/dev/null | sort # D. テスト実行結果の取得 -bun test 2>&1 | tail -5 +set -o pipefail; bun run test 2>&1 | tail -5 bun run lint 2>&1 | tail -5 ``` @@ -163,9 +165,10 @@ bun run lint 2>&1 | tail -5 - [ ] 起動手順、テストの実行、定義に変更はないか。 - [ ] 最終更新日のタイムスタンプが最新化されているか。 - [ ] **`MIGRATION_PROGRESS.md` 監査** - - [ ] `最新 HEAD` が `git rev-parse --short HEAD` の出力と完全に一致しているか。 + - [ ] `最新実装 HEAD` が、進捗同期コミットの直前に保存した実装コミットと完全に一致しているか。 + - [ ] `最新進捗同期コミット` が、直前の進捗同期コミットと完全に一致し、`最新実装 HEAD` と混同されていないか。 - [ ] `ビルド状態` の `bun test` の pass 数が現在の実測値と一致しているか。 - - [ ] `## 次回セッションでの再開プロンプト` の `最新 HEAD`、`テスト件数` が上記と同期しているか。 + - [ ] `## 次回セッションでの再開プロンプト` の `最新実装 HEAD`、`最新進捗同期コミット`、`テスト件数` が上記とそれぞれの意味で同期しているか。 - [ ] 最終更新日(タイムスタンプ)が更新されているか。 - [ ] **`docs/TEST_COVERAGE_PROGRESS.md` 監査** - [ ] Section 1 の全体サマリーが、最新の `dashboard` スクリプト出力値と同期しているか。 @@ -186,13 +189,13 @@ bun run lint 2>&1 | tail -5 仕様書のみの同期更新のコミットには**ソースコードの変更を一切含めない**でください(TDD コミット分割ルール)。 ```bash -# 1. .claude 内のルール・スキル変更を .gemini に同期 -rm -rf .gemini/rules/* .gemini/skills/* -cp -R .claude/rules/* .gemini/rules/ -cp -R .claude/skills/* .gemini/skills/ +# 1. .claude 内のルール・スキル変更を既存設定を保持したまま .gemini に同期 +rsync -a .claude/rules/ .gemini/rules/ +rsync -a .claude/skills/ .gemini/skills/ -# 2. 変更された仕様書とルール・スキルをステージングしてコミット -git add CLAUDE.md GEMINI.md README.md MIGRATION_PROGRESS.md docs/TEST_COVERAGE_PROGRESS.md docs/coverage-dashboard.html .claude/ .gemini/ +# 2. 同期対象4ディレクトリだけをステージし、内容を確認してコミット +git add .claude/rules/ .claude/skills/ .gemini/rules/ .gemini/skills/ +git diff --cached git commit -m "chore(docs): sync spec files — <具体的な更新理由や同期内容>" ``` From 5b0593b7b68a76c6f13e04684d136252024e38c7 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 13:08:32 +0900 Subject: [PATCH 068/123] test(fix-mermaid): add failing pipeline regressions --- .../fix-mermaid/render-pipeline.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 __tests__/skills/fix-mermaid/render-pipeline.test.ts diff --git a/__tests__/skills/fix-mermaid/render-pipeline.test.ts b/__tests__/skills/fix-mermaid/render-pipeline.test.ts new file mode 100644 index 000000000..289902e28 --- /dev/null +++ b/__tests__/skills/fix-mermaid/render-pipeline.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, test } from 'vitest'; +import { + applyPipeline, + ensureInitFlags, +} from '../../../.agents/skills/fix-mermaid/scripts/apply_render_pipeline.mjs'; +import { extractDiagramsDefinition } from '../../../.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs'; + +const FIXTURE = ` + + + +
flowchart TD +A --> B
+ + +`; + +describe('ensureInitFlags', () => { + test('initialize 外の securityLevel は設定済みと誤認しない', () => { + const input = ` + const unrelated = { securityLevel: 'strict' }; + const label = "securityLevel: loose"; + // securityLevel: strict + mermaid.initialize({ theme: 'dark' }); + `; + const out = ensureInitFlags(input); + + expect(out).toContain("mermaid.initialize({ securityLevel: 'loose', theme: 'dark' })"); + expect(out.match(/securityLevel/g)?.length).toBe(4); + }); +}); + +describe('applyPipeline', () => { + test('DIAGRAMS 未定義なら入力を変更せず即座に失敗する', () => { + const input = FIXTURE.replace(/\s*const DIAGRAMS = \{[\s\S]*?\};/, ''); + const original = input; + + expect(() => applyPipeline(input)).toThrow(/DIAGRAMS/); + expect(input).toBe(original); + }); +}); + +describe('extractDiagramsDefinition', () => { + test('JSON互換の正準DIAGRAMS形式を解析する', () => { + const html = ``; + + expect(extractDiagramsDefinition(html).diagrams).toEqual({ + 'diag-1': 'flowchart TD\nA --> B', + }); + }); + + test('既存のJavaScriptテンプレートリテラル形式を評価せず解析する', () => { + const html = ``; + + expect(extractDiagramsDefinition(html).diagrams).toEqual({ + 'diag-1': 'flowchart TD\nA["${notEvaluated}"] --> B', + 'diag-2': 'sequenceDiagram\nA->>B: escaped `tick`', + }); + }); +}); From e5d5d72851f261a70261ba2325f1e92f1800fbfe Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 13:10:38 +0900 Subject: [PATCH 069/123] fix(fix-mermaid): harden render pipeline parsing --- .agents/skills/fix-mermaid/SKILL.md | 15 +- .../scripts/apply_render_pipeline.mjs | 140 ++++++++++++++---- .../fix-mermaid/scripts/restore_diagrams.mjs | 112 ++++++++++++-- 3 files changed, 222 insertions(+), 45 deletions(-) diff --git a/.agents/skills/fix-mermaid/SKILL.md b/.agents/skills/fix-mermaid/SKILL.md index 016aaac85..8ce1a52a0 100644 --- a/.agents/skills/fix-mermaid/SKILL.md +++ b/.agents/skills/fix-mermaid/SKILL.md @@ -9,16 +9,18 @@ description: > # Mermaid 構文・描画修正スキル +(最終更新日: 2026-08-09) + ## 🚀 まず再利用スクリプトを使う(トークン節約・最優先) 静的 HTML の Mermaid 描画崩れを直すときは、**ボイラープレート(render ループ・SVG 後処理・中央寄せ CSS)を手書きで再生成しないこと**。以下の再利用スクリプトで機械的処理を一括適用できる。 -1. **図ソースを JS テンプレートリテラルで定義**(LLM の判断が必要なのはここだけ): - 各図を 1 ステートメント 1 行・カラム 0・改行は `
` で `const DIAGRAMS = { 'diag-1': \`flowchart TD ...\` }` として HTML の ``; + const { diagrams } = extractDiagramsDefinition(html); + expect(diagrams["diag-1"]).toBe("flowchart TD\nA --> B"); + }); + + test("既存のテンプレートリテラル形式との互換性を維持する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + expect(diagrams["diag-1"]).toBe("flowchart TD\nA --> B"); + }); + + test("コメントと文字列の false match を飛ばして実宣言を抽出する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + expect(diagrams).toEqual({ "diag-1": "flowchart TD\nA --> B" }); + }); + + test.each([ + "// const DIAGRAMS = {};", + "/* const DIAGRAMS = {}; */", + "const example = 'const DIAGRAMS = {};'", + 'const example = "const DIAGRAMS = {};"', + "const example = `const DIAGRAMS = {};`", + ])("コメントや文字列だけの宣言候補を拒否する: %s", (falseMatch) => { + expect(() => extractDiagramsDefinition(``)).toThrow( + "const DIAGRAMS の定義が見つかりません", + ); + }); +}); + +describe("serializeDiagramsDefinition", () => { + test("実改行を正準 JSON の単一エスケープで出力する", () => { + const serialized = serializeDiagramsDefinition({ + "diag-1": "flowchart TD\nA --> B", + }); + expect(serialized).toContain(String.raw`flowchart TD\nA --> B`); + expect(serialized).not.toContain(String.raw`flowchart TD\\nA --> B`); + }); +}); From fa7c3b2621e84e76eb3c3235ebb637afde5ef5d1 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 14:12:35 +0900 Subject: [PATCH 080/123] fix(fix-mermaid): parse options and declarations safely --- .agents/skills/fix-mermaid/SKILL.md | 2 +- .../scripts/apply_render_pipeline.mjs | 95 ++++++++++++++----- .../fix-mermaid/scripts/javascript_source.mjs | 90 ++++++++++++++++++ .../fix-mermaid/scripts/restore_diagrams.mjs | 16 +++- 4 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 .agents/skills/fix-mermaid/scripts/javascript_source.mjs diff --git a/.agents/skills/fix-mermaid/SKILL.md b/.agents/skills/fix-mermaid/SKILL.md index 8ce1a52a0..7439f8f3a 100644 --- a/.agents/skills/fix-mermaid/SKILL.md +++ b/.agents/skills/fix-mermaid/SKILL.md @@ -16,7 +16,7 @@ description: > 静的 HTML の Mermaid 描画崩れを直すときは、**ボイラープレート(render ループ・SVG 後処理・中央寄せ CSS)を手書きで再生成しないこと**。以下の再利用スクリプトで機械的処理を一括適用できる。 1. **図ソースを JSON 互換の正準オブジェクトで定義**(LLM の判断が必要なのはここだけ): - 各図を 1 ステートメント 1 行・カラム 0・改行は `\n` または `
` とし、`const DIAGRAMS = { "diag-1": "flowchart TD\\nA --> B" };` の形式で HTML の ` + `; + + expect(() => extractDiagramsDefinition(html)).toThrow( + "DIAGRAMS のキーはクォートされた文字列リテラルで指定してください。", + ); + }); + test("コメントと文字列の false match を飛ばして実宣言を抽出する", () => { const html = ``; - - expect(extractDiagramsDefinition(html).diagrams).toEqual({ - 'diag-1': 'flowchart TD\nA --> B', - }); - }); - - test('既存のJavaScriptテンプレートリテラル形式を評価せず解析する', () => { - const html = ``; - - expect(extractDiagramsDefinition(html).diagrams).toEqual({ - 'diag-1': 'flowchart TD\nA["${notEvaluated}"] --> B', - 'diag-2': 'sequenceDiagram\nA->>B: escaped `tick`', - }); }); }); From 145787aee46b19bc93669b03f87c74285bebd594 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 16:14:04 +0900 Subject: [PATCH 088/123] fix(styles): scope GCP tokens to owning page --- .agents/AGENTS.md | 4 ++-- .agents/rules/css-cache-reset.md | 24 +++++++++++++++---- .../skills/html-to-nextjs-migration/SKILL.md | 8 +++---- .../section1/page.css | 12 +++++++--- app/globals.css | 7 ------ 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 8ac01219a..2bd6c8308 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -7,8 +7,8 @@ ## 1. デザイン完全移転原則(厳守) - **元HTMLの `:root` スタイル変数が表すデザイン値の100%全量移植**: - - 静的HTMLから Next.js への移行時、元HTMLの `|` ブロック・本文・末尾 ` + + +`; + const out = injectRenderLoop(input); + + expect(out.indexOf("function applySvgFixups")).toBeGreaterThan( + out.indexOf("mermaid.initialize({ startOnLoad: false })"), + ); + }); }); describe("injectCenteringCss", () => { @@ -199,4 +222,13 @@ describe("applyPipeline (統合・冪等性)", () => { ); expect(applyPipeline(input).html).toContain('id="diag-1"'); }); + + test("正規表現リテラル内の候補より後にある実宣言を検出する", () => { + const input = FIXTURE.replace( + " const DIAGRAMS = {", + ` const declarationPattern = /const DIAGRAMS = \\{[^}]*\\}/g; + const DIAGRAMS = {`, + ); + expect(applyPipeline(input).html).toContain('id="diag-1"'); + }); }); diff --git a/.agents/skills/fix-mermaid/scripts/restore_diagrams.test.ts b/.agents/skills/fix-mermaid/scripts/restore_diagrams.test.ts index 27c61024a..48c3db0f6 100644 --- a/.agents/skills/fix-mermaid/scripts/restore_diagrams.test.ts +++ b/.agents/skills/fix-mermaid/scripts/restore_diagrams.test.ts @@ -43,6 +43,33 @@ const DIAGRAMS = { "diag-1": "flowchart TD\\nA --> B" }; expect(diagrams).toEqual({ "diag-1": "flowchart TD\nA --> B" }); }); + test("オブジェクト内コメントの波括弧と DIAGRAMS 候補を無視する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + + expect(diagrams).toEqual({ + "diag-1": "flowchart TD\nA --> B", + "diag-2": "flowchart LR\nB --> C", + }); + }); + + test("正規表現リテラル内の false match を飛ばして実宣言を抽出する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + + expect(diagrams).toEqual({ "diag-1": "flowchart TD\nA --> B" }); + }); + test.each([ "// const DIAGRAMS = {};", "/* const DIAGRAMS = {}; */", diff --git a/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts b/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts index d9c280fbb..e90514cec 100644 --- a/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts +++ b/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.test.ts @@ -126,6 +126,18 @@ describe("ensureInitFlags", () => { expect(out).toMatch(/\nmermaid\.initialize\(\{[^}]*startOnLoad: false/); expect(out.match(/securityLevel: 'loose'/g)?.length).toBe(1); }); + + test("customMermaid.initialize を無視して実際の mermaid.initialize を更新する", () => { + const input = `customMermaid.initialize({ startOnLoad: true }); +custommermaid.initialize({ startOnLoad: true }); +mermaid.initialize({ startOnLoad: true });`; + const out = ensureInitFlags(input); + + expect(out).toContain("customMermaid.initialize({ startOnLoad: true });"); + expect(out).toContain("custommermaid.initialize({ startOnLoad: true });"); + expect(out).toMatch(/\nmermaid\.initialize\(\{[^}]*startOnLoad: false/); + expect(out.match(/securityLevel: 'loose'/g)?.length).toBe(1); + }); }); describe("injectRenderLoop", () => { @@ -152,6 +164,17 @@ describe("injectRenderLoop", () => { const twice = injectRenderLoop(once); expect(twice).toBe(once); }); + + test("customMermaid.initialize より後の実際の初期化スクリプトへ注入する", () => { + const input = ` + +`; + const out = injectRenderLoop(input); + + expect(out.indexOf("function applySvgFixups")).toBeGreaterThan( + out.indexOf("mermaid.initialize({ startOnLoad: false })"), + ); + }); }); describe("injectCenteringCss", () => { @@ -199,4 +222,13 @@ describe("applyPipeline (統合・冪等性)", () => { ); expect(applyPipeline(input).html).toContain('id="diag-1"'); }); + + test("正規表現リテラル内の候補より後にある実宣言を検出する", () => { + const input = FIXTURE.replace( + " const DIAGRAMS = {", + ` const declarationPattern = /const DIAGRAMS = \\{[^}]*\\}/g; + const DIAGRAMS = {`, + ); + expect(applyPipeline(input).html).toContain('id="diag-1"'); + }); }); diff --git a/.gemini/skills/fix-mermaid/scripts/restore_diagrams.test.ts b/.gemini/skills/fix-mermaid/scripts/restore_diagrams.test.ts index 27c61024a..48c3db0f6 100644 --- a/.gemini/skills/fix-mermaid/scripts/restore_diagrams.test.ts +++ b/.gemini/skills/fix-mermaid/scripts/restore_diagrams.test.ts @@ -43,6 +43,33 @@ const DIAGRAMS = { "diag-1": "flowchart TD\\nA --> B" }; expect(diagrams).toEqual({ "diag-1": "flowchart TD\nA --> B" }); }); + test("オブジェクト内コメントの波括弧と DIAGRAMS 候補を無視する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + + expect(diagrams).toEqual({ + "diag-1": "flowchart TD\nA --> B", + "diag-2": "flowchart LR\nB --> C", + }); + }); + + test("正規表現リテラル内の false match を飛ばして実宣言を抽出する", () => { + const html = ``; + const { diagrams } = extractDiagramsDefinition(html); + + expect(diagrams).toEqual({ "diag-1": "flowchart TD\nA --> B" }); + }); + test.each([ "// const DIAGRAMS = {};", "/* const DIAGRAMS = {}; */", From 229b6b450fef8d23ba64f8a84527fd3b26d60677 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 17:38:30 +0900 Subject: [PATCH 096/123] fix(mermaid): harden JavaScript source scanning --- .agents/skills/fix-mermaid/SKILL.md | 8 ++- .../scripts/apply_render_pipeline.mjs | 39 ++++++++++++--- .../fix-mermaid/scripts/javascript_source.mjs | 49 +++++++++++++++++-- .../fix-mermaid/scripts/restore_diagrams.mjs | 48 ++++++++++-------- .gemini/skills/fix-mermaid/SKILL.md | 8 ++- .../scripts/apply_render_pipeline.mjs | 39 ++++++++++++--- .../fix-mermaid/scripts/javascript_source.mjs | 49 +++++++++++++++++-- .../fix-mermaid/scripts/restore_diagrams.mjs | 48 ++++++++++-------- 8 files changed, 224 insertions(+), 64 deletions(-) diff --git a/.agents/skills/fix-mermaid/SKILL.md b/.agents/skills/fix-mermaid/SKILL.md index 7439f8f3a..f0b38914e 100644 --- a/.agents/skills/fix-mermaid/SKILL.md +++ b/.agents/skills/fix-mermaid/SKILL.md @@ -315,8 +315,14 @@ const DISPLAY = { 'wide-topology': { frameWidth: measured.wideTopologyFrame, naturalScale: true }, } as const; +const chart = DIAGRAMS[id]; +if (!chart) return null;
- +
``` diff --git a/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.mjs b/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.mjs index 787897b46..0478decb9 100644 --- a/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.mjs +++ b/.agents/skills/fix-mermaid/scripts/apply_render_pipeline.mjs @@ -113,10 +113,9 @@ export function injectIds(html) { * `startOnLoad: true` を false にし、未指定なら `securityLevel: 'loose'` を付与する。 */ export function ensureInitFlags(html) { - const maskedHtml = maskCommentsAndStrings(html); - const callMatch = /mermaid\.initialize\(\s*/.exec(maskedHtml); - if (!callMatch) return html; - const optionsStart = callMatch.index + callMatch[0].length; + const initializeCall = findMermaidInitialize(html); + if (!initializeCall) return html; + const { maskedSource: maskedHtml, optionsStart } = initializeCall; if (html[optionsStart] !== '{') return html; const optionsEnd = findMatchingBrace(maskedHtml, optionsStart); if (optionsEnd === -1) return html; @@ -164,6 +163,33 @@ export function ensureInitFlags(html) { return out; } +function findMermaidInitialize(source) { + const scriptPattern = /]*>([\s\S]*?)<\/script\s*>/gi; + const scripts = [...source.matchAll(scriptPattern)]; + const segments = scripts.length > 0 + ? scripts.map((script) => ({ + offset: script.index + script[0].indexOf('>') + 1, + source: script[1], + })) + : [{ offset: 0, source }]; + + for (const segment of segments) { + const maskedSegment = maskCommentsAndStrings(segment.source); + const match = /(^|[^.$\w])mermaid\.initialize\(\s*/.exec(maskedSegment); + if (!match) continue; + const prefixLength = match[1].length; + const maskedSource = source.slice(0, segment.offset) + + maskedSegment + + source.slice(segment.offset + segment.source.length); + return { + index: segment.offset + match.index + prefixLength, + optionsStart: segment.offset + match.index + match[0].length, + maskedSource, + }; + } + return null; +} + function findMatchingBrace(maskedSource, openingIndex) { let depth = 0; for (let index = openingIndex; index < maskedSource.length; index += 1) { @@ -226,10 +252,11 @@ function readPropertyNameBeforeColon(source, colonIndex) { */ export function injectRenderLoop(html) { if (html.includes(RENDER_LOOP_MARKER)) return html; - const initIdx = html.indexOf('mermaid.initialize('); - if (initIdx === -1) { + const initializeCall = findMermaidInitialize(html); + if (!initializeCall) { throw new Error('mermaid.initialize( が見つかりません。初期化ブロックを先に用意してください。'); } + const initIdx = initializeCall.index; const closeIdx = html.indexOf('', initIdx); if (closeIdx === -1) { throw new Error('mermaid.initialize 以降に が見つかりません。'); diff --git a/.agents/skills/fix-mermaid/scripts/javascript_source.mjs b/.agents/skills/fix-mermaid/scripts/javascript_source.mjs index deb805113..fd2ffb83d 100644 --- a/.agents/skills/fix-mermaid/scripts/javascript_source.mjs +++ b/.agents/skills/fix-mermaid/scripts/javascript_source.mjs @@ -30,16 +30,14 @@ function findInCode(source) { } /** - * コメントと文字列を元のオフセットを維持した空白へ置換する。 - * - * 正規表現リテラルおよびテンプレートリテラル内の `${...}` 補間は未対応。 - * これらに `const DIAGRAMS =` 相当の文字列が含まれる場合、後続の正しい - * DIAGRAMS 宣言を見落とす可能性がある。 + * コメント、文字列、テンプレートリテラル、正規表現リテラルを + * 元のオフセットを維持した空白へ置換する。 */ export function maskCommentsAndStrings(source) { const chars = source.split(''); let state = 'code'; let escaped = false; + let inRegexCharacterClass = false; for (let index = 0; index < source.length; index += 1) { const char = source[index]; @@ -59,6 +57,25 @@ export function maskCommentsAndStrings(source) { } continue; } + if (state === 'regex') { + chars[index] = char === '\n' ? '\n' : ' '; + if (escaped) { + escaped = false; + } else if (char === '\\') { + escaped = true; + } else if (char === '[') { + inRegexCharacterClass = true; + } else if (char === ']') { + inRegexCharacterClass = false; + } else if (char === '/' && !inRegexCharacterClass) { + while (/[a-z]/i.test(source[index + 1] ?? '')) { + chars[index + 1] = ' '; + index += 1; + } + state = 'code'; + } + continue; + } if (state !== 'code') { chars[index] = char === '\n' ? '\n' : ' '; if (escaped) escaped = false; @@ -81,6 +98,11 @@ export function maskCommentsAndStrings(source) { chars[index] = chars[index + 1] = ' '; index += 1; state = 'block-comment'; + } else if (char === '/' && canStartRegexLiteral(source, index)) { + chars[index] = ' '; + state = 'regex'; + escaped = false; + inRegexCharacterClass = false; } else if (char === "'") { chars[index] = ' '; state = 'single-quote'; @@ -95,3 +117,20 @@ export function maskCommentsAndStrings(source) { return chars.join(''); } + +function canStartRegexLiteral(source, slashIndex) { + let cursor = slashIndex - 1; + while (/\s/.test(source[cursor] ?? '')) cursor -= 1; + if (cursor < 0) return true; + + if ('[({,:;=!?&|+*%^~<>-'.includes(source[cursor])) return true; + + if (/[\w$]/.test(source[cursor])) { + const end = cursor + 1; + while (/[\w$]/.test(source[cursor] ?? '')) cursor -= 1; + const keyword = source.slice(cursor + 1, end); + return /^(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield)$/.test(keyword); + } + + return false; +} diff --git a/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs b/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs index f7e00a773..fe2228469 100644 --- a/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs +++ b/.agents/skills/fix-mermaid/scripts/restore_diagrams.mjs @@ -9,7 +9,10 @@ * bun run .agents/skills/fix-mermaid/scripts/restore_diagrams.mjs */ import fs from 'fs'; -import { findDiagramsDeclaration } from './javascript_source.mjs'; +import { + findDiagramsDeclaration, + maskCommentsAndStrings, +} from './javascript_source.mjs'; /** * Markdown 内の ```mermaid ブロックを抽出する。 @@ -75,19 +78,11 @@ export function restoreDiagrams(diagrams, mdBlocks) { } function findObjectEnd(source, openingIndex) { + const maskedSource = maskCommentsAndStrings(source); let depth = 0; - let quote = null; - let escaped = false; - for (let index = openingIndex; index < source.length; index += 1) { - const char = source[index]; - if (quote) { - if (escaped) escaped = false; - else if (char === '\\') escaped = true; - else if (char === quote) quote = null; - continue; - } - if (char === "'" || char === '"' || char === '`') quote = char; - else if (char === '{') depth += 1; + for (let index = openingIndex; index < maskedSource.length; index += 1) { + const char = maskedSource[index]; + if (char === '{') depth += 1; else if (char === '}') { depth -= 1; if (depth === 0) return index; @@ -121,11 +116,24 @@ function readString(source, start, allowedQuotes) { function parseTemplateLiteralObject(objectSource) { const diagrams = {}; let index = 1; - const skipSpace = () => { - while (/\s/.test(objectSource[index] ?? '')) index += 1; + const skipTrivia = () => { + while (index < objectSource.length) { + if (/\s/.test(objectSource[index] ?? '')) { + index += 1; + } else if (objectSource[index] === '/' && objectSource[index + 1] === '/') { + index += 2; + while (index < objectSource.length && objectSource[index] !== '\n') index += 1; + } else if (objectSource[index] === '/' && objectSource[index + 1] === '*') { + const commentEnd = objectSource.indexOf('*/', index + 2); + if (commentEnd === -1) throw new Error('閉じられていないブロックコメントです。'); + index = commentEnd + 2; + } else { + break; + } + } }; while (index < objectSource.length - 1) { - skipSpace(); + skipTrivia(); if (objectSource[index] === ',') { index += 1; continue; @@ -136,14 +144,14 @@ function parseTemplateLiteralObject(objectSource) { } const key = readString(objectSource, index, ["'", '"']); index = key.end; - skipSpace(); + skipTrivia(); if (objectSource[index] !== ':') throw new Error(`DIAGRAMS のキー ${key.value} に ':' がありません。`); index += 1; - skipSpace(); - const value = readString(objectSource, index, ['`']); + skipTrivia(); + const value = readString(objectSource, index, ["'", '"', '`']); diagrams[key.value] = value.value; index = value.end; - skipSpace(); + skipTrivia(); if (objectSource[index] === ',') index += 1; } return diagrams; diff --git a/.gemini/skills/fix-mermaid/SKILL.md b/.gemini/skills/fix-mermaid/SKILL.md index 7439f8f3a..f0b38914e 100644 --- a/.gemini/skills/fix-mermaid/SKILL.md +++ b/.gemini/skills/fix-mermaid/SKILL.md @@ -315,8 +315,14 @@ const DISPLAY = { 'wide-topology': { frameWidth: measured.wideTopologyFrame, naturalScale: true }, } as const; +const chart = DIAGRAMS[id]; +if (!chart) return null;
- +
``` diff --git a/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.mjs b/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.mjs index 787897b46..0478decb9 100644 --- a/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.mjs +++ b/.gemini/skills/fix-mermaid/scripts/apply_render_pipeline.mjs @@ -113,10 +113,9 @@ export function injectIds(html) { * `startOnLoad: true` を false にし、未指定なら `securityLevel: 'loose'` を付与する。 */ export function ensureInitFlags(html) { - const maskedHtml = maskCommentsAndStrings(html); - const callMatch = /mermaid\.initialize\(\s*/.exec(maskedHtml); - if (!callMatch) return html; - const optionsStart = callMatch.index + callMatch[0].length; + const initializeCall = findMermaidInitialize(html); + if (!initializeCall) return html; + const { maskedSource: maskedHtml, optionsStart } = initializeCall; if (html[optionsStart] !== '{') return html; const optionsEnd = findMatchingBrace(maskedHtml, optionsStart); if (optionsEnd === -1) return html; @@ -164,6 +163,33 @@ export function ensureInitFlags(html) { return out; } +function findMermaidInitialize(source) { + const scriptPattern = /]*>([\s\S]*?)<\/script\s*>/gi; + const scripts = [...source.matchAll(scriptPattern)]; + const segments = scripts.length > 0 + ? scripts.map((script) => ({ + offset: script.index + script[0].indexOf('>') + 1, + source: script[1], + })) + : [{ offset: 0, source }]; + + for (const segment of segments) { + const maskedSegment = maskCommentsAndStrings(segment.source); + const match = /(^|[^.$\w])mermaid\.initialize\(\s*/.exec(maskedSegment); + if (!match) continue; + const prefixLength = match[1].length; + const maskedSource = source.slice(0, segment.offset) + + maskedSegment + + source.slice(segment.offset + segment.source.length); + return { + index: segment.offset + match.index + prefixLength, + optionsStart: segment.offset + match.index + match[0].length, + maskedSource, + }; + } + return null; +} + function findMatchingBrace(maskedSource, openingIndex) { let depth = 0; for (let index = openingIndex; index < maskedSource.length; index += 1) { @@ -226,10 +252,11 @@ function readPropertyNameBeforeColon(source, colonIndex) { */ export function injectRenderLoop(html) { if (html.includes(RENDER_LOOP_MARKER)) return html; - const initIdx = html.indexOf('mermaid.initialize('); - if (initIdx === -1) { + const initializeCall = findMermaidInitialize(html); + if (!initializeCall) { throw new Error('mermaid.initialize( が見つかりません。初期化ブロックを先に用意してください。'); } + const initIdx = initializeCall.index; const closeIdx = html.indexOf('', initIdx); if (closeIdx === -1) { throw new Error('mermaid.initialize 以降に が見つかりません。'); diff --git a/.gemini/skills/fix-mermaid/scripts/javascript_source.mjs b/.gemini/skills/fix-mermaid/scripts/javascript_source.mjs index deb805113..fd2ffb83d 100644 --- a/.gemini/skills/fix-mermaid/scripts/javascript_source.mjs +++ b/.gemini/skills/fix-mermaid/scripts/javascript_source.mjs @@ -30,16 +30,14 @@ function findInCode(source) { } /** - * コメントと文字列を元のオフセットを維持した空白へ置換する。 - * - * 正規表現リテラルおよびテンプレートリテラル内の `${...}` 補間は未対応。 - * これらに `const DIAGRAMS =` 相当の文字列が含まれる場合、後続の正しい - * DIAGRAMS 宣言を見落とす可能性がある。 + * コメント、文字列、テンプレートリテラル、正規表現リテラルを + * 元のオフセットを維持した空白へ置換する。 */ export function maskCommentsAndStrings(source) { const chars = source.split(''); let state = 'code'; let escaped = false; + let inRegexCharacterClass = false; for (let index = 0; index < source.length; index += 1) { const char = source[index]; @@ -59,6 +57,25 @@ export function maskCommentsAndStrings(source) { } continue; } + if (state === 'regex') { + chars[index] = char === '\n' ? '\n' : ' '; + if (escaped) { + escaped = false; + } else if (char === '\\') { + escaped = true; + } else if (char === '[') { + inRegexCharacterClass = true; + } else if (char === ']') { + inRegexCharacterClass = false; + } else if (char === '/' && !inRegexCharacterClass) { + while (/[a-z]/i.test(source[index + 1] ?? '')) { + chars[index + 1] = ' '; + index += 1; + } + state = 'code'; + } + continue; + } if (state !== 'code') { chars[index] = char === '\n' ? '\n' : ' '; if (escaped) escaped = false; @@ -81,6 +98,11 @@ export function maskCommentsAndStrings(source) { chars[index] = chars[index + 1] = ' '; index += 1; state = 'block-comment'; + } else if (char === '/' && canStartRegexLiteral(source, index)) { + chars[index] = ' '; + state = 'regex'; + escaped = false; + inRegexCharacterClass = false; } else if (char === "'") { chars[index] = ' '; state = 'single-quote'; @@ -95,3 +117,20 @@ export function maskCommentsAndStrings(source) { return chars.join(''); } + +function canStartRegexLiteral(source, slashIndex) { + let cursor = slashIndex - 1; + while (/\s/.test(source[cursor] ?? '')) cursor -= 1; + if (cursor < 0) return true; + + if ('[({,:;=!?&|+*%^~<>-'.includes(source[cursor])) return true; + + if (/[\w$]/.test(source[cursor])) { + const end = cursor + 1; + while (/[\w$]/.test(source[cursor] ?? '')) cursor -= 1; + const keyword = source.slice(cursor + 1, end); + return /^(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield)$/.test(keyword); + } + + return false; +} diff --git a/.gemini/skills/fix-mermaid/scripts/restore_diagrams.mjs b/.gemini/skills/fix-mermaid/scripts/restore_diagrams.mjs index f7e00a773..fe2228469 100644 --- a/.gemini/skills/fix-mermaid/scripts/restore_diagrams.mjs +++ b/.gemini/skills/fix-mermaid/scripts/restore_diagrams.mjs @@ -9,7 +9,10 @@ * bun run .agents/skills/fix-mermaid/scripts/restore_diagrams.mjs */ import fs from 'fs'; -import { findDiagramsDeclaration } from './javascript_source.mjs'; +import { + findDiagramsDeclaration, + maskCommentsAndStrings, +} from './javascript_source.mjs'; /** * Markdown 内の ```mermaid ブロックを抽出する。 @@ -75,19 +78,11 @@ export function restoreDiagrams(diagrams, mdBlocks) { } function findObjectEnd(source, openingIndex) { + const maskedSource = maskCommentsAndStrings(source); let depth = 0; - let quote = null; - let escaped = false; - for (let index = openingIndex; index < source.length; index += 1) { - const char = source[index]; - if (quote) { - if (escaped) escaped = false; - else if (char === '\\') escaped = true; - else if (char === quote) quote = null; - continue; - } - if (char === "'" || char === '"' || char === '`') quote = char; - else if (char === '{') depth += 1; + for (let index = openingIndex; index < maskedSource.length; index += 1) { + const char = maskedSource[index]; + if (char === '{') depth += 1; else if (char === '}') { depth -= 1; if (depth === 0) return index; @@ -121,11 +116,24 @@ function readString(source, start, allowedQuotes) { function parseTemplateLiteralObject(objectSource) { const diagrams = {}; let index = 1; - const skipSpace = () => { - while (/\s/.test(objectSource[index] ?? '')) index += 1; + const skipTrivia = () => { + while (index < objectSource.length) { + if (/\s/.test(objectSource[index] ?? '')) { + index += 1; + } else if (objectSource[index] === '/' && objectSource[index + 1] === '/') { + index += 2; + while (index < objectSource.length && objectSource[index] !== '\n') index += 1; + } else if (objectSource[index] === '/' && objectSource[index + 1] === '*') { + const commentEnd = objectSource.indexOf('*/', index + 2); + if (commentEnd === -1) throw new Error('閉じられていないブロックコメントです。'); + index = commentEnd + 2; + } else { + break; + } + } }; while (index < objectSource.length - 1) { - skipSpace(); + skipTrivia(); if (objectSource[index] === ',') { index += 1; continue; @@ -136,14 +144,14 @@ function parseTemplateLiteralObject(objectSource) { } const key = readString(objectSource, index, ["'", '"']); index = key.end; - skipSpace(); + skipTrivia(); if (objectSource[index] !== ':') throw new Error(`DIAGRAMS のキー ${key.value} に ':' がありません。`); index += 1; - skipSpace(); - const value = readString(objectSource, index, ['`']); + skipTrivia(); + const value = readString(objectSource, index, ["'", '"', '`']); diagrams[key.value] = value.value; index = value.end; - skipSpace(); + skipTrivia(); if (objectSource[index] === ',') index += 1; } return diagrams; From dfb6063c8ebfd62e0d3780940abac1d3b3869225 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 9 Aug 2026 17:39:43 +0900 Subject: [PATCH 097/123] refactor(css): centralize GCP guide theme tokens --- .agents/skills/html-to-nextjs-migration/SKILL.md | 9 +++++---- .agents/skills/md-to-nextjs-migration/SKILL.md | 2 +- .gemini/skills/html-to-nextjs-migration/SKILL.md | 9 +++++---- .gemini/skills/md-to-nextjs-migration/SKILL.md | 2 +- app/gcl/associate-cloud-engineer/section1/page.css | 11 ++--------- app/globals.css | 13 +++++++++++-- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.agents/skills/html-to-nextjs-migration/SKILL.md b/.agents/skills/html-to-nextjs-migration/SKILL.md index 73eb606dd..eb25cb92e 100644 --- a/.agents/skills/html-to-nextjs-migration/SKILL.md +++ b/.agents/skills/html-to-nextjs-migration/SKILL.md @@ -68,13 +68,13 @@ content-heavy な単一HTML(hero + サイドバー + 多数セクション + M ### 2. GCP / ダークテーマ トークンマップ(確定値) HTML の `:root` ローカル変数を、本リポジトリの `globals.css` 既存トークンへ機械的に置換する。 -既存トークンに無いテーマ値は、ページ固有 CSS のページルートセレクタに定義し、対応する `page.tsx` または `layout.tsx` から直接 import する。 +既存トークンに無いテーマ値は、承認済みの3層デザイントークンとして `app/globals.css` の `@theme` に追加してから参照する。ページ固有 CSS では新規 custom property を定義しない。 | HTML ローカル変数 | 置換先 | 備考 | |---|---|---| | `--gcp-blue` / `-green` / `-yellow` / `-red` | `var(--color-google-blue / -green / -yellow / -red)` | 既存トークン | -| `--gcp-purple` | `var(--color-gcp-purple)` | ページ固有トークン | -| `--gcp-teal` | `var(--color-gcp-teal)` | ページ固有トークン | +| `--gcp-purple` | `var(--color-gcp-purple)` | グローバルテーマトークン | +| `--gcp-teal` | `var(--color-gcp-teal)` | グローバルテーマトークン | | `--bg-primary` | `var(--color-background)` | | | `--bg-card` / `--bg-card-hover` | `var(--color-card)` / `var(--color-gcp-card-hover)` | | | `--bg-code` | `var(--color-gcp-code-background)` | | @@ -210,7 +210,7 @@ Map every HTML CSS variable to the project's `globals.css` `@theme` token. Do NO GCP 系ガイド HTML(`--gcp-blue` / `--bg-*` / `--text-*` などの `:root` 変数)は、 **「正準リファレンス §2 GCP / ダークテーマ トークンマップ」の確定表をそのまま適用**する (毎回 `globals.css` を grep して導出しない)。紫・ティール・コード背景・カードホバー・青み境界線・グローを含め、 -§2 に定義したトークンを使用し、既存グローバルトークンで表現できない値だけをページルートへ定義する。 +§2 に定義したグローバルトークンを使用する。不足する値は `app/globals.css` の承認済み3層トークンへ追加してから参照し、ページルートへ定義しない。 **Critical**: The project uses a **unified dark theme**. Light-theme HTML pages must be re-themed to match the dark color system. Do not attempt to preserve the original light color scheme. @@ -387,6 +387,7 @@ Do NOT redefine these in page-specific CSS. Use them directly in TSX: - **Never import external fonts via `` tags** — Use `next/font/google` in `layout.tsx` only. - **Never define duplicate CSS variables** in page CSS that already exist in `globals.css @theme` +- **Never define new theme custom properties in page CSS** — add approved three-layer tokens to `app/globals.css @theme` and reference them - **Never use `@layer components`** for page-specific styles — plain CSS only for proper specificity - **Never duplicate z-index in CSS** when Tailwind class is used in JSX - **Never place responsive overrides outside `@media` queries** diff --git a/.agents/skills/md-to-nextjs-migration/SKILL.md b/.agents/skills/md-to-nextjs-migration/SKILL.md index b59319f36..8e2fdf3d3 100644 --- a/.agents/skills/md-to-nextjs-migration/SKILL.md +++ b/.agents/skills/md-to-nextjs-migration/SKILL.md @@ -256,7 +256,7 @@ git commit -m "docs(gcl//SN): sync migration progress" - **テストランナーは bun**: `npm run test` ではなく `bun run test` を使う - **新ページ追加時**: `app/constants.ts` の `EXAMS` にエントリを追加する(`Header.tsx` は `toNavTree(EXAMS)` で自動反映されるため直接編集しない) - **ページ固有の共通定数**: `constants.ts` に集約する(グローバルに置かない) -- **CSS テーマ**: ページ固有テーマは専用 `.css` ファイルに定義し、そのルートを所有する `page.tsx` または `layout.tsx` からインポートする。レイアウトスコープが不要な場合は `page.tsx` を優先し、不要な `layout.tsx` の作成を避ける(CLAUDE.md と整合) +- **CSS テーマ**: ページ CSS では新規テーマ custom property を定義しない。`app/globals.css` の既存または追加済みの承認済み3層デザイントークンを参照し、ページ固有 CSS にはセレクタとスタイル規則だけを置く - **分割方針(第一選択)**: `page.tsx` が ~400〜600 行を超えた場合は、新規セクションを `components/sections/Section*.tsx` などの独立コンポーネントに切り出すこと。再利用可能なロジックは hooks / util モジュールへ分離する。「編集を小分けにする」運用で肥大化を温存しないこと - **Edit サイズ(補助ルール)**: コンポーネント分割後もやむを得ず大きな編集が発生する場合に限り、1 回の Edit は 300 行以内に収める - **SVG 移行品質**: オリジナルにリッチな SVG(チップ表示、ステータス、詳細な注釈等)が含まれる場合は簡略化せず全詳細を再現すること。プレースホルダーへの置き換えは禁止。属性は camelCase に変換し `style` はオブジェクト形式で記述すること diff --git a/.gemini/skills/html-to-nextjs-migration/SKILL.md b/.gemini/skills/html-to-nextjs-migration/SKILL.md index 73eb606dd..eb25cb92e 100644 --- a/.gemini/skills/html-to-nextjs-migration/SKILL.md +++ b/.gemini/skills/html-to-nextjs-migration/SKILL.md @@ -68,13 +68,13 @@ content-heavy な単一HTML(hero + サイドバー + 多数セクション + M ### 2. GCP / ダークテーマ トークンマップ(確定値) HTML の `:root` ローカル変数を、本リポジトリの `globals.css` 既存トークンへ機械的に置換する。 -既存トークンに無いテーマ値は、ページ固有 CSS のページルートセレクタに定義し、対応する `page.tsx` または `layout.tsx` から直接 import する。 +既存トークンに無いテーマ値は、承認済みの3層デザイントークンとして `app/globals.css` の `@theme` に追加してから参照する。ページ固有 CSS では新規 custom property を定義しない。 | HTML ローカル変数 | 置換先 | 備考 | |---|---|---| | `--gcp-blue` / `-green` / `-yellow` / `-red` | `var(--color-google-blue / -green / -yellow / -red)` | 既存トークン | -| `--gcp-purple` | `var(--color-gcp-purple)` | ページ固有トークン | -| `--gcp-teal` | `var(--color-gcp-teal)` | ページ固有トークン | +| `--gcp-purple` | `var(--color-gcp-purple)` | グローバルテーマトークン | +| `--gcp-teal` | `var(--color-gcp-teal)` | グローバルテーマトークン | | `--bg-primary` | `var(--color-background)` | | | `--bg-card` / `--bg-card-hover` | `var(--color-card)` / `var(--color-gcp-card-hover)` | | | `--bg-code` | `var(--color-gcp-code-background)` | | @@ -210,7 +210,7 @@ Map every HTML CSS variable to the project's `globals.css` `@theme` token. Do NO GCP 系ガイド HTML(`--gcp-blue` / `--bg-*` / `--text-*` などの `:root` 変数)は、 **「正準リファレンス §2 GCP / ダークテーマ トークンマップ」の確定表をそのまま適用**する (毎回 `globals.css` を grep して導出しない)。紫・ティール・コード背景・カードホバー・青み境界線・グローを含め、 -§2 に定義したトークンを使用し、既存グローバルトークンで表現できない値だけをページルートへ定義する。 +§2 に定義したグローバルトークンを使用する。不足する値は `app/globals.css` の承認済み3層トークンへ追加してから参照し、ページルートへ定義しない。 **Critical**: The project uses a **unified dark theme**. Light-theme HTML pages must be re-themed to match the dark color system. Do not attempt to preserve the original light color scheme. @@ -387,6 +387,7 @@ Do NOT redefine these in page-specific CSS. Use them directly in TSX: - **Never import external fonts via `` tags** — Use `next/font/google` in `layout.tsx` only. - **Never define duplicate CSS variables** in page CSS that already exist in `globals.css @theme` +- **Never define new theme custom properties in page CSS** — add approved three-layer tokens to `app/globals.css @theme` and reference them - **Never use `@layer components`** for page-specific styles — plain CSS only for proper specificity - **Never duplicate z-index in CSS** when Tailwind class is used in JSX - **Never place responsive overrides outside `@media` queries** diff --git a/.gemini/skills/md-to-nextjs-migration/SKILL.md b/.gemini/skills/md-to-nextjs-migration/SKILL.md index b59319f36..8e2fdf3d3 100644 --- a/.gemini/skills/md-to-nextjs-migration/SKILL.md +++ b/.gemini/skills/md-to-nextjs-migration/SKILL.md @@ -256,7 +256,7 @@ git commit -m "docs(gcl//SN): sync migration progress" - **テストランナーは bun**: `npm run test` ではなく `bun run test` を使う - **新ページ追加時**: `app/constants.ts` の `EXAMS` にエントリを追加する(`Header.tsx` は `toNavTree(EXAMS)` で自動反映されるため直接編集しない) - **ページ固有の共通定数**: `constants.ts` に集約する(グローバルに置かない) -- **CSS テーマ**: ページ固有テーマは専用 `.css` ファイルに定義し、そのルートを所有する `page.tsx` または `layout.tsx` からインポートする。レイアウトスコープが不要な場合は `page.tsx` を優先し、不要な `layout.tsx` の作成を避ける(CLAUDE.md と整合) +- **CSS テーマ**: ページ CSS では新規テーマ custom property を定義しない。`app/globals.css` の既存または追加済みの承認済み3層デザイントークンを参照し、ページ固有 CSS にはセレクタとスタイル規則だけを置く - **分割方針(第一選択)**: `page.tsx` が ~400〜600 行を超えた場合は、新規セクションを `components/sections/Section*.tsx` などの独立コンポーネントに切り出すこと。再利用可能なロジックは hooks / util モジュールへ分離する。「編集を小分けにする」運用で肥大化を温存しないこと - **Edit サイズ(補助ルール)**: コンポーネント分割後もやむを得ず大きな編集が発生する場合に限り、1 回の Edit は 300 行以内に収める - **SVG 移行品質**: オリジナルにリッチな SVG(チップ表示、ステータス、詳細な注釈等)が含まれる場合は簡略化せず全詳細を再現すること。プレースホルダーへの置き換えは禁止。属性は camelCase に変換し `style` はオブジェクト形式で記述すること diff --git a/app/gcl/associate-cloud-engineer/section1/page.css b/app/gcl/associate-cloud-engineer/section1/page.css index ad1258604..d98a3a60c 100644 --- a/app/gcl/associate-cloud-engineer/section1/page.css +++ b/app/gcl/associate-cloud-engineer/section1/page.css @@ -1,7 +1,7 @@ /* Scoped CSS for GCP ACE Section 1 Complete Guide * 正本: Ace-section1-complete-guide.html の + + + + +
+ +
+
+
+ Associate Google Workspace Administrator 試験対策ガイド +
+

Section 2: コアWorkspaceサービスの管理

+
出題比率 約23% / 7タスク(2.1〜2.7)
+
+ +
+

+ 本ガイドはGoogle Cloud公式のAssociate Google Workspace Administrator認定ページおよび公式Exam Guide PDFが定義するSection 2「Managing core Workspace + services」の7つのタスク(2.1〜2.7)に厳密に対応し、Google + Workspace管理者ヘルプセンター(support.google.com/aおよびknowledge.workspace.google.com)の一次情報に基づいて、中級者〜上級者向けに実務レベルの詳細解説とGoogle推奨ベストプラクティスをまとめたものです。 +

+
+ +
+ +

Section 2の全体像

+

+ Exam Guideにおいて、Section 2「Managing core Workspace services」はSection + 1(ユーザー・ドメイン・ディレクトリ管理、約20%)に次いで出題比率が最も高い領域の一つで、約23%を占めます。対象範囲はGmail、Google + DriveとDocs、Google Calendar、Google Meet、Google + Chat、生成AI(Gemini)、そしてAppSheet/Apps + Scriptによる開発支援の7タスクにまたがり、Google + Workspaceの「日常業務で最も使われるコアサービス」を管理者としてどう構成し、どう安全に運用するかが問われます。 +

+
+flowchart LR
+    S2["Section 2<br/>コアWorkspaceサービスの管理<br/>約23%"]
+    S2 --> T1["2.1 Gmail<br/>ルーティング・認証・コンプライアンス"]
+    S2 --> T2["2.2 Drive/Docs<br/>共有・ストレージ・ラベル"]
+    S2 --> T3["2.3 Calendar<br/>リソース・共有・委任"]
+    S2 --> T4["2.4 Meet<br/>セーフティ・ビデオ設定"]
+    S2 --> T5["2.5 Chat<br/>スペース・外部連携"]
+    S2 --> T6["2.6 生成AI<br/>Geminiのプライバシーと管理"]
+    S2 --> T7["2.7 開発支援<br/>AppSheet・Apps Script"]
+
+    style S2 fill:#7c9eff,stroke:#333,stroke-width:2px,color:#000
+

+ これらのタスクに共通する設計思想は、Admin console上の「組織部門(OU)」または「設定グループ(Configuration + Group)」を単位として、サービスごとに粒度の細かいポリシーを適用するという一貫したモデルです。この構造を理解しておくことは、Section + 2全体、さらには試験全体を通じて有効です。 +

+
+

2.1 Gmailの設定

+

2.1.1 MXレコードの設定

+

+ MXレコード(Mail Exchange + record)は、ドメイン宛のメールをどのメールサーバーに配送するかを指定するDNSレコードです。Google + Workspaceを利用するには、ドメインのMXレコードをGoogleのメールサーバーに向ける必要があります。 +

+

+ 2023年4月の仕様変更として、Googleはそれまでの複数レコード構成(ASPMX.L.GOOGLE.COMなどの5レコード、優先度違い)から、**単一のMXレコードsmtp.google.com**に設定を簡素化しました。既存の複数レコード構成(レガシー値)は引き続きサポートされており、正常に機能している場合は変更不要です。新規セットアップでは単一レコード方式が推奨されます。 +

+

設定手順の概要:

+
    +
  1. + ドメインレジストラ(お名前.com、Cloudflare、GoDaddyなど)のDNS管理画面にサインインする +
  2. +
  3. 既存のMXレコードをすべて削除する(残存すると配送障害の原因になる)
  4. +
  5. 新しいMXレコードを追加する
  6. +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
項目値
TypeMX
Name / Host + 空欄または@(サブドメインの場合はサブドメイン名) +
TTL + レジストラのデフォルト値、または1(3600秒/1時間を推奨するレジストラもある) +
Priority1
Value / Destination + smtp.google.com(レジストラによっては末尾にピリオドが必要:smtp.google.com.) +
+
+
    +
  1. 変更を保存する(DNS伝播に最大72時間かかる場合がある)
  2. +
  3. + Admin + consoleで「アカウント」→「ドメイン」→「ドメインを管理」→対象ドメインの「Gmailを有効にする」をクリックし、MXレコードの検証を行う(ドメイン設定の管理者権限が必要) +
  4. +
+

トラブルシューティングのポイント:

+
    +
  • + ドメインの所有権確認(TXTレコードによるベリフィケーション)が完了しているか確認する +
  • +
  • レジストラごとのフォーマット差異(末尾ピリオドの有無など)を確認する
  • +
  • + Admin Toolbox Digを使って、実際にインターネット上に公開されているMXレコードを検証する +
  • +
  • + レジストラのサポートに問い合わせる(レジストラが不明な場合はドメインレジストラの特定方法を参照) +
  • +
+

特殊なルーティングシナリオ:

+

Gmailだけがメール処理を行うとは限らないケースがあります。

+
    +
  • + オンプレミスのメールハイジーン/ジャーナリング製品を前段に置く場合:MXレコードはオンプレミスサービスを指し、そこからGoogle + Workspaceへ配送する構成にする +
  • +
  • + ハイブリッドメール環境(一部ユーザーがGoogle + Workspace、一部がオンプレミスのExchangeなど):MXレコードは通常どおりsmtp.google.comのままにし、Admin + console側でSplit Delivery(分割配信)を設定する +
  • +
+
+

+ 💡ベストプラクティス: + MXレコードを切り替える前に、必ず新しいGoogle + Workspaceのユーザーアカウントを作成しておくこと。MXレコード切り替え前にアカウントが存在しないと、メールがバウンス(配送不能)する原因になる。 +

+
+

+ 2.1.2 基本的なメールルーティングの設定 +

+

+ Google WorkspaceにはDefault routing(デフォルトルーティング)とRouting(ルーティング)という2つの主要なルーティング設定があり、この2つの使い分けが試験でも実務でも頻出のポイントです。 +

+
+ + + + + + + + + + + + + + + + + + + + +
設定用途優先度
Default routing + 組織全体、またはOU全体に対する既定のメール配送方法を設定する(例:組織のほぼ全メールを2つの受信箱に配送するデュアルデリバリー) + + コンテンツ/添付ファイルコンプライアンス設定より低い優先度。最大1000件まで作成可能で、優先順位を並べ替え可能 +
Routing + より特定条件に基づく高度な配送ルールを設定する。Default + routingの挙動を上書きする用途にも使う(例:CEO宛メールのコピーをアシスタントにも送る) + Default routingより高い優先度
+
+

代表的なルーティングシナリオは以下の3つです。

+
+flowchart TD
+    Start["メールルーティング要件"] --> Q1{"移行・監査目的で<br/>全メールを2箇所に<br/>複製したいか?"}
+    Q1 -- "はい" --> Dual["Dual Delivery<br/>(デュアルデリバリー)<br/>Default routing + Also deliver to"]
+    Q1 -- "いいえ" --> Q2{"ユーザーの一部がGmail、<br/>一部が別メールシステム<br/>(例: 移行期のExchange)か?"}
+    Q2 -- "はい" --> Split["Split Delivery<br/>(分割配信)<br/>Add Route + Routing設定"]
+    Q2 -- "いいえ" --> Q3{"存在しない宛先への<br/>メールを取りこぼしなく<br/>受け取りたいか?"}
+    Q3 -- "はい" --> CatchAll["Catch-All メールボックス<br/>Default routing(Unknown recipient)"]
+    Q3 -- "いいえ" --> Custom["特定の送信者・件名・<br/>添付・内容に基づく<br/>カスタムRoutingルール"]
+
+    style Dual fill:#7c9eff,color:#000
+    style Split fill:#7c9eff,color:#000
+    style CatchAll fill:#7c9eff,color:#000
+    style Custom fill:#7c9eff,color:#000
+
    +
  1. + デュアルデリバリー(Dual delivery):受信メッセージを2つ以上の受信箱に配送する。組織移行時の監査や、外部アーカイブシステムとの並行運用に利用される。Admin + consoleでは「Gmail」→「Routing」(レガシーの「Email + routing」設定は非推奨化され順次廃止予定)で設定し、「Also deliver + to」で追加の宛先を指定する。 +
  2. +
  3. + 分割配信(Split delivery):ドメイン内で一部のユーザーがGoogle + Workspace、一部が別のメールシステムを使っている場合に、受信者に応じて配送先を分岐させる。事前に「Add + Route」設定で非Gmailサーバーを追加しておく必要がある。Microsoft + 365との共存移行(フェーズドマイグレーション)で特によく使われる。 +
  4. +
  5. + キャッチオールメールボックス(Catch-all mailbox):誤って宛先を間違えたメールや、存在しない宛先宛のメールを取りこぼさないように、Default + routingで「Unknown recipient」に対する配送ルールを設定する。 +
  6. +
+

+ 設定手順(共通): Admin console → 「アプリ」→「Google + Workspace」→「Gmail」→「Routing」または「Default routing」→ + 「設定」または「別のルールを追加」。変更の反映には最大24時間かかる(通常はより早く反映される)。 +

+
+

+ 💡ベストプラクティス: + ルーティングルールのテストは、いきなり全社展開せず、まず特定のOUやテストユーザーに絞り込んで検証してから全社展開する(Googleの「より高速なルールのテストに関するベストプラクティス」を参照)。 +

+
+

+ 2.1.3 コンテンツコンプライアンスルール +

+

+ コンテンツコンプライアンス(Content + compliance)は、メール本文や件名が特定の条件(キーワード、正規表現、事前定義された検出器)に一致した場合に、そのメッセージをどう処理するかを制御する高度なフィルタリング機能です。 +

+

+ 設定場所: Admin console → 「アプリ」→「Google + Workspace」→「Gmail」→「コンプライアンス」(Gmail設定の管理者権限が必要) +

+

主な用途:

+
    +
  • + 送信メール(Outbound)に「confidential」という単語が含まれる場合、配送を拒否する +
  • +
  • 特定のIPアドレス範囲からの受信メールを隔離(Quarantine)する
  • +
  • 特定のテキストパターンに一致するメッセージを法務部門にルーティングする
  • +
  • + ホワイトリスト化(第三者フィッシングシミュレーションサービスなど)にも応用可能 +
  • +
+

+ 適用範囲の指定: + ルールは「Inbound(受信)」「Outbound(送信)」「Internal-sending/Internal-receiving(組織内送受信)」のいずれか、または組み合わせに適用できる。ここでの「内部(internal)」とは、検証済みのWorkspaceドメインまたはそのサブドメイン・親ドメインを指す。 +

+

+ 事前定義された検出器(Predefined content detectors): + クレジットカード番号や社会保障番号、パスポート番号など、機密データを検出するために、正規表現を自前で書かなくても使える組み込みの検出器が用意されている。これらはキーワードや正規表現と組み合わせて、より高度なポリシーを構築できる。 +

+

+ アタッチメントコンプライアンス(Attachment compliance): + ファイルの種類・ファイル名・メッセージサイズに基づいてメッセージの扱いを指定する別設定。暗号化された添付ファイルの検出にも対応し、ファイル拡張子を偽装したファイルでも実際のファイル種別を検出できる。 +

+
+

+ ⚠重要な注意点: + コンテンツフィルタは正規表現やその他のパラメータに基づく確率的な一致判定であり、すべての機微情報や添付ファイルを100%検出できることを保証するものではない(誤検知・見逃しが発生し得る)。 +

+
+

+ 複数のコンプライアンスルールを設定した場合、どのルールが優先されるかは条件と優先順位によって決まる(「How + multiple settings affect message behavior」を参照)。 +

+

+ 2.1.4 スパム・フィッシング・マルウェア対策 +

+

+ 「Spam, Phishing and + Malware」設定は、Gmailの標準的なスパム判定を補完・上書きするための管理者向けコントロールです。試験で問われる主要な要素は次の4つです。 +

+
+flowchart TB
+    Inbound["受信メール"] --> IG{"インバウンドゲートウェイ<br/>経由か?"}
+    IG -- "はい(送信元IPがGateway IPsに一致)" --> GWCheck{"Reject all mail not<br/>from gateway IPs が<br/>オンか?"}
+    GWCheck -- "オン" --> RejectOther["ゲートウェイ外のIPからの<br/>直接メールは拒否"]
+    GWCheck -- "オフ" --> HeaderEval["ヘッダーregexp評価<br/>または通常のGmail<br/>スパム評価を適用"]
+    IG -- "いいえ" --> AllowCheck{"送信元IPが<br/>Email allowlistに<br/>含まれるか?"}
+    AllowCheck -- "はい" --> BypassSpam["標準スパムフィルタを<br/>バイパス(誤検知防止)"]
+    AllowCheck -- "いいえ" --> DenyCheck{"Blocked senders<br/>(denylist)に<br/>一致するか?"}
+    DenyCheck -- "はい" --> Block["メッセージをブロック"]
+    DenyCheck -- "いいえ" --> NormalSpam["通常のGmail<br/>スパム・フィッシング・<br/>マルウェア判定"]
+
+    style BypassSpam fill:#7c9eff,color:#000
+    style Block fill:#e57373,color:#000
+    style RejectOther fill:#e57373,color:#000
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
機能説明用途
Email allowlist(許可リスト) + 特定の送信元IPアドレスからのメールについて、Gmailの標準スパムフィルタを完全にバイパスする。ドメイン全体に対してのみ設定可能で、OU単位では許可リストを設定できない + + 正規の一斉配信サービス(フィッシング訓練ツールなど)からのメールが誤ってスパム判定されるのを防ぐ +
Blocked senders(denylist)特定のメールアドレスまたはドメインをブロックする既知の迷惑送信元を明示的に遮断する
+ Inbound gateway(インバウンドゲートウェイ) + + 自組織にメールを中継する前段のメールサーバー(セキュリティゲートウェイなど)のIPアドレスを指定する。トップレベル組織でのみ設定可能で、全組織に適用される + + オンプレミスのメールセキュリティ製品やサードパーティの一斉送信サービスを経由させる場合 +
IP allowlist(インバウンドゲートウェイ内) + インバウンドゲートウェイ設定内で「Gateway + IPs」として登録したIPからのメールを信頼する + + ゲートウェイ経由のメールに対してGmail自体のスパム評価を無効化し、ヘッダー値のみで判定させることも可能(Disable Gmail spam evaluation on mail from this gateway; + only use header value) +
+
+

+ 設定場所: Admin console → 「アプリ」→「Google + Workspace」→「Gmail」→「スパム、フィッシング、マルウェア」 +

+
+

💡ベストプラクティス:

+
    +
  • + IPアドレスによる許可リストは、ドメイン全体に影響するため慎重に使用し、可能な限りコンテンツコンプライアンスルールやアドレスリストとの組み合わせでスコープを絞り込む +
  • +
  • + インバウンドゲートウェイを設定する場合、Reject all mail not from gateway IPsのチェックは、本当にそのゲートウェイ以外からの直接配信を完全に禁止したい場合のみ有効にする(誤設定すると正規メールが届かなくなるリスクがある) +
  • +
  • + 許可リストへの追加は「配送性を上げる」ための機能であり、フィッシング対策そのものを弱体化させる可能性があるため、追加するIPは信頼できる送信元に限定する +
  • +
+
+

+ 2.1.5 添付ファイルサイズ制限とブロックするファイル形式 +

+

+ コンプライアンス設定内の「Attachment + compliance(添付ファイルコンプライアンス)」を使うと、ファイルの種類、ファイル名、メッセージサイズに基づいて、メッセージの扱い(拒否・隔離・変更など)を指定できます。 +

+

制御できる主な観点:

+
    +
  • + 特定の拡張子(.exe、.batなど実行可能ファイル)を持つ添付ファイルの拒否 +
  • +
  • ファイル名パターンによるフィルタリング
  • +
  • メッセージ全体のサイズ上限に基づく制御
  • +
  • + アーカイブファイル(ZIPなど)内部のファイル名もスキャン対象にできる +
  • +
  • + ファイル拡張子を偽装(リネーム)した悪意あるファイルも、実際のファイル種別を検出してブロックできる +
  • +
  • + 暗号化された添付ファイルの検出(アーカイブサーバーへの非暗号化コピー送付などに活用) +
  • +
+
+

+ 💡ベストプラクティス: + 添付ファイルコンプライアンスルールは、コンテンツコンプライアンスルールと同様に複数設定できるが、複数ルールが競合した場合の優先順位(条件とルールの並び順)を必ず確認し、意図しないブロック・許可が発生しないようテストすること。 +

+
+

2.1.6 Gmail転送とPOP/IMAPアクセス

+

自動転送(Automatic forwarding):

+
    +
  • + エンドユーザーは自身のGmail設定(「Forwarding and + POP/IMAP」タブ)から個人的な転送先アドレスを1つ設定できる。転送先には確認メールが送られ、受信者側でリンクをクリックして初めて有効化される +
  • +
  • + 管理者は、ユーザーによる自動転送設定そのものを許可・禁止するコントロールを持つ(Admin + console → Gmail → エンドユーザーアクセス) +
  • +
  • + 組織レベルでより高度な転送・複製が必要な場合は、個人設定ではなく管理者によるRouting設定(アドレスマップによる転送、コンプライアンスルールと組み合わせた外部転送のブロックなど)を使う +
  • +
+

POP/IMAPアクセス:

+
    +
  • + 管理者はユーザーまたはOU単位でPOP・IMAPアクセスのオン・オフを制御できる(Admin + console → Gmail → ユーザー設定 → 「POPとIMAPアクセス」) +
  • +
  • + 2025年5月1日以降、Google + WorkspaceアカウントはOAuthを使用しないサードパーティアプリ・デバイスからのログイン(いわゆる「安全性の低いアプリ」)をサポートしなくなった。サードパーティのメールクライアントを使う場合はOAuth認証が必須 +
  • +
  • + OAuthクライアントIDを指定して、特定のクライアントのみに同期を限定するオプションもある(この場合、サービスアカウントによるドメイン全体の委任を使うクライアントはサポート対象外になる点に注意) +
  • +
+
+

+ 💡ベストプラクティス: + 外部への自動転送は情報漏えいのリスクがあるため、必要なOU以外では転送機能自体を無効化し、どうしても外部転送が必要な場合はコンテンツコンプライアンスルールで「外部への自動転送をブロックする」設定と組み合わせて多層防御にする。 +

+
+

+ 2.1.7 Google推奨のメールセキュリティ対策(SPF・DKIM・DMARC) +

+

+ SPF・DKIM・DMARCは、送信ドメイン認証(メールなりすまし対策)の三本柱であり、試験・実務の両面で最重要トピックの一つです。 +

+
+sequenceDiagram
+    participant Sender as 送信元<br/>(Google Workspace)
+    participant DNS as 送信ドメインのDNS
+    participant Receiver as 受信側メールサーバー
+
+    Sender->>DNS: SPFレコード公開<br/>("どのIPが送信を許可されるか")
+    Sender->>DNS: DKIM公開鍵をTXTレコードで公開
+    Sender->>DNS: DMARCポリシー(p=none/quarantine/reject)を公開
+
+    Sender->>Receiver: メール送信(DKIM署名付与)
+    Receiver->>DNS: SPFレコードを検証<br/>(送信元IPが許可リストにあるか)
+    Receiver->>DNS: DKIM公開鍵を取得し署名を検証
+    Receiver->>Receiver: SPF/DKIMとFromヘッダーの<br/>ドメイン一致(alignment)を確認
+    Receiver->>DNS: DMARCポリシーを取得
+    Receiver->>Receiver: ポリシーに従い配信/隔離/拒否を判定
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目役割Google Workspaceでの設定要点
SPF(Sender Policy Framework) + 「どのメールサーバーがこのドメインの代理で送信してよいか」をDNS + TXTレコードで宣言する + + include:_spf.google.comを含むSPFレコードをDNSに公開する。1ドメインにつきSPFレコードは1つのみ(複数ある場合はマージが必要)。反映まで最大48時間 +
DKIM(DomainKeys Identified Mail) + メールに暗号署名を付与し、経路上での改ざんがないことと送信ドメインの真正性を証明する + + Admin console + →「Gmail」→「メールを認証」でDKIM鍵ペア(1024ビットまたは2048ビット)を生成し、公開鍵をTXTレコードとしてDNSに公開後、「認証を開始」をクリックして有効化する +
+ DMARC(Domain-based Message Authentication, + Reporting & Conformance) + + SPF・DKIMの結果とFrom:ヘッダーのドメイン一致(アライメント)を基に、認証に失敗したメールをどう扱うか(none/quarantine/reject)をポリシーとして宣言し、レポートを受け取る + + _dmarc.yourdomain.comにTXTレコードとしてDMARCポリシーを公開する。まずp=noneから開始し、レポートを分析しながら段階的にquarantine→rejectへ引き上げるロールアウトが推奨される +
+
+

Googleの送信者ガイドライン(Email sender guidelines):

+
    +
  • すべての送信者:SPFまたはDKIMのいずれかの設定が必須
  • +
  • + 大量送信者(1日5,000通以上のメッセージをGmail宛に送信する場合):SPF・DKIM・DMARCすべての設定が必須(2024年2月施行) +
  • +
+
+

💡ベストプラクティス:

+
    +
  • + SPF・DKIM・DMARCは単独ではなく必ず三点セットで運用する。DMARCはSPF/DKIMの検証結果を土台にしているため、土台なしでDMARCだけ設定しても効果が限定的 +
  • +
  • + DMARCはp=none(モニタリングのみ)から始め、集計レポートで正規の送信元をすべて洗い出してから段階的に強制力を高める「Recommended + DMARC rollout」に従う +
  • +
  • + SPFレコードは新しいメール配信サービスを追加・廃止するたびに更新し、使われなくなったドメイン・IPを削除して古いレコードを放置しない +
  • +
  • + 追加のブランド対策として、DMARCの上にBIMI(ブランドロゴのメール表示)の導入も検討できる +
  • +
+
+

2.1.8 メールデータの移行

+

+ 他のメールプロバイダやGoogle + Workspaceの別アカウントからGmailへメールデータを移行するには、Googleが提供する複数の移行ツールを使い分けます。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ツール用途アクセス場所
+ 新しいデータ移行サービス(Data migration, GA) + + Google + Workspace同士、Gmail(個人アカウント)、IMAP対応メールサーバーからのメール移行。デルタ移行(差分同期)に対応し、既存データを重複させずに新着・更新分のみ取り込める + + Admin console + →「データ」→「データのインポートとエクスポート」→「データ移行」 +
データインポートツール(Data import) + Microsoft Exchange Online、IMAPベースのWebメール(Yahoo!、iCloud + Mail、GoDaddy、Zohoなど)、別のWorkspaceアカウント、個人のGmailアカウントからのインポート + + Admin console + →「データ」→「データのインポートとエクスポート」→「データインポート」 +
レガシーData Migration Service(DMS)app + 従来型の移行アプリ。引き続き利用可能(admin.google.com/ac/dms) + Admin console内
+
+

移行フローの要点(Gmailアカウントからの移行の例):

+
    +
  1. + スーパー管理者としてサインインし、移行先のAdmin + consoleで移行元アドレスを指定して「認証をリクエスト」する +
  2. +
  3. + 移行元アカウントの所有者が接続リクエストを承認する(自分自身が所有者であればAdmin + console上で直接承認できる) +
  4. +
  5. 承認後、移行を実行する
  6. +
  7. + Advanced Protection + Program(高度な保護機能プログラム)に登録済みのユーザーは、移行前・移行中は同プログラムを一時的にオフにする必要がある +
  8. +
+
+

💡ベストプラクティス:

+
    +
  • + 大規模な移行では、まず少人数のパイロットグループで移行してから全社展開する +
  • +
  • + 移行完了後もデルタ移行(差分インポート)を実行し、初回移行後に追加・更新されたデータや、初回移行で失敗したデータを再取り込みする +
  • +
  • + Exchange OnlineやMicrosoft 365からの移行では、Data + Importが「ドメイン全体の委任(Domain-wide + delegation)」のAPIクライアントとして認証される点を踏まえ、事前にMicrosoft + 365側でグローバル管理者権限を用意しておく +
  • +
+
+

2.1.9 Gmailアクセスの委任

+

+ メールの委任(Mail + delegation)を使うと、あるユーザー(アカウント所有者)が別のユーザー(委任先)に対して、自分の受信トレイの閲覧・送信・管理権限を付与できます。 +

+

設定手順:

+
    +
  1. + Admin console →「アプリ」→「Google + Workspace」→「Gmail」→「ユーザー設定」→「メールの委任」 +
  2. +
  3. + 「ユーザーがメールボックスへのアクセスをドメイン内の他のユーザーに委任できるようにする」をオンにする +
  4. +
  5. + 委任者が送信したメールの送信者情報として、アカウント所有者・委任者のどちらのアドレスを受信者に表示するかを管理者が選択する +
  6. +
  7. + エンドユーザーに対し、個人単位・Googleグループ単位で委任先を追加できることを周知する +
  8. +
+

押さえるべきポイント:

+
    +
  • + Googleグループをアカウントの委任先として追加できる(1つのグループは1人の委任者としてカウントされる) +
  • +
  • + メールエイリアスはGoogleアカウントではないため、委任先として設定できない +
  • +
  • + 委任先は受信トレイの閲覧・送信・返信・削除ができるが、パスワード変更やアカウント設定の変更はできない +
  • +
+

+ 委任 vs 共有メールボックス(Collaborative Inbox): + 1対1の代理対応(秘書によるメール代理管理など)には委任、複数人でチケットのように受信箱を分担管理する場合はGoogleグループのCollaborative + Inbox機能が適している(Section 1のグループ管理も参照)。 +

+

+ 2.1.10 コンプライアンスフッターとメール隔離(Quarantine) +

+

+ コンプライアンスフッター(Append footer): + 法的な免責事項や社内ポリシーの通知など、定型のフッターテキストを送信メッセージに自動的に追加する機能。Admin + console →「Gmail」→「コンプライアンス」→「フッターを追加」で設定する。 +

+

+ メール隔離(Email quarantine): + コンテンツコンプライアンスルールやDLPルールに一致したメッセージを、配信もブロックもせず一時的に「隔離エリア」に留め置き、管理者や指定ユーザーがレビューして解放・削除を判断できるようにする仕組み。 +

+

Quarantineへのアクセス権付与のベストプラクティス:

+
+ + + + + + + + + + + + + + + + + + + + +
オプション方法用途
+ オプション1:全隔離メッセージへのアクセスを付与 + + Access Admin Quarantine権限を持つカスタム管理者ロールを作成し、ユーザーに割り当てる + + 全社のセキュリティチームなど、すべての隔離メッセージを横断的にレビューする担当者向け +
+ オプション2:特定の隔離メッセージのみへのアクセスを付与 + + Access Restricted Quarantines権限を持つカスタムロールを作成し、Googleグループと紐づけて、隔離設定作成時に該当グループへのアクセスを許可する + + 例:個人情報や機密情報を含むメッセージのレビューをコンプライアンスチームに限定する場合 +
+
+
+

+ 💡ベストプラクティス: + Quarantineへのアクセスはスーパー管理者に限定せず、必要最小限の権限を持つカスタムロールを作成して委任する(最小権限の原則)。全メッセージへの無制限アクセスを安易に多数の担当者へ配布しない。 +

+
+
+

2.2 Google DriveとDocsの設定

+

+ 2.2.1 新規ファイルのデフォルト共有設定 +

+

+ Google Driveのファイル共有ポリシーの起点となるのが「General access + default(全般的なアクセスのデフォルト設定)」です。Admin console + →「アプリ」→「Google Workspace」→「Drive and + Docs」→「共有設定」→「全般的なアクセスのデフォルト設定」から構成します(Drive & Docs管理者権限が必要)。 +

+

既定の選択肢:

+
    +
  • + Restricted(限定公開):ファイルはオーナーのみアクセス可能で、ユーザーが明示的に共有するまで非公開。Googleが多くのユーザーに推奨する既定値であり、ユーザーが準備できたときにだけ共有し、個人ファイルは非公開のままにできる +
  • +
  • + your organization(組織全体):組織内の全ユーザーがアクセス可能 +
  • +
+

+ さらに「ターゲットオーディエンス(後述2.2.4)」を作成することで、この2択に加えて任意のカスタムオーディエンス(例:特定部門、Employees + Onlyなど)を選択肢に追加できます。 +

+

+ 内部共有 vs 外部共有: Google WorkspaceのAdmin + consoleには「内部共有」を直接制限する専用トグルは存在せず、内部共有の制御は主に「全般的なアクセスのデフォルト設定」と「ターゲットオーディエンス」の組み合わせで実現します。一方、「外部共有」については専用の制御群(2.2.3参照)が用意されています。 +

+
+

+ 💡ベストプラクティス: + 一般ユーザー向けにはRestrictedをデフォルトにし、ターゲットオーディエンスで「Employees + Only」のようなオーディエンスを作成した上でこれを優先(プライマリ)オーディエンスに設定することで、ユーザーが誤って外部ベンダーなどに広く共有してしまうリスクを下げる。 +

+
+

2.2.2 Drive信頼ルール(Trust Rules)

+

+ Trust + Rulesは、外部ドメイン・特定の組織部門(OU)・特定のグループ・特定のユーザーを基準に、Driveファイルの共有を許可(Allow)・拒否(Deny)・警告表示(Warn)という形で制御するルールベースの新しい制御フレームワークです。従来の「Drive共有設定」(外部共有のオン・オフ、信頼済みドメインの許可リスト)を置き換えるものとして提供されています。 +

+

Trust Rulesが有効なユースケース:

+
    +
  • + 財務部門が所有するファイルを、社内の他部署とは共有できないようにブロックする +
  • +
  • 契約関係のある特定の外部ドメインとのみ共有を許可する
  • +
  • + 外部ユーザーからのスパム・フィッシングファイル共有を防ぐため、通常は外部との共有が発生しないOUに対して「信頼できるドメインの外部ユーザーのみ共有を許可する」ルールを適用する +
  • +
  • + 一時プロジェクトにおいて、一定期間後に外部コラボレーターのアクセスを自動的に失効させる +
  • +
+

+ 既存のDrive共有設定との関係: Trust + Rulesを有効化すると、既存の「組織外との共有」設定は自動的にTrust + Rulesへ変換される(プレビュー可能)。Trust + Rulesを有効化した時点で、対応するDrive共有設定は無効になる(Trust + Rulesはいつでもオフに戻し、従来のDrive共有設定に戻すことも可能)。 +

+
+flowchart TD
+    Rule["ファイル共有アクション"] --> Check{"Trust Rulesの<br/>条件に一致するか?<br/>(OU・グループ・ドメイン・ユーザー単位)"}
+    Check -- "Allow" --> Allowed["共有を許可"]
+    Check -- "Deny" --> Denied["共有をブロック"]
+    Check -- "Warn" --> Warned["警告を表示した上で<br/>ユーザーの判断に委ねる"]
+    Check -- "一致なし" --> Fallback["デフォルトの<br/>Drive共有設定/<br/>ターゲットオーディエンス設定を適用"]
+
+    style Allowed fill:#7c9eff,color:#000
+    style Denied fill:#e57373,color:#000
+    style Warned fill:#ffd54f,color:#000
+

+ いつTrust Rulesを使うべきか(試験ポイント): 「Identifying + when Drive trust rules should be + used」という出題観点に対応する判断基準は、単純な組織全体のオン・オフでは表現できない、部門・グループ・ドメイン単位できめ細かい共有制御が必要な場合にTrust + Rulesを選択する、という点です。 +

+

+ 2.2.3 組織ポリシーに基づく外部共有の制限 +

+

+ 外部共有の制御はAdmin console →「Drive and + Docs」→「共有設定」に集約されており、主な制御レバーは次の通りです。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
制御説明
オン/オフ/制限 + 外部共有を全面許可、全面禁止、または特定条件下でのみ許可に設定する(全面禁止は高セキュリティ環境以外では稀) +
信頼済み(許可リスト)ドメイン + 全面許可・全面禁止の二択ではなく、パートナー企業など特定ドメインとの共有のみを許可し、それ以外をブロックする +
ターゲットオーディエンス + 「組織内全員」のような推奨共有先を提示し、ユーザーが安易に「リンクを知っている全員」を選択しないよう誘導する +
外部共有時の警告 + 組織外への共有を行おうとした際にDriveが警告を表示し、意図しない外部共有(ヒューマンエラー)に気づかせる +
+
+

+ Visitor Sharing(訪問者共有): + Googleアカウントを持たない外部ユーザー(非Google利用者)に対して、確認コードベースでファイルへの一時アクセスを許可する機能。外部共有を厳格に制限しつつ、特定の非Google利用者とだけ安全に共有したい場合に利用する。 +

+
+

+ 💡ベストプラクティス(外部共有の5層防御モデル): +

+
    +
  1. + 信頼済み外部ドメイン(契約関係のあるパートナー・ベンダー)を許可リスト化する +
  2. +
  3. + ターゲットオーディエンスと組み合わせ、ワンクリックで定義済みの外部グループに共有できるようにする +
  4. +
  5. + 信頼済みドメインだからといって無制限アクセスにはならない点に留意する(信頼はあくまで摩擦を減らすためのものであり、権限レベルはファイルオーナーが個別に付与する) +
  6. +
  7. + 高リスク部門(法務・財務など)はTrust + Rulesで外部共有を個別にブロックする +
  8. +
  9. + Security + CenterでDrive/Gmailの共有アクティビティを継続的に監視し、ポリシー違反のアラートを設定する +
  10. +
+
+

+ 2.2.4 ターゲットオーディエンスの管理 +

+

+ ターゲットオーディエンス(Target + audiences)は、共有ダイアログでユーザーに提示される「推奨共有先」のリストであり、Drive/Docs・Chatサービスで利用できます(対応エディションはFrontline + Plus、Business Plus、Enterprise Standard/Plus、Education Standard/Plusなど)。 +

+

展開のベストプラクティス(Googleの公式推奨手順):

+
    +
  1. + すべての正社員を含む「Employees Only」オーディエンスを作成する:ダイナミックグループ(対応エディションの場合)または全社員を含む既存グループを使う +
  2. +
  3. + 組織の全ユーザーを含む「Employees and + Vendors」等のオーディエンスも作成する:全ユーザーを含むグループを作成して追加する +
  4. +
  5. + ターゲットオーディエンス向けのDrive/Docs共有ポリシーを作成する:全社、または特定OU・設定グループに適用する +
  6. +
  7. + 「Employees Only」をプライマリ(優先)オーディエンスに設定する:優先オーディエンスをドラッグ操作で最上位に配置することで、ユーザーが誤ってベンダーを含む広いオーディエンスと共有するのを防ぎやすくなる +
  8. +
+

+ 設定場所: Admin console →「Drive and + Docs」→「共有設定」→「ターゲットオーディエンス」 +

+
+

+ ⚠注意点: + ターゲットオーディエンスの構成グループとして、ダイナミックグループ(Dynamic + groups)を組み合わせると、入退社に応じてメンバーが自動的に更新されるため、手動メンテナンスの負荷を大幅に減らせる(Section + 1のグループ管理を参照)。 +

+
+

+ 2.2.5 カスタムDocsテンプレートの設定 +

+

+ 組織独自のブランドを反映したテンプレート(Docs、Sheets、Slides、Forms、Sitesなど)をテンプレートギャラリーに追加できます。 +

+

設定手順:

+
    +
  1. + Admin console →「Drive and + Docs」→「テンプレート」→「テンプレートギャラリーの設定」 +
  2. +
  3. 「組織のカスタムテンプレートを有効にする」にチェックを入れて保存する
  4. +
  5. + (任意)カテゴリを追加する:例)マーケティング、営業、人事などのチーム別カテゴリ +
  6. +
  7. テンプレート送信の承認ポリシーを設定する:
  8. +
+
+ + + + + + + + + + + + + + + + + + + + + +
送信モード説明
Open(オープン)組織内の誰でも承認なしにテンプレートを追加・削除できる
Moderated(モデレート) + Docs + Templates権限を持つ管理者に新規テンプレート追加のメール承認リクエストが届く。いずれかの管理者が応答すると完了。承認されたテンプレートはカスタムギャラリーに追加され、却下された場合は再提出できる +
Restricted(制限) + Docs Templates権限を持つ管理者のみがテンプレートを追加できる +
+
+

+ 関連する管理者権限: 「Docs + Templates」権限を持つ管理者は、テンプレートギャラリー内のテンプレートの削除・カテゴリ分けができ、モデレート方式の場合はテンプレート送信の承認・却下も行える。 +

+
+

+ 💡ベストプラクティス: + ブランドガイドラインが厳格な組織ではRestrictedを選び、少人数のデザイン・広報チームのみがテンプレートを管理する体制にする。全社的なテンプレート活用を促進したい組織ではModeratedを使い、品質管理をしながら現場からの提案も取り入れる。 +

+
+

2.2.6 共有ドライブの作成と管理

+

+ 共有ドライブ(Shared + drives)は、特定の個人ではなくチーム・部門が所有者となるファイルストレージ空間で、メンバーの退職・異動があってもファイルが失われない点がマイドライブ(My + Drive)との大きな違いです。 +

+

+ 共有ドライブの作成許可: Admin console →「Drive and + Docs」→「共有設定」→「共有ドライブの作成」で、組織全体または一部のユーザーにのみ共有ドライブの作成を許可できる。作成を許可しないユーザーであっても、他者が作成した共有ドライブに追加されて利用することは可能。 +

+

+ OUへの割り当てとポリシーの継承: + 既定では、トップレベル組織部門で設定したポリシーがすべての共有ドライブに適用される。共有ドライブを子OUに割り当てることで、そのOU固有のポリシー(データ共有・セキュリティ・ストレージ)を個別に適用できる。既定でどのOUに共有ドライブが作成されるかも設定可能。 +

+

ストレージ容量の管理:

+
    +
  • 既定の共有ドライブ容量上限は100GB
  • +
  • + Admin console + →「ストレージ」→「ストレージ設定を管理」→対象OUを選択→「共有ドライブのストレージ上限」で、OUごとに異なる上限を設定できる +
  • +
  • + プロジェクト単位で一時的により多くのストレージが必要なユーザー向けに、設定グループ(Configuration + group)を作成し、そのグループのメンバーをOUのストレージ上限から除外することも可能 +
  • +
+
+flowchart LR
+    MyDrive["マイドライブ<br/>(個人所有)"] -->|"退職・異動時に<br/>所有権移転が必要"| Risk["データ喪失リスク"]
+    SharedDrive["共有ドライブ<br/>(チーム/組織所有)"] -->|"メンバー変更があっても<br/>ファイルは残る"| Stable["データの継続性"]
+
+    SharedDrive --> OU1["OUに割り当て"]
+    OU1 --> Policy["OU固有のポリシー継承<br/>(共有・ストレージ上限・セキュリティ)"]
+
+    style SharedDrive fill:#7c9eff,color:#000
+    style Stable fill:#7c9eff,color:#000
+
+

+ 💡ベストプラクティス: + 部門・プロジェクト単位の永続的なファイル資産(契約書、会計資料、進行中プロジェクトの成果物)は個人のマイドライブではなく共有ドライブに保存する運用を徹底し、退職者データ喪失リスクを構造的に排除する。ストレージ使用状況は「ストレージ設定」画面から定期的にレビューし、上位使用者・共有ドライブを把握する。 +

+
+

2.2.7 ストレージ容量の設定と調整

+

+ Google Workspaceのストレージは、Drive・Gmail・Google + Photosで共有される「プールドストレージ(Pooled + storage)」というモデルを採用しています。ライセンスごとに割り当てられたストレージが組織全体のプールに加算され、個々のユーザーはライセンス割り当て量を超えて利用することも可能です(プール全体に余裕がある限り)。 +

+

+ ユーザー単位のストレージ上限設定: Admin console + →「ストレージ」→「ストレージ設定を管理」からOU単位でストレージ上限を設定できる。ユーザーが「ストレージ容量不足」の通知を受け取った場合、多くのケースでサポートに連絡する必要はなく、Admin + console側でストレージ上限の設定を見直すことで解決する。 +

+

+ モニタリングツール: + ストレージ管理ツールでは、以下が確認できる: +

+
    +
  • 製品別(Drive、Gmailなど)のストレージ使用量
  • +
  • 組織内でストレージを最も使用しているユーザーの一覧
  • +
  • ストレージを最も使用している共有ドライブの一覧
  • +
  • ストレージ上限に近づいている警告
  • +
+
+

+ 💡ベストプラクティス: + ストレージポリシーを変更・強制する際は、事前にユーザーへポリシー変更の通知テンプレートを用いて周知し、不要なファイルの削除を促してから上限を適用する。バックアップ・アーカイブ用途でのDrive利用は推奨されておらず(Google + Workspaceはリアルタイムコラボレーションと共有に最適化されている)、大容量アーカイブには別のストレージソリューションを案内する。 +

+
+

+ 2.2.8 Google Drive for desktopの許可・禁止 +

+

+ Drive for desktop(旧Drive File Stream / Backup and + Sync)は、ローカルコンピュータ上でGoogle + Driveのファイルをストリーミング形式で利用可能にするデスクトップクライアントです。管理者はAdmin + console →「Drive and + Docs」→「機能とアプリケーション」から、組織またはOU単位で許可・禁止を制御できます。 +

+

関連設定:

+
    +
  • + Drive SDK:Drive SDK + APIを介したユーザーのDriveアクセスを許可するか +
  • +
  • + アドオン:Docsのアドオンストア経由でのアドオンインストールを許可するか +
  • +
  • + ランサムウェア検出と復元(Drive for desktop向け):Drive + for desktop環境でのランサムウェア検出・復元機能を有効化できる +
  • +
+
+

+ 💡ベストプラクティス: マネージドデバイスにはDrive for + desktopの配布・自動更新ポリシーを適用し(Google Workspace + Updatesの管理)、BYOD(私物端末)環境では組織のセキュリティポリシーに応じて許可の可否を判断する。ファイアウォール・プロキシ環境がある場合は、Drive/Sitesのファイアウォール要件を事前に確認しておく。 +

+
+

2.2.9 ファイル・フォルダの所有権移転

+

+ ユーザーの退職や異動の際、そのユーザーが所有していたDriveファイルの所有権を別のユーザーへ移転できます。 +

+

設定手順:

+
    +
  1. Admin console →「Drive and Docs」→「所有権の移転」を開く
  2. +
  3. + 移転元ユーザー(現在の所有者)と移転先ユーザー(新しい所有者)を指定する +
  4. +
  5. 移転を実行する
  6. +
+

+ 必要な管理者権限: 「Drive & + Docsサービスの設定」権限、「データ移行(Data + Transfer)」権限、「ユーザー:読み取り専用」権限の3つが必要(Admin + consoleでの「所有権の移転」設定へのアクセスには加えて「Driveサービス」権限も必要)。この権限セットはOU単位に限定できない(組織全体に適用される)。 +

+

+ 移転後の挙動: + 所有権移転後、元の所有者にはそのファイルへの編集権限が付与された状態が維持される(元所有者が削除されたり、編集権限を明示的に外されたりしない限り、引き続きアクセス可能)。 +

+
+

+ 🎯試験・実務でのポイント: + ユーザーを削除する前に必ず所有権移転を実施することが重要である。これはユーザー作成者が去った後もチームの重要データを失わないための基本的な運用手順であり、Section + 1の「アカウントの削除・保留・アーカイブ」(1.1)とも密接に関連する。 +

+
+

2.2.10 Driveラベルの管理

+

+ Driveラベル(Classification + labels)は、ファイルにメタデータ(機密度、承認ステータス、期限など)を付与し、検索性の向上、ポリシーの自動適用、DLPとの連携を可能にする分類機能です(Gmailメッセージへのラベル付けはベータ機能として提供)。 +

+

+ ラベルの作成: Admin console + →「セキュリティ」→「アクセスとデータ管理」→「ラベルマネージャ」(Manage Classification Labels管理者権限が必要)。1組織で最大150個のラベル(バッジ付きラベルを含む)を作成可能。 +

+

自動適用の3つの方法:

+
+flowchart TD
+    NewFile["新規ファイル作成/<br/>所有権移転/<br/>共有ドライブへの移動"] --> Method{"ラベル自動適用方式"}
+    Method -->|"1. デフォルト分類"| Default["Default classification<br/>OU/グループ単位で<br/>既定ラベル値を自動付与"]
+    Method -->|"2. DLPルール"| DLP["DLPルールの検出条件に<br/>一致した場合にラベルを付与"]
+    Method -->|"3. AI分類"| AI["Geminiによる<br/>コンテンツ内容の<br/>自動分類・ラベル付与"]
+
+    Default -.優先順位.-> Priority["DLPラベル値 > AI分類ラベル値<br/>> デフォルト分類ラベル値<br/>(同種のルールが競合する場合は<br/>ラベルの選択肢リストでより上位の値が優先)"]
+    DLP -.-> Priority
+    AI -.-> Priority
+
+    style Priority fill:#ffd54f,color:#000
+

+ ロックの概念: + デフォルト分類、DLP、Vault保持ルールのいずれかでラベルが参照されると、そのラベルはラベルマネージャ内で「ロック」され、編集・無効化・削除ができなくなる(ビジネスポリシーを壊すような変更を防止するため)。ロックを解除するには、すべてのデフォルト分類ポリシーからそのラベルを削除する必要がある。 +

+

+ 閲覧・適用権限: + ラベルの閲覧・適用にはファイルへの閲覧権限に加え、ラベル自体への閲覧権限が必要。管理者は「レポート」権限があれば、ラベルマネージャ権限がなくてもDriveのレポート・監査でラベル情報を確認できる。 +

+
+

+ 💡ベストプラクティス: + ラベル名・フィールド名・選択肢に機密情報そのものを含めない(ラベルマネージャの閲覧権限を持つ全管理者に見えてしまうため)。ユーザーにラベル入力を促す場合は「必須フィールド」設定を活用し、未入力時にバナー表示で入力を促す(ただし必須フィールド未入力でも共有・編集自体はブロックされない点に注意)。 +

+
+

+ 2.2.11 オフラインアクセスの有効化・無効化 +

+

+ オフラインアクセスは、インターネット接続がない状態でもDocs・Sheets・Slidesを作成・編集できるようにする機能です。既定では組織全体でオンになっており、ユーザーは自分のアカウントで個別にオン・オフを切り替えられます。 +

+

+ 制御オプション(Admin console →「Drive and + Docs」→「機能とアプリケーション」→「オフライン」): +

+
+ + + + + + + + + + + + + + + + + +
オプション説明
+ 全ユーザーにオフラインアクセスを許可(推奨) + + 最も簡便な方法。ユーザーは自分の端末・信頼する端末でオフラインアクセスを有効化できる +
デバイスポリシーでオフラインアクセスを制御 + マネージドデバイスにポリシーを導入して制御する。ポリシーを導入しないままこのオプションを選ぶと、以前オフラインアクセスできていたユーザーが24時間後にアクセスできなくなる点に要注意 +
+
+

+ 適用範囲の限界: この設定はChrome・Microsoft + Edgeブラウザ上でのDocs/Sheets/Slidesのオフライン利用に対するものであり、Google Drive for desktopには適用されない(Drive for desktopのオフラインファイル利用は別の仕組み)。 +

+
+

+ 💡ベストプラクティス: + 機密情報や社外秘ファイルを扱うユーザー(役員、法務、人事など)に対しては、端末紛失時の情報漏えいリスクを踏まえてオフラインアクセスを無効化することを検討する。それ以外の一般ユーザーには利便性を優先してオンのままにするのが一般的な運用。 +

+
+
+

2.3 Google Calendarの設定

+

+ 2.3.1 リソースカレンダーの作成と管理 +

+

+ リソースカレンダー(会議室、車両、機材など)の設定は、Section + 1で扱う「建物とリソース管理」(1.5)の実務基盤の上に成り立っています。ここではCalendarサービスの観点から、リソースの予約・共有をどう運用するかを扱います。 +

+

基本構造:

+
    +
  1. + 建物(Buildings) をまず作成する(最大10,000棟/ドメイン) +
  2. +
  3. 建物に フロア(Floors) を定義する
  4. +
  5. + 機能(Features)(プロジェクター、ホワイトボード、車椅子対応など)を最大100個まで作成する +
  6. +
  7. + リソース(Resources)(会議室 = Conference room、それ以外 = + Other)を最大10,000件まで作成し、建物・フロア・機能と紐づける +
  8. +
+

+ 設定場所: Admin console + →「ディレクトリ」→「建物とリソース」→「概要」(建物とリソースの管理者権限が必要)。CSVによる一括アップロードやAPI(resources.buildings、resources.features、resources.calendars)による一括更新にも対応。 +

+

+ Enterprise Plus / Assured Controls限定機能: + リソースを特定の組織部門(OU)に割り当てることで、そのリソースカレンダーのイベントデータが当該OUのデータリージョンポリシーなどのデータポリシーを継承するようにできる(リソースのメタデータ自体はルート組織のポリシーに従う)。 +

+
+

+ 💡ベストプラクティス: + リソース名には「建物-フロア-フロアセクション-リソース名(収容人数)[機能]」形式の自動生成命名規則を活用し、ユーザーが予約画面で場所と設備を一目で判断できるようにする。 +

+
+

2.3.2 リソースの予約ポリシーの設定

+

+ リソース予約の承認フローには、大きく分けて「自動承認」と「リソースマネージャーによる手動承認」の2パターンがあります。 +

+
+flowchart TD
+    Book["ユーザーがリソースを<br/>会議に招待"] --> AutoAccept{"リソースの<br/>Auto-accept invitations<br/>設定"}
+    AutoAccept -- "競合しない招待を<br/>自動承認" --> CheckConflict{"時間帯が<br/>既存予約と<br/>競合するか?"}
+    CheckConflict -- "競合なし" --> Confirmed1["自動的に予約確定"]
+    CheckConflict -- "競合あり" --> Declined1["自動的に辞退"]
+    AutoAccept -- "すべての招待を<br/>カレンダーに追加<br/>(リソースマネージャー運用)" --> Manager["リソースマネージャーに通知"]
+    Manager --> Decision{"マネージャーが<br/>確認"}
+    Decision -- "承認" --> Confirmed2["予約確定"]
+    Decision -- "却下/変更依頼" --> Declined2["辞退"]
+
+    style Confirmed1 fill:#7c9eff,color:#000
+    style Confirmed2 fill:#7c9eff,color:#000
+    style Declined1 fill:#e57373,color:#000
+    style Declined2 fill:#e57373,color:#000
+

リソースマネージャー方式の設定手順:

+
    +
  1. スーパー管理者権限を持つ管理者としてGoogleカレンダーにサインインする
  2. +
  3. リソースを組織全体、または特定の人に共有する
  4. +
  5. + リソースマネージャーとなるユーザーにもリソースを共有し、「変更を加える権限」および「共有を管理する権限」を付与する +
  6. +
  7. + リソースの「カレンダーの詳細」タブで、Auto-accept + invitationsを「すべての招待を自動的にこのカレンダーに追加する」に設定する +
  8. +
  9. + リソースマネージャーはリソース通知を受け取る設定を行い、招待が入るたびに承認・辞退・仮承諾を判断する +
  10. +
+

+ Free/Busy共有リソースの予約許可: + リソースが「Free/busyのみ表示」で共有されている場合、既定ではユーザーはそのリソースを予約できる。管理者はAdmin + console →「Calendar」→「全般設定」→「リソースの予約権限」で、「See only + free/busyとして共有されているリソースの予約をユーザーに許可する」のチェックを制御できる(機密性の高いイベント情報を隠しつつ予約自体は可能にする、という使い分けができる)。スーパー管理者は、この設定に関わらず常に全リソースを予約できる。 +

+
+

+ 💡ベストプラクティス: + 高需要の会議室(大会議室、役員会議室など)はリソースマネージャーによる手動承認にし、稼働状況をコントロールする。一般的な小会議室・電話ブースは自動承認にして、従業員の摩擦を減らす。 +

+
+

+ 2.3.3 カレンダー・リソースアクセスの委任 +

+

+ カレンダーの委任は、Gmailのメール委任(2.1.9)と対になる概念で、管理職のカレンダー管理をアシスタントに任せるようなシナリオで利用されます。 +

+

+ 設定方法: + カレンダー所有者がGoogleカレンダーの「設定と共有」からカレンダーを特定のユーザーに共有し、「予定の変更」権限を付与することで、事実上の委任が成立する(Gmailのメール委任のような専用の「委任」ボタンではなく、共有権限の付与という形で実現される点に注意)。 +

+

+ Google Workspace Sync for Microsoft + Outlook(GWSMO)を利用する場合: + Outlookからカレンダー・メールの委任機能を使いたい場合は、まずGoogleカレンダー側で共有設定を行った上で、委任先ユーザーが自分のOutlookプロファイルに委任元のGoogle + Workspaceアカウントを追加する、という手順を踏む。委任先は委任元のパスワード変更や他のアカウント設定変更はできない。 +

+

+ 建物・リソースの管理権限としての「委任」: + リソースカレンダーについては、前述のリソースマネージャー方式が実質的な「委任」の役割を果たす。 +

+
+

+ 🎯試験ポイント: + 「カレンダーとリソースへのアクセスを別のユーザーに委任する」という出題観点は、(1) + 個人カレンダーの委任=共有権限の付与、(2) + リソースカレンダーの委任=リソースマネージャー設定、という2つの異なるメカニズムを区別して理解しているかを問うている。 +

+
+

+ 2.3.4 プライマリ・セカンダリカレンダーのデフォルト内部共有設定 +

+

+ Admin console + →「Calendar」→「共有設定」で、組織内でカレンダーがどの程度共有されるかの既定値を設定できます。OU単位で異なるポリシーを適用でき、たとえば学校・教育機関ではより制限的な設定にし、一般企業ではよりオープンな設定にするといった使い分けが可能です。 +

+

内部共有の一般的な選択肢(制限が緩い順):

+
    +
  • 予定の詳細をすべて共有する(Share all information)
  • +
  • + 空き時間・予定ありのみ共有する(Free/busy情報のみ、時間帯は見えるが内容は見えない) +
  • +
  • 共有しない(デフォルトでは他ユーザーから見えない)
  • +
+

+ OU間の共有制限: + 部門間・学生グループ間でのカレンダー共有によるプライバシー・情報漏えいリスクを軽減するために、OUごとに「内部共有オプション(プライマリカレンダー用)」をより制限的な値に設定できる。合わせて「外部共有オプション」も無効化することで、OU単位での多層防御が可能。 +

+
+

+ 💡ベストプラクティス: + 役員・人事・法務など機密性の高い予定を扱う部門は、内部共有のデフォルトを「Free/busyのみ」以下に制限し、一般部門はコラボレーションを促進するためより開かれた設定にする、というOUベースの階層化ポリシーを設計する。 +

+
+

+ 2.3.5 チーム・グループ向け共有カレンダーの設定 +

+

+ チームやプロジェクト単位で共有するグループカレンダー(Secondary + calendar)を作成・共有することで、個人のプライマリカレンダーとは別に、チーム全体のイベント(休暇予定、チーム定例、プロジェクトマイルストーンなど)を一元管理できます。 +

+

作成の流れ(概要):

+
    +
  1. + 管理者またはカレンダー作成権限を持つユーザーがセカンダリカレンダーを新規作成する +
  2. +
  3. + 対象チーム・グループに対して適切なアクセスレベル(閲覧のみ/予定の変更/管理権限)で共有する +
  4. +
  5. + 必要に応じてGoogleグループのメンバー全体に一括共有する(Section + 1「グループのすべてのユーザーへの追加」参照) +
  6. +
+

+ 「マイカレンダー」に他ユーザーのカレンダーが表示される理由: + 管理者が明示的にユーザーへ共有した、あるいはユーザー自身が「マイカレンダー」欄に追加したカレンダー(他ユーザーのカレンダーやリソースカレンダーなど)は、そのユーザーのカレンダー一覧に表示され続ける。これは共有設定に起因する正常な挙動であり、トラブルシューティングの際にユーザーからの問い合わせの原因になりやすいポイントである。 +

+
+

+ 💡ベストプラクティス: チームカレンダーの命名規則(例:[チーム名] Team Calendar)を統一し、検索・識別性を高める。カレンダーの「管理権限」を持つメンバーは最小限に絞り、誤削除・誤設定変更のリスクを抑える。 +

+
+

+ 2.3.6 カレンダーの外部共有オプションの管理 +

+

+ 組織外のユーザーとカレンダーを共有する範囲も、Admin console + →「Calendar」→「共有設定」の「外部共有オプション」でOU単位に制御します。 +

+

+ 外部共有制限の効果: + 組織の外部共有を制限すると、ユーザーは個々のイベント単位でその制限を超えた共有はできなくなる(例:組織レベルでFree/busyのみに制限していれば、個別のイベントでそれ以上の詳細を外部共有することはできない)。 +

+

+ 外部ゲスト招待時の確認プロンプト: + ユーザーが組織外のゲストを含むイベントを作成すると、既定では「本当に組織外のゲストを含めてよいか」の確認プロンプトが表示される。Admin + console + →「Calendar」→「共有設定」→「外部での招待」で、このプロンプトの表示・非表示をOU単位で切り替えられる(例:日常的に外部顧客とやり取りする営業部門はプロンプトを無効化し、それ以外の部門は有効のままにする)。 +

+
+

+ 💡ベストプラクティス: + 外部共有オプションは、Drive/Docsの外部共有制御(2.2.3)と一貫したポリシー思想(信頼できる相手とのコラボレーションは円滑に、それ以外は摩擦を残す)で設計し、部門ごとに整合性を持たせる。 +

+
+

2.3.7 イベント所有権の移転

+

+ イベントの主催者(オーナー)が退職・異動する際、そのユーザーが作成したイベントの所有権を別のユーザーに移す必要があります。 +

+

+ エンドユーザー操作(イベント単位): + Googleカレンダーで自分がオーナーであるイベントを開き、「その他のオプション」→「オーナーの変更」から新しいオーナーのメールアドレスを入力する。 +

+

+ 管理者によるユーザー削除前の一括対応: + ユーザーを削除する前に、そのユーザーが所有するイベントおよびセカンダリカレンダーを取り消すか、他のユーザーに移管する必要がある(「ユーザーを削除する前にイベントやセカンダリカレンダーをキャンセル・移管する」手順)。対応しないまま削除すると、他の参加者のカレンダー上でイベントが不正な状態のまま残る可能性がある。 +

+
+

+ 🎯試験ポイント: + Drive/Docsのファイル所有権移転(2.2.9)と同様に、「ユーザー削除の前に所有物の移転・整理を行う」という運用順序が、Calendar・Drive双方で共通する重要な原則である。 +

+
+

2.3.8 不明な送信者からの招待の防止

+

+ 見知らぬ送信者からのスパム的なカレンダー招待は、ユーザーのカレンダーを埋め尽くし、フィッシングの温床にもなり得るため、Google + Workspaceには専用の対策が用意されています。 +

+

+ 組織レベルの制御(管理者): Admin console + →「Calendar」→「共有設定」→「外部での招待」で、「送信者とやり取りしたことがある招待のみを表示する」オプションを選択する。この設定を有効にすると、ユーザーが既に何らかの形でやり取りしたことのある送信者(連絡先に登録済み、Gmailでやり取り済み、同一Workspaceドメインなど)からの招待のみが自動的にカレンダーに追加され、それ以外の招待はカレンダーに自動追加されなくなる。 +

+

+ ユーザーレベルの制御: + ユーザー個人でも、Googleカレンダーの設定→「予定の設定」→「マイカレンダーへの招待の追加」で「送信元が判明している場合のみ」を選択できる。判定基準は、送信者が連絡先に登録されている、Gmailでメールをやり取りしたことがある、同一Workspaceドメインに所属している、のいずれかを満たす場合。 +

+

+ スパム招待への対応で避けるべき行動: + 不審な招待に対して「欠席」を含むいかなる返信(欠席・出席・未定・コメント付き返信)も行わないことが推奨される。返信すると招待元(スパム送信者)に「このアドレスは有効で監視されている」という情報を与えてしまい、標的として狙われやすくなる。安全な対応は「スパムとして報告」「返信せず削除」、またはそのまま無視することのみ。 +

+
+

+ 💡ベストプラクティス: + この設定を全社的に有効化した場合でも、既にカレンダーに登録されてしまった過去のスパムイベントは自動削除されないため、ユーザー自身での手動クリーンアップが必要になる点をヘルプデスク対応時に案内しておく。法務・金融など標的型攻撃のリスクが高い部門では、Gmailの外部送信者警告設定(2.1章)と組み合わせて多層的に対策する。 +

+
+
+

2.4 Google Meetの設定

+

+ 2.4.1 組織・OU単位でのMeetの有効化・無効化 +

+

+ Google Meetのサービス自体のオン・オフは、他のWorkspaceサービスと同様に、Admin + console →「アプリ」→「Google Workspace」→「Google + Meet」から、組織全体または特定のOU・設定グループ単位で制御します(Service Settings管理者権限が必要)。 +

+

+ 基本的な考え方: + サービスをオフにしたユーザーは、既存の会議への参加や新規会議の開催ができなくなる。段階的な展開(パイロット部門から先行導入し、順次全社展開する)を行う場合は、OUまたは設定グループを使って対象範囲を絞り込む。 +

+
+

+ 💡ベストプラクティス: + 大規模組織や大人数の会議(全社集会、ウェビナー等)を予定している場合は、事前に「大規模組織向けのGoogle + Meetセットアップ」ガイドに従い、ネットワーク要件の事前確認・会議スペースの設計・サポート体制の準備を行う。 +

+
+

2.4.2 Meetセーフティ設定の構成

+

+ Meetのセーフティ設定は、会議中の不正参加・荒らし行為(Zoombombing的な被害)を防ぐための中核的な管理機能です。中心となる概念が + Host Management(ホスト管理) です。 +

+
+flowchart TD
+    HM["Host Management<br/>(ホスト管理)"] -->|"オン"| HMOn["ホスト・co-hostのみが<br/>以下を制御可能:"]
+    HMOn --> C1["チャットの許可"]
+    HMOn --> C2["画面共有の許可"]
+    HMOn --> C3["リアクション・Q&A・投票の許可"]
+    HMOn --> C4["参加者の全員ミュート/解除"]
+    HMOn --> C5["録画・文字起こしの開始権限"]
+
+    HM -->|"オフ"| HMOff["同一ドメインの参加者は<br/>誰でも上記操作が可能<br/>(既定の緩い状態)"]
+
+    Waiting["Waiting Room<br/>(待機室)"] -->|"Quick accessが<br/>オフの場合に有効"| WaitFlow["参加者は待機室で<br/>ホストの承認を待つ"]
+    QuickAccess["Quick access<br/>(クイックアクセス)"] -->|"オンの場合"| QAFlow["カレンダー招待に含まれる<br/>参加者はホスト不在でも<br/>入室可能"]
+
+    style HMOn fill:#7c9eff,color:#000
+    style WaitFlow fill:#7c9eff,color:#000
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
設定概要既定値
Host Management + ホスト・co-hostに会議内コントロール(チャット、画面共有、ミュート、録画・文字起こし開始権限など)を集約させる機能。オフの場合、ドメイン内の参加者は誰でもこれらの操作が可能 + + Business系エディションは既定でオフ、Education系・Frontlineは既定でオン(管理者がAdmin + console上のデフォルト状態を変更可能) +
Waiting Room(待機室) + 参加者を待機室に留め、ホストが個別に入室を許可する。ホストは待機中の参加者にメッセージを送ったり、会議添付資料などの情報を共有したりできる + Quick accessがオフの場合に事実上の待機室として機能する
Quick access + カレンダー招待に事前に含まれている参加者、または既に会議に参加している組織内メンバーから招待された参加者は、ホスト不在でも入室できる + + Google Workspace全顧客・レガシーG Suite + Basic/Business顧客で既定オン +
外部参加者への追加の予防措置 + 外部参加者が「リクエストなしで」参加できるのは、(a) + 会議開始前のカレンダー招待に含まれている、または(b) + 既に会議に参加している組織内メンバーから招待された場合で、かつ(c) + 予定開始時刻の前後15分以内、のいずれかを満たす場合のみ + 常時適用される仕様
+
+

+ 設定場所: Admin console →「Google + Meet」→「Meetセーフティ設定」 +

+

その他のセキュリティ・プライバシー機能:

+
    +
  • + 会議コード:長く推測困難なコード体系で不正アクセスを防止 +
  • +
  • + 電話でのダイヤルイン:電話番号とPINは、予定されている会議時間内のみ有効 +
  • +
  • + SAML SSOによる追加認証:全エディションでSAMLベースのシングルサインオンに対応 +
  • +
  • 監査ログ:Admin console上でMeetの監査ログを確認可能
  • +
  • + アクセス透過性(Access Transparency):管理者がMeet録画にアクセスするたびにログとその理由を記録 +
  • +
  • + データリージョン:録画データをGoogle + Driveに保存する地理的リージョン(米国・欧州など)を指定可能 +
  • +
+
+

+ 💡ベストプラクティス: + 外部参加者を含む機密性の高い会議(人事面談、取締役会など)では、Host + Managementを明示的にオンにし、待機室を併用する。全社集会・ウェビナーのような大規模会議では、Quick + accessをオンにしたままHost + Managementでチャット・画面共有権限のみを制限する、といった使い分けが実務的。 +

+
+

+ 2.4.3 Meetビデオ設定の構成(画質・録画・文字起こし・ノートテイキング) +

+

+ Admin console →「Google + Meet」→「Meetビデオ設定」から、会議の画質・録画・文字起こし・Gemini + によるノートテイキングを制御します。 +

+

主なビデオ設定項目:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
設定説明
既定の動画画質(Default video quality)会議の画質を選択する(帯域幅要件に影響)
録画(Recording) + 「ユーザーに会議の録画を許可する」設定。既定では3か月間録画データが保存される。録画のダウンロード・コピーを既定で許可するかも設定可能。Host + Managementがオンでホストが録画を許可していない場合、録画は利用不可。ブレイクアウトルーム内では録画不可 +
ストリーム(Stream) + 組織内、信頼済みドメイン、またはYouTube経由でのライブ配信を許可するか +
ゲートウェイの相互運用性 + サードパーティのビデオ会議システムのユーザーが自組織のMeet会議に参加できるようにする(追加設定が必要) +
クライアントログのアップロード + ユーザーのメールアドレスを含むブラウザ・モバイルアプリのログ情報の収集を許可し、サポート対応に活用する +
+
+

+ 文字起こし(Transcripts): + Education(学生ライセンス)を除く全エディションで既定オン。会議の音声のみが文字起こし対象で(チャットメッセージの記録には録画が必要)、文字起こし結果は会議主催者のGoogle + Driveに保存される。Host + Managementがオンの場合、Transcriptsを開始できるのはホスト・co-hostのみに制限される。 +

+

+ 自動的な会議成果物の設定(録画・文字起こし・Gemini + ノートテイキングの自動化): + Admin console →「Google + Meet」→「Meetビデオ設定」→「自動文字起こし」「自動録画」、および「Google + Meetの設定」→「Gemini設定」から、新規作成される会議について録画・文字起こし・「自分の代わりにメモを取る(Gemini + ノートテイキング)」を既定でオンにできる。ホスト・co-hostはカレンダー招待や会議中にこれらの設定を編集・オフにできる。これらの成果物がオンの場合、参加者には入室時に通知される。「自分の代わりにメモを取る」機能はGemini + Business/Enterprise/EducationまたはAI Meetings & Messagesアドオンが必要。 +

+
+flowchart LR
+    Meeting["Meet設定"] --> Video["ビデオ設定"]
+    Video --> Quality["既定の画質"]
+    Video --> Rec["録画<br/>(既定3か月保存)"]
+    Video --> Trans["文字起こし<br/>(音声のみ、Drive保存)"]
+    Video --> Notes["Gemini ノートテイキング<br/>(Business/Enterprise/Education<br/>アドオンが必要)"]
+
+    Rec -.HostManagementの制約.-> HMcheck["ホスト管理がオン かつ<br/>ホストが許可していない場合は<br/>録画不可"]
+
+    style Rec fill:#7c9eff,color:#000
+    style Trans fill:#7c9eff,color:#000
+    style Notes fill:#7c9eff,color:#000
+
+

+ 💡ベストプラクティス: + 「営業商談やタウンホールなど、常に記録を残したい会議シリーズ」に対しては、自動録画・自動文字起こしをOU/グループ単位でオンにし、手動操作への依存をなくす。一方、機密性の高いミーティングが多い部門では、既定オフのままにして、必要な場合にホストが都度有効化する運用にする。 +

+
+
+

2.5 Google Chatの設定

+

+ 2.5.1 組織・OU単位でのChatの有効化・無効化 +

+

+ Chatサービスの有効化・無効化は、Admin console →「アプリ」→「Google + Workspace」→「Google + Chat」→「Chatを有効または無効にする」から、組織全体・OU単位・グループ単位で制御します。 +

+

+ Chat設定の初期展開(Set up Chat for your organization)の流れ: +

+
    +
  1. 要件を確認する
  2. +
  3. + ディレクトリの連絡先共有をオンにする(ユーザー同士がChatで検索・発見できるようにするため) +
  4. +
  5. Chatサービス自体のオン・オフを設定する
  6. +
  7. Chatの履歴設定をオン・オフにする(2.5.2参照)
  8. +
  9. スペースの履歴オプションを設定する
  10. +
  11. Chat招待の自動承諾を設定する(2.5.3参照)
  12. +
  13. 外部ユーザーとのやり取りを制御する
  14. +
  15. Chatアプリのインストールを許可する(2.5.4参照)
  16. +
  17. GIFピッカーのオン・オフを設定する
  18. +
  19. ユーザーへの告知・トレーニングを行う
  20. +
+
+

+ 💡ベストプラクティス: + Chatの全社展開前に、まずディレクトリ連絡先共有の設定を確認する(オフのままだとユーザー同士が検索できずChatの利便性が著しく損なわれる)。 +

+
+

2.5.2 Admin consoleでのChat設定

+

+ チャット履歴(History for chats): + 1対1メッセージやグループメッセージの履歴を保持するかどうかの既定値を制御する。 +

+

+ スペース履歴(History for spaces): + インラインスレッド形式のスペースにおける履歴の既定設定。選択肢は以下の4種類: +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
オプション動作
History is ON by default(ユーザーが変更可)既定オンだが個々のスペースで変更できる
History is OFF by default(ユーザーが変更可)既定オフだが個々のスペースで変更できる
History is ALWAYS ON(変更不可)常にオンで固定、ユーザーは変更できない
History is ALWAYS OFF(変更不可)常にオフで固定、ユーザーは変更できない
+
+

+ 「Let History for chats setting override Space history + setting」のチェックボックスにより、1対1・グループメッセージの履歴設定がスペースの履歴設定より優先されるかどうかを制御できる。両者を意図的に逆方向で強制すると、グループ会話をスペースにアップグレードした際に履歴設定が切り替わる可能性があるため注意。 +

+

外部ドメインとのChat・スペース:

+
+flowchart TD
+    Ext["組織外とのChat連携"] --> Q1{"1対1/スペース単位で<br/>組織外とメッセージ<br/>送受信を許可するか?"}
+    Q1 -- "On" --> Trust{"信頼済みドメイン<br/>(allowlist)のみに<br/>制限するか?"}
+    Trust -- "はい" --> Restricted["特定ドメインとのみ<br/>外部Chatを許可"]
+    Trust -- "いいえ" --> AnyExternal["任意の外部ドメインと<br/>Chat可能"]
+    Q1 -- "Off" --> NoExternal["外部とのメッセージ<br/>送受信を禁止"]
+
+    Q1 --> Q2{"外部スペース<br/>(External spaces)を<br/>許可するか?"}
+    Q2 -- "はい" --> ExtSpace["メンバー・認可されたChatアプリが<br/>外部ユーザー/ゲストを招待可能"]
+    Q2 -- "いいえ" --> NoExtSpace["スペースへの<br/>外部ユーザー招待不可"]
+
+    style Restricted fill:#7c9eff,color:#000
+    style AnyExternal fill:#ffd54f,color:#000
+    style NoExternal fill:#e57373,color:#000
+

+ 外部チャットの設定手順: Admin console →「Google + Chat」→「External Chat + Settings」で、「組織外へのメッセージ送信をユーザーに許可する」をオン・オフし、オンの場合は「許可リスト化されたドメインのみに制限する」「面識のある外部ゲスト・ユーザーからの招待を自動承諾する」を任意で追加設定できる。 +

+

+ ゲストアカウントと外部ユーザーの違い: + 外部ユーザーはGoogleアカウントを保有する組織外の人物、ゲストはGoogleアカウントを持たない人物を指し、ゲストにはアカウント作成の招待が送られる。外部ユーザー・ゲストはグループメッセージには直接追加できず、1対1メッセージまたは外部メンバーを許可するスペースでのみやり取りできる。信頼済みドメインを設定すると、Workspaceアカウントを持たないコンシューマー向けGoogleアカウント利用者とのコラボレーションができなくなる点に注意(業務用メールで作成された個人アカウントとの意図しない連携を防ぐ効果もある)。 +

+

+ コンテンツモデレーション(Moderation): + ユーザーが不適切なメッセージを報告できる仕組みを、Admin console →「Google + Chat」→コンテンツ保護設定から有効化する。報告はモデレーターロールを持つ管理者のみが確認可能(Google自身はレポート内容を閲覧しない)。管理者はレポートカテゴリ・対象となる会話タイプを管理し、報告されたコンテンツに対してアクションを実行できる。2020年12月以前に作成された外部ユーザーを含むグループ会話は、Chat/クラシックHangoutsの仕様変更により報告対象外となる。 +

+

+ 設定場所まとめ: すべてAdmin console →「アプリ」→「Google + Workspace」→「Google Chat」配下(Service Settings管理者権限、モデレーションなど一部はGoogle Chat管理者権限が必要)。 +

+
+

+ 💡ベストプラクティス: + コンプライアンス要件が厳しい業界(金融・医療)では、履歴を「ALWAYS + ON」に固定し、Vaultによる法的保持・eDiscovery対応を前提とした運用にする。外部連携が多い営業・パートナーシップ部門は「External + Chat + Settings」で信頼済みドメインの許可リストを整備し、それ以外の部門は外部Chatをオフのままにする。 +

+
+

2.5.3 Chat招待設定の管理

+

+ Chat招待の自動承諾(Automatically accept chat invitations): + 組織内の他ユーザーからのChat招待を自動的に承諾させるかどうかを制御する。オフの場合、既定では新しい連絡先からの招待をユーザーが手動で承諾する必要がある。設定場所:Admin + console →「Google Chat」→「Chat Invitations」で、OU単位でオン・オフを選択する。 +

+

+ Chat・スペースの作成制限(Chat and Space restrictions): + 特定のユーザーグループに対して、新規の1対1メッセージ・グループメッセージ・スペースの作成を制限できる。制限対象グループは、既存の会話へのメンバー管理操作も制限される。設定場所:Admin + console →「Google Chat」→「Chat and Space restrictions」。 +

+
+

+ 💡ベストプラクティス: + 大規模組織では新入社員のオンボーディング時の摩擦を減らすため、自動承諾を組織内でオンにしておくことが一般的。逆に、外部からの不審な招待を警戒すべき業種では、手動承諾のままにしてユーザーの判断を介在させる。 +

+
+

2.5.4 Chatアプリの追加

+

+ Chatアプリ(ポーリング、タスク管理、サードパーティ連携ボットなど)のインストール許可も、Admin + console →「Google Chat」→「Chat apps」で制御します。 +

+

+ ユーザーによるインストールの許可: Admin console →「Google + Chat」→「Chat + apps」でOU単位に許可を設定する。一部のアプリはトップレベル組織単位でのアクセスを要求する場合があり、これを許可しないとChat + APIが正しく動作しない可能性がある点に注意。 +

+

+ 管理者による組織への一括インストール: 管理者はGoogle Workspace + Marketplaceからアプリを選び、「管理者としてインストール」→対象を「組織内の全員」に設定してインストールできる。この場合、ユーザーは自分でインストール操作をせずとも「アプリ」セクションにそのアプリが表示され、利用可能になる(通知はオフにできるが、アンインストールはユーザー側ではできない)。 +

+

+ 自動インストール(2025年9月以降): + ユーザーが最初にアプリの機能を使おうとした時点(例:投票アプリで初めて投票を作成しようとした時)に自動的にそのアプリがインストールされ、以降はどのスペースでもすぐに利用できるようになる仕組みが導入されている。自動インストールを望まない場合、管理者は特定のアプリのインストール自体を禁止するか、特定スペースでのアプリインストール権限を制限することで防止できる。 +

+

+ アプリ権限(App authorization): + Chatアプリがユーザーのように振る舞うための権限(メッセージの送受信など)はOAuth + 2.0スコープ(https://www.googleapis.com/auth/chat.app.*)で要求される。アプリ権限はユーザー権限と異なりOUごとに付与範囲を絞ることができない点も押さえておくべき。 +

+
+

+ 💡ベストプラクティス: + 生産性向上に資する社内標準アプリ(承認ワークフロー、勤怠管理ボットなど)は管理者が組織全体に一括インストールし、利用開始の障壁をなくす。一方、サードパーティ製の汎用チャットアプリはMarketplaceの許可リスト(Section + 4「Marketplaceの許可リスト管理」も参照)で個別審査してから許可する。 +

+
+
+

+ 2.6 Google Workspaceにおける生成AIの活用 +

+

+ 2.6.1 生成AI利用時の組織データのプライバシーとセキュリティ確保 +

+

+ Gemini for Google + Workspaceは、エンタープライズ利用を前提としたデータガバナンスモデルの上に構築されています。管理者としてまず理解すべきは、Geminiのデータ取り扱いポリシーが通常のGoogle + Workspaceサービスと同様の契約・プライバシー保護の枠内にあるという点です。 +

+

Googleが明示するデータ保護の原則:

+
    +
  • ユーザーのデータはユーザー自身のものであり、Googleの所有物ではない
  • +
  • Googleはユーザーデータを広告目的で利用しない
  • +
  • Googleはユーザーデータを第三者に販売しない
  • +
  • + データは転送時に暗号化され、Google + Driveに保存される会議録画等は保存時にも暗号化される +
  • +
  • + Vaultを使ってGeminiに関連するデータ(例:Meet録画)の保持ポリシーを設定できる +
  • +
  • HIPAAやFedRAMP Highなど、規制業界のコンプライアンス要件をサポート
  • +
+

+ Workspace Intelligence(サイドパネルのGemini)のデータソース制御: + Gmail・Drive・Calendar・Chatの各サービスについて、Geminiがバックグラウンドでアクティブに検索対象とするかどうかを個別にオン・オフできる。特定サービスをオフにすると、Geminiはそのアプリを能動的に検索しなくなる。 +

+

会話履歴の保持ポリシー: 管理者は以下を選択できる:

+
    +
  • ユーザーによる個別会話の手動削除を許可する(既定で有効)
  • +
  • 一定期間(3か月、18か月、3年など)でGeminiの会話履歴を自動削除する
  • +
  • 組織として無期限に会話を保持する
  • +
+

+ Gemini会話履歴がオフの場合でも、新しいチャットはサービス提供とフィードバック処理のために最大72時間、ユーザーアカウント内に一時保存される(この72時間分のデータはGemini + Apps Activityには表示されない)。 +

+

+ プロンプトインジェクション対策: + Geminiは新興の攻撃ベクトルであるプロンプトインジェクションに対して、多層防御戦略(layered + defense strategy)で対応するよう設計されている。 +

+
+

💡ベストプラクティス:

+
    +
  • + 業務上不要なサービスへのGeminiのアクセスは、Workspace + Intelligenceのデータソース設定でオフにし、最小権限の原則を適用する +
  • +
  • + 規制業界(医療・金融・公共部門)では、Vaultを用いたGemini関連データの保持ポリシー設定を、既存の他サービスの保持ポリシーと整合させる +
  • +
  • + 個人アカウントとしてGeminiアプリを追加サービスとして利用しているユーザーには、機密情報・社外秘情報を入力しないよう周知する(追加サービスとしてのGeminiは、Workspaceアカウントに紐づく組織ポリシーの一部が適用されない場合がある) +
  • +
+
+

+ 2.6.2 組織・OU単位でのGeminiの有効化・無効化 +

+

+ Geminiの有効化・無効化には、複数のレイヤーが存在する点が試験・実務双方で頻出の理解ポイントです。 +

+
+flowchart TD
+    Layer1["レイヤー1: Workspaceサービス内のGemini機能<br/>(Gmail/Docs/Sheets/Slides/Driveのサイドパネル)"]
+    Layer2["レイヤー2: Geminiアプリ(gemini.google.com)へのアクセス"]
+    Layer3["レイヤー3: Geminiアプリ内でのWorkspace拡張機能<br/>(旧Workspace Extensions/Apps)"]
+
+    Layer1 -->|"Admin console →<br/>生成AI → Gemini for Workspace →<br/>Feature access"| L1Detail["サービスごとに個別On/Off<br/>(あるアプリでオフでも、<br/>別アプリ経由でそのデータに<br/>アクセスされる場合がある点に注意)"]
+    Layer2 -->|"Admin console →<br/>Turn the Gemini app on/off"| L2Detail["既定で18歳以上かつ<br/>ライセンス保有ユーザーに提供。<br/>全ユーザーへの拡大も可能"]
+    Layer3 -->|"Admin console →<br/>Turn Google apps in Gemini on/off"| L3Detail["Calendar/Docs/Drive/Gmail/<br/>Keep/TasksのコンテンツをGeminiアプリの<br/>プロンプトに利用可能にするか"]
+
+    style Layer1 fill:#7c9eff,color:#000
+    style Layer2 fill:#7c9eff,color:#000
+    style Layer3 fill:#7c9eff,color:#000
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
レイヤー設定名設定場所既定値・補足
① Workspaceサービス内のGemini機能Feature access + Admin console →「生成AI」→「Gemini for Workspace」→「Feature + access」パネル + + 既定オン。あるアプリでGeminiをオフにしても、別のアプリ経由でそのデータをGeminiが参照できる場合がある(例:Driveでオフでも、GmailからGeminiにDriveファイルについて質問すると参照される) +
② Geminiアプリ自体へのアクセスTurn the Gemini app on or offAdmin console →「生成AI」→「Gemini App」 + 既定で18歳以上かつ対応ライセンスを持つ全ユーザーに提供。ライセンスの有無に関わらず全ユーザーへのアクセス拡大も設定可能。モバイルアプリ専用の管理者コントロールは存在しないが、デバイス管理設定でアプリ自体をブロックすることは可能 +
③ Geminiアプリ内のWorkspace拡張機能Turn Google apps in Gemini on or offAdmin console →「生成AI」→「Gemini App」→「Apps」 + Geminiアプリの利用には会話履歴のオンが前提。組織側で該当のWorkspaceサービス(例:Tasks)自体をオフにしている場合、対応する拡張機能も利用不可 +
+
+

+ 利用の前提条件: Workspace拡張機能(Apps)を使うには + Gemini会話履歴(Gemini conversation history) + をオンにする必要がある。また、複数のアプリ・サービスをまたぐ複合的なプロンプト(例:「カレンダーに予定を作成し、リマインダーも追加して」)を実行する場合、関与するすべてのサービス(この例ではCalendarとTasks)が有効になっていないと、一部のアクションが実行されないまま終わってしまう。 +

+

+ Gemini for App Creation / Gemini in AppSheet Solutions: + AppSheet関連のGemini機能(自然言語でのアプリ作成、自動化タスクへのAI組み込み)は別個の設定として、チーム単位でオン・オフできる(2.7も参照)。 +

+
+

+ 💡ベストプラクティス: + 段階的ロールアウトの初期フェーズでは、パイロットOUのみでレイヤー①②③をすべてオンにして検証し、フィードバックを収集してから全社展開する。生成AIの利用に消極的な組織は、まずレイヤー①(Workspaceアプリ内の機能)のみを許可し、レイヤー③(外部アプリであるGeminiアプリへのWorkspaceデータ連携)は慎重に判断する、という段階的な導入戦略も有効。 +

+
+

+ 2.6.3 Geminiアプリ向けGoogle Workspace拡張機能の有効化 +

+

+ 「Turn Google apps in Gemini on or off」(Workspace apps、旧称Workspace + Extensions)は、Geminiアプリ(gemini.google.com)がCalendar・Docs・Drive・Gmail・Keep・Tasksなどのユーザーコンテンツを参照し、より文脈に沿った応答を返せるようにする設定です。 +

+

この機能で可能になること:

+
    +
  • Gmailに関する追加情報へのリンク(引用)をGeminiの応答に含める
  • +
  • + ユーザーがDriveファイルをGeminiアプリやGemの指示(Gem + instructions)にアップロードして利用する +
  • +
+

+ オフにした場合の挙動: 既にWorkspace + apps機能を使って行われたチャットのやり取り自体はユーザーに引き続き表示されるが、Geminiアプリは元のプロンプトに応答するために必要だった情報以外へのアクセスができなくなる(再度オンにするまで、Workspaceからの追加情報取得はできない)。 +

+

+ 適用対象外のケース: + 追加サービスとしてGeminiにアクセスしているユーザー(=Workspaceの標準ライセンスに含まれない形でGeminiアプリのみ追加提供されているケース)は、Workspace + appsを利用できない。 +

+
+

+ 💡ベストプラクティス: Workspace + appsをオンにする際は、Drive内のファイルアクセス制御(本人が所有またはアクセス権を持つファイルのみ、共有ドライブ経由のファイルは除く)が、既存のDrive共有ポリシーと一貫していることを確認し、意図せず機密ファイルへのアクセス経路が広がらないようにする。 +

+
+

2.6.4 Gemini利用状況レポートの生成

+

+ 管理者は、Gemini機能の利用状況を可視化し、導入効果・利用パターン・潜在的なセキュリティリスクを把握するための複数のレポートにアクセスできます。 +

+

+ 設定場所: Admin console →「生成AI」→「Gemini + reports」→「ユーザーレベルの使用状況」 +

+

主なレポートの種類:

+
+ + + + + + + + + + + + + + + + + + + + + +
レポート内容
組織レベルの利用状況(Org-level usage) + アクティブなGeminiユーザー数、ライセンス割り当て状況、全体の利用推移、アプリ別の利用状況(Gemini + Adoption per app) +
+ ユーザーレベルの利用状況(User-level usage) + + 利用強度(高・中・低・ゼロ)、アクティブ日数、ユーザーごとのアプリ別詳細な利用状況、利用上限に達した日数(Days + at limit) +
+ 利用インタラクション(Usage per interaction) + + Gmailの「文章作成のヒント」やSheetsでのデータ整理支援など、機能単位での詳細な利用実績 +
+
+

+ フィルタリング: + OU単位・グループ単位でレポートをフィルタリングできる。組織構造の変更が反映されるまで最大72時間かかり、組織変更前の履歴データはその変更を遡って反映されない点に注意。 +

+

+ Gemini監査ログ(Reporting API経由): Admin SDKのReporting + APIを通じて、Geminiアプリ・Workspaceアプリ内でのユーザーのアクティビティ(実行されたアクション、使用されたアプリ、使用された機能、最終利用日時など)をより詳細に取得できる。このデータはセキュリティ調査ツール・監査調査ツールでも利用可能。 +

+

活用シーン:

+
    +
  • + 利用状況が高いパワーユーザーを特定し、他ユーザーへの展開・トレーニングのアンバサダーとして活用する +
  • +
  • + ライセンス上限(使用制限)に達しているユーザーの傾向から、アドオン(AI + Expanded Accessなど)へのアップグレード要否を判断する +
  • +
  • + 特定ユーザーのGemini利用パターンの急激な変化から、潜在的な誤用・セキュリティリスクの兆候を検知する +
  • +
+
+

+ 💡ベストプラクティス: + ライセンスコストの最適化のため、四半期ごとにGemini利用状況レポートをレビューし、利用実績が「ゼロ」のユーザーからライセンスを再配布することを検討する。同時に、利用強度が「高」のユーザー層を特定し、そのユースケースを社内のベストプラクティス共有会で展開する。 +

+
+
+

2.7 Workspace開発のサポート

+

+ 2.7.1 AppSheetとApps Scriptのユースケース +

+

+ AppSheetとApps + Scriptは、いずれも「コードを書かずに、または最小限のコードでGoogle + Workspaceを拡張・自動化する」ためのツールですが、性質が異なり、試験でも両者の使い分けの理解が問われます。 +

+
+flowchart LR
+    Need["業務自動化の要件"] --> Q{"要件の性質は?"}
+    Q -->|"データ駆動型の<br/>業務アプリ・モバイルUIが必要<br/>(現場担当者向けフォーム、<br/>在庫管理、承認ワークフロー等)"| AppSheet["AppSheet<br/>(ノーコードアプリ開発)"]
+    Q -->|"既存のWorkspaceアプリの<br/>内部処理をカスタムコードで<br/>自動化したい<br/>(トリガー起動のスクリプト、<br/>カスタム関数、API連携)"| AppsScript["Apps Script<br/>(JavaScriptベースの<br/>クラウドスクリプト実行基盤)"]
+
+    AppSheet -.連携可能.-> AppsScript
+    AppsScript -.複雑なロジックを提供.-> AppSheet
+
+    style AppSheet fill:#7c9eff,color:#000
+    style AppsScript fill:#7c9eff,color:#000
+

AppSheetの主なユースケース:

+
    +
  • Google Sheetsのデータからモバイル対応の業務アプリを作成する
  • +
  • + 出張申請フローで、申請が行われた際に上長を自動検索してChatまたはメールで承認通知を送る +
  • +
  • + 現場作業員がモバイル端末で撮影した点検写真をDriveにアップロードし、監査担当者がアクセスできるよう共有設定を自動調整する +
  • +
  • + シフト・予約管理の簡易Webインターフェースを提供し、予約が入るとCalendarに自動でイベントを作成し招待する +
  • +
  • Google Chat内でアプリを起動する(Launch apps in Google Chat)
  • +
+

Apps Scriptの主なユースケース:

+
    +
  • ボタンクリックでカレンダーの予定を作成する
  • +
  • 新しい行が追加された際にスライドを自動追加する
  • +
  • + フォーム経由でアップロードされた写真をDriveに保存し、特定の人と自動共有する +
  • +
  • テーブルデータから監査ログとしてGoogle Docsファイルを自動生成する
  • +
  • + 外部の機械学習サービスを呼び出し、予測結果を新しい行のデータとして書き戻す +
  • +
+

+ AppSheet Apps Scriptコネクタ: + AppSheetの自動化(Automation)から直接Apps + Scriptの関数を呼び出せるコネクタが提供されており、AppSheetアプリからGoogle + Workspace API(Drive、Docs、Sheets、Admin SDKなど)やYouTube、Google + Analytics、BigQueryなど他のGoogleサービスにアクセスするワークフローを構築できる。この連携により、AppSheetのノーコードの手軽さと、Apps + Scriptの柔軟なカスタムロジックを組み合わせられる。Apps Scriptサービスが組織で有効になっている必要がある点に注意(AppSheetの自動化からApps Scriptを呼び出すための前提条件)。 +

+

+ 制約事項: AppSheetからはスタンドアロンのApps + Scriptスクリプトのみ呼び出し可能で、コンテナバインド型のスクリプト(特定のスプレッドシート等に紐づくスクリプト)は現時点でサポートされていない。また、Apps + Scriptは常にヘッドデプロイメント(最新の保存済みバージョン)を実行し、特定のデプロイバージョンを指定して呼び出すことはできない。 +

+
+

+ 🎯試験のポイント: 「AppSheetとApps + Scriptのユースケースを特定する」という出題観点は、両者の二者択一ではなく、組み合わせて使うケースが多いことを理解しているかを問う。AppSheetは「UI・データ入力・ワークフロートリガーの民主化」、Apps + Scriptは「Workspaceサービスへの深いプログラマティックな統合」という役割分担で捉えるのが実務的。 +

+
+

+ 2.7.2 組織・OU単位でのAppSheetの有効化 +

+

+ AppSheetは他の追加Googleサービスと同様に、Admin + console上で組織・OU・グループ単位の粒度で有効化・無効化を制御できます。 +

+

+ 設定手順: Admin console + →「アプリ」→「追加のGoogleサービス」→「AppSheetの設定」から、組織全体または特定のOU・グループ・ユーザー単位でオン・オフを切り替える。 +

+

+ サブスクリプションがない場合の挙動(過去の移行時の仕様): + AppSheetのサブスクリプションを契約していない組織では、「個別に管理されないサービスへのアクセス管理」設定がOUごとにオン・オフ混在している場合、AppSheetの制御はそのOUレベルの設定に自動的に整合する。全OUでオフに設定されている組織では、AppSheet自体も全体でオフになる。 +

+

AppSheet管理の階層構造:

+
+ + + + + + + + + + + + + + + + + + + + + +
概念説明
Organization(組織) + Google + Workspace管理者に組織内の全チームを管理する一元的なツールを提供し、チーム管理をチーム管理者に委任できる +
Team(チーム) + 個々のアプリ開発チーム単位。Gemini for App + Creation(自然言語でのアプリ作成)などの機能はチーム単位でオン・オフを設定する +
AppSheet Admin Console + サブスクリプションのライセンス購入・割り当て・使用状況を可視化するための専用管理コンソール +
+
+

Gemini関連機能の有効化:

+
    +
  • + Gemini for App Creation:自然言語の指示だけでAppSheetアプリを構築できる機能。チーム単位で有効化を制御 +
  • +
  • + Gemini in AppSheet Solutions:自動化(Automation)内にAIタスクを追加し、情報の抽出・分類を行える機能。どのアプリ作成者がAI機能を自動化内で使えるかを管理者が制御可能 +
  • +
+
+

+ 💡ベストプラクティス: + AppSheetを初めて全社導入する際は、まず「Organization」機能を使って部門ごとの「Team」を作成し、各チームにチーム管理者を委任することで、シャドーIT化を防ぎながらも現場主導のアプリ開発を促進する、というガバナンスモデルを構築する。Apps + Script同様、既定では組織全体でオンになっているため、まだ利用ポリシーが整っていない組織は、正式な運用ルール策定までの間、Admin + console上で意図的にオフに設定しておくことも選択肢となる。 +

+
+
+

Section 2 ベストプラクティス総括表

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
サービス重要設定ベストプラクティスの要点
GmailMXレコード + smtp.google.com単一レコードへの移行、切り替え前のユーザー作成完了 +
Gmailルーティング + Default + routing(既定配送)とRouting(特定条件配送)を使い分け、テストは小規模OUから開始 +
Gmailコンテンツコンプライアンス + 事前定義された検出器を活用し、正規表現の自作を最小化。ルール優先順位を必ず検証 +
Gmailスパム・フィッシング対策 + IPアレースリストはドメイン全体に影響するため範囲を絞り込み、インバウンドゲートウェイのReject all mail not from gateway IPsは慎重に判断 +
Gmailメール認証 + SPF・DKIM・DMARCを三点セットで運用し、DMARCはp=noneから段階的にrejectへロールアウト +
Gmail移行 + パイロットグループでの先行移行、デルタ移行による差分同期の活用 +
GmailQuarantine + 最小権限のカスタムロール(Access Admin Quarantine/Access + Restricted Quarantines)でアクセスを委任 +
Drive/Docsデフォルト共有 + Restrictedを既定にし、ターゲットオーディエンスの「Employees + Only」をプライマリに設定 +
Drive/DocsTrust Rules + 部門・ドメイン・グループ単位の細かい共有制御が必要な場合に採用し、既存Drive共有設定からの変換をプレビューで確認 +
Drive/Docs共有ドライブ + 永続的なチーム資産は共有ドライブに保存し、退職時のデータ喪失リスクを構造的に排除 +
Drive/Docsストレージ + プールドストレージの使用状況を定期レビューし、ポリシー変更前にユーザー通知テンプレートで周知 +
Drive/Docsラベル + ラベル名・選択肢に機密情報を含めず、必須フィールドで入力を促す +
Calendarリソース予約 + 高需要リソースはリソースマネージャーによる手動承認、一般リソースは自動承認 +
Calendar委任 + 個人カレンダー=共有権限付与、リソースカレンダー=リソースマネージャー設定、と使い分けを明確化 +
Calendar不明送信者対策 + 「やり取りしたことがある送信者のみ表示」をOU単位で有効化し、既存スパムイベントは手動クリーンアップが必要な点をユーザーに周知 +
Meetセーフティ + 機密性の高い会議はHost + Managementをオン+待機室併用、大規模会議はQuick + accessオン+チャット権限制限 +
Meetビデオ設定 + 記録が必須の会議シリーズ(商談等)は自動録画・自動文字起こしをOU/グループ単位で有効化 +
Chat履歴設定規制業界は履歴をALWAYS ONに固定しVaultで法的保持に対応
Chat外部連携 + External Chat + Settingsで信頼済みドメインの許可リストを部門単位に整備 +
Chatアプリ + 社内標準アプリは管理者が一括インストール、サードパーティアプリはMarketplace許可リストで審査 +
生成AIデータ保護 + Workspace + Intelligenceのデータソース設定で不要なサービスへのアクセスをオフにし最小権限を徹底 +
生成AI有効化の階層 + Feature access/Geminiアプリアクセス/Workspace apps in + Geminiの3層を区別して段階的に展開 +
生成AI利用状況レポート + 四半期ごとにレビューし、未利用ユーザーのライセンス再配布とパワーユーザーの知見共有を実施 +
開発支援AppSheet/Apps Script + AppSheet=UI・トリガーの民主化、Apps + Script=深いプログラマティック統合、という役割分担で組み合わせて活用 +
開発支援ガバナンス + AppSheet「Organization/Team」構造でチーム管理者に委任し、シャドーIT化を防止 +
+
+
+

学習チェックリスト

+
+
+ 進捗: 0 / 24 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

参考文献

+

+ すべてGoogle公式ドキュメント(support.google.com/a、knowledge.workspace.google.com、workspaceupdates.googleblog.com、developers.google.com)を一次情報として使用しています。 +

+
+ + + + + + + + +
+ + +
+
+ + + + + diff --git a/Agwa-section2.md b/Agwa-section2.md new file mode 100644 index 000000000..7092c01f3 --- /dev/null +++ b/Agwa-section2.md @@ -0,0 +1,1190 @@ +# Associate Google Workspace Administrator試験対策ガイド +## Section 2: コアWorkspaceサービスの管理(出題比率 約23%) + +> 本ガイドはGoogle Cloud公式の[Associate Google Workspace Administrator認定ページ](https://cloud.google.com/learn/certification/associate-google-workspace-administrator?hl=en)および[公式Exam Guide PDF](https://services.google.com/fh/files/misc/associate_google_workspace_administrator_exam_guide_english.pdf)が定義するSection 2「Managing core Workspace services」の7つのタスク(2.1〜2.7)に厳密に対応し、Google Workspace管理者ヘルプセンター(`support.google.com/a`および`knowledge.workspace.google.com`)の一次情報に基づいて、中級者〜上級者向けに実務レベルの詳細解説とGoogle推奨ベストプラクティスをまとめたものです。 + +--- + +## 目次 + +- [Section 2の全体像](#section-2の全体像) +- [2.1 Gmailの設定](#21-gmailの設定) + - [2.1.1 MXレコードの設定](#211-mxレコードの設定) + - [2.1.2 基本的なメールルーティングの設定](#212-基本的なメールルーティングの設定) + - [2.1.3 コンテンツコンプライアンスルール](#213-コンテンツコンプライアンスルール) + - [2.1.4 スパム・フィッシング・マルウェア対策](#214-スパムフィッシングマルウェア対策) + - [2.1.5 添付ファイルサイズ制限とブロックするファイル形式](#215-添付ファイルサイズ制限とブロックするファイル形式) + - [2.1.6 Gmail転送とPOP/IMAPアクセス](#216-gmail転送とpopimapアクセス) + - [2.1.7 Google推奨のメールセキュリティ対策(SPF・DKIM・DMARC)](#217-google推奨のメールセキュリティ対策spfdkimdmarc) + - [2.1.8 メールデータの移行](#218-メールデータの移行) + - [2.1.9 Gmailアクセスの委任](#219-gmailアクセスの委任) + - [2.1.10 コンプライアンスフッターとメール隔離(Quarantine)](#2110-コンプライアンスフッターとメール隔離quarantine) +- [2.2 Google DriveとDocsの設定](#22-google-driveとdocsの設定) + - [2.2.1 新規ファイルのデフォルト共有設定](#221-新規ファイルのデフォルト共有設定) + - [2.2.2 Drive信頼ルール(Trust Rules)](#222-drive信頼ルールtrust-rules) + - [2.2.3 組織ポリシーに基づく外部共有の制限](#223-組織ポリシーに基づく外部共有の制限) + - [2.2.4 ターゲットオーディエンスの管理](#224-ターゲットオーディエンスの管理) + - [2.2.5 カスタムDocsテンプレートの設定](#225-カスタムdocsテンプレートの設定) + - [2.2.6 共有ドライブの作成と管理](#226-共有ドライブの作成と管理) + - [2.2.7 ストレージ容量の設定と調整](#227-ストレージ容量の設定と調整) + - [2.2.8 Google Drive for desktopの許可・禁止](#228-google-drive-for-desktopの許可禁止) + - [2.2.9 ファイル・フォルダの所有権移転](#229-ファイルフォルダの所有権移転) + - [2.2.10 Driveラベルの管理](#2210-driveラベルの管理) + - [2.2.11 オフラインアクセスの有効化・無効化](#2211-オフラインアクセスの有効化無効化) +- [2.3 Google Calendarの設定](#23-google-calendarの設定) + - [2.3.1 リソースカレンダーの作成と管理](#231-リソースカレンダーの作成と管理) + - [2.3.2 リソースの予約ポリシーの設定](#232-リソースの予約ポリシーの設定) + - [2.3.3 カレンダー・リソースアクセスの委任](#233-カレンダーリソースアクセスの委任) + - [2.3.4 プライマリ・セカンダリカレンダーのデフォルト内部共有設定](#234-プライマリセカンダリカレンダーのデフォルト内部共有設定) + - [2.3.5 チーム・グループ向け共有カレンダーの設定](#235-チームグループ向け共有カレンダーの設定) + - [2.3.6 カレンダーの外部共有オプションの管理](#236-カレンダーの外部共有オプションの管理) + - [2.3.7 イベント所有権の移転](#237-イベント所有権の移転) + - [2.3.8 不明な送信者からの招待の防止](#238-不明な送信者からの招待の防止) +- [2.4 Google Meetの設定](#24-google-meetの設定) + - [2.4.1 組織・OU単位でのMeetの有効化・無効化](#241-組織ou単位でのmeetの有効化無効化) + - [2.4.2 Meetセーフティ設定の構成](#242-meetセーフティ設定の構成) + - [2.4.3 Meetビデオ設定の構成(画質・録画・文字起こし・ノートテイキング)](#243-meetビデオ設定の構成画質録画文字起こしノートテイキング) +- [2.5 Google Chatの設定](#25-google-chatの設定) + - [2.5.1 組織・OU単位でのChatの有効化・無効化](#251-組織ou単位でのchatの有効化無効化) + - [2.5.2 Admin consoleでのChat設定](#252-admin-consoleでのchat設定) + - [2.5.3 Chat招待設定の管理](#253-chat招待設定の管理) + - [2.5.4 Chatアプリの追加](#254-chatアプリの追加) +- [2.6 Google Workspaceにおける生成AIの活用](#26-google-workspaceにおける生成aiの活用) + - [2.6.1 生成AI利用時の組織データのプライバシーとセキュリティ確保](#261-生成ai利用時の組織データのプライバシーとセキュリティ確保) + - [2.6.2 組織・OU単位でのGeminiの有効化・無効化](#262-組織ou単位でのgeminiの有効化無効化) + - [2.6.3 Geminiアプリ向けGoogle Workspace拡張機能の有効化](#263-geminiアプリ向けgoogle-workspace拡張機能の有効化) + - [2.6.4 Gemini利用状況レポートの生成](#264-gemini利用状況レポートの生成) +- [2.7 Workspace開発のサポート](#27-workspace開発のサポート) + - [2.7.1 AppSheetとApps Scriptのユースケース](#271-appsheetとapps-scriptのユースケース) + - [2.7.2 組織・OU単位でのAppSheetの有効化](#272-組織ou単位でのappsheetの有効化) +- [Section 2 ベストプラクティス総括表](#section-2-ベストプラクティス総括表) +- [学習チェックリスト](#学習チェックリスト) +- [参考文献](#参考文献) + +--- + +## Section 2の全体像 + +Exam Guideにおいて、Section 2「Managing core Workspace services」はSection 1(ユーザー・ドメイン・ディレクトリ管理、約20%)に次いで出題比率が最も高い領域の一つで、**約23**%を占めます。対象範囲はGmail、Google DriveとDocs、Google Calendar、Google Meet、Google Chat、生成AI(Gemini)、そしてAppSheet/Apps Scriptによる開発支援の7タスクにまたがり、Google Workspaceの「日常業務で最も使われるコアサービス」を管理者としてどう構成し、どう安全に運用するかが問われます。 + +```mermaid +flowchart LR + S2["Section 2
コアWorkspaceサービスの管理
約23%"] + S2 --> T1["2.1 Gmail
ルーティング・認証・コンプライアンス"] + S2 --> T2["2.2 Drive/Docs
共有・ストレージ・ラベル"] + S2 --> T3["2.3 Calendar
リソース・共有・委任"] + S2 --> T4["2.4 Meet
セーフティ・ビデオ設定"] + S2 --> T5["2.5 Chat
スペース・外部連携"] + S2 --> T6["2.6 生成AI
Geminiのプライバシーと管理"] + S2 --> T7["2.7 開発支援
AppSheet・Apps Script"] + + style S2 fill:#7c9eff,stroke:#333,stroke-width:2px,color:#000 +``` + +これらのタスクに共通する設計思想は、**Admin console上の「組織部門(OU)」または「設定グループ(Configuration Group)」を単位として、サービスごとに粒度の細かいポリシーを適用する**という一貫したモデルです。この構造を理解しておくことは、Section 2全体、さらには試験全体を通じて有効です。 + +--- + +## 2.1 Gmailの設定 + +### 2.1.1 MXレコードの設定 + +MXレコード(Mail Exchange record)は、ドメイン宛のメールをどのメールサーバーに配送するかを指定するDNSレコードです。Google Workspaceを利用するには、ドメインのMXレコードをGoogleのメールサーバーに向ける必要があります。 + +**2023年4月の仕様変更**として、Googleはそれまでの複数レコード構成(`ASPMX.L.GOOGLE.COM`などの5レコード、優先度違い)から、**単一のMXレコード`smtp.google.com`**に設定を簡素化しました。既存の複数レコード構成(レガシー値)は引き続きサポートされており、正常に機能している場合は変更不要です。新規セットアップでは単一レコード方式が推奨されます。 + +**設定手順の概要:** + +1. ドメインレジストラ(お名前.com、Cloudflare、GoDaddyなど)のDNS管理画面にサインインする +2. 既存のMXレコードをすべて削除する(残存すると配送障害の原因になる) +3. 新しいMXレコードを追加する + +| 項目 | 値 | +| --- | --- | +| Type | MX | +| Name / Host | 空欄または`@`(サブドメインの場合はサブドメイン名) | +| TTL | レジストラのデフォルト値、または`1`(3600秒/1時間を推奨するレジストラもある) | +| Priority | `1` | +| Value / Destination | `smtp.google.com`(レジストラによっては末尾にピリオドが必要:`smtp.google.com.`) | + +4. 変更を保存する(DNS伝播に**最大72時間**かかる場合がある) +5. Admin consoleで「アカウント」→「ドメイン」→「ドメインを管理」→対象ドメインの「Gmailを有効にする」をクリックし、MXレコードの検証を行う(**ドメイン設定の管理者権限**が必要) + +**トラブルシューティングのポイント:** + +- ドメインの所有権確認(TXTレコードによるベリフィケーション)が完了しているか確認する +- レジストラごとのフォーマット差異(末尾ピリオドの有無など)を確認する +- [Admin Toolbox Dig](https://toolbox.googleapps.com/apps/dig/#MX/)を使って、実際にインターネット上に公開されているMXレコードを検証する +- レジストラのサポートに問い合わせる(レジストラが不明な場合はドメインレジストラの特定方法を参照) + +**特殊なルーティングシナリオ:** + +Gmailだけがメール処理を行うとは限らないケースがあります。 + +- **オンプレミスのメールハイジーン/ジャーナリング製品を前段に置く場合**:MXレコードはオンプレミスサービスを指し、そこからGoogle Workspaceへ配送する構成にする +- **ハイブリッドメール環境(一部ユーザーがGoogle Workspace、一部がオンプレミスのExchangeなど)**:MXレコードは通常どおり`smtp.google.com`のままにし、Admin console側でSplit Delivery(分割配信)を設定する + +> **ベストプラクティス:** MXレコードを切り替える前に、必ず新しいGoogle Workspaceのユーザーアカウントを作成しておくこと。MXレコード切り替え前にアカウントが存在しないと、メールがバウンス(配送不能)する原因になる。 + +### 2.1.2 基本的なメールルーティングの設定 + +Google Workspaceには**Default routing**(デフォルトルーティング)と**Routing**(ルーティング)という2つの主要なルーティング設定があり、この2つの使い分けが試験でも実務でも頻出のポイントです。 + +| 設定 | 用途 | 優先度 | +| --- | --- | --- | +| **Default routing** | 組織全体、またはOU全体に対する既定のメール配送方法を設定する(例:組織のほぼ全メールを2つの受信箱に配送するデュアルデリバリー) | コンテンツ/添付ファイルコンプライアンス設定より**低い**優先度。最大1000件まで作成可能で、優先順位を並べ替え可能 | +| **Routing** | より特定条件に基づく高度な配送ルールを設定する。Default routingの挙動を上書きする用途にも使う(例:CEO宛メールのコピーをアシスタントにも送る) | Default routingより高い優先度 | + +代表的なルーティングシナリオは以下の3つです。 + +```mermaid +flowchart TD + Start["メールルーティング要件"] --> Q1{"移行・監査目的で
全メールを2箇所に
複製したいか?"} + Q1 -- "はい" --> Dual["Dual Delivery
(デュアルデリバリー)
Default routing + Also deliver to"] + Q1 -- "いいえ" --> Q2{"ユーザーの一部がGmail、
一部が別メールシステム
(例: 移行期のExchange)か?"} + Q2 -- "はい" --> Split["Split Delivery
(分割配信)
Add Route + Routing設定"] + Q2 -- "いいえ" --> Q3{"存在しない宛先への
メールを取りこぼしなく
受け取りたいか?"} + Q3 -- "はい" --> CatchAll["Catch-All メールボックス
Default routing(Unknown recipient)"] + Q3 -- "いいえ" --> Custom["特定の送信者・件名・
添付・内容に基づく
カスタムRoutingルール"] + + style Dual fill:#7c9eff,color:#000 + style Split fill:#7c9eff,color:#000 + style CatchAll fill:#7c9eff,color:#000 + style Custom fill:#7c9eff,color:#000 +``` + +1. **デュアルデリバリー(Dual delivery)**:受信メッセージを2つ以上の受信箱に配送する。組織移行時の監査や、外部アーカイブシステムとの並行運用に利用される。Admin consoleでは「Gmail」→「Routing」(レガシーの「Email routing」設定は非推奨化され順次廃止予定)で設定し、「Also deliver to」で追加の宛先を指定する。 +2. **分割配信(Split delivery)**:ドメイン内で一部のユーザーがGoogle Workspace、一部が別のメールシステムを使っている場合に、受信者に応じて配送先を分岐させる。事前に「Add Route」設定で非Gmailサーバーを追加しておく必要がある。Microsoft 365との共存移行(フェーズドマイグレーション)で特によく使われる。 +3. **キャッチオールメールボックス(Catch-all mailbox)**:誤って宛先を間違えたメールや、存在しない宛先宛のメールを取りこぼさないように、Default routingで「Unknown recipient」に対する配送ルールを設定する。 + +**設定手順(共通):** Admin console → 「アプリ」→「Google Workspace」→「Gmail」→「Routing」または「Default routing」→ 「設定」または「別のルールを追加」。変更の反映には最大24時間かかる(通常はより早く反映される)。 + +> **ベストプラクティス:** ルーティングルールのテストは、いきなり全社展開せず、まず特定のOUやテストユーザーに絞り込んで検証してから全社展開する(Googleの「より高速なルールのテストに関するベストプラクティス」を参照)。 + +### 2.1.3 コンテンツコンプライアンスルール + +コンテンツコンプライアンス(Content compliance)は、メール本文や件名が特定の条件(キーワード、正規表現、事前定義された検出器)に一致した場合に、そのメッセージをどう処理するかを制御する高度なフィルタリング機能です。 + +**設定場所:** Admin console → 「アプリ」→「Google Workspace」→「Gmail」→「コンプライアンス」(**Gmail設定の管理者権限**が必要) + +**主な用途:** + +- 送信メール(Outbound)に「confidential」という単語が含まれる場合、配送を拒否する +- 特定のIPアドレス範囲からの受信メールを隔離(Quarantine)する +- 特定のテキストパターンに一致するメッセージを法務部門にルーティングする +- ホワイトリスト化(第三者フィッシングシミュレーションサービスなど)にも応用可能 + +**適用範囲の指定:** ルールは「Inbound(受信)」「Outbound(送信)」「Internal-sending/Internal-receiving(組織内送受信)」のいずれか、または組み合わせに適用できる。ここでの「内部(internal)」とは、検証済みのWorkspaceドメインまたはそのサブドメイン・親ドメインを指す。 + +**事前定義された検出器(Predefined content detectors):** クレジットカード番号や社会保障番号、パスポート番号など、機密データを検出するために、正規表現を自前で書かなくても使える組み込みの検出器が用意されている。これらはキーワードや正規表現と組み合わせて、より高度なポリシーを構築できる。 + +**アタッチメントコンプライアンス(Attachment compliance):** ファイルの種類・ファイル名・メッセージサイズに基づいてメッセージの扱いを指定する別設定。暗号化された添付ファイルの検出にも対応し、ファイル拡張子を偽装したファイルでも実際のファイル種別を検出できる。 + +> **重要な注意点:** コンテンツフィルタは正規表現やその他のパラメータに基づく確率的な一致判定であり、すべての機微情報や添付ファイルを100%検出できることを保証するものではない(誤検知・見逃しが発生し得る)。 + +複数のコンプライアンスルールを設定した場合、どのルールが優先されるかは条件と優先順位によって決まる(「How multiple settings affect message behavior」を参照)。 + +### 2.1.4 スパム・フィッシング・マルウェア対策 + +「Spam, Phishing and Malware」設定は、Gmailの標準的なスパム判定を補完・上書きするための管理者向けコントロールです。試験で問われる主要な要素は次の4つです。 + +```mermaid +flowchart TB + Inbound["受信メール"] --> IG{"インバウンドゲートウェイ
経由か?"} + IG -- "はい(送信元IPがGateway IPsに一致)" --> GWCheck{"Reject all mail not
from gateway IPs が
オンか?"} + GWCheck -- "オン" --> RejectOther["ゲートウェイ外のIPからの
直接メールは拒否"] + GWCheck -- "オフ" --> HeaderEval["ヘッダーregexp評価
または通常のGmail
スパム評価を適用"] + IG -- "いいえ" --> AllowCheck{"送信元IPが
Email allowlistに
含まれるか?"} + AllowCheck -- "はい" --> BypassSpam["標準スパムフィルタを
バイパス(誤検知防止)"] + AllowCheck -- "いいえ" --> DenyCheck{"Blocked senders
(denylist)に
一致するか?"} + DenyCheck -- "はい" --> Block["メッセージをブロック"] + DenyCheck -- "いいえ" --> NormalSpam["通常のGmail
スパム・フィッシング・
マルウェア判定"] + + style BypassSpam fill:#7c9eff,color:#000 + style Block fill:#e57373,color:#000 + style RejectOther fill:#e57373,color:#000 +``` + +| 機能 | 説明 | 用途 | +| --- | --- | --- | +| **Email allowlist(許可リスト)** | 特定の送信元IPアドレスからのメールについて、Gmailの標準スパムフィルタを完全にバイパスする。**ドメイン全体に対してのみ設定可能で、OU単位では許可リストを設定できない** | 正規の一斉配信サービス(フィッシング訓練ツールなど)からのメールが誤ってスパム判定されるのを防ぐ | +| **Blocked senders(denylist)** | 特定のメールアドレスまたはドメインをブロックする | 既知の迷惑送信元を明示的に遮断する | +| **Inbound gateway(インバウンドゲートウェイ)** | 自組織にメールを中継する前段のメールサーバー(セキュリティゲートウェイなど)のIPアドレスを指定する。トップレベル組織でのみ設定可能で、全組織に適用される | オンプレミスのメールセキュリティ製品やサードパーティの一斉送信サービスを経由させる場合 | +| **IP allowlist(インバウンドゲートウェイ内)** | インバウンドゲートウェイ設定内で「Gateway IPs」として登録したIPからのメールを信頼する | ゲートウェイ経由のメールに対してGmail自体のスパム評価を無効化し、ヘッダー値のみで判定させることも可能(`Disable Gmail spam evaluation on mail from this gateway; only use header value`) | + +**設定場所:** Admin console → 「アプリ」→「Google Workspace」→「Gmail」→「スパム、フィッシング、マルウェア」 + +> **ベストプラクティス:** +> - IPアドレスによる許可リストは、ドメイン全体に影響するため慎重に使用し、可能な限りコンテンツコンプライアンスルールやアドレスリストとの組み合わせでスコープを絞り込む +> - インバウンドゲートウェイを設定する場合、`Reject all mail not from gateway IPs`のチェックは、本当にそのゲートウェイ以外からの直接配信を完全に禁止したい場合のみ有効にする(誤設定すると正規メールが届かなくなるリスクがある) +> - 許可リストへの追加は「配送性を上げる」ための機能であり、フィッシング対策そのものを弱体化させる可能性があるため、追加するIPは信頼できる送信元に限定する + +### 2.1.5 添付ファイルサイズ制限とブロックするファイル形式 + +コンプライアンス設定内の「Attachment compliance(添付ファイルコンプライアンス)」を使うと、ファイルの種類、ファイル名、メッセージサイズに基づいて、メッセージの扱い(拒否・隔離・変更など)を指定できます。 + +**制御できる主な観点:** + +- 特定の拡張子(`.exe`、`.bat`など実行可能ファイル)を持つ添付ファイルの拒否 +- ファイル名パターンによるフィルタリング +- メッセージ全体のサイズ上限に基づく制御 +- アーカイブファイル(ZIPなど)**内部**のファイル名もスキャン対象にできる +- ファイル拡張子を偽装(リネーム)した悪意あるファイルも、実際のファイル種別を検出してブロックできる +- 暗号化された添付ファイルの検出(アーカイブサーバーへの非暗号化コピー送付などに活用) + +> **ベストプラクティス:** 添付ファイルコンプライアンスルールは、コンテンツコンプライアンスルールと同様に複数設定できるが、複数ルールが競合した場合の優先順位(条件とルールの並び順)を必ず確認し、意図しないブロック・許可が発生しないようテストすること。 + +### 2.1.6 Gmail転送とPOP/IMAPアクセス + +**自動転送(Automatic forwarding):** + +- エンドユーザーは自身のGmail設定(「Forwarding and POP/IMAP」タブ)から個人的な転送先アドレスを1つ設定できる。転送先には確認メールが送られ、受信者側でリンクをクリックして初めて有効化される +- 管理者は、ユーザーによる自動転送設定そのものを許可・禁止するコントロールを持つ(Admin console → Gmail → エンドユーザーアクセス) +- 組織レベルでより高度な転送・複製が必要な場合は、個人設定ではなく管理者によるRouting設定(アドレスマップによる転送、コンプライアンスルールと組み合わせた外部転送のブロックなど)を使う + +**POP/IMAPアクセス:** + +- 管理者はユーザーまたはOU単位でPOP・IMAPアクセスのオン・オフを制御できる(Admin console → Gmail → ユーザー設定 → 「POPとIMAPアクセス」) +- **2025年5月1日以降**、Google WorkspaceアカウントはOAuthを使用しないサードパーティアプリ・デバイスからのログイン(いわゆる「安全性の低いアプリ」)をサポートしなくなった。サードパーティのメールクライアントを使う場合はOAuth認証が必須 +- OAuthクライアントIDを指定して、特定のクライアントのみに同期を限定するオプションもある(この場合、サービスアカウントによるドメイン全体の委任を使うクライアントはサポート対象外になる点に注意) + +> **ベストプラクティス:** 外部への自動転送は情報漏えいのリスクがあるため、必要なOU以外では転送機能自体を無効化し、どうしても外部転送が必要な場合はコンテンツコンプライアンスルールで「外部への自動転送をブロックする」設定と組み合わせて多層防御にする。 + +### 2.1.7 Google推奨のメールセキュリティ対策(SPF・DKIM・DMARC) + +SPF・DKIM・DMARCは、送信ドメイン認証(メールなりすまし対策)の三本柱であり、試験・実務の両面で最重要トピックの一つです。 + +```mermaid +sequenceDiagram + participant Sender as 送信元
(Google Workspace) + participant DNS as 送信ドメインのDNS + participant Receiver as 受信側メールサーバー + + Sender->>DNS: SPFレコード公開
("どのIPが送信を許可されるか") + Sender->>DNS: DKIM公開鍵をTXTレコードで公開 + Sender->>DNS: DMARCポリシー(p=none/quarantine/reject)を公開 + + Sender->>Receiver: メール送信(DKIM署名付与) + Receiver->>DNS: SPFレコードを検証
(送信元IPが許可リストにあるか) + Receiver->>DNS: DKIM公開鍵を取得し署名を検証 + Receiver->>Receiver: SPF/DKIMとFromヘッダーの
ドメイン一致(alignment)を確認 + Receiver->>DNS: DMARCポリシーを取得 + Receiver->>Receiver: ポリシーに従い配信/隔離/拒否を判定 +``` + +| 項目 | 役割 | Google Workspaceでの設定要点 | +| --- | --- | --- | +| **SPF**(Sender Policy Framework) | 「どのメールサーバーがこのドメインの代理で送信してよいか」をDNS TXTレコードで宣言する | `include:_spf.google.com`を含むSPFレコードをDNSに公開する。**1ドメインにつきSPFレコードは1つのみ**(複数ある場合はマージが必要)。反映まで最大48時間 | +| **DKIM**(DomainKeys Identified Mail) | メールに暗号署名を付与し、経路上での改ざんがないことと送信ドメインの真正性を証明する | Admin console →「Gmail」→「メールを認証」でDKIM鍵ペア(1024ビットまたは2048ビット)を生成し、公開鍵をTXTレコードとしてDNSに公開後、「認証を開始」をクリックして有効化する | +| **DMARC**(Domain-based Message Authentication, Reporting & Conformance) | SPF・DKIMの結果と`From:`ヘッダーのドメイン一致(アライメント)を基に、認証に失敗したメールをどう扱うか(`none`/`quarantine`/`reject`)をポリシーとして宣言し、レポートを受け取る | `_dmarc.yourdomain.com`にTXTレコードとしてDMARCポリシーを公開する。**まず`p=none`から開始**し、レポートを分析しながら段階的に`quarantine`→`reject`へ引き上げるロールアウトが推奨される | + +**Googleの送信者ガイドライン(Email sender guidelines):** + +- すべての送信者:SPFまたはDKIMのいずれかの設定が必須 +- 大量送信者(1日5,000通以上のメッセージをGmail宛に送信する場合):**SPF・DKIM・DMARCすべての設定が必須**(2024年2月施行) + +> **ベストプラクティス:** +> - SPF・DKIM・DMARCは単独ではなく**必ず三点セットで運用**する。DMARCはSPF/DKIMの検証結果を土台にしているため、土台なしでDMARCだけ設定しても効果が限定的 +> - DMARCは`p=none`(モニタリングのみ)から始め、集計レポートで正規の送信元をすべて洗い出してから段階的に強制力を高める「Recommended DMARC rollout」に従う +> - SPFレコードは新しいメール配信サービスを追加・廃止するたびに更新し、使われなくなったドメイン・IPを削除して古いレコードを放置しない +> - 追加のブランド対策として、DMARCの上にBIMI(ブランドロゴのメール表示)の導入も検討できる + +### 2.1.8 メールデータの移行 + +他のメールプロバイダやGoogle Workspaceの別アカウントからGmailへメールデータを移行するには、Googleが提供する複数の移行ツールを使い分けます。 + +| ツール | 用途 | アクセス場所 | +| --- | --- | --- | +| **新しいデータ移行サービス(Data migration, GA)** | Google Workspace同士、Gmail(個人アカウント)、IMAP対応メールサーバーからのメール移行。デルタ移行(差分同期)に対応し、既存データを重複させずに新着・更新分のみ取り込める | Admin console →「データ」→「データのインポートとエクスポート」→「データ移行」 | +| **データインポートツール(Data import)** | Microsoft Exchange Online、IMAPベースのWebメール(Yahoo!、iCloud Mail、GoDaddy、Zohoなど)、別のWorkspaceアカウント、個人のGmailアカウントからのインポート | Admin console →「データ」→「データのインポートとエクスポート」→「データインポート」 | +| **レガシーData Migration Service(DMS)app** | 従来型の移行アプリ。引き続き利用可能(`admin.google.com/ac/dms`) | Admin console内 | + +**移行フローの要点(Gmailアカウントからの移行の例):** + +1. スーパー管理者としてサインインし、移行先のAdmin consoleで移行元アドレスを指定して「認証をリクエスト」する +2. 移行元アカウントの所有者が接続リクエストを承認する(自分自身が所有者であればAdmin console上で直接承認できる) +3. 承認後、移行を実行する +4. Advanced Protection Program(高度な保護機能プログラム)に登録済みのユーザーは、移行前・移行中は同プログラムを一時的にオフにする必要がある + +> **ベストプラクティス:** +> - 大規模な移行では、まず少人数のパイロットグループで移行してから全社展開する +> - 移行完了後もデルタ移行(差分インポート)を実行し、初回移行後に追加・更新されたデータや、初回移行で失敗したデータを再取り込みする +> - Exchange OnlineやMicrosoft 365からの移行では、Data Importが「ドメイン全体の委任(Domain-wide delegation)」のAPIクライアントとして認証される点を踏まえ、事前にMicrosoft 365側でグローバル管理者権限を用意しておく + +### 2.1.9 Gmailアクセスの委任 + +メールの委任(Mail delegation)を使うと、あるユーザー(アカウント所有者)が別のユーザー(委任先)に対して、自分の受信トレイの閲覧・送信・管理権限を付与できます。 + +**設定手順:** + +1. Admin console →「アプリ」→「Google Workspace」→「Gmail」→「ユーザー設定」→「メールの委任」 +2. 「ユーザーがメールボックスへのアクセスをドメイン内の他のユーザーに委任できるようにする」をオンにする +3. 委任者が送信したメールの送信者情報として、アカウント所有者・委任者のどちらのアドレスを受信者に表示するかを管理者が選択する +4. エンドユーザーに対し、個人単位・Googleグループ単位で委任先を追加できることを周知する + +**押さえるべきポイント:** + +- **Googleグループをアカウントの委任先として追加できる**(1つのグループは1人の委任者としてカウントされる) +- メールエイリアスはGoogleアカウントではないため、委任先として設定できない +- 委任先は受信トレイの閲覧・送信・返信・削除ができるが、パスワード変更やアカウント設定の変更はできない + +**委任 vs 共有メールボックス(Collaborative Inbox):** 1対1の代理対応(秘書によるメール代理管理など)には委任、複数人でチケットのように受信箱を分担管理する場合はGoogleグループのCollaborative Inbox機能が適している(Section 1のグループ管理も参照)。 + +### 2.1.10 コンプライアンスフッターとメール隔離(Quarantine) + +**コンプライアンスフッター(Append footer):** 法的な免責事項や社内ポリシーの通知など、定型のフッターテキストを送信メッセージに自動的に追加する機能。Admin console →「Gmail」→「コンプライアンス」→「フッターを追加」で設定する。 + +**メール隔離(Email quarantine):** コンテンツコンプライアンスルールやDLPルールに一致したメッセージを、配信もブロックもせず一時的に「隔離エリア」に留め置き、管理者や指定ユーザーがレビューして解放・削除を判断できるようにする仕組み。 + +**Quarantineへのアクセス権付与のベストプラクティス:** + +| オプション | 方法 | 用途 | +| --- | --- | --- | +| **オプション1:全隔離メッセージへのアクセスを付与** | `Access Admin Quarantine`権限を持つカスタム管理者ロールを作成し、ユーザーに割り当てる | 全社のセキュリティチームなど、すべての隔離メッセージを横断的にレビューする担当者向け | +| **オプション2:特定の隔離メッセージのみへのアクセスを付与** | `Access Restricted Quarantines`権限を持つカスタムロールを作成し、Googleグループと紐づけて、隔離設定作成時に該当グループへのアクセスを許可する | 例:個人情報や機密情報を含むメッセージのレビューをコンプライアンスチームに限定する場合 | + +> **ベストプラクティス:** Quarantineへのアクセスはスーパー管理者に限定せず、必要最小限の権限を持つカスタムロールを作成して委任する(最小権限の原則)。全メッセージへの無制限アクセスを安易に多数の担当者へ配布しない。 + +--- + +## 2.2 Google DriveとDocsの設定 + +### 2.2.1 新規ファイルのデフォルト共有設定 + +Google Driveのファイル共有ポリシーの起点となるのが「General access default(全般的なアクセスのデフォルト設定)」です。Admin console →「アプリ」→「Google Workspace」→「Drive and Docs」→「共有設定」→「全般的なアクセスのデフォルト設定」から構成します(**Drive & Docs管理者権限**が必要)。 + +**既定の選択肢:** + +- **Restricted(限定公開)**:ファイルはオーナーのみアクセス可能で、ユーザーが明示的に共有するまで非公開。**Googleが多くのユーザーに推奨する既定値**であり、ユーザーが準備できたときにだけ共有し、個人ファイルは非公開のままにできる +- **your organization(組織全体)**:組織内の全ユーザーがアクセス可能 + +さらに「ターゲットオーディエンス(後述2.2.4)」を作成することで、この2択に加えて任意のカスタムオーディエンス(例:特定部門、Employees Onlyなど)を選択肢に追加できます。 + +**内部共有 vs 外部共有:** Google WorkspaceのAdmin consoleには「内部共有」を直接制限する専用トグルは存在せず、内部共有の制御は主に「全般的なアクセスのデフォルト設定」と「ターゲットオーディエンス」の組み合わせで実現します。一方、「外部共有」については専用の制御群(2.2.3参照)が用意されています。 + +> **ベストプラクティス:** 一般ユーザー向けには`Restricted`をデフォルトにし、ターゲットオーディエンスで「Employees Only」のようなオーディエンスを作成した上でこれを優先(プライマリ)オーディエンスに設定することで、ユーザーが誤って外部ベンダーなどに広く共有してしまうリスクを下げる。 + +### 2.2.2 Drive信頼ルール(Trust Rules) + +Trust Rulesは、外部ドメイン・特定の組織部門(OU)・特定のグループ・特定のユーザーを基準に、Driveファイルの共有を**許可**(Allow)・**拒否**(Deny)・**警告表示**(Warn)という形で制御するルールベースの新しい制御フレームワークです。従来の「Drive共有設定」(外部共有のオン・オフ、信頼済みドメインの許可リスト)を置き換えるものとして提供されています。 + +**Trust Rulesが有効なユースケース:** + +- 財務部門が所有するファイルを、社内の他部署とは共有できないようにブロックする +- 契約関係のある特定の外部ドメインとのみ共有を許可する +- 外部ユーザーからのスパム・フィッシングファイル共有を防ぐため、通常は外部との共有が発生しないOUに対して「信頼できるドメインの外部ユーザーのみ共有を許可する」ルールを適用する +- 一時プロジェクトにおいて、一定期間後に外部コラボレーターのアクセスを自動的に失効させる + +**既存のDrive共有設定との関係:** Trust Rulesを有効化すると、既存の「組織外との共有」設定は自動的にTrust Rulesへ変換される(プレビュー可能)。Trust Rulesを有効化した時点で、対応するDrive共有設定は無効になる(Trust Rulesはいつでもオフに戻し、従来のDrive共有設定に戻すことも可能)。 + +```mermaid +flowchart TD + Rule["ファイル共有アクション"] --> Check{"Trust Rulesの
条件に一致するか?
(OU・グループ・ドメイン・ユーザー単位)"} + Check -- "Allow" --> Allowed["共有を許可"] + Check -- "Deny" --> Denied["共有をブロック"] + Check -- "Warn" --> Warned["警告を表示した上で
ユーザーの判断に委ねる"] + Check -- "一致なし" --> Fallback["デフォルトの
Drive共有設定/
ターゲットオーディエンス設定を適用"] + + style Allowed fill:#7c9eff,color:#000 + style Denied fill:#e57373,color:#000 + style Warned fill:#ffd54f,color:#000 +``` + +**いつTrust Rulesを使うべきか(試験ポイント):** 「Identifying when Drive trust rules should be used」という出題観点に対応する判断基準は、**単純な組織全体のオン・オフでは表現できない、部門・グループ・ドメイン単位できめ細かい共有制御が必要な場合**にTrust Rulesを選択する、という点です。 + +### 2.2.3 組織ポリシーに基づく外部共有の制限 + +外部共有の制御はAdmin console →「Drive and Docs」→「共有設定」に集約されており、主な制御レバーは次の通りです。 + +| 制御 | 説明 | +| --- | --- | +| **オン/オフ/制限** | 外部共有を全面許可、全面禁止、または特定条件下でのみ許可に設定する(全面禁止は高セキュリティ環境以外では稀) | +| **信頼済み(許可リスト)ドメイン** | 全面許可・全面禁止の二択ではなく、パートナー企業など特定ドメインとの共有のみを許可し、それ以外をブロックする | +| **ターゲットオーディエンス** | 「組織内全員」のような推奨共有先を提示し、ユーザーが安易に「リンクを知っている全員」を選択しないよう誘導する | +| **外部共有時の警告** | 組織外への共有を行おうとした際にDriveが警告を表示し、意図しない外部共有(ヒューマンエラー)に気づかせる | + +**Visitor Sharing(訪問者共有):** Googleアカウントを持たない外部ユーザー(非Google利用者)に対して、確認コードベースでファイルへの一時アクセスを許可する機能。外部共有を厳格に制限しつつ、特定の非Google利用者とだけ安全に共有したい場合に利用する。 + +> **ベストプラクティス(外部共有の5層防御モデル):** +> 1. 信頼済み外部ドメイン(契約関係のあるパートナー・ベンダー)を許可リスト化する +> 2. ターゲットオーディエンスと組み合わせ、ワンクリックで定義済みの外部グループに共有できるようにする +> 3. 信頼済みドメインだからといって無制限アクセスにはならない点に留意する(信頼はあくまで摩擦を減らすためのものであり、権限レベルはファイルオーナーが個別に付与する) +> 4. 高リスク部門(法務・財務など)はTrust Rulesで外部共有を個別にブロックする +> 5. Security CenterでDrive/Gmailの共有アクティビティを継続的に監視し、ポリシー違反のアラートを設定する + +### 2.2.4 ターゲットオーディエンスの管理 + +ターゲットオーディエンス(Target audiences)は、共有ダイアログでユーザーに提示される「推奨共有先」のリストであり、Drive/Docs・Chatサービスで利用できます(対応エディションはFrontline Plus、Business Plus、Enterprise Standard/Plus、Education Standard/Plusなど)。 + +**展開のベストプラクティス(Googleの公式推奨手順):** + +1. **すべての正社員を含む「Employees Only」オーディエンスを作成する**:ダイナミックグループ(対応エディションの場合)または全社員を含む既存グループを使う +2. **組織の全ユーザーを含む「Employees and Vendors」等のオーディエンスも作成する**:全ユーザーを含むグループを作成して追加する +3. **ターゲットオーディエンス向けのDrive/Docs共有ポリシーを作成する**:全社、または特定OU・設定グループに適用する +4. **「Employees Only」をプライマリ(優先)オーディエンスに設定する**:優先オーディエンスをドラッグ操作で最上位に配置することで、ユーザーが誤ってベンダーを含む広いオーディエンスと共有するのを防ぎやすくなる + +**設定場所:** Admin console →「Drive and Docs」→「共有設定」→「ターゲットオーディエンス」 + +> **注意点:** ターゲットオーディエンスの構成グループとして、ダイナミックグループ(Dynamic groups)を組み合わせると、入退社に応じてメンバーが自動的に更新されるため、手動メンテナンスの負荷を大幅に減らせる(Section 1のグループ管理を参照)。 + +### 2.2.5 カスタムDocsテンプレートの設定 + +組織独自のブランドを反映したテンプレート(Docs、Sheets、Slides、Forms、Sitesなど)をテンプレートギャラリーに追加できます。 + +**設定手順:** + +1. Admin console →「Drive and Docs」→「テンプレート」→「テンプレートギャラリーの設定」 +2. 「組織のカスタムテンプレートを有効にする」にチェックを入れて保存する +3. (任意)カテゴリを追加する:例)マーケティング、営業、人事などのチーム別カテゴリ +4. テンプレート送信の承認ポリシーを設定する: + +| 送信モード | 説明 | +| --- | --- | +| **Open(オープン)** | 組織内の誰でも承認なしにテンプレートを追加・削除できる | +| **Moderated(モデレート)** | Docs Templates権限を持つ管理者に新規テンプレート追加のメール承認リクエストが届く。いずれかの管理者が応答すると完了。承認されたテンプレートはカスタムギャラリーに追加され、却下された場合は再提出できる | +| **Restricted(制限)** | Docs Templates権限を持つ管理者のみがテンプレートを追加できる | + +**関連する管理者権限:** 「Docs Templates」権限を持つ管理者は、テンプレートギャラリー内のテンプレートの削除・カテゴリ分けができ、モデレート方式の場合はテンプレート送信の承認・却下も行える。 + +> **ベストプラクティス:** ブランドガイドラインが厳格な組織ではRestrictedを選び、少人数のデザイン・広報チームのみがテンプレートを管理する体制にする。全社的なテンプレート活用を促進したい組織ではModeratedを使い、品質管理をしながら現場からの提案も取り入れる。 + +### 2.2.6 共有ドライブの作成と管理 + +共有ドライブ(Shared drives)は、特定の個人ではなくチーム・部門が所有者となるファイルストレージ空間で、メンバーの退職・異動があってもファイルが失われない点がマイドライブ(My Drive)との大きな違いです。 + +**共有ドライブの作成許可:** Admin console →「Drive and Docs」→「共有設定」→「共有ドライブの作成」で、組織全体または一部のユーザーにのみ共有ドライブの作成を許可できる。作成を許可しないユーザーであっても、他者が作成した共有ドライブに追加されて利用することは可能。 + +**OUへの割り当てとポリシーの継承:** 既定では、トップレベル組織部門で設定したポリシーがすべての共有ドライブに適用される。共有ドライブを子OUに割り当てることで、そのOU固有のポリシー(データ共有・セキュリティ・ストレージ)を個別に適用できる。既定でどのOUに共有ドライブが作成されるかも設定可能。 + +**ストレージ容量の管理:** + +- 既定の共有ドライブ容量上限は**100GB** +- Admin console →「ストレージ」→「ストレージ設定を管理」→対象OUを選択→「共有ドライブのストレージ上限」で、OUごとに異なる上限を設定できる +- プロジェクト単位で一時的により多くのストレージが必要なユーザー向けに、設定グループ(Configuration group)を作成し、そのグループのメンバーをOUのストレージ上限から除外することも可能 + +```mermaid +flowchart LR + MyDrive["マイドライブ
(個人所有)"] -->|"退職・異動時に
所有権移転が必要"| Risk["データ喪失リスク"] + SharedDrive["共有ドライブ
(チーム/組織所有)"] -->|"メンバー変更があっても
ファイルは残る"| Stable["データの継続性"] + + SharedDrive --> OU1["OUに割り当て"] + OU1 --> Policy["OU固有のポリシー継承
(共有・ストレージ上限・セキュリティ)"] + + style SharedDrive fill:#7c9eff,color:#000 + style Stable fill:#7c9eff,color:#000 +``` + +> **ベストプラクティス:** 部門・プロジェクト単位の永続的なファイル資産(契約書、会計資料、進行中プロジェクトの成果物)は個人のマイドライブではなく共有ドライブに保存する運用を徹底し、退職者データ喪失リスクを構造的に排除する。ストレージ使用状況は「ストレージ設定」画面から定期的にレビューし、上位使用者・共有ドライブを把握する。 + +### 2.2.7 ストレージ容量の設定と調整 + +Google Workspaceのストレージは、Drive・Gmail・Google Photosで共有される「プールドストレージ(Pooled storage)」というモデルを採用しています。ライセンスごとに割り当てられたストレージが組織全体のプールに加算され、個々のユーザーはライセンス割り当て量を超えて利用することも可能です(プール全体に余裕がある限り)。 + +**ユーザー単位のストレージ上限設定:** Admin console →「ストレージ」→「ストレージ設定を管理」からOU単位でストレージ上限を設定できる。ユーザーが「ストレージ容量不足」の通知を受け取った場合、多くのケースでサポートに連絡する必要はなく、Admin console側でストレージ上限の設定を見直すことで解決する。 + +**モニタリングツール:** ストレージ管理ツールでは、以下が確認できる: + +- 製品別(Drive、Gmailなど)のストレージ使用量 +- 組織内でストレージを最も使用しているユーザーの一覧 +- ストレージを最も使用している共有ドライブの一覧 +- ストレージ上限に近づいている警告 + +> **ベストプラクティス:** ストレージポリシーを変更・強制する際は、事前にユーザーへポリシー変更の通知テンプレートを用いて周知し、不要なファイルの削除を促してから上限を適用する。バックアップ・アーカイブ用途でのDrive利用は推奨されておらず(Google Workspaceはリアルタイムコラボレーションと共有に最適化されている)、大容量アーカイブには別のストレージソリューションを案内する。 + +### 2.2.8 Google Drive for desktopの許可・禁止 + +Drive for desktop(旧Drive File Stream / Backup and Sync)は、ローカルコンピュータ上でGoogle Driveのファイルをストリーミング形式で利用可能にするデスクトップクライアントです。管理者はAdmin console →「Drive and Docs」→「機能とアプリケーション」から、組織またはOU単位で許可・禁止を制御できます。 + +**関連設定:** + +- **Drive SDK**:Drive SDK APIを介したユーザーのDriveアクセスを許可するか +- **アドオン**:Docsのアドオンストア経由でのアドオンインストールを許可するか +- **ランサムウェア検出と復元**(Drive for desktop向け):Drive for desktop環境でのランサムウェア検出・復元機能を有効化できる + +> **ベストプラクティス:** マネージドデバイスにはDrive for desktopの配布・自動更新ポリシーを適用し(Google Workspace Updatesの管理)、BYOD(私物端末)環境では組織のセキュリティポリシーに応じて許可の可否を判断する。ファイアウォール・プロキシ環境がある場合は、Drive/Sitesのファイアウォール要件を事前に確認しておく。 + +### 2.2.9 ファイル・フォルダの所有権移転 + +ユーザーの退職や異動の際、そのユーザーが所有していたDriveファイルの所有権を別のユーザーへ移転できます。 + +**設定手順:** + +1. Admin console →「Drive and Docs」→「所有権の移転」を開く +2. 移転元ユーザー(現在の所有者)と移転先ユーザー(新しい所有者)を指定する +3. 移転を実行する + +**必要な管理者権限:** 「Drive & Docsサービスの設定」権限、「データ移行(Data Transfer)」権限、「ユーザー:読み取り専用」権限の3つが必要(Admin consoleでの「所有権の移転」設定へのアクセスには加えて「Driveサービス」権限も必要)。この権限セットはOU単位に限定できない(組織全体に適用される)。 + +**移転後の挙動:** 所有権移転後、元の所有者にはそのファイルへの編集権限が付与された状態が維持される(元所有者が削除されたり、編集権限を明示的に外されたりしない限り、引き続きアクセス可能)。 + +> **試験・実務でのポイント:** ユーザーを削除する前に必ず所有権移転を実施することが重要である。これはユーザー作成者が去った後もチームの重要データを失わないための基本的な運用手順であり、Section 1の「アカウントの削除・保留・アーカイブ」(1.1)とも密接に関連する。 + +### 2.2.10 Driveラベルの管理 + +Driveラベル(Classification labels)は、ファイルにメタデータ(機密度、承認ステータス、期限など)を付与し、検索性の向上、ポリシーの自動適用、DLPとの連携を可能にする分類機能です(Gmailメッセージへのラベル付けはベータ機能として提供)。 + +**ラベルの作成:** Admin console →「セキュリティ」→「アクセスとデータ管理」→「ラベルマネージャ」(**Manage Classification Labels管理者権限**が必要)。1組織で最大**150個**のラベル(バッジ付きラベルを含む)を作成可能。 + +**自動適用の3つの方法:** + +```mermaid +flowchart TD + NewFile["新規ファイル作成/
所有権移転/
共有ドライブへの移動"] --> Method{"ラベル自動適用方式"} + Method -->|"1. デフォルト分類"| Default["Default classification
OU/グループ単位で
既定ラベル値を自動付与"] + Method -->|"2. DLPルール"| DLP["DLPルールの検出条件に
一致した場合にラベルを付与"] + Method -->|"3. AI分類"| AI["Geminiによる
コンテンツ内容の
自動分類・ラベル付与"] + + Default -.優先順位.-> Priority["DLPラベル値 > AI分類ラベル値
> デフォルト分類ラベル値
(同種のルールが競合する場合は
ラベルの選択肢リストでより上位の値が優先)"] + DLP -.-> Priority + AI -.-> Priority + + style Priority fill:#ffd54f,color:#000 +``` + +**ロックの概念:** デフォルト分類、DLP、Vault保持ルールのいずれかでラベルが参照されると、そのラベルはラベルマネージャ内で「ロック」され、編集・無効化・削除ができなくなる(ビジネスポリシーを壊すような変更を防止するため)。ロックを解除するには、すべてのデフォルト分類ポリシーからそのラベルを削除する必要がある。 + +**閲覧・適用権限:** ラベルの閲覧・適用にはファイルへの閲覧権限に加え、ラベル自体への閲覧権限が必要。管理者は「レポート」権限があれば、ラベルマネージャ権限がなくてもDriveのレポート・監査でラベル情報を確認できる。 + +> **ベストプラクティス:** ラベル名・フィールド名・選択肢に機密情報そのものを含めない(ラベルマネージャの閲覧権限を持つ全管理者に見えてしまうため)。ユーザーにラベル入力を促す場合は「必須フィールド」設定を活用し、未入力時にバナー表示で入力を促す(ただし必須フィールド未入力でも共有・編集自体はブロックされない点に注意)。 + +### 2.2.11 オフラインアクセスの有効化・無効化 + +オフラインアクセスは、インターネット接続がない状態でもDocs・Sheets・Slidesを作成・編集できるようにする機能です。既定では組織全体でオンになっており、ユーザーは自分のアカウントで個別にオン・オフを切り替えられます。 + +**制御オプション(Admin console →「Drive and Docs」→「機能とアプリケーション」→「オフライン」):** + +| オプション | 説明 | +| --- | --- | +| **全ユーザーにオフラインアクセスを許可(推奨)** | 最も簡便な方法。ユーザーは自分の端末・信頼する端末でオフラインアクセスを有効化できる | +| **デバイスポリシーでオフラインアクセスを制御** | マネージドデバイスにポリシーを導入して制御する。**ポリシーを導入しないままこのオプションを選ぶと、以前オフラインアクセスできていたユーザーが24時間後にアクセスできなくなる**点に要注意 | + +**適用範囲の限界:** この設定はChrome・Microsoft Edgeブラウザ上でのDocs/Sheets/Slidesのオフライン利用に対するものであり、**Google Drive for desktopには適用されない**(Drive for desktopのオフラインファイル利用は別の仕組み)。 + +> **ベストプラクティス:** 機密情報や社外秘ファイルを扱うユーザー(役員、法務、人事など)に対しては、端末紛失時の情報漏えいリスクを踏まえてオフラインアクセスを無効化することを検討する。それ以外の一般ユーザーには利便性を優先してオンのままにするのが一般的な運用。 + +--- + +## 2.3 Google Calendarの設定 + +### 2.3.1 リソースカレンダーの作成と管理 + +リソースカレンダー(会議室、車両、機材など)の設定は、Section 1で扱う「建物とリソース管理」(1.5)の実務基盤の上に成り立っています。ここではCalendarサービスの観点から、リソースの予約・共有をどう運用するかを扱います。 + +**基本構造:** + +1. **建物(Buildings)** をまず作成する(最大10,000棟/ドメイン) +2. 建物に **フロア(Floors)** を定義する +3. **機能(Features)**(プロジェクター、ホワイトボード、車椅子対応など)を最大100個まで作成する +4. **リソース(Resources)**(会議室 = Conference room、それ以外 = Other)を最大10,000件まで作成し、建物・フロア・機能と紐づける + +**設定場所:** Admin console →「ディレクトリ」→「建物とリソース」→「概要」(**建物とリソースの管理者権限**が必要)。CSVによる一括アップロードやAPI(`resources.buildings`、`resources.features`、`resources.calendars`)による一括更新にも対応。 + +**Enterprise Plus / Assured Controls限定機能:** リソースを特定の組織部門(OU)に割り当てることで、そのリソースカレンダーのイベントデータが当該OUのデータリージョンポリシーなどのデータポリシーを継承するようにできる(リソースのメタデータ自体はルート組織のポリシーに従う)。 + +> **ベストプラクティス:** リソース名には「建物-フロア-フロアセクション-リソース名(収容人数)[機能]」形式の自動生成命名規則を活用し、ユーザーが予約画面で場所と設備を一目で判断できるようにする。 + +### 2.3.2 リソースの予約ポリシーの設定 + +リソース予約の承認フローには、大きく分けて「自動承認」と「リソースマネージャーによる手動承認」の2パターンがあります。 + +```mermaid +flowchart TD + Book["ユーザーがリソースを
会議に招待"] --> AutoAccept{"リソースの
Auto-accept invitations
設定"} + AutoAccept -- "競合しない招待を
自動承認" --> CheckConflict{"時間帯が
既存予約と
競合するか?"} + CheckConflict -- "競合なし" --> Confirmed1["自動的に予約確定"] + CheckConflict -- "競合あり" --> Declined1["自動的に辞退"] + AutoAccept -- "すべての招待を
カレンダーに追加
(リソースマネージャー運用)" --> Manager["リソースマネージャーに通知"] + Manager --> Decision{"マネージャーが
確認"} + Decision -- "承認" --> Confirmed2["予約確定"] + Decision -- "却下/変更依頼" --> Declined2["辞退"] + + style Confirmed1 fill:#7c9eff,color:#000 + style Confirmed2 fill:#7c9eff,color:#000 + style Declined1 fill:#e57373,color:#000 + style Declined2 fill:#e57373,color:#000 +``` + +**リソースマネージャー方式の設定手順:** + +1. スーパー管理者権限を持つ管理者としてGoogleカレンダーにサインインする +2. リソースを組織全体、または特定の人に共有する +3. リソースマネージャーとなるユーザーにもリソースを共有し、「変更を加える権限」および「共有を管理する権限」を付与する +4. リソースの「カレンダーの詳細」タブで、Auto-accept invitationsを「すべての招待を自動的にこのカレンダーに追加する」に設定する +5. リソースマネージャーはリソース通知を受け取る設定を行い、招待が入るたびに承認・辞退・仮承諾を判断する + +**Free/Busy共有リソースの予約許可:** リソースが「Free/busyのみ表示」で共有されている場合、既定ではユーザーはそのリソースを予約できる。管理者はAdmin console →「Calendar」→「全般設定」→「リソースの予約権限」で、「See only free/busyとして共有されているリソースの予約をユーザーに許可する」のチェックを制御できる(機密性の高いイベント情報を隠しつつ予約自体は可能にする、という使い分けができる)。スーパー管理者は、この設定に関わらず常に全リソースを予約できる。 + +> **ベストプラクティス:** 高需要の会議室(大会議室、役員会議室など)はリソースマネージャーによる手動承認にし、稼働状況をコントロールする。一般的な小会議室・電話ブースは自動承認にして、従業員の摩擦を減らす。 + +### 2.3.3 カレンダー・リソースアクセスの委任 + +カレンダーの委任は、Gmailのメール委任(2.1.9)と対になる概念で、管理職のカレンダー管理をアシスタントに任せるようなシナリオで利用されます。 + +**設定方法:** カレンダー所有者がGoogleカレンダーの「設定と共有」からカレンダーを特定のユーザーに共有し、「予定の変更」権限を付与することで、事実上の委任が成立する(Gmailのメール委任のような専用の「委任」ボタンではなく、共有権限の付与という形で実現される点に注意)。 + +**Google Workspace Sync for Microsoft Outlook(GWSMO)を利用する場合:** Outlookからカレンダー・メールの委任機能を使いたい場合は、まずGoogleカレンダー側で共有設定を行った上で、委任先ユーザーが自分のOutlookプロファイルに委任元のGoogle Workspaceアカウントを追加する、という手順を踏む。委任先は委任元のパスワード変更や他のアカウント設定変更はできない。 + +**建物・リソースの管理権限としての「委任」:** リソースカレンダーについては、前述のリソースマネージャー方式が実質的な「委任」の役割を果たす。 + +> **試験ポイント:** 「カレンダーとリソースへのアクセスを別のユーザーに委任する」という出題観点は、(1) 個人カレンダーの委任=共有権限の付与、(2) リソースカレンダーの委任=リソースマネージャー設定、という2つの異なるメカニズムを区別して理解しているかを問うている。 + +### 2.3.4 プライマリ・セカンダリカレンダーのデフォルト内部共有設定 + +Admin console →「Calendar」→「共有設定」で、組織内でカレンダーがどの程度共有されるかの既定値を設定できます。OU単位で異なるポリシーを適用でき、たとえば学校・教育機関ではより制限的な設定にし、一般企業ではよりオープンな設定にするといった使い分けが可能です。 + +**内部共有の一般的な選択肢(制限が緩い順):** + +- 予定の詳細をすべて共有する(Share all information) +- 空き時間・予定ありのみ共有する(Free/busy情報のみ、時間帯は見えるが内容は見えない) +- 共有しない(デフォルトでは他ユーザーから見えない) + +**OU間の共有制限:** 部門間・学生グループ間でのカレンダー共有によるプライバシー・情報漏えいリスクを軽減するために、OUごとに「内部共有オプション(プライマリカレンダー用)」をより制限的な値に設定できる。合わせて「外部共有オプション」も無効化することで、OU単位での多層防御が可能。 + +> **ベストプラクティス:** 役員・人事・法務など機密性の高い予定を扱う部門は、内部共有のデフォルトを「Free/busyのみ」以下に制限し、一般部門はコラボレーションを促進するためより開かれた設定にする、というOUベースの階層化ポリシーを設計する。 + +### 2.3.5 チーム・グループ向け共有カレンダーの設定 + +チームやプロジェクト単位で共有するグループカレンダー(Secondary calendar)を作成・共有することで、個人のプライマリカレンダーとは別に、チーム全体のイベント(休暇予定、チーム定例、プロジェクトマイルストーンなど)を一元管理できます。 + +**作成の流れ(概要):** + +1. 管理者またはカレンダー作成権限を持つユーザーがセカンダリカレンダーを新規作成する +2. 対象チーム・グループに対して適切なアクセスレベル(閲覧のみ/予定の変更/管理権限)で共有する +3. 必要に応じてGoogleグループのメンバー全体に一括共有する(Section 1「グループのすべてのユーザーへの追加」参照) + +**「マイカレンダー」に他ユーザーのカレンダーが表示される理由:** 管理者が明示的にユーザーへ共有した、あるいはユーザー自身が「マイカレンダー」欄に追加したカレンダー(他ユーザーのカレンダーやリソースカレンダーなど)は、そのユーザーのカレンダー一覧に表示され続ける。これは共有設定に起因する正常な挙動であり、トラブルシューティングの際にユーザーからの問い合わせの原因になりやすいポイントである。 + +> **ベストプラクティス:** チームカレンダーの命名規則(例:`[チーム名] Team Calendar`)を統一し、検索・識別性を高める。カレンダーの「管理権限」を持つメンバーは最小限に絞り、誤削除・誤設定変更のリスクを抑える。 + +### 2.3.6 カレンダーの外部共有オプションの管理 + +組織外のユーザーとカレンダーを共有する範囲も、Admin console →「Calendar」→「共有設定」の「外部共有オプション」でOU単位に制御します。 + +**外部共有制限の効果:** 組織の外部共有を制限すると、ユーザーは個々のイベント単位でその制限を超えた共有はできなくなる(例:組織レベルでFree/busyのみに制限していれば、個別のイベントでそれ以上の詳細を外部共有することはできない)。 + +**外部ゲスト招待時の確認プロンプト:** ユーザーが組織外のゲストを含むイベントを作成すると、既定では「本当に組織外のゲストを含めてよいか」の確認プロンプトが表示される。Admin console →「Calendar」→「共有設定」→「外部での招待」で、このプロンプトの表示・非表示をOU単位で切り替えられる(例:日常的に外部顧客とやり取りする営業部門はプロンプトを無効化し、それ以外の部門は有効のままにする)。 + +> **ベストプラクティス:** 外部共有オプションは、Drive/Docsの外部共有制御(2.2.3)と一貫したポリシー思想(信頼できる相手とのコラボレーションは円滑に、それ以外は摩擦を残す)で設計し、部門ごとに整合性を持たせる。 + +### 2.3.7 イベント所有権の移転 + +イベントの主催者(オーナー)が退職・異動する際、そのユーザーが作成したイベントの所有権を別のユーザーに移す必要があります。 + +**エンドユーザー操作(イベント単位):** Googleカレンダーで自分がオーナーであるイベントを開き、「その他のオプション」→「オーナーの変更」から新しいオーナーのメールアドレスを入力する。 + +**管理者によるユーザー削除前の一括対応:** ユーザーを削除する前に、そのユーザーが所有するイベントおよびセカンダリカレンダーを取り消すか、他のユーザーに移管する必要がある(「ユーザーを削除する前にイベントやセカンダリカレンダーをキャンセル・移管する」手順)。対応しないまま削除すると、他の参加者のカレンダー上でイベントが不正な状態のまま残る可能性がある。 + +> **試験ポイント:** Drive/Docsのファイル所有権移転(2.2.9)と同様に、「ユーザー削除の**前に**所有物の移転・整理を行う」という運用順序が、Calendar・Drive双方で共通する重要な原則である。 + +### 2.3.8 不明な送信者からの招待の防止 + +見知らぬ送信者からのスパム的なカレンダー招待は、ユーザーのカレンダーを埋め尽くし、フィッシングの温床にもなり得るため、Google Workspaceには専用の対策が用意されています。 + +**組織レベルの制御(管理者):** Admin console →「Calendar」→「共有設定」→「外部での招待」で、「送信者とやり取りしたことがある招待のみを表示する」オプションを選択する。この設定を有効にすると、ユーザーが既に何らかの形でやり取りしたことのある送信者(連絡先に登録済み、Gmailでやり取り済み、同一Workspaceドメインなど)からの招待のみが自動的にカレンダーに追加され、それ以外の招待はカレンダーに自動追加されなくなる。 + +**ユーザーレベルの制御:** ユーザー個人でも、Googleカレンダーの設定→「予定の設定」→「マイカレンダーへの招待の追加」で「送信元が判明している場合のみ」を選択できる。判定基準は、送信者が連絡先に登録されている、Gmailでメールをやり取りしたことがある、同一Workspaceドメインに所属している、のいずれかを満たす場合。 + +**スパム招待への対応で避けるべき行動:** 不審な招待に対して「欠席」を含むいかなる返信(欠席・出席・未定・コメント付き返信)も行わないことが推奨される。返信すると招待元(スパム送信者)に「このアドレスは有効で監視されている」という情報を与えてしまい、標的として狙われやすくなる。安全な対応は「スパムとして報告」「返信せず削除」、またはそのまま無視することのみ。 + +> **ベストプラクティス:** この設定を全社的に有効化した場合でも、既にカレンダーに登録されてしまった過去のスパムイベントは自動削除されないため、ユーザー自身での手動クリーンアップが必要になる点をヘルプデスク対応時に案内しておく。法務・金融など標的型攻撃のリスクが高い部門では、Gmailの外部送信者警告設定(2.1章)と組み合わせて多層的に対策する。 + +--- + +## 2.4 Google Meetの設定 + +### 2.4.1 組織・OU単位でのMeetの有効化・無効化 + +Google Meetのサービス自体のオン・オフは、他のWorkspaceサービスと同様に、Admin console →「アプリ」→「Google Workspace」→「Google Meet」から、組織全体または特定のOU・設定グループ単位で制御します(**Service Settings管理者権限**が必要)。 + +**基本的な考え方:** サービスをオフにしたユーザーは、既存の会議への参加や新規会議の開催ができなくなる。段階的な展開(パイロット部門から先行導入し、順次全社展開する)を行う場合は、OUまたは設定グループを使って対象範囲を絞り込む。 + +> **ベストプラクティス:** 大規模組織や大人数の会議(全社集会、ウェビナー等)を予定している場合は、事前に「大規模組織向けのGoogle Meetセットアップ」ガイドに従い、ネットワーク要件の事前確認・会議スペースの設計・サポート体制の準備を行う。 + +### 2.4.2 Meetセーフティ設定の構成 + +Meetのセーフティ設定は、会議中の不正参加・荒らし行為(Zoombombing的な被害)を防ぐための中核的な管理機能です。中心となる概念が **Host Management(ホスト管理)** です。 + +```mermaid +flowchart TD + HM["Host Management
(ホスト管理)"] -->|"オン"| HMOn["ホスト・co-hostのみが
以下を制御可能:"] + HMOn --> C1["チャットの許可"] + HMOn --> C2["画面共有の許可"] + HMOn --> C3["リアクション・Q&A・投票の許可"] + HMOn --> C4["参加者の全員ミュート/解除"] + HMOn --> C5["録画・文字起こしの開始権限"] + + HM -->|"オフ"| HMOff["同一ドメインの参加者は
誰でも上記操作が可能
(既定の緩い状態)"] + + Waiting["Waiting Room
(待機室)"] -->|"Quick accessが
オフの場合に有効"| WaitFlow["参加者は待機室で
ホストの承認を待つ"] + QuickAccess["Quick access
(クイックアクセス)"] -->|"オンの場合"| QAFlow["カレンダー招待に含まれる
参加者はホスト不在でも
入室可能"] + + style HMOn fill:#7c9eff,color:#000 + style WaitFlow fill:#7c9eff,color:#000 +``` + +| 設定 | 概要 | 既定値 | +| --- | --- | --- | +| **Host Management** | ホスト・co-hostに会議内コントロール(チャット、画面共有、ミュート、録画・文字起こし開始権限など)を集約させる機能。オフの場合、ドメイン内の参加者は誰でもこれらの操作が可能 | Business系エディションは**既定でオフ**、Education系・Frontlineは**既定でオン**(管理者がAdmin console上のデフォルト状態を変更可能) | +| **Waiting Room(待機室)** | 参加者を待機室に留め、ホストが個別に入室を許可する。ホストは待機中の参加者にメッセージを送ったり、会議添付資料などの情報を共有したりできる | Quick accessがオフの場合に事実上の待機室として機能する | +| **Quick access** | カレンダー招待に事前に含まれている参加者、または既に会議に参加している組織内メンバーから招待された参加者は、ホスト不在でも入室できる | Google Workspace全顧客・レガシーG Suite Basic/Business顧客で既定オン | +| **外部参加者への追加の予防措置** | 外部参加者が「リクエストなしで」参加できるのは、(a) 会議開始前のカレンダー招待に含まれている、または(b) 既に会議に参加している組織内メンバーから招待された場合で、かつ(c) 予定開始時刻の前後15分以内、のいずれかを満たす場合のみ | 常時適用される仕様 | + +**設定場所:** Admin console →「Google Meet」→「Meetセーフティ設定」 + +**その他のセキュリティ・プライバシー機能:** + +- **会議コード**:長く推測困難なコード体系で不正アクセスを防止 +- **電話でのダイヤルイン**:電話番号とPINは、予定されている会議時間内のみ有効 +- **SAML SSOによる追加認証**:全エディションでSAMLベースのシングルサインオンに対応 +- **監査ログ**:Admin console上でMeetの監査ログを確認可能 +- **アクセス透過性(Access Transparency)**:管理者がMeet録画にアクセスするたびにログとその理由を記録 +- **データリージョン**:録画データをGoogle Driveに保存する地理的リージョン(米国・欧州など)を指定可能 + +> **ベストプラクティス:** 外部参加者を含む機密性の高い会議(人事面談、取締役会など)では、Host Managementを明示的にオンにし、待機室を併用する。全社集会・ウェビナーのような大規模会議では、Quick accessをオンにしたままHost Managementでチャット・画面共有権限のみを制限する、といった使い分けが実務的。 + +### 2.4.3 Meetビデオ設定の構成(画質・録画・文字起こし・ノートテイキング) + +Admin console →「Google Meet」→「Meetビデオ設定」から、会議の画質・録画・文字起こし・Gemini によるノートテイキングを制御します。 + +**主なビデオ設定項目:** + +| 設定 | 説明 | +| --- | --- | +| **既定の動画画質(Default video quality)** | 会議の画質を選択する(帯域幅要件に影響) | +| **録画(Recording)** | 「ユーザーに会議の録画を許可する」設定。既定では**3か月間**録画データが保存される。録画のダウンロード・コピーを既定で許可するかも設定可能。Host Managementがオンでホストが録画を許可していない場合、録画は利用不可。ブレイクアウトルーム内では録画不可 | +| **ストリーム(Stream)** | 組織内、信頼済みドメイン、またはYouTube経由でのライブ配信を許可するか | +| **ゲートウェイの相互運用性** | サードパーティのビデオ会議システムのユーザーが自組織のMeet会議に参加できるようにする(追加設定が必要) | +| **クライアントログのアップロード** | ユーザーのメールアドレスを含むブラウザ・モバイルアプリのログ情報の収集を許可し、サポート対応に活用する | + +**文字起こし(Transcripts):** Education(学生ライセンス)を除く全エディションで既定オン。会議の音声のみが文字起こし対象で(チャットメッセージの記録には録画が必要)、文字起こし結果は会議主催者のGoogle Driveに保存される。Host Managementがオンの場合、Transcriptsを開始できるのはホスト・co-hostのみに制限される。 + +**自動的な会議成果物の設定(録画・文字起こし・Gemini ノートテイキングの自動化):** Admin console →「Google Meet」→「Meetビデオ設定」→「自動文字起こし」「自動録画」、および「Google Meetの設定」→「Gemini設定」から、新規作成される会議について録画・文字起こし・「自分の代わりにメモを取る(Gemini ノートテイキング)」を既定でオンにできる。ホスト・co-hostはカレンダー招待や会議中にこれらの設定を編集・オフにできる。これらの成果物がオンの場合、参加者には入室時に通知される。「自分の代わりにメモを取る」機能はGemini Business/Enterprise/EducationまたはAI Meetings & Messagesアドオンが必要。 + +```mermaid +flowchart LR + Meeting["Meet設定"] --> Video["ビデオ設定"] + Video --> Quality["既定の画質"] + Video --> Rec["録画
(既定3か月保存)"] + Video --> Trans["文字起こし
(音声のみ、Drive保存)"] + Video --> Notes["Gemini ノートテイキング
(Business/Enterprise/Education
アドオンが必要)"] + + Rec -.HostManagementの制約.-> HMcheck["ホスト管理がオン かつ
ホストが許可していない場合は
録画不可"] + + style Rec fill:#7c9eff,color:#000 + style Trans fill:#7c9eff,color:#000 + style Notes fill:#7c9eff,color:#000 +``` + +> **ベストプラクティス:** 「営業商談やタウンホールなど、常に記録を残したい会議シリーズ」に対しては、自動録画・自動文字起こしをOU/グループ単位でオンにし、手動操作への依存をなくす。一方、機密性の高いミーティングが多い部門では、既定オフのままにして、必要な場合にホストが都度有効化する運用にする。 + +--- + +## 2.5 Google Chatの設定 + +### 2.5.1 組織・OU単位でのChatの有効化・無効化 + +Chatサービスの有効化・無効化は、Admin console →「アプリ」→「Google Workspace」→「Google Chat」→「Chatを有効または無効にする」から、組織全体・OU単位・グループ単位で制御します。 + +**Chat設定の初期展開(Set up Chat for your organization)の流れ:** + +1. 要件を確認する +2. ディレクトリの連絡先共有をオンにする(ユーザー同士がChatで検索・発見できるようにするため) +3. Chatサービス自体のオン・オフを設定する +4. Chatの履歴設定をオン・オフにする(2.5.2参照) +5. スペースの履歴オプションを設定する +6. Chat招待の自動承諾を設定する(2.5.3参照) +7. 外部ユーザーとのやり取りを制御する +8. Chatアプリのインストールを許可する(2.5.4参照) +9. GIFピッカーのオン・オフを設定する +10. ユーザーへの告知・トレーニングを行う + +> **ベストプラクティス:** Chatの全社展開前に、まずディレクトリ連絡先共有の設定を確認する(オフのままだとユーザー同士が検索できずChatの利便性が著しく損なわれる)。 + +### 2.5.2 Admin consoleでのChat設定 + +**チャット履歴(History for chats):** 1対1メッセージやグループメッセージの履歴を保持するかどうかの既定値を制御する。 + +**スペース履歴(History for spaces):** インラインスレッド形式のスペースにおける履歴の既定設定。選択肢は以下の4種類: + +| オプション | 動作 | +| --- | --- | +| History is ON by default(ユーザーが変更可) | 既定オンだが個々のスペースで変更できる | +| History is OFF by default(ユーザーが変更可) | 既定オフだが個々のスペースで変更できる | +| History is ALWAYS ON(変更不可) | 常にオンで固定、ユーザーは変更できない | +| History is ALWAYS OFF(変更不可) | 常にオフで固定、ユーザーは変更できない | + +「Let History for chats setting override Space history setting」のチェックボックスにより、1対1・グループメッセージの履歴設定がスペースの履歴設定より優先されるかどうかを制御できる。両者を意図的に逆方向で強制すると、グループ会話をスペースにアップグレードした際に履歴設定が切り替わる可能性があるため注意。 + +**外部ドメインとのChat・スペース:** + +```mermaid +flowchart TD + Ext["組織外とのChat連携"] --> Q1{"1対1/スペース単位で
組織外とメッセージ
送受信を許可するか?"} + Q1 -- "On" --> Trust{"信頼済みドメイン
(allowlist)のみに
制限するか?"} + Trust -- "はい" --> Restricted["特定ドメインとのみ
外部Chatを許可"] + Trust -- "いいえ" --> AnyExternal["任意の外部ドメインと
Chat可能"] + Q1 -- "Off" --> NoExternal["外部とのメッセージ
送受信を禁止"] + + Q1 --> Q2{"外部スペース
(External spaces)を
許可するか?"} + Q2 -- "はい" --> ExtSpace["メンバー・認可されたChatアプリが
外部ユーザー/ゲストを招待可能"] + Q2 -- "いいえ" --> NoExtSpace["スペースへの
外部ユーザー招待不可"] + + style Restricted fill:#7c9eff,color:#000 + style AnyExternal fill:#ffd54f,color:#000 + style NoExternal fill:#e57373,color:#000 +``` + +**外部チャットの設定手順:** Admin console →「Google Chat」→「External Chat Settings」で、「組織外へのメッセージ送信をユーザーに許可する」をオン・オフし、オンの場合は「許可リスト化されたドメインのみに制限する」「面識のある外部ゲスト・ユーザーからの招待を自動承諾する」を任意で追加設定できる。 + +**ゲストアカウントと外部ユーザーの違い:** 外部ユーザーはGoogleアカウントを保有する組織外の人物、ゲストはGoogleアカウントを持たない人物を指し、ゲストにはアカウント作成の招待が送られる。外部ユーザー・ゲストはグループメッセージには直接追加できず、1対1メッセージまたは外部メンバーを許可するスペースでのみやり取りできる。信頼済みドメインを設定すると、Workspaceアカウントを持たないコンシューマー向けGoogleアカウント利用者とのコラボレーションができなくなる点に注意(業務用メールで作成された個人アカウントとの意図しない連携を防ぐ効果もある)。 + +**コンテンツモデレーション(Moderation):** ユーザーが不適切なメッセージを報告できる仕組みを、Admin console →「Google Chat」→コンテンツ保護設定から有効化する。報告はモデレーターロールを持つ管理者のみが確認可能(Google自身はレポート内容を閲覧しない)。管理者はレポートカテゴリ・対象となる会話タイプを管理し、報告されたコンテンツに対してアクションを実行できる。2020年12月以前に作成された外部ユーザーを含むグループ会話は、Chat/クラシックHangoutsの仕様変更により報告対象外となる。 + +**設定場所まとめ:** すべてAdmin console →「アプリ」→「Google Workspace」→「Google Chat」配下(**Service Settings管理者権限**、モデレーションなど一部は**Google Chat管理者権限**が必要)。 + +> **ベストプラクティス:** コンプライアンス要件が厳しい業界(金融・医療)では、履歴を「ALWAYS ON」に固定し、Vaultによる法的保持・eDiscovery対応を前提とした運用にする。外部連携が多い営業・パートナーシップ部門は「External Chat Settings」で信頼済みドメインの許可リストを整備し、それ以外の部門は外部Chatをオフのままにする。 + +### 2.5.3 Chat招待設定の管理 + +**Chat招待の自動承諾(Automatically accept chat invitations):** 組織内の他ユーザーからのChat招待を自動的に承諾させるかどうかを制御する。オフの場合、既定では新しい連絡先からの招待をユーザーが手動で承諾する必要がある。設定場所:Admin console →「Google Chat」→「Chat Invitations」で、OU単位でオン・オフを選択する。 + +**Chat・スペースの作成制限(Chat and Space restrictions):** 特定のユーザーグループに対して、新規の1対1メッセージ・グループメッセージ・スペースの作成を制限できる。制限対象グループは、既存の会話へのメンバー管理操作も制限される。設定場所:Admin console →「Google Chat」→「Chat and Space restrictions」。 + +> **ベストプラクティス:** 大規模組織では新入社員のオンボーディング時の摩擦を減らすため、自動承諾を組織内でオンにしておくことが一般的。逆に、外部からの不審な招待を警戒すべき業種では、手動承諾のままにしてユーザーの判断を介在させる。 + +### 2.5.4 Chatアプリの追加 + +Chatアプリ(ポーリング、タスク管理、サードパーティ連携ボットなど)のインストール許可も、Admin console →「Google Chat」→「Chat apps」で制御します。 + +**ユーザーによるインストールの許可:** Admin console →「Google Chat」→「Chat apps」でOU単位に許可を設定する。一部のアプリはトップレベル組織単位でのアクセスを要求する場合があり、これを許可しないとChat APIが正しく動作しない可能性がある点に注意。 + +**管理者による組織への一括インストール:** 管理者はGoogle Workspace Marketplaceからアプリを選び、「管理者としてインストール」→対象を「組織内の全員」に設定してインストールできる。この場合、ユーザーは自分でインストール操作をせずとも「アプリ」セクションにそのアプリが表示され、利用可能になる(通知はオフにできるが、アンインストールはユーザー側ではできない)。 + +**自動インストール(2025年9月以降):** ユーザーが最初にアプリの機能を使おうとした時点(例:投票アプリで初めて投票を作成しようとした時)に自動的にそのアプリがインストールされ、以降はどのスペースでもすぐに利用できるようになる仕組みが導入されている。自動インストールを望まない場合、管理者は特定のアプリのインストール自体を禁止するか、特定スペースでのアプリインストール権限を制限することで防止できる。 + +**アプリ権限(App authorization):** Chatアプリがユーザーのように振る舞うための権限(メッセージの送受信など)はOAuth 2.0スコープ(`https://www.googleapis.com/auth/chat.app.*`)で要求される。アプリ権限はユーザー権限と異なりOUごとに付与範囲を絞ることができない点も押さえておくべき。 + +> **ベストプラクティス:** 生産性向上に資する社内標準アプリ(承認ワークフロー、勤怠管理ボットなど)は管理者が組織全体に一括インストールし、利用開始の障壁をなくす。一方、サードパーティ製の汎用チャットアプリはMarketplaceの許可リスト(Section 4「Marketplaceの許可リスト管理」も参照)で個別審査してから許可する。 + +--- + +## 2.6 Google Workspaceにおける生成AIの活用 + +### 2.6.1 生成AI利用時の組織データのプライバシーとセキュリティ確保 + +Gemini for Google Workspaceは、エンタープライズ利用を前提としたデータガバナンスモデルの上に構築されています。管理者としてまず理解すべきは、**Geminiのデータ取り扱いポリシーが通常のGoogle Workspaceサービスと同様の契約・プライバシー保護の枠内にある**という点です。 + +**Googleが明示するデータ保護の原則:** + +- ユーザーのデータはユーザー自身のものであり、Googleの所有物ではない +- Googleはユーザーデータを広告目的で利用しない +- Googleはユーザーデータを第三者に販売しない +- データは転送時に暗号化され、Google Driveに保存される会議録画等は保存時にも暗号化される +- Vaultを使ってGeminiに関連するデータ(例:Meet録画)の保持ポリシーを設定できる +- HIPAAやFedRAMP Highなど、規制業界のコンプライアンス要件をサポート + +**Workspace Intelligence(サイドパネルのGemini)のデータソース制御:** Gmail・Drive・Calendar・Chatの各サービスについて、Geminiがバックグラウンドでアクティブに検索対象とするかどうかを個別にオン・オフできる。特定サービスをオフにすると、Geminiはそのアプリを能動的に検索しなくなる。 + +**会話履歴の保持ポリシー:** 管理者は以下を選択できる: + +- ユーザーによる個別会話の手動削除を許可する(既定で有効) +- 一定期間(3か月、18か月、3年など)でGeminiの会話履歴を自動削除する +- 組織として無期限に会話を保持する + +Gemini会話履歴がオフの場合でも、新しいチャットはサービス提供とフィードバック処理のために最大72時間、ユーザーアカウント内に一時保存される(この72時間分のデータはGemini Apps Activityには表示されない)。 + +**プロンプトインジェクション対策:** Geminiは新興の攻撃ベクトルであるプロンプトインジェクションに対して、多層防御戦略(layered defense strategy)で対応するよう設計されている。 + +> **ベストプラクティス:** +> - 業務上不要なサービスへのGeminiのアクセスは、Workspace Intelligenceのデータソース設定でオフにし、最小権限の原則を適用する +> - 規制業界(医療・金融・公共部門)では、Vaultを用いたGemini関連データの保持ポリシー設定を、既存の他サービスの保持ポリシーと整合させる +> - 個人アカウントとしてGeminiアプリを追加サービスとして利用しているユーザーには、機密情報・社外秘情報を入力しないよう周知する(追加サービスとしてのGeminiは、Workspaceアカウントに紐づく組織ポリシーの一部が適用されない場合がある) + +### 2.6.2 組織・OU単位でのGeminiの有効化・無効化 + +Geminiの有効化・無効化には、**複数のレイヤー**が存在する点が試験・実務双方で頻出の理解ポイントです。 + +```mermaid +flowchart TD + Layer1["レイヤー1: Workspaceサービス内のGemini機能
(Gmail/Docs/Sheets/Slides/Driveのサイドパネル)"] + Layer2["レイヤー2: Geminiアプリ(gemini.google.com)へのアクセス"] + Layer3["レイヤー3: Geminiアプリ内でのWorkspace拡張機能
(旧Workspace Extensions/Apps)"] + + Layer1 -->|"Admin console →
生成AI → Gemini for Workspace →
Feature access"| L1Detail["サービスごとに個別On/Off
(あるアプリでオフでも、
別アプリ経由でそのデータに
アクセスされる場合がある点に注意)"] + Layer2 -->|"Admin console →
Turn the Gemini app on/off"| L2Detail["既定で18歳以上かつ
ライセンス保有ユーザーに提供。
全ユーザーへの拡大も可能"] + Layer3 -->|"Admin console →
Turn Google apps in Gemini on/off"| L3Detail["Calendar/Docs/Drive/Gmail/
Keep/TasksのコンテンツをGeminiアプリの
プロンプトに利用可能にするか"] + + style Layer1 fill:#7c9eff,color:#000 + style Layer2 fill:#7c9eff,color:#000 + style Layer3 fill:#7c9eff,color:#000 +``` + +| レイヤー | 設定名 | 設定場所 | 既定値・補足 | +| --- | --- | --- | --- | +| **① Workspaceサービス内のGemini機能** | Feature access | Admin console →「生成AI」→「Gemini for Workspace」→「Feature access」パネル | 既定オン。あるアプリでGeminiをオフにしても、別のアプリ経由でそのデータをGeminiが参照できる場合がある(例:Driveでオフでも、GmailからGeminiにDriveファイルについて質問すると参照される) | +| **② Geminiアプリ自体へのアクセス** | Turn the Gemini app on or off | Admin console →「生成AI」→「Gemini App」 | 既定で18歳以上かつ対応ライセンスを持つ全ユーザーに提供。ライセンスの有無に関わらず全ユーザーへのアクセス拡大も設定可能。モバイルアプリ専用の管理者コントロールは存在しないが、デバイス管理設定でアプリ自体をブロックすることは可能 | +| **③ Geminiアプリ内のWorkspace拡張機能** | Turn Google apps in Gemini on or off | Admin console →「生成AI」→「Gemini App」→「Apps」 | Geminiアプリの利用には会話履歴のオンが前提。組織側で該当のWorkspaceサービス(例:Tasks)自体をオフにしている場合、対応する拡張機能も利用不可 | + +**利用の前提条件:** Workspace拡張機能(Apps)を使うには **Gemini会話履歴(Gemini conversation history)** をオンにする必要がある。また、複数のアプリ・サービスをまたぐ複合的なプロンプト(例:「カレンダーに予定を作成し、リマインダーも追加して」)を実行する場合、関与するすべてのサービス(この例ではCalendarとTasks)が有効になっていないと、一部のアクションが実行されないまま終わってしまう。 + +**Gemini for App Creation / Gemini in AppSheet Solutions:** AppSheet関連のGemini機能(自然言語でのアプリ作成、自動化タスクへのAI組み込み)は別個の設定として、チーム単位でオン・オフできる(2.7も参照)。 + +> **ベストプラクティス:** 段階的ロールアウトの初期フェーズでは、パイロットOUのみでレイヤー①②③をすべてオンにして検証し、フィードバックを収集してから全社展開する。生成AIの利用に消極的な組織は、まずレイヤー①(Workspaceアプリ内の機能)のみを許可し、レイヤー③(外部アプリであるGeminiアプリへのWorkspaceデータ連携)は慎重に判断する、という段階的な導入戦略も有効。 + +### 2.6.3 Geminiアプリ向けGoogle Workspace拡張機能の有効化 + +「Turn Google apps in Gemini on or off」(Workspace apps、旧称Workspace Extensions)は、Geminiアプリ(gemini.google.com)がCalendar・Docs・Drive・Gmail・Keep・Tasksなどのユーザーコンテンツを参照し、より文脈に沿った応答を返せるようにする設定です。 + +**この機能で可能になること:** + +- Gmailに関する追加情報へのリンク(引用)をGeminiの応答に含める +- ユーザーがDriveファイルをGeminiアプリやGemの指示(Gem instructions)にアップロードして利用する + +**オフにした場合の挙動:** 既にWorkspace apps機能を使って行われたチャットのやり取り自体はユーザーに引き続き表示されるが、Geminiアプリは元のプロンプトに応答するために必要だった情報以外へのアクセスができなくなる(再度オンにするまで、Workspaceからの追加情報取得はできない)。 + +**適用対象外のケース:** 追加サービスとしてGeminiにアクセスしているユーザー(=Workspaceの標準ライセンスに含まれない形でGeminiアプリのみ追加提供されているケース)は、Workspace appsを利用できない。 + +> **ベストプラクティス:** Workspace appsをオンにする際は、Drive内のファイルアクセス制御(本人が所有またはアクセス権を持つファイルのみ、共有ドライブ経由のファイルは除く)が、既存のDrive共有ポリシーと一貫していることを確認し、意図せず機密ファイルへのアクセス経路が広がらないようにする。 + +### 2.6.4 Gemini利用状況レポートの生成 + +管理者は、Gemini機能の利用状況を可視化し、導入効果・利用パターン・潜在的なセキュリティリスクを把握するための複数のレポートにアクセスできます。 + +**設定場所:** Admin console →「生成AI」→「Gemini reports」→「ユーザーレベルの使用状況」 + +**主なレポートの種類:** + +| レポート | 内容 | +| --- | --- | +| **組織レベルの利用状況(Org-level usage)** | アクティブなGeminiユーザー数、ライセンス割り当て状況、全体の利用推移、アプリ別の利用状況(Gemini Adoption per app) | +| **ユーザーレベルの利用状況(User-level usage)** | 利用強度(高・中・低・ゼロ)、アクティブ日数、ユーザーごとのアプリ別詳細な利用状況、利用上限に達した日数(Days at limit) | +| **利用インタラクション(Usage per interaction)** | Gmailの「文章作成のヒント」やSheetsでのデータ整理支援など、機能単位での詳細な利用実績 | + +**フィルタリング:** OU単位・グループ単位でレポートをフィルタリングできる。組織構造の変更が反映されるまで最大72時間かかり、組織変更前の履歴データはその変更を遡って反映されない点に注意。 + +**Gemini監査ログ(Reporting API経由):** Admin SDKのReporting APIを通じて、Geminiアプリ・Workspaceアプリ内でのユーザーのアクティビティ(実行されたアクション、使用されたアプリ、使用された機能、最終利用日時など)をより詳細に取得できる。このデータはセキュリティ調査ツール・監査調査ツールでも利用可能。 + +**活用シーン:** + +- 利用状況が高いパワーユーザーを特定し、他ユーザーへの展開・トレーニングのアンバサダーとして活用する +- ライセンス上限(使用制限)に達しているユーザーの傾向から、アドオン(AI Expanded Accessなど)へのアップグレード要否を判断する +- 特定ユーザーのGemini利用パターンの急激な変化から、潜在的な誤用・セキュリティリスクの兆候を検知する + +> **ベストプラクティス:** ライセンスコストの最適化のため、四半期ごとにGemini利用状況レポートをレビューし、利用実績が「ゼロ」のユーザーからライセンスを再配布することを検討する。同時に、利用強度が「高」のユーザー層を特定し、そのユースケースを社内のベストプラクティス共有会で展開する。 + +--- + +## 2.7 Workspace開発のサポート + +### 2.7.1 AppSheetとApps Scriptのユースケース + +AppSheetとApps Scriptは、いずれも「コードを書かずに、または最小限のコードでGoogle Workspaceを拡張・自動化する」ためのツールですが、性質が異なり、試験でも両者の使い分けの理解が問われます。 + +```mermaid +flowchart LR + Need["業務自動化の要件"] --> Q{"要件の性質は?"} + Q -->|"データ駆動型の
業務アプリ・モバイルUIが必要
(現場担当者向けフォーム、
在庫管理、承認ワークフロー等)"| AppSheet["AppSheet
(ノーコードアプリ開発)"] + Q -->|"既存のWorkspaceアプリの
内部処理をカスタムコードで
自動化したい
(トリガー起動のスクリプト、
カスタム関数、API連携)"| AppsScript["Apps Script
(JavaScriptベースの
クラウドスクリプト実行基盤)"] + + AppSheet -.連携可能.-> AppsScript + AppsScript -.複雑なロジックを提供.-> AppSheet + + style AppSheet fill:#7c9eff,color:#000 + style AppsScript fill:#7c9eff,color:#000 +``` + +**AppSheetの主なユースケース:** + +- Google Sheetsのデータからモバイル対応の業務アプリを作成する +- 出張申請フローで、申請が行われた際に上長を自動検索してChatまたはメールで承認通知を送る +- 現場作業員がモバイル端末で撮影した点検写真をDriveにアップロードし、監査担当者がアクセスできるよう共有設定を自動調整する +- シフト・予約管理の簡易Webインターフェースを提供し、予約が入るとCalendarに自動でイベントを作成し招待する +- Google Chat内でアプリを起動する(Launch apps in Google Chat) + +**Apps Scriptの主なユースケース:** + +- ボタンクリックでカレンダーの予定を作成する +- 新しい行が追加された際にスライドを自動追加する +- フォーム経由でアップロードされた写真をDriveに保存し、特定の人と自動共有する +- テーブルデータから監査ログとしてGoogle Docsファイルを自動生成する +- 外部の機械学習サービスを呼び出し、予測結果を新しい行のデータとして書き戻す + +**AppSheet Apps Scriptコネクタ:** AppSheetの自動化(Automation)から直接Apps Scriptの関数を呼び出せるコネクタが提供されており、AppSheetアプリからGoogle Workspace API(Drive、Docs、Sheets、Admin SDKなど)やYouTube、Google Analytics、BigQueryなど他のGoogleサービスにアクセスするワークフローを構築できる。この連携により、AppSheetのノーコードの手軽さと、Apps Scriptの柔軟なカスタムロジックを組み合わせられる。**Apps Scriptサービスが組織で有効になっている必要がある**点に注意(AppSheetの自動化からApps Scriptを呼び出すための前提条件)。 + +**制約事項:** AppSheetからはスタンドアロンのApps Scriptスクリプトのみ呼び出し可能で、コンテナバインド型のスクリプト(特定のスプレッドシート等に紐づくスクリプト)は現時点でサポートされていない。また、Apps Scriptは常にヘッドデプロイメント(最新の保存済みバージョン)を実行し、特定のデプロイバージョンを指定して呼び出すことはできない。 + +> **試験のポイント:** 「AppSheetとApps Scriptのユースケースを特定する」という出題観点は、両者の**二者択一ではなく、組み合わせて使うケースが多い**ことを理解しているかを問う。AppSheetは「UI・データ入力・ワークフロートリガーの民主化」、Apps Scriptは「Workspaceサービスへの深いプログラマティックな統合」という役割分担で捉えるのが実務的。 + +### 2.7.2 組織・OU単位でのAppSheetの有効化 + +AppSheetは他の追加Googleサービスと同様に、Admin console上で組織・OU・グループ単位の粒度で有効化・無効化を制御できます。 + +**設定手順:** Admin console →「アプリ」→「追加のGoogleサービス」→「AppSheetの設定」から、組織全体または特定のOU・グループ・ユーザー単位でオン・オフを切り替える。 + +**サブスクリプションがない場合の挙動(過去の移行時の仕様):** AppSheetのサブスクリプションを契約していない組織では、「個別に管理されないサービスへのアクセス管理」設定がOUごとにオン・オフ混在している場合、AppSheetの制御はそのOUレベルの設定に自動的に整合する。全OUでオフに設定されている組織では、AppSheet自体も全体でオフになる。 + +**AppSheet管理の階層構造:** + +| 概念 | 説明 | +| --- | --- | +| **Organization(組織)** | Google Workspace管理者に組織内の全チームを管理する一元的なツールを提供し、チーム管理をチーム管理者に委任できる | +| **Team(チーム)** | 個々のアプリ開発チーム単位。Gemini for App Creation(自然言語でのアプリ作成)などの機能はチーム単位でオン・オフを設定する | +| **AppSheet Admin Console** | サブスクリプションのライセンス購入・割り当て・使用状況を可視化するための専用管理コンソール | + +**Gemini関連機能の有効化:** + +- **Gemini for App Creation**:自然言語の指示だけでAppSheetアプリを構築できる機能。チーム単位で有効化を制御 +- **Gemini in AppSheet Solutions**:自動化(Automation)内にAIタスクを追加し、情報の抽出・分類を行える機能。どのアプリ作成者がAI機能を自動化内で使えるかを管理者が制御可能 + +> **ベストプラクティス:** AppSheetを初めて全社導入する際は、まず「Organization」機能を使って部門ごとの「Team」を作成し、各チームにチーム管理者を委任することで、シャドーIT化を防ぎながらも現場主導のアプリ開発を促進する、というガバナンスモデルを構築する。Apps Script同様、既定では組織全体でオンになっているため、まだ利用ポリシーが整っていない組織は、正式な運用ルール策定までの間、Admin console上で意図的にオフに設定しておくことも選択肢となる。 + +--- + +## Section 2 ベストプラクティス総括表 + +| サービス | 重要設定 | ベストプラクティスの要点 | +| --- | --- | --- | +| **Gmail** | MXレコード | `smtp.google.com`単一レコードへの移行、切り替え前のユーザー作成完了 | +| **Gmail** | ルーティング | Default routing(既定配送)とRouting(特定条件配送)を使い分け、テストは小規模OUから開始 | +| **Gmail** | コンテンツコンプライアンス | 事前定義された検出器を活用し、正規表現の自作を最小化。ルール優先順位を必ず検証 | +| **Gmail** | スパム・フィッシング対策 | IPアレースリストはドメイン全体に影響するため範囲を絞り込み、インバウンドゲートウェイの`Reject all mail not from gateway IPs`は慎重に判断 | +| **Gmail** | メール認証 | SPF・DKIM・DMARCを三点セットで運用し、DMARCは`p=none`から段階的に`reject`へロールアウト | +| **Gmail** | 移行 | パイロットグループでの先行移行、デルタ移行による差分同期の活用 | +| **Gmail** | Quarantine | 最小権限のカスタムロール(Access Admin Quarantine/Access Restricted Quarantines)でアクセスを委任 | +| **Drive/Docs** | デフォルト共有 | `Restricted`を既定にし、ターゲットオーディエンスの「Employees Only」をプライマリに設定 | +| **Drive/Docs** | Trust Rules | 部門・ドメイン・グループ単位の細かい共有制御が必要な場合に採用し、既存Drive共有設定からの変換をプレビューで確認 | +| **Drive/Docs** | 共有ドライブ | 永続的なチーム資産は共有ドライブに保存し、退職時のデータ喪失リスクを構造的に排除 | +| **Drive/Docs** | ストレージ | プールドストレージの使用状況を定期レビューし、ポリシー変更前にユーザー通知テンプレートで周知 | +| **Drive/Docs** | ラベル | ラベル名・選択肢に機密情報を含めず、必須フィールドで入力を促す | +| **Calendar** | リソース予約 | 高需要リソースはリソースマネージャーによる手動承認、一般リソースは自動承認 | +| **Calendar** | 委任 | 個人カレンダー=共有権限付与、リソースカレンダー=リソースマネージャー設定、と使い分けを明確化 | +| **Calendar** | 不明送信者対策 | 「やり取りしたことがある送信者のみ表示」をOU単位で有効化し、既存スパムイベントは手動クリーンアップが必要な点をユーザーに周知 | +| **Meet** | セーフティ | 機密性の高い会議はHost Managementをオン+待機室併用、大規模会議はQuick accessオン+チャット権限制限 | +| **Meet** | ビデオ設定 | 記録が必須の会議シリーズ(商談等)は自動録画・自動文字起こしをOU/グループ単位で有効化 | +| **Chat** | 履歴設定 | 規制業界は履歴をALWAYS ONに固定しVaultで法的保持に対応 | +| **Chat** | 外部連携 | External Chat Settingsで信頼済みドメインの許可リストを部門単位に整備 | +| **Chat** | アプリ | 社内標準アプリは管理者が一括インストール、サードパーティアプリはMarketplace許可リストで審査 | +| **生成AI** | データ保護 | Workspace Intelligenceのデータソース設定で不要なサービスへのアクセスをオフにし最小権限を徹底 | +| **生成AI** | 有効化の階層 | Feature access/Geminiアプリアクセス/Workspace apps in Geminiの3層を区別して段階的に展開 | +| **生成AI** | 利用状況レポート | 四半期ごとにレビューし、未利用ユーザーのライセンス再配布とパワーユーザーの知見共有を実施 | +| **開発支援** | AppSheet/Apps Script | AppSheet=UI・トリガーの民主化、Apps Script=深いプログラマティック統合、という役割分担で組み合わせて活用 | +| **開発支援** | ガバナンス | AppSheet「Organization/Team」構造でチーム管理者に委任し、シャドーIT化を防止 | + +--- + +## 学習チェックリスト + +- [ ] MXレコードを単一レコード方式(`smtp.google.com`)とレガシー方式(複数aspmxレコード)の両方について説明できる +- [ ] Default routingとRoutingの優先順位の違い、およびデュアルデリバリー・分割配信・キャッチオールの使い分けを説明できる +- [ ] コンテンツコンプライアンスと添付ファイルコンプライアンスの違い、事前定義検出器の役割を説明できる +- [ ] Email allowlist・Blocked senders・Inbound gatewayの3つの機能の違いと適用範囲(ドメイン全体かOU単位か)を説明できる +- [ ] SPF・DKIM・DMARCそれぞれの役割と、大量送信者(1日5,000通以上)に対する必須要件を説明できる +- [ ] データ移行サービス(Data migration)とデータインポートツール(Data import)の対象範囲の違いを説明できる +- [ ] Gmailの委任機能(個人・グループ)とCollaborative Inboxの使い分けを説明できる +- [ ] Email quarantineへのアクセス権を委任する2つの方法(全体アクセス/グループ単位アクセス)を説明できる +- [ ] Drive共有の「全般的なアクセスのデフォルト設定」とターゲットオーディエンスの関係を説明できる +- [ ] Trust Rulesが従来のDrive共有設定(外部共有オン・オフ・信頼済みドメイン)とどう関係し、いつ採用すべきかを説明できる +- [ ] 共有ドライブのOU割り当てとストレージ上限のカスタマイズ方法を説明できる +- [ ] カスタムDocsテンプレートの3つの送信モード(Open/Moderated/Restricted)の違いを説明できる +- [ ] Driveラベルの3つの自動適用方法(デフォルト分類・DLP・AI分類)とその優先順位を説明できる +- [ ] オフラインアクセス設定がDrive for desktopには適用されない点を説明できる +- [ ] 建物・フロア・機能・リソースの階層構造とリソース予約の自動承認/手動承認の使い分けを説明できる +- [ ] カレンダー・リソースの委任のメカニズムの違い(共有権限 vs リソースマネージャー)を説明できる +- [ ] 「送信元が判明している場合のみ」設定によるカレンダースパム対策のロジックを説明できる +- [ ] Host Management・Waiting Room・Quick accessの3つの関係性を説明できる +- [ ] Meet録画・文字起こし・Geminiノートテイキングの自動化設定とその前提ライセンスを説明できる +- [ ] Chatの履歴設定(Chat履歴 vs Space履歴)とオーバーライドの仕組みを説明できる +- [ ] External Chat SettingsとExternal spacesの設定の違い、ゲストと外部ユーザーの違いを説明できる +- [ ] Geminiの有効化を構成する3つのレイヤー(Feature access/Geminiアプリ/Workspace apps in Gemini)を説明できる +- [ ] Gemini利用状況レポートで取得できる情報の種類(組織レベル・ユーザーレベル・インタラクション単位)を説明できる +- [ ] AppSheetとApps Scriptの役割分担、および両者を連携させるAppSheet Apps Scriptコネクタの仕組みを説明できる + +--- + +## 参考文献 + +すべてGoogle公式ドキュメント(`support.google.com/a`、`knowledge.workspace.google.com`、`workspaceupdates.googleblog.com`、`developers.google.com`)を一次情報として使用しています。 + +**認定試験公式情報** +- [Associate Google Workspace Administrator 認定ページ(Google Cloud)](https://cloud.google.com/learn/certification/associate-google-workspace-administrator?hl=en) +- [Associate Google Workspace Administrator Exam Guide(PDF)](https://services.google.com/fh/files/misc/associate_google_workspace_administrator_exam_guide_english.pdf) + +**2.1 Gmail** +- [Set up MX records for Google Workspace](https://knowledge.workspace.google.com/admin/domains/set-up-mx-records-for-google-workspace) +- [Avoid issues when changing MX records](https://knowledge.workspace.google.com/admin/domains/avoid-issues-when-changing-mx-records) +- [Email routing and delivery options for Google Workspace](https://support.google.com/a/answer/2685650?hl=en) +- [Set up Default routing for your organization](https://support.google.com/a/answer/2368153) +- [Send email to 2 email systems with split delivery](https://support.google.com/a/answer/12971016?hl=en) +- [Set up rules for advanced email content filtering](https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-rules-for-advanced-email-content-filtering) +- [Enhance rules for advanced email content filtering with predefined detectors](https://support.google.com/a/answer/6280516?hl=en) +- [Filter messages with attachments](https://support.google.com/a/answer/2364580?hl=en) +- [Add IP addresses to allowlists in Gmail](https://knowledge.workspace.google.com/admin/gmail/advanced/add-ip-addresses-to-allowlists-in-gmail) +- [Working with Gmail Admin settings in Google Workspace](https://support.google.com/a/answer/2786758?hl=en) +- [Turn POP & IMAP on or off for users](https://support.google.com/a/answer/105694?hl=en) +- [Let users automatically forward their own Gmail emails](https://support.google.com/a/answer/14724207?hl=en) +- [Set up SPF](https://knowledge.workspace.google.com/admin/security/set-up-spf) +- [Set up DKIM](https://knowledge.workspace.google.com/admin/security/set-up-dkim) +- [Set up DMARC](https://knowledge.workspace.google.com/admin/security/set-up-dmarc) +- [Migrate email from a Gmail account](https://knowledge.workspace.google.com/admin/migrate/migrate-email-from-a-gmail-account?hl=en) +- [Import email with the data import tool](https://support.google.com/a/answer/9476255?hl=en) +- [Delegate a user's email address](https://knowledge.workspace.google.com/admin/users/delegate-a-users-email-address) +- [Give your users access to email quarantine](https://knowledge.workspace.google.com/admin/gmail/advanced/give-your-users-access-to-email-quarantine) + +**2.2 Google DriveとDocs** +- [Set general access sharing options for your organization](https://support.google.com/a/answer/12732365?hl=en) +- [Create and manage trust rules for Drive sharing](https://knowledge.workspace.google.com/admin/security/create-and-manage-trust-rules-for-drive-sharing) +- [Best practices for deploying target audiences](https://support.google.com/a/answer/10356781?hl=en) +- [Turn custom Drive templates on or off for users](https://support.google.com/a/answer/3055325) +- [Manage data policies for specific shared drives](https://knowledge.workspace.google.com/admin/drive/manage-data-policies-for-specific-shared-drives) +- [4. Set storage limits(Getting started)](https://support.google.com/a/answer/12033430?hl=en) +- [Google Workspace storage FAQ for admins](https://support.google.com/a/answer/9214707?hl=en) +- [Get started: Drive setup guide for admins](https://knowledge.workspace.google.com/admin/drive/get-started-drive-setup-guide-for-admins) +- [Create classification labels for your organization](https://knowledge.workspace.google.com/admin/security/create-classification-labels-for-your-organization) +- [Apply Default classification labels to new files automatically](https://support.google.com/a/answer/11280938) +- [Get started as a classification labels admin](https://support.google.com/a/answer/9292382?hl=en-) +- [Set up offline access to Docs, Sheets & Slides](https://support.google.com/a/answer/1642623?hl=en) +- [Administrator privilege definitions](https://knowledge.workspace.google.com/admin/users/administrator-privilege-definitions) + +**2.3 Google Calendar** +- [Create buildings, features & Calendar resources](https://knowledge.workspace.google.com/admin/calendar/create-buildings-features-and-calendar-resources) +- [Approve or deny Calendar room & resource bookings](https://support.google.com/a/answer/7046439?hl=en) +- [Allow Free/Busy Google Calendar room booking](https://knowledge.workspace.google.com/admin/calendar/allow-free-busy-google-calendar-room-booking) +- [Allow external invitations in Google Calendar events](https://knowledge.workspace.google.com/admin/calendar/allow-external-invitations-in-google-calendar-events) +- [Delegate access to your mail or calendar](https://support.google.com/a/users/answer/168126?hl=en) + +**2.4 Google Meet** +- [Google Meet security & privacy for IT admins](https://support.google.com/a/answer/7582940) +- [Manage waiting room settings for your users](https://knowledge.workspace.google.com/admin/meet/manage-waiting-room-settings-for-your-users) +- [Manage Meet settings (for admins)](https://knowledge.workspace.google.com/admin/meet/manage-meet-settings) +- [Turn Meet recording on or off for your organization](https://knowledge.workspace.google.com/admin/meet/turn-meet-recording-on-or-off-for-your-organization) +- [Use Transcripts with Google Meet](https://support.google.com/meet/answer/12849897?hl=en) + +**2.5 Google Chat** +- [Set up Chat for your organization](https://support.google.com/a/answer/9269628) +- [Set a space history option for users](https://knowledge.workspace.google.com/admin/chat/set-a-space-history-option-for-users) +- [Chatting with external users & guest accounts](https://knowledge.workspace.google.com/admin/chat/chatting-with-external-users) +- [Control external Chat & spaces chat options](https://knowledge.workspace.google.com/admin/chat/control-external-chat-and-spaces-chat-options) +- [Set up content moderation for Chat](https://support.google.com/a/answer/13471510) +- [Automatically accept chat invitations](https://support.google.com/a/answer/9269535?hl=en) +- [Allow users to install Chat apps](https://support.google.com/a/answer/7651360) +- [Set up app authorization for Chat](https://knowledge.workspace.google.com/admin/chat/set-up-app-authorization-for-chat) + +**2.6 生成AI(Gemini)** +- [Enterprise security controls for Gemini in Google Workspace](https://workspace.google.com/blog/ai-and-machine-learning/enterprise-security-controls-google-workspace-gemini) +- [Generative AI in Google Workspace Privacy Hub](https://support.google.com/a/answer/15706919?hl=en) +- [Manage access to Gemini features in Workspace services](https://knowledge.workspace.google.com/admin/generative-ai/workspace-with-gemini/manage-access-to-gemini-features-in-workspace-services) +- [Turn the Gemini app on or off](https://support.google.com/a/answer/14571493) +- [Control Google Apps in Gemini on or off(Turn Google apps in Gemini on or off)](https://knowledge.workspace.google.com/admin/gemini/turn-google-apps-in-gemini-on-or-off) +- [Review Gemini usage in your organization](https://knowledge.workspace.google.com/admin/generative-ai/review-gemini-usage-in-your-organization) +- [Manage access to Gemini in AppSheet](https://knowledge.workspace.google.com/admin/appsheet/manage-access-to-gemini-in-appsheet) + +**2.7 Workspace開発のサポート** +- [Manage AppSheet in your organization](https://knowledge.workspace.google.com/admin/appsheet/manage-appsheet-in-your-organization) +- [Call Apps Script from an automation(AppSheet Help)](https://support.google.com/appsheet/answer/11997142?hl=en) +- [Announcing the Apps Script connector for AppSheet(Google Developers Blog)](https://developers.googleblog.com/en/announcing-the-apps-script-connector-for-appsheet-automate-workflows-for-google-workspace/) +- [Turn Apps Script on or off for users](https://knowledge.workspace.google.com/admin/users/access/turn-apps-script-on-or-off-for-users) +- [Google Workspace Updates: Control access to AppSheet with a new Admin console setting](https://workspaceupdates.googleblog.com/2020/11/admin-control-appsheet.html?m=1) + +--- + +*本ガイドはGoogle Workspace管理者ヘルプセンターの公開情報(2026年8月時点)に基づいて作成されています。Admin consoleのUIやポリシーはGoogleにより随時更新されるため、実際の設定時は必ず最新の公式ヘルプページを確認してください。* diff --git a/Associate-google-workspace-admin-s1.html b/Associate-google-workspace-admin-s1.html new file mode 100644 index 000000000..5c344abba --- /dev/null +++ b/Associate-google-workspace-admin-s1.html @@ -0,0 +1,2845 @@ + + + + + + Associate Google Workspace Administrator 試験対策ガイド | Section 1 + + + + +
+ +
+
+ Associate Google Workspace Administrator · Section 1 · + 出題比率 約20% +

ユーザーアカウント・ドメイン・ディレクトリの管理

+
+ +

+ Section 1: ユーザーアカウント・ドメイン・ディレクトリの管理 +

+
+

+ 本ガイドは Google 公式の + Associate Google Workspace Administrator 認定ページ + および + 公式 Exam Guide (PDF) + の + Section 1: Managing user accounts, domains, and Directory(出題比率 + 約20%) + に完全準拠して構成しています。中級〜上級管理者を対象に、Admin console + の操作手順だけでなく「なぜそう設計するのか」という設計判断の根拠までを解説します。 +

+
+

この章の位置づけ

+

Exam Guide が定義する Section 1 は、以下の5つのタスクで構成されます。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
タスク内容本ガイドの章
1.1 + ユーザーライフサイクルの管理(移行・作成・プロビジョニング・SSO・同期・属性変更・削除/保留/復元/アーカイブ・所有権移転・ライセンス・パスワード) + 1.1
1.2組織部門(OU)の設計と作成1.2
1.3 + グループの管理(構造設計・配布リスト・Collaborative + Inbox・ダイナミックグループ・セキュリティグループ) + 1.3
1.4 + ドメインの管理(プライマリ/セカンダリドメインの追加と検証・ドメインエイリアス) + 1.4
1.5 + 建物とリソースの管理(建物/部屋の一括作成・リソース管理・予約権限・機能設定) + 1.5
+
+
+
+ +

1.1 ユーザーライフサイクルの管理

+

+ ユーザーライフサイクル管理とは、入社(プロビジョニング)から異動、退職(デプロビジョニング)までの一連のアカウント状態遷移を、安全かつ再現性高く運用する仕組みです。試験では「どのツールをどの規模・要件に使うか」という判断軸が繰り返し問われます。 +

+

1.1.1 移行戦略とツールの選定

+

+ 既存のメール基盤(Microsoft Exchange、他社IMAPサーバーなど)から Google Workspace + へ移行する際、組織の規模とデータ種別によって適切なツールが変わります。 +

+
+flowchart TD
+    Start["移行を計画する"] --> Q1{"移行対象は<br/>メール/カレンダー/連絡先のみか?"}
+    Q1 -- "Yes(個人/小規模チーム)" --> Q2{"移行元は?"}
+    Q2 -- "IMAP/Gmail/Workspace" --> GWMME["Google Workspace Migration<br/>for Microsoft Outlook (GWMME)"]
+    Q2 -- "Exchange/Outlook.com" --> DataMigration["データ移行サービス<br/>(Admin console内蔵)"]
+    Q1 -- "No(大規模組織全体)" --> Q3{"対象ユーザー数は?"}
+    Q3 -- "1,000人以下" --> DataMigration
+    Q3 -- "1,001人以上" --> GWMigrate["Google Workspace Migrate"]
+    GWMigrate --> Note["メール/カレンダー/連絡先に加え<br/>Drive・SharePoint等も移行対象に含められる"]
+
    +
  • + データ移行サービス(新しいセルフガイド型ツール):Admin + console + から直接実行できるシンプルな移行フロー。管理者向けの移行では、データソースの種類と組織の規模に応じて適切なドキュメントを選ぶことが推奨されている。 +
  • +
  • + GWMME(Google Workspace Migration for Microsoft Outlook):IMAP・Gmail・Google + Workspaceからの移行ではメールとラベルデータのみをコピーし、カレンダーの予定・カレンダーリソース・連絡先・Google + Driveのファイル・Google + Sitesなどのメール以外のコンテンツは対象に含まれない点に注意。 +
  • +
  • + Google Workspace Migrate:1,001ユーザー以上の大規模移行向けに設計されており、製品のインストールや構成が複雑なため利用が難しい場合がある。 +
  • +
+
+

+ 出典: + Google Workspace migration product matrix +

+
+

+ 1.1.2 手動でのユーザーアカウント作成 +

+

+ 小規模組織や例外的なアカウントでは、Admin console + から個別にユーザーを作成します。CSVファイルによる一括作成(複数ユーザーの追加・更新)も可能です。 +

+

ベストプラクティス:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目推奨事項
命名規則 + 一貫した命名規則(例:firstname.lastname)を早期に確立し、後からの変更コストを避ける +
アカウント共有の禁止 + 1人1アカウントを原則とし、複数人での共同利用アカウントは作らない(監査証跡・セキュリティの観点) +
初期OU配置 + 作成時点で適切なOUに配置する(後からの移動も可能だが、初期設計を明確にしておくと運用が楽になる) +
デフォルト言語・タイムゾーン + 新規ユーザーの既定言語・タイムゾーンを組織の主要拠点に合わせて設定しておく +
+
+

+ 1.1.3 プロビジョニング・デプロビジョニングの自動化 +

+

+ 大規模組織では、人事システムや外部IdPと連携した自動プロビジョニングが標準です。Google + Workspace + は多数のSaaSアプリに対して自動ユーザープロビジョニングの構成ガイドを提供しています(Slack、Salesforce、Box、Zendesk、AWS + など50以上のアプリ)。 +

+
+flowchart LR
+    HR["人事システム / IdP<br/>(Okta, Azure AD等)"] -->|"SCIM/自動プロビジョニング"| GWS["Google Workspace<br/>Directory"]
+    GWS -->|"属性同期"| Apps["連携先SaaSアプリ<br/>(Slack, Salesforce等)"]
+    HR -->|"退職イベント"| Deprov["デプロビジョニング<br/>(アカウント自動停止)"]
+    Deprov --> GWS
+

+ 自動プロビジョニングを監視するには、Admin console + でエラーを可視化する専用の画面が用意されています。設定後は必ず + 自動プロビジョニングエラーの表示 + 機能でエラーを定期的に確認する運用を組み込むことがベストプラクティスです。 +

+
+

+ 出典: + About automated user provisioning + / + View auto-provisioning errors +

+
+

+ 1.1.4 サードパーティIDプロバイダによるプロビジョニングと認可 +

+

+ 「プロビジョニング(アカウントの作成・属性同期)」と「認可(サインイン時の認証)」は別の関心事です。Okta、Microsoft + Entra ID(旧Azure AD)、Ping Identity + のような外部IdPを使う場合、一般的な構成は次の通りです。 +

+
    +
  1. + プロビジョニング:外部IdP側でユーザー・グループの正が管理され、SCIMまたはGoogle Cloud + Directory Sync(GCDS)/Directory Syncを通じてGoogle + Workspace側にミラーリングされる。 +
  2. +
  3. + 認可(認証):ユーザーがGoogleサービスにアクセスする際、SAML + SSOを通じて外部IdPで認証が行われ、Googleはこれを信頼する(Google = Service + Provider)。 +
  4. +
+

+ この「プロビジョニングは同期ツール、認証はSAML」という役割分担は、試験で頻出する設計上の区別です。 +

+

1.1.5 基本的なSAML SSOの設定

+

Google Workspace は SAML ベースの SSO を2方向でサポートします。

+
+sequenceDiagram
+    participant User as ユーザー
+    participant Google as Google (Service Provider)
+    participant IdP as サードパーティIdP
+
+    User->>Google: Googleアプリ(Gmail等)にアクセス
+    Google->>Google: SAML認証リクエストを生成しURLに埋め込む
+    Google-->>User: IdPのSSO URLへリダイレクト<br/>(RelayStateにアクセス先を保持)
+    User->>IdP: リダイレクトされSSOページへ到達
+    IdP->>IdP: ユーザーを認証
+    IdP-->>User: SAMLレスポンス(署名付き)をACS URLへPOST
+    User->>Google: SAMLレスポンスをAssertion Consumer Service (ACS) へ送信
+    Google->>Google: 署名を証明書で検証しRelayStateへリダイレクト
+    Google-->>User: 目的のGoogleアプリへアクセス許可
+

+ ユーザーがGmailやカレンダーなどのホスト型Googleアプリケーションにアクセスしようとすると、GoogleはSAML認証リクエストを生成し、これをエンコードしてパートナーのSSOサービスへのURLに埋め込む。RelayStateパラメータにはユーザーが本来アクセスしようとしていたGoogleアプリケーションのURLがエンコードされて含まれ、このRelayStateは変更・検査されることなくそのまま返送される、不透明な識別子として扱われる。 +

+

Admin console での設定手順(Google = Service Provider):

+
    +
  1. + セキュリティ > 認証 > SSO with third-party IdP + へ移動(Security Settings 管理者権限が必要)。 +
  2. +
  3. + 「Third-party SSO profiles」で + Add SAML profile をクリックし、プロファイル名を入力。 +
  4. +
  5. + IdP側から取得した IdP entity ID、Sign-in page URL(SSO URL)、Sign-out page URL を入力。 +
  6. +
  7. Change password URL にIdP側のパスワード変更URLを設定。
  8. +
  9. + IdPから発行された署名検証用証明書をアップロード(ローテーション用に最大2枚まで登録可能)。 +
  10. +
  11. + 保存後に生成される Entity ID と + ACS URL をコピーし、IdP側の設定に反映する。 +
  12. +
+
+

+ 出典: + Setting up SSO + / About SSO / + Technical overview of SAML-based SSO +

+
+

+ 反対に、Google Workspace アカウントを + Identity Provider + としてサードパーティアプリのSSOに使う場合は、アプリ > Web と モバイルアプリ > Add App > Add custom SAML + app + からカスタムSAMLアプリを登録し、Google IdPのメタデータ(SSO URL・Entity + ID・証明書)をサービスプロバイダ側に設定します。 +

+
+

+ 出典: + Set up your own custom SAML app +

+
+

+ 1.1.6 ファーストパーティ同期ツールのユースケース +

+

+ 「同期(Directory Sync系)」と「移行(GWMMEなど)」は別物です。GCDS(Google Cloud + Directory + Sync)はコンテンツ(メールメッセージ・カレンダーの予定・ファイルなど)を一切移行せず、LDAPサーバーの情報に合わせてGoogleのユーザー・グループ・共有連絡先を同期するためだけに使われる。 +

+

+ Google は現在、レガシーな + GCDS(オンプレミスにインストールするユーティリティ)と、新しい + Directory Sync(クラウドベースのベータ機能)の2つの同期ツールを提供しています。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
比較項目GCDSDirectory Sync
インストールオンプレミスソフトウェアのインストールが必要クラウドベースのソリューションでインストール不要
対応ディレクトリ + Active DirectoryやOpenLDAPを含む全てのLDAP準拠ディレクトリに対応 + + Microsoft Active Directory (AD) と Microsoft Azure Active + Directory (Azure AD) に対応 +
接続方式通常はLDAPサーバーと同一ネットワーク上に配置される + ADの場合はCloud VPNまたはCloud + Interconnectを使用してオンプレミスのLDAPサーバーにアクセスし、Azure + ADの場合は管理者のMicrosoft資格情報を使って接続する +
同期対象データ + 管理者を含むユーザー、グループ、カレンダーリソース、外部連絡先、パスワード + 非管理者ユーザーとグループのみ
複数ソース同期不可 + ADは複数ディレクトリからの同期に対応するが、Azure + ADは1つのディレクトリのみ対応 +
セットアップの複雑さ組織のニーズによっては非常に複雑になり得るGoogle Admin consoleを使ったシンプルなセットアップ
同期頻度 + 管理者が構成可能。自動化には別途スケジューリングソフトウェアが必要 + + フル同期は前回の同期完了から1時間後に開始し、この間隔は変更不可 +
トラブルシューティング複数サーバーからログファイルを集約する必要がある場合がある + Google Admin + consoleで一元的にレポートされ、フィルタ・検索・カスタムアラートの設定が可能 +
属性マッピング最大35のシステム属性とカスタム属性をマッピング可能 + 姓・名・メールアドレス・復旧用電話番号・復旧用メールアドレスをマッピング可能 +
OUマッピング指定したOUへ自動的にユーザーを配置ユーザーを指定したOUへマッピング可能
+
+
+flowchart TD
+    LDAP["オンプレミス LDAP /<br/>Active Directory サーバー"] -->|"一方向の読み取り専用同期<br/>(LDAPデータは変更されない)"| GCDS["GCDS<br/>(サーバー環境で実行)"]
+    GCDS -->|"ルールに基づき<br/>ユーザー/グループ/連絡先を比較"| List1["エクスポートされたリスト"]
+    GCDS -->|"Google Account の情報を<br/>取得し比較"| List2["Google側の現在のリスト"]
+    List1 --> Compare["差分を計算し<br/>Google Accountを更新"]
+    List2 --> Compare
+    Compare --> Report["同期完了後<br/>メールレポートを送信"]
+

+ GCDSの動作は、まずルールを設定してデータのリストをどのように生成するかを指定し、同期時にそのリストをLDAPサーバーからエクスポートし、GCDSがGoogleアカウントに接続して指定したユーザー・グループ・共有連絡先のリストを生成し、これらのリストを比較してGoogleアカウントをデータに一致するよう更新し、同期後に監視できるようメールレポートを受け取るという流れになる。 +

+

+ 試験のポイント:「LDAP準拠ディレクトリ全般(OpenLDAPを含む)からの同期」「35属性のカスタムマッピング」「パスワード同期」が必要な場合は + GCDS。「AD/Azure + ADのみで、クラウドネイティブかつセットアップの簡易性」を重視する場合は + Directory Sync + を選ぶ、という判断軸を押さえておきます。 +

+
+

+ 出典: + About Google Cloud Directory Sync + / + Compare Directory Sync with GCDS +

+
+

1.1.7 ユーザー属性の変更

+

+ Admin console または Directory API + を通じて、氏名・メールアドレス・パスワード・エイリアスなどの属性を変更できます。 +

+
    +
  • + 氏名の変更:プロフィール名の変更はDirectory上の表示に反映されるが、既存ファイルの共有権限やメール履歴には旧名義の記録が残る場合がある。 +
  • +
  • + メールアドレス(プライマリ)の変更:多くのGoogleアプリのデータ(Drive、カレンダーなど)に影響するため、変更前に利用者への周知が推奨される。 +
  • +
  • + エイリアス(alternate email address)の追加:1ユーザーに最大30個のメールエイリアスを追加可能。エイリアス宛のメールはプライマリの受信トレイに届く。 +
  • +
  • + カスタム属性:組織固有の情報(社員番号、コストセンターなど)をユーザープロフィールに追加し、検索やダイナミックグループのクエリ条件として利用できる。 +
  • +
+
+

+ 出典: + Overview: Changing a Directory user's name or email address + / + Add or delete an alternate email address (email alias) + / + Create custom attributes for user profiles +

+
+

1.1.8 削除・保留・復元・アーカイブ

+

+ ユーザーアカウントは以下の状態を遷移します。試験では「一時的な利用停止(保留)」「完全な削除」「ライセンスコストを抑えつつデータを保持するアーカイブ」の3つの使い分けが問われます。 +

+
+stateDiagram-v2
+    direction LR
+    [*] --> Active: アカウント作成
+    Active --> Suspended: 一時停止<br/>(不正利用調査・休職等)
+    Suspended --> Active: 保留解除
+    Active --> Archived: アーカイブ<br/>(ライセンスを維持しつつ低コスト化)
+    Archived --> Active: アーカイブ解除
+    Active --> Deleted: 削除
+    Suspended --> Deleted: 削除
+    Deleted --> Active: 復元<br/>(削除から20日以内)
+    Deleted --> [*]: 20日経過後は<br/>完全に削除され復元不可
+
    +
  • + 保留(Suspend):アカウントを一時的に無効化する。ログイン不可になるがデータ・ライセンスはそのまま保持される。不正が疑われるアカウントの緊急停止や、長期休職者の一時無効化に利用する。 +
  • +
  • + 削除(Delete):アカウントを組織から削除する。削除後20日間は管理者が復元可能な猶予期間があり、それを過ぎると完全に削除されデータは失われる。 +
  • +
  • + 復元(Restore):削除猶予期間内であれば、Admin + consoleからユーザーとそのデータ(Gmail、Drive、カレンダーなど)を復元できる。 +
  • +
  • + アーカイブ(Archive):退職者アカウントのデータを保持しつつ、通常ライセンスより低コストな + Archived User license + に切り替える機能。ログインはできないが、Vaultによる保持・eDiscoveryの対象にはなり続ける。 +
  • +
+

+ ベストプラクティス:退職者アカウントは即座に削除せず、まず保留にしてからデータ移転(下記1.1.9)とライセンス精算を行い、法的保持義務がある場合はアーカイブへ切り替える、という順序を踏むことでデータ損失リスクを避けられます。 +

+
+

+ 出典: + Delete or remove a user from your organization + / + Suspend a user temporarily + / + Restore a recently deleted user + / + Archive former employee accounts + / + Maintain data security after an employee leaves +

+
+

1.1.9 Driveデータの所有権移転

+

+ 退職・異動時には、個人のマイドライブ配下にあるファイル・フォルダの所有権を後任者やチームの共有ドライブへ移す必要があります。Admin + consoleの Transfer ownership + ツール、またはユーザー削除フロー内の「データ移行」オプションから一括移転が可能です。移転後、新しい所有者は元の共有権限を引き継いだ状態でファイルを管理できます。恒久的なコラボレーションが前提のファイルは、個人所有からそもそも + 共有ドライブ + へ移しておくことで、退職時の所有権移転作業自体を不要にできます。 +

+
+

+ 出典: + Transfer Drive files to a new owner as an admin +

+
+

1.1.10 ライセンス管理

+

+ ユーザーごとに異なるエディション(Business + Starter/Standard/Plus、Enterprise各種など)や追加プロダクト(Vault、Voice、Colab + Proなど)のライセンスを個別に割り当てられます。GCDSを使う場合は同期プロセスの一部としてライセンスの自動割り当て・削除も設定可能です。ライセンス管理のベストプラクティスとして、OU単位でデフォルトのライセンスセットを決めておき、例外的なライセンス(高コストなアドオン等)は個別ユーザー単位で付与する運用が推奨されます。 +

+
+

+ 出典: + Manage and assign licenses (GCDS) +

+
+

1.1.11 パスワード管理

+

+ 管理者はパスワードポリシー(最小文字数、複雑性、有効期限)を組織またはOU単位で強制でき、パスワードのリセット・強制変更・強度モニタリングを行えます。 +

+
    +
  • + パスワードリセット:Admin + consoleから個別ユーザーのパスワードを即座にリセット可能。次回ログイン時の強制変更も設定できる。 +
  • +
  • + 強制変更(Force password change):侵害の疑いがあるアカウントに対して、次回ログイン時にパスワード変更を要求する。 +
  • +
  • + パスワード強度の監視:Admin + consoleの「パスワード監査」レポートで、脆弱なパスワードや使い回されているパスワードを持つユーザーを一覧表示できる(パスワード自体は管理者にも表示されない)。 +
  • +
  • + リカバリー情報:スーパー管理者自身のパスワード復旧用に、復旧用メールアドレス・電話番号の設定が強く推奨される(管理者アカウントのロックアウトを防ぐため)。 +
  • +
+
+

+ 出典: + Enforce and monitor password requirements for users + / + Reset a user's password + / + Allow super administrators to recover their password +

+
+
+

1.2 組織部門(OU)の設計と作成

+

1.2.1 OUとドメイン・グループの違い

+

+ OU(Organizational + Unit)は「どのユーザー・デバイスに、どのサービス設定を適用するか」を制御するための階層コンテナです。試験で頻出する誤解を、公式FAQに基づき整理します。 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
質問回答
組織構造を必ず定義する必要があるか + いいえ。組織構造を定義しない場合、Admin + consoleで行う設定はすべてのユーザー・デバイスに等しく適用される +
OUはドメインと関連しているか + いいえ。ユーザーの組織部門はそのユーザーに利用可能なサービス・機能を決定し、ユーザーのドメインはアカウントのユーザー名・メールアドレスを決定する。OUはドメインをまたいで複数のユーザーを含むことができ、同一ドメイン内のユーザーも任意の数のOUに分散できる +
OUはアクセスグループ・構成グループと同じか + いいえ。OUはユーザー集合に対して利用可能なサービス・機能を決定するもので、アクセスグループはOU内または複数OUをまたいだユーザー集合に対してサービスをオンにするもの、構成グループはユーザー集合に対して設定をカスタマイズするもの +
OUは社内LDAP構造と一致させる必要があるか + いいえ。Admin + console内の組織構造はどのサービス・機能がユーザーに利用可能かのみを制御するものであり、LDAP構造に合わせることは任意。合わせたい場合はGoogle + Cloud Directory Syncツールを使って実現できる +
単一ユーザーだけに設定をカスタマイズできるか + はい。特定の1ユーザーだけにサービスアクセスをカスタマイズしたい場合は、そのユーザーのみを含むOUを作成すればよい +
特定OUに対してのみ操作できる管理者を作れるか + はい。User + managementロールを持つ管理者を割り当て、特定の組織部門内のユーザーに対してのみ操作を許可できる +
大規模組織でOU構造は速度に影響するか + はい。5万人以上のユーザーを追加する場合は、アカウント作成のパフォーマンスを高めるため組織構造をできるだけシンプルかつフラットに保つのがよく、後からより深い階層を追加することも可能 +
+
+

1.2.2 OU設計のベストプラクティス

+
+flowchart TD
+    Root["/(トップレベルOU)<br/>組織全体の既定ポリシー"] --> Dept1["/従業員"]
+    Root --> Dept2["/契約社員"]
+    Root --> Dept3["/サービスアカウント・共有端末"]
+    Dept1 --> Eng["/従業員/エンジニアリング"]
+    Dept1 --> Sales["/従業員/営業"]
+    Dept1 --> HR["/従業員/人事・法務"]
+    Eng --> EngContractor["/従業員/エンジニアリング/一時アクセス"]
+
    +
  • + ポリシーは上位、例外は下位に:全社共通ポリシーはトップレベルOUで設定し、部門固有の例外のみを子OUで上書きする。子OUは親の設定を継承し、必要な項目だけをオーバーライドする。 +
  • +
  • + 職務・アクセス要件で分割する:部署名そのものより「どのサービスアクセスが必要か」という軸で設計すると、組織変更(部署名の変更など)に強い構造になる。 +
  • +
  • + 階層は浅く保つ:深すぎるネストは管理・トラブルシューティングを複雑にする。特に大規模ユーザー追加時はパフォーマンスにも影響する。 +
  • +
  • + 命名規則を統一する:OUパスは一意な識別子であり、命名が曖昧だと誤設定のリスクが高まる。 +
  • +
  • + 定期的な棚卸し:異動・組織変更に応じてOU所属を定期的に見直す運用を組み込む。 +
  • +
  • + 単一ユーザー例外にはOUを使う:特定の1人にだけ異なるサービス設定を適用したい場合、アクセスグループより先にOUによる分離を検討する(公式FAQで明示的にサポートされる方法)。 +
  • +
+
+

+ 出典: + Organizational policies FAQ + / + How the organizational structure works +

+
+

1.2.3 OUの作成と管理

+
    +
  1. + ディレクトリ > 組織部門 + から対象の親OUにカーソルを合わせ、新しい組織部門を作成 + を選択。 +
  2. +
  3. OU名と説明を入力し、親OUを指定して作成する。
  4. +
  5. + ユーザーやデバイスは作成時、または後から + ユーザーを組織部門に移動 の操作でOUへ割り当てる。 +
  6. +
  7. + OUの移動・名称変更・削除を行う場合、ローカル設定(そのOUで個別に設定した項目)は保持されるが、継承設定(親から引き継いでいた項目)は新しい親OUの値に変わる点に注意する。 +
  8. +
+
+

+ 出典: + Add an organizational unit + / + Move users to an organizational unit + / + Move, rename, or delete an organizational unit +

+
+
+

1.3 グループの管理

+

1.3.1 グループ構造の設計

+

+ Google Groups + は「メーリングリスト」「Q&Aフォーラム/コミュニティフォーラム」「Collaborative + Inbox(共同トレイ)」「アクセスグループ」「セキュリティグループ」など複数の機能を1つの基盤(Groups + for + Business)で提供します。設計時は「このグループの主目的は何か」を先に決め、目的に応じたグループ種別・アクセス設定を選びます。 +

+
+flowchart TD
+    Start["グループを作りたい"] --> Q1{"主な目的は?"}
+    Q1 -- "全員への一括メール配信のみ" --> DL["配布リスト<br/>(投稿を管理者/特定メンバーに限定)"]
+    Q1 -- "support@/info@等の<br/>共有受信箱を運用" --> CI["Collaborative Inbox<br/>(会話の割り当て・ステータス管理)"]
+    Q1 -- "部署・属性の変化に応じて<br/>自動的にメンバーを増減したい" --> DG["ダイナミックグループ<br/>(クエリベースの自動メンバー管理)"]
+    Q1 -- "機密データやリソースへの<br/>アクセス制御に使う" --> SG["セキュリティグループ<br/>(Securityラベル付与)"]
+    Q1 -- "サービス設定を特定ユーザーに<br/>適用/オフにしたい" --> AG["アクセスグループ / 構成グループ"]
+

1.3.2 配布リストの作成と管理

+

+ 最も基本的なグループ形態で、グループ宛のメールをメンバー全員に配信します。作成は + ディレクトリ > グループ > グループを作成 + から行い、名前・メールアドレス・説明を設定した後、アクセス設定(誰が投稿できるか、外部からの投稿を許可するか等)を構成します。 +

+
+

+ 出典: + Create a group in your organization +

+
+

+ 1.3.3 Collaborative Inbox(共同トレイ)の作成と管理 +

+

+ Collaborative Inbox は Google Groups + を拡張し、チームで共有メールアドレス(support@、info@など)を運用するための機能です。標準の配布リストに対して以下の機能が追加されます。 +

+
    +
  • 会話をメンバーに 割り当て(assign) できる
  • +
  • + 会話に ステータス(未対応・対応中・完了・重複) を設定できる +
  • +
  • + 誰が現在どのメールに対応しているかを可視化する「衝突検知」に近い運用ができる +
  • +
+

+ 設定はグループの 管理 > 全般設定 から + Collaborative Inbox + のポスティング権限を有効化することで行います。既存の配布リストを後から + Collaborative Inbox に切り替えることも可能です。 +

+
+

+ 出典: + Make a group a Collaborative Inbox + / + Add features and manage conversations in Google Groups +

+
+

+ 1.3.4 ダイナミックグループの作成と管理 +

+

+ ダイナミックグループは、メンバーシップクエリの条件に一致するユーザーを + 自動的に + 追加・削除するグループです。部署異動や拠点変更が多い組織で、手動メンテナンスの負荷を大きく下げられます。 +

+

+ ダイナミックグループのメンバーシップは他のグループと異なり、メンバーを手動で追加することはできずメンバーを変更するにはメンバーシップクエリ自体を変更する必要があり、メンバーになれるのはユーザーのみでグループはメンバーシップ条件を満たせないためグループをダイナミックグループに追加することはできず、ダイナミックグループ自体も他のグループのメンバーにすることはできない。 +

+
+flowchart LR
+    Attr["ユーザープロフィール属性<br/>(部署・国コード・カスタム属性等)"] -->|"クエリ条件に合致"| Query["メンバーシップクエリ<br/>(例: user.organizations.exists(...))"]
+    Query -->|"自動追加"| DynGroup["ダイナミックグループ"]
+    Attr -->|"条件から外れる"| Query
+    Query -->|"自動削除"| Removed["メンバーから除外"]
+    DynGroup -->|"Securityラベル付与"| SecPolicy["ポリシー自動適用<br/>(構成グループ経由)"]
+

作成手順の要点:

+
    +
  1. + ディレクトリ > グループ から + ダイナミックグループを作成 + を選択(Groups管理者権限が必要)。 +
  2. +
  3. + 条件リスト(例:ユーザーの部署)と + 値(具体的な部署名)を選び、条件式(クエリ)を組み立てる。クエリの最大文字数は10,000文字。 +
  4. +
  5. + 複数条件は And(&&) または + Or(||) で結合可能。特定条件を除外する場合は Exclude + を使う。 +
  6. +
  7. + ゲストユーザーは外部コラボレーターであるためダイナミックグループには既定で含まれず、クエリには自動的に + is_guest_user == false の除外条件が付加される。 +
  8. +
  9. + プレビューでメンバー候補を確認してから作成。1組織あたり最大500個のダイナミックグループを作成できる(上限緩和は個別申請が必要)。 +
  10. +
+

+ ダイナミックセキュリティグループ:ダイナミックグループでポリシーを強制するには、まず条件を満たすユーザーのダイナミックグループを作成し、そのグループにSecurityラベルを追加し、構成グループの手順に従ってポリシーを作成し優先順位を選択するという3ステップで実現する。これにより、例えば「特定拠点に異動した瞬間に該当のセキュリティポリシーが自動適用される」といった運用が可能になります。 +

+
+

+ 出典: + Manage membership automatically with dynamic groups + / + Valid user fields for dynamic group queries +

+
+

+ 1.3.5 セキュリティグループの作成・管理・適用 +

+

+ セキュリティグループは、機密データやリソースへのアクセス制御を目的として設計された、より厳格なガバナンスを持つグループです。 +

+

+ グループにSecurityラベルを付与することでセキュリティグループになり、この操作は永続的でセキュリティ機能を追加するが元のグループの他の機能を削除するものではない。Securityラベルが付いたグループはGoogle + Admin console上で簡単にソートできる。 +

+

セキュリティグループを使うべき場面:

+
    +
  • + 外部または非セキュリティグループが特定のグループに参加するのを防ぎたいとき + —— セキュリティグループに参加できるのは同一組織内のセキュリティグループのみ +
  • +
  • + 親グループが許可するメンバーのみをグループに含めたいとき —— + セキュリティグループに参加するグループは、同等かそれ以上に制限的なメンバーシップ権限を持つ必要がある +
  • +
  • + グループにセキュリティポリシーを適用したいとき —— + ポリシーを適用するグループは全てセキュリティグループにすることが推奨される +
  • +
  • + 組織の全ユーザーをグループに自動追加するオプションを無効化したいとき —— + セキュリティグループのメンバーシップは、許可したユーザー・サービスアカウント・セキュリティグループのみに限定される +
  • +
  • + Groups ReaderまたはGroups + Editorロールを持つユーザーに、特定のグループのみへの権限を与えたいとき —— + セキュリティグループがある組織では、これらのロールの権限範囲を全グループ・セキュリティグループのみ・非セキュリティグループのみのいずれかに限定できる +
  • +
+
+

+ 注意: + 外部サービスプロバイダのセキュリティ慣行を検証できないため、非Googleアカウントをセキュリティグループに追加することはできない。 +

+
+

+ 設定手順:新規作成時は作成ウィザードで + Security + チェックボックスを選択する。既存グループを後からセキュリティグループにする場合は、グループ名 + → グループ情報 > ラベル から + Security + にチェックを入れて保存する(この操作は元に戻せない)。 +

+
+

+ 出典: + Control access to sensitive data with security groups +

+
+

1.3.6 グループ種別の比較まとめ

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
グループ種別メンバー管理方式主な用途ポリシー適用への適性
配布リスト手動メール一括配信低
Collaborative Inbox手動共有受信箱の運用(support@等)低〜中
ダイナミックグループクエリによる自動属性ベースの自動メンバー管理高(セキュリティラベル併用時)
セキュリティグループ手動 or ダイナミック(Securityラベル併用)機密データ・リソースへのアクセス制御最も高い(ポリシー適用の推奨形態)
アクセス/構成グループ手動 or ダイナミックサービスのオン/オフ・設定のカスタマイズ中〜高
+
+
+

1.4 ドメインの管理

+

+ 1.4.1 プライマリドメインとセカンダリドメインの追加・検証 +

+

+ Google Workspace アカウントには1つの + プライマリドメイン と、複数の + セカンダリドメイン を追加できます。組織のGoogle + WorkspaceまたはCloud Identity Premiumアカウントには最大600ドメインを追加できる。 +

+
+flowchart TD
+    Primary["プライマリドメイン<br/>(例: example.com)"] --> Secondary["セカンダリドメイン<br/>(例: example-branch.com)"]
+    Secondary -->|"独自のユーザーアカウントを<br/>作成できる"| NewUsers["user@example-branch.com<br/>として新規ユーザー作成"]
+    Primary --> Alias["ドメインエイリアス<br/>(例: example-alias.com)"]
+    Alias -->|"既存ユーザーに<br/>追加のメールアドレスを付与"| ExistingUsers["user@example.com が<br/>user@example-alias.com でも受信可能"]
+

+ ドメインを追加する際はセカンダリドメインまたはユーザーエイリアスドメインのいずれかとして追加し、いずれの場合もそのドメイン名を所有していることを確認・検証する必要がある。 +

+

+ セカンダリドメイン vs ドメインエイリアスの使い分け:追加するドメインが独自のユーザーセットを持つ場合はセカンダリドメインとして追加し、既存の全ユーザーに別ドメインでの代替メールアドレスを付与したいだけの場合はユーザーエイリアスドメインとして追加する。例えばsolarmora.comをexample.comのエイリアスとして追加すると、bob@example.comはbob@solarmora.comという別のメールアドレスも持つことになる。 +

+

ドメイン検証の手順(TXTレコード方式が一般的):

+
    +
  1. + アカウント > ドメイン > ドメインを管理 から + ドメインを追加 をクリックし、ドメイン種別(セカンダリ or + ユーザーエイリアス)を選択。 +
  2. +
  3. + Googleが提示するTXTレコードの値を、ドメインレジストラのDNS設定に追加する。 +
  4. +
  5. + DNSの伝播(通常は数分〜最大8時間程度)を待ち、Admin console側で + 確認 をクリックして検証を完了する。 +
  6. +
  7. + メール送受信を行う場合は、別途MXレコードの設定も必要になる(ドメイン所有権の検証とメールルーティングの設定は別の作業)。 +
  8. +
+
+

+ 出典: + Add a user alias domain or secondary domain + / + FAQ for multiple domains +

+
+

+ 制限事項:Admin + consoleではプライマリドメインに対してのみドメインエイリアスを直接追加でき、セカンダリドメインにドメインエイリアスを追加したい場合はDirectory + APIを使う必要がある。また、ドメインエイリアスから他の形態への移行はサポートされておらず、Googleは現時点でドメインエイリアスをマルチドメインアカウントへ変換することをサポートしていない。さらに全ドメインが共有できるグローバルなサービスURLは提供されないため、プライマリドメインと追加ドメインそれぞれに対してカスタムURL(例: + mail.primary_domain.com と mail.secondary_domain.com)を用意する必要がある。 +

+

+ プライマリドメインの変更:プライマリドメインを変更したい場合は、対象ドメインを先にセカンダリドメインとして追加・検証してから、プライマリドメインへ切り替えるという手順を踏む。切り替え可能になるまで検証完了後最大24時間待つ必要がある場合もある。切り替え後も旧ドメインをドメインエイリアスとして残しておけば、新旧両方のメールアドレスで受信を継続できます。 +

+
+

+ 出典: + Change your primary domain for Google Workspace + / + Limitations with multiple domains +

+
+

1.4.2 ドメインエイリアスの管理

+

+ ドメインエイリアスは全ユーザーに対してグローバルに適用され、特定ユーザーだけに限定することはできません。エイリアスはメール送受信のためのものであり、原則としてサインイン用のドメインとしては使えません(高度な設定を除く)。管理は + アカウント > ドメイン > ドメインを管理 > + ドメインエイリアスを追加 + から行い、TXTレコードによる検証を経て有効化されます。 +

+

ユースケース例:

+
    +
  • + 多言語・地域別サイトの統一的なメール受信(例:company.com と + company.co.jp を統合) +
  • +
  • + 企業合併・ブランド統合時に、旧ブランドのメールアドレス宛メールを新ドメインのメールボックスでシームレスに受信 +
  • +
  • マーケティング上の別ブランド名でのメール送受信の一元管理
  • +
+
+

+ 出典: + FAQ for multiple domains +

+
+
+

1.5 建物とリソースの管理

+

1.5.1 建物と部屋の一括作成

+

+ 会議室・機材などの予約可能リソースは、まず「建物(Building)」を定義してから、その配下に「リソース(部屋・備品等)」を作成する構造になっています。 +

+
+flowchart TD
+    Building["建物(Building)<br/>例: 本社ビル、大阪オフィス"] --> Floor1["フロア情報"]
+    Building --> Resource1["会議室リソース<br/>(Conference room)"]
+    Building --> Resource2["その他リソース<br/>(社用車・備品等)"]
+    Resource1 --> Feature1["機能(Features)<br/>例: モニター・ホワイトボード・車椅子対応"]
+    Resource2 --> Feature2["機能(Features)<br/>例: カーナビ搭載"]
+

+ 建物は + ディレクトリ > 建物とリソース > 概要 + から作成でき、多数の建物を一度に登録する場合はCSV一括アップロードが利用できます(建物一覧のフォーマットに従ったCSVをインポート)。リソースは会社全体または各ドメインごとに最大10,000個まで追加でき、追加後は数分で利用可能になるのが一般的だが、場合によっては全員のカレンダーに反映されるまで最大24時間かかることもある。 +

+
+

+ 出典: + Create buildings, features & Calendar resources +

+
+

1.5.2 新規リソースの作成・管理

+

+ リソース作成は + ディレクトリ > 建物とリソース > リソース管理 + から行います。作成時に指定する主な項目: +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目内容
リソースタイプ会議室(Conference room)、その他(社用車、備品等)
建物・フロア + 所属する建物とフロア(構造化リソースを使う場合、Calendarの自動室提案機能に活用される) +
収容人数会議室の場合、参加人数に応じた検索・自動提案に使われる
説明・機能ホワイトボード、モニター、ビデオ会議設備など
+
+

+ Calendarの + 自動ルーム提案 + 機能を活用するには、全ユーザーの主要な勤務地(work + location)を設定しておくこと、構造化フォーマットでリソースを登録しておくことの2点が推奨される。ユーザーに勤務地が設定されていない場合、Calendarはその人を提案対象として考慮せず、提案される部屋が小さすぎたり、そもそも建物内の部屋が提案されなかったりする可能性がある。また、構造化された情報を持つ部屋のみが自動ルーム提案の対象になる。 +

+
+

+ 出典: + Create buildings, features & Calendar resources + / + Set up Google Calendar room booking suggestions +

+
+

1.5.3 リソース予約権限の設定

+

リソースカレンダーの共有範囲・自動承認の可否を管理者が設定します。

+
    +
  • + 既定の共有範囲:組織内の全員に共有し、競合しない予約は自動承認(Auto-accept)にするのが一般的な既定値。 +
  • +
  • + 限定共有:特定の部署やグループのみが予約できるようにカレンダー共有設定を制限することも可能。 +
  • +
  • + 承認制(Approve/Deny):役員会議室など重要なリソースについては、予約リクエストを管理者や特定の承認者が個別に承認・却下する運用に切り替えられる。 +
  • +
  • + 予約権限の委任:カレンダー・リソースへのアクセスを他のユーザーへ委任し、代理予約を可能にする。 +
  • +
+

+ 権限管理のベストプラクティス:建物とリソース(Buildings and + Resources)の管理権限は、信頼できる施設管理担当者やIT担当者にのみ付与することが重要である。大規模組織では大量の部屋を管理するために、CSV一括アップロードに加えてAdmin + SDK Directory APIの + resources.calendars.insert + を使ったスクリプトによる登録・レポーティングも選択肢になります。 +

+
+

+ 出典: + Approve or deny Calendar room & resource bookings + / + Share room and resource calendars +

+
+

+ 1.5.4 リソースの詳細機能(Features)の作成 +

+

+ 部屋やその他のリソースにどのような設備・特徴が備わっているかをユーザーに知らせたい場合、Admin + consoleを使ってその機能(Feature)を追加できる。例えばどの社用車にナビゲーションシステムが搭載されているかを知らせたい場合などに利用する。機能の追加はAdmin + consoleまたはAPIを使う必要があり、CSVファイルへの機能詳細のアップロードによる追加はできない。機能(Feature)は、会社全体または各ドメインごとに最大100個まで作成できる。 +

+

作成手順:

+
    +
  1. + ディレクトリ > 建物とリソース > 概要 の + リソース管理 セクションを開く。 +
  2. +
  3. + 管理 > リソースの機能を管理 から + 機能を追加 をクリック。 +
  4. +
  5. + 機能名(例:「ホワイトボードあり」「車椅子対応」「収容人数20名以上向け設備」)を入力して保存。 +
  6. +
  7. + 作成した機能は、各リソースの編集画面から個別に紐づける。ユーザーはリソース検索時にこれらの機能で絞り込める。 +
  8. +
+
+

+ 出典: + Create buildings, features & Calendar resources +

+
+
+

まとめ:実装チェックリスト

+

Section 1 の内容を実務導入する際に確認すべき項目です。

+
+
+ 進捗: + 0 / + 0 完了 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+

参考文献

+
+ + + + + + +
+ +
+ 本ガイドは Google 公式の認定ページおよび Exam Guide PDF、Google Workspace + 管理者ヘルプセンターの一次情報に基づき作成されています。各セクション末尾および参考文献に出典URLを明記しています。 +
+
+
+ + + + diff --git a/Gcp-challenge-lab-storage-compute-nginx.html b/Gcp-challenge-lab-storage-compute-nginx.html new file mode 100644 index 000000000..af2abc78e --- /dev/null +++ b/Gcp-challenge-lab-storage-compute-nginx.html @@ -0,0 +1,1831 @@ + + + + + + + Google Cloud Challenge Lab 攻略ガイド | Cloud Storage / Compute Engine / NGINX + + + + + + +
+ + +
+
+
+ Best practice guide +
+

Google Cloud Challenge Lab 攻略ガイド

+

+ Cloud Storage バケット / Compute Engine + 永続ディスク / NGINX Web + サーバー構築 +

+ +
+ +

+ Challenge Lab + は「手順を丸暗記する」ものではなく「学んだスキルを自力で組み合わせる」ことが目的の演習です。本ガイドは各タスクの実施手順だけでなく、なぜその設定が正しいのか(ベストプラクティスの根拠)を公式ドキュメントへのリンク付きで解説します。 +

+
+
+ +
+

この記事の使い方

+

+ Challenge Lab + の概要にある通り、このラボには新しいコンセプトの説明はなく、既存スキルの応用が求められます。そのため本ガイドでは、次の順で構成しています。 +

+
    +
  1. 各タスクの要件を表で整理
  2. +
  3. + Console(GUI)と gcloud CLI の両方の手順 +
  4. +
  5. + 各設定値がなぜベストプラクティスなのかという根拠と公式ソース + URL +
  6. +
  7. Mermaid によるフローチャート/シーケンス図
  8. +
  9. 詰まりやすいポイントのトラブルシューティング表
  10. +
+

+ PROJECT_ID / Region / + Zone + はラボ開始時に画面左側のパネルに表示される実際の値に読み替えてください(このラボでは + Region/Zone は固定値ではなく起動ごとに割り当てられます)。 +

+
+ +
+

シナリオと要件の整理

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
タスク作成するリソース主要な要件
Task 1Cloud Storage バケット + 名前: PROJECT_ID-bucket / ロケーション: US + マルチリージョン +
Task 2Compute Engine VM + 永続ディスク + VM 名: my-instance(E2 / + e2-medium)、ディスク名: + mydisk(200GB)をアタッチ +
Task 3NGINX Web サーバー + my-instance に SSH 接続し、OS 更新後に NGINX + をインストール・起動確認 +
+
+

+ すべてのリソースは、特に指示がない限り指定された Region / + Zone に作成します。 +

+
+ +
+

全体アーキテクチャ

+

+ 3つのタスクを完了すると、以下のような構成になります。Cloud Storage + バケットはアプリケーションの実行系(VM)とは独立したストレージですが、チームがビルド成果物や起動スクリプトを置く場所として同じプロジェクト内に存在します。 +

+
+ diagram loading... +
+
    +
  • + Cloud Storage + はオブジェクトストレージ(ファイル置き場)で、VM + のディスクとは別物です。ビルド成果物や起動スクリプトの保管に向いています。 +
  • +
  • + Compute Engine の永続ディスク(Persistent Disk) は VM + から独立したブロックストレージで、VM + を削除してもディスクだけ残すことができます。 +
  • +
  • + NGINX は VM の中で動作する Web + サーバーソフトウェアで、ポート + 80(HTTP)でリクエストを待ち受けます。外部からアクセスするには + Firewall ルールで tcp:80 を許可する必要があります。 +
  • +
+
+ +
+

Task 1: Cloud Storage バケットの作成

+ +

1.1 要件

+
+ + + + + + + + + + + + + + + + + + + + + +
項目値
バケット名 + PROJECT_ID-bucket(PROJECT_ID + は自分のプロジェクト ID に置換) +
ロケーションタイプMulti-region
ロケーションUS
+
+ +

1.2 手順(Console)

+
+ diagram loading... +
+ +

1.3 手順(gcloud CLI)

+

+ Cloud Shell から実行する場合は以下のコマンドで同等のバケットを作成できます。 +

+
+
+ bash +
+
+
+ + +

1.4 なぜこの設定がベストプラクティスなのか

+
    +
  • + バケット名にプロジェクト ID を含める: Cloud Storage + のバケット名は Google Cloud 全体で一意である必要があります。プロジェクト + ID + をプレフィックスにすることで命名衝突を避けられます。この命名規約はラボの要件でもあり、実運用でも一般的なパターンです。 +
  • +
  • + US マルチリージョンを選ぶ理由: + マルチリージョンは複数のリージョンにまたがってデータを複製するため、単一リージョンより可用性・耐久性が高く、地理的に分散したユーザーへの配信レイテンシも平準化されます。トレードオフとしてリージョン単体構成よりストレージ単価がやや高くなります。今回のように「まずは汎用のファイル置き場を作る」用途では、コストよりも可用性を優先するデフォルトの + US マルチリージョンが妥当な選択です。 +
  • +
  • + Uniform bucket-level access(デフォルト): + オブジェクト単位の ACL ではなく IAM + ポリシーでバケット全体のアクセス制御を統一でき、権限管理がシンプルになります。 +
  • +
  • + 最小権限の原則: バケット作成には + roles/storage.admin + などバケット作成権限を持つロールが必要ですが、プロジェクト全体の Owner + 権限を都度使うのではなく、必要な権限のみを持つロールを利用するのが望ましいプラクティスです。 +
  • +
+ + +
+ +
+

+ Task 2: Compute Engine VM + の作成と永続ディスクの作成・アタッチ +

+ +

2.1 要件

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Instance namemy-instance
SeriesE2
Machine typee2-medium
Boot disk typeNew balanced persistent disk
Boot disk size10 GB
Boot disk imageラボ開始時に指定されたイメージ
FirewallAllow HTTP traffic を有効化
追加ディスクmydisk(200GB)を作成しアタッチ
+
+ +

2.2 VM 作成の手順(Console)

+
+ diagram loading... +
+ +

2.3 VM 作成の手順(gcloud CLI)

+
+
+ bash +
+
+
+ +

+ --tags=http-server を付けることで、後述する「Allow HTTP + traffic」チェックボックスと同じ動作(default-allow-http + ファイアウォールルールの対象になる)を CLI からも再現できます。 +

+ +

2.4 永続ディスクの作成とアタッチ(Console)

+
+ diagram loading... +
+ +

Console で作成する代わりに、CLI では以下の2コマンドで完結します。

+
+
+ bash +
+
+
+ + +

2.5 なぜこの設定がベストプラクティスなのか

+
    +
  • + ディスクと VM は同じ Zone に作る: Persistent Disk + はゾーンリソースであり、作成されたゾーン内でのみ VM + にアタッチできます。異なる Zone + のディスクは直接アタッチできないため、mydisk は必ず + my-instance と同じ Zone に作成します。 +
  • +
  • + --device-name を明示的に指定する: OS + 内でのデバイス名(/dev/sdb + など)は再起動のたびに変わる可能性がありますが、device-name + を指定しておくと + /dev/disk/by-id/google-mydisk + という安定したシンボリックリンクが作られ、スクリプトや + /etc/fstab から確実にディスクを参照できます。 +
  • +
  • + balanced persistent disk をブートディスクに選ぶ理由: + pd-balanced + はコストとパフォーマンスのバランスに優れた汎用タイプで、Console + でブートディスクを作成する際のデフォルトでもあります。要件で明示的に「New + balanced persistent disk」と指定されているのもこのためです。 +
  • +
  • + (発展)アタッチ後はフォーマット・マウントが必要: + 本ラボの採点基準はディスクの作成とアタッチまでですが、実運用でこの新規ディスクにデータを書き込む場合は、SSH + 接続後に mkfs.ext4 でフォーマットし、/etc/fstab + に UUID + ベースでエントリを追加しておくと、再起動後も自動的にマウントされ、デバイス名の変動に影響されません。 +
  • +
+ + +
+ +
+

Task 3: NGINX Web サーバーのインストール

+ +

3.1 要件

+
    +
  1. my-instance に SSH 接続する
  2. +
  3. OS を更新する(Update the OS)
  4. +
  5. NGINX をインストールする
  6. +
  7. NGINX が起動していることを確認する
  8. +
+ +

3.2 手順とベストプラクティスの解説

+
+ diagram loading... +
+ +
+
+ bash +
+
+
+ + +

+ systemctl status nginx の出力に + Active: active (running) + と表示されていれば起動確認は完了です。加えて、再起動後も自動起動するように有効化しておくと安心です。 +

+ +
+
+ bash +
+
+
+ + +

3.3 なぜこの手順がベストプラクティスなのか

+
    +
  • + インストール前に apt-get update を実行する理由: + パッケージインデックスが古いままだと、依存パッケージのバージョン不整合やダウンロード失敗の原因になります。ラボの「Update + the OS」という指示は、このパッケージインデックス更新(および必要に応じた + apt-get upgrade)を意味します。 +
  • +
  • + ディストリビューション標準リポジトリからのインストールで十分: 本ラボの目的は「動作する Web + サーバーを構築できること」の確認であるため、Debian/Ubuntu + 標準リポジトリの nginx パッケージ(apt-get install nginx)で要件を満たせます。最新の mainline 版が必要な場合は、公式 + nginx.org の APT + リポジトリを追加する方法もありますが、その分セットアップ手順が増えます。 +
  • +
  • + systemctl enable で自動起動を有効化する: + VM が再起動した際に手動で NGINX を起動し直す必要がなくなり、Web + サーバーとしての可用性が向上します。 +
  • +
  • + Firewall との関係: NGINX 自体はポート 80 + で待ち受けますが、Task 2 で「Allow HTTP + traffic」を有効化していないと外部からアクセスできません。これは VM に + http-server タグが付与され、default-allow-http + という名前のファイアウォールルール(tcp:80、送信元 + 0.0.0.0/0)の対象になる、という仕組みです。ラボでは学習目的のためこの全世界許可のルールで問題ありませんが、本番環境では送信元 + IP + 範囲を絞る、あるいはロードバランサ配下に置くといった追加の制御を検討するのがベストプラクティスです。 +
  • +
+ + +
+ +
+

Web アプリケーションのテスト

+
+ diagram loading... +
+

+ External IP をコピーして + http://EXTERNAL_IP/ + の形式で新しいタブに貼り付けても同様に確認できます。 +

+
+ +
+

トラブルシューティング

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状想定される原因対処
ブラウザで接続できない(タイムアウト)Firewall で HTTP が許可されていない + VM の詳細画面でネットワークタグに + http-server + が付いているか確認。付いていなければ Edit から追加、または + default-allow-http ルールの存在を確認する +
nginx: command not found + インストールが完了していない、または別パッケージ名でインストールしようとした + + sudo apt-get install -y nginx + を再実行し、途中でエラーが出ていないかログを確認する +
+ systemctl status nginx が + inactive (dead) + インストール直後に自動起動していない場合がある + sudo systemctl start nginx で起動し、sudo systemctl enable nginx + で自動起動を設定する +
+ バケット作成時に Bucket name already in use + バケット名がグローバルで既に使用されている + PROJECT_ID + を正しく含めているか確認する(プロジェクト ID + はグローバルに一意なので通常は衝突しない) +
ディスクをアタッチできないディスクと VM の Zone が異なる + gcloud compute disks describe mydisk --zone=ZONE + で Zone を確認し、VM と同じ Zone にディスクを作り直す +
Check my progress が失敗するリソース名・設定値がラボの要件と完全一致していない + リソース名(my-instance / mydisk / + PROJECT_ID-bucket)や Region/Zone + の綴りを再確認する +
+
+
+ +
+

まとめ: 全体フロー

+
+ diagram loading... +
+

+ 3つのタスクはほぼ独立していますが、Task 3(NGINX インストール)は Task 2 で + my-instance が作成済みであることが前提です。採点は各タスクの + Check my progress + ボタンでこまめに確認しながら進めると、途中の設定ミスに早く気づけます。 +

+
+ +
+

参考文献・引用ソース一覧

+
+
+ Google Cloud 公式 +

Create a bucket

+

バケット作成手順、デフォルト設定、必要な IAM ロール

+ cloud.google.com/storage/docs/creating-buckets +
+
+ Google Cloud 公式 +

Bucket locations

+

Region / Dual-region / Multi-region の違いと選定基準

+ cloud.google.com/storage/docs/locations +
+
+ Google Cloud 公式 +

Create a Linux VM instance

+

+ Console での VM 作成手順、Allow HTTP traffic チェックボックスの効果 +

+ cloud.google.com/compute/docs/create-linux-vm-instance +
+
+ Google Cloud 公式 +

Create and start a Compute Engine instance

+

+ VM 作成に必要な IAM + ロール(roles/compute.instanceAdmin.v1)と権限の概要 +

+ cloud.google.com/compute/docs/instances/create-start-instance +
+
+ Google Cloud 公式 +

Create a new Persistent Disk volume

+

永続ディスク作成手順とディスクタイプ一覧(pd-balanced 等)

+ cloud.google.com/compute/docs/disks/add-persistent-disk +
+
+ Google Cloud 公式 +

Attach a non-boot disk to a VM

+

attach-disk コマンドと device-name オプションの解説

+ cloud.google.com/compute/docs/disks/attach-disks +
+
+ Google Cloud 公式 +

Format and mount a non-boot disk on a Linux VM

+

+ フォーマット手順と UUID ベースの /etc/fstab + 設定(発展的ベストプラクティス) +

+ cloud.google.com/compute/docs/disks/format-mount-disk-linux +
+
+ Google Cloud 公式 +

Add network tags

+

ネットワークタグとファイアウォールルールの紐付けの仕組み

+ cloud.google.com/vpc/docs/add-remove-network-tags +
+
+ Google Cloud 公式 +

Use VPC firewall rules

+

ファイアウォールルールの基本概念とコマンド

+ cloud.google.com/firewall/docs/using-firewalls +
+
+ nginx 公式(F5/NGINX, Inc.) +

Installing NGINX Open Source

+

Debian/Ubuntu 向け NGINX インストール手順

+ docs.nginx.com/.../installing-nginx-open-source +
+
+ nginx.org 公式サイト +

nginx: Linux packages

+

nginx.org 提供パッケージによるインストール手順

+ nginx.org/en/linux_packages.html +
+
+ Google Cloud Skills Boost +

対象 Challenge Lab

+

本ガイドが解説しているラボ本体

+ skills.google/course_templates/754/labs/597890 +
+
+
+ +
+

Cloud Storage / Compute Engine / NGINX — best practice walkthrough

+

各セクションの根拠はすべて公式ドキュメントにリンクしています

+
+
+
+ + + + + + diff --git a/Gcp-challenge-lab-storage-compute-nginx.md b/Gcp-challenge-lab-storage-compute-nginx.md new file mode 100644 index 000000000..cd0d8e20a --- /dev/null +++ b/Gcp-challenge-lab-storage-compute-nginx.md @@ -0,0 +1,402 @@ +# Google Cloud Challenge Lab 攻略ガイド +## Cloud Storage バケット / Compute Engine + 永続ディスク / NGINX Web サーバー構築 + +> 対象 Lab: *Build and Operate Infrastructure with Compute Engine and Cloud Storage*(Challenge Lab) +> Lab URL: https://www.skills.google/course_templates/754/labs/597890 +> 対象読者: Google Cloud 初学者〜初級 Cloud Architect +> 本ガイドの立ち位置: Challenge Lab は「手順を丸暗記する」ものではなく「学んだスキルを自力で組み合わせる」ことが目的の演習です。本ガイドは各タスクの **実施手順** だけでなく、**なぜその設定が正しいのか(ベストプラクティスの根拠)** を公式ドキュメントへのリンク付きで解説します。 + +--- + +## この記事の使い方 + +Challenge Lab の概要にある通り、このラボには新しいコンセプトの説明はなく、既存スキルの応用が求められます。そのため本ガイドでは、 + +1. 各タスクの **要件を表で整理** +2. Console(GUI)と `gcloud` CLI の **両方の手順** +3. 各設定値が **なぜベストプラクティスなのか** という根拠と公式ソース URL +4. Mermaid によるフローチャート/シーケンス図 +5. 詰まりやすいポイントの **トラブルシューティング表** + +の順で構成しています。`PROJECT_ID` / `Region` / `Zone` はラボ開始時に画面左側のパネルに表示される実際の値に読み替えてください(このラボでは Region/Zone は固定値ではなく起動ごとに割り当てられます)。 + +--- + +## シナリオと要件の整理 + +| タスク | 作成するリソース | 主要な要件 | +|---|---|---| +| Task 1 | Cloud Storage バケット | 名前: `PROJECT_ID-bucket` / ロケーション: US マルチリージョン | +| Task 2 | Compute Engine VM + 永続ディスク | VM 名: `my-instance`(E2 / e2-medium)、ディスク名: `mydisk`(200GB)をアタッチ | +| Task 3 | NGINX Web サーバー | `my-instance` に SSH 接続し、OS 更新後に NGINX をインストール・起動確認 | + +すべてのリソースは、特に指示がない限り指定された **Region** / **Zone** に作成します。 + +--- + +## 全体アーキテクチャ + +3つのタスクを完了すると、以下のような構成になります。Cloud Storage バケットはアプリケーションの実行系(VM)とは独立したストレージですが、チームがビルド成果物や起動スクリプトを置く場所として同じプロジェクト内に存在します。 + +```mermaid +flowchart TB + User["User Browser"] + + subgraph GCP["Google Cloud Project (PROJECT_ID)"] + subgraph StorageBlock["Cloud Storage"] + Bucket["Bucket: PROJECT_ID-bucket (US multi-region)"] + end + + subgraph ComputeBlock["Compute Engine (Region / Zone)"] + VM["VM: my-instance (E2 / e2-medium)"] + BootDisk["Boot disk: balanced PD, 10GB"] + DataDisk["Persistent disk: mydisk (200GB)"] + Nginx["NGINX (port 80)"] + + VM --- BootDisk + VM -- "attached as data disk" --- DataDisk + VM --> Nginx + end + + FW["Firewall rule: default-allow-http (tcp:80, tag http-server)"] + end + + User -- "HTTP GET http://EXTERNAL_IP/" --> FW + FW --> Nginx + Nginx -- "Welcome to nginx!" --> User +``` + +**ポイント(初学者向け)** + +- **Cloud Storage** はオブジェクトストレージ(ファイル置き場)で、VM のディスクとは別物です。ビルド成果物や起動スクリプトの保管に向いています。 +- **Compute Engine の永続ディスク(Persistent Disk)** は VM から独立したブロックストレージで、VM を削除してもディスクだけ残すことができます。 +- **NGINX** は VM の中で動作する Web サーバーソフトウェアで、ポート 80(HTTP)でリクエストを待ち受けます。外部からアクセスするには **Firewall ルール** で tcp:80 を許可する必要があります。 + +--- + +## Task 1: Cloud Storage バケットの作成 + +### 1.1 要件 + +| 項目 | 値 | +|---|---| +| バケット名 | `PROJECT_ID-bucket`(`PROJECT_ID` は自分のプロジェクト ID に置換) | +| ロケーションタイプ | Multi-region | +| ロケーション | US | + +### 1.2 手順(Console) + +```mermaid +flowchart TD + A["Navigation menu > Cloud Storage > Buckets"] --> B["Create bucket をクリック"] + B --> C["Name your bucket: PROJECT_ID-bucket と入力"] + C --> D["Choose where to store your data: Multi-region"] + D --> E["Location: US を選択"] + E --> F["Storage class: Standard のままにする(デフォルト)"] + F --> G["Access control: Uniform のままにする(デフォルト)"] + G --> H["Protection tools: デフォルトのまま"] + H --> I["Create をクリック"] + I --> J["Check my progress で検証"] +``` + +### 1.3 手順(gcloud CLI) + +Cloud Shell から実行する場合は以下のコマンドで同等のバケットを作成できます。 + +```bash +# 現在のプロジェクト ID を変数に格納 +export PROJECT_ID=$(gcloud config get-value project) + +# US マルチリージョンにバケットを作成 +gcloud storage buckets create gs://${PROJECT_ID}-bucket \ + --location=US \ + --default-storage-class=STANDARD +``` + +### 1.4 なぜこの設定がベストプラクティスなのか + +- **バケット名にプロジェクト ID を含める**: Cloud Storage のバケット名は Google Cloud 全体で一意である必要があります。プロジェクト ID をプレフィックスにすることで命名衝突を避けられます。この命名規約はラボの要件でもあり、実運用でも一般的なパターンです。 +- **US マルチリージョンを選ぶ理由**: マルチリージョンは複数のリージョンにまたがってデータを複製するため、単一リージョンより **可用性・耐久性が高く**、地理的に分散したユーザーへの配信レイテンシも平準化されます。トレードオフとしてリージョン単体構成よりストレージ単価がやや高くなります。今回のように「まずは汎用のファイル置き場を作る」用途では、コストよりも可用性を優先するデフォルトの US マルチリージョンが妥当な選択です。 +- **Uniform bucket-level access(デフォルト)**: オブジェクト単位の ACL ではなく IAM ポリシーでバケット全体のアクセス制御を統一でき、権限管理がシンプルになります。 +- **最小権限の原則**: バケット作成には `roles/storage.admin` などバケット作成権限を持つロールが必要ですが、プロジェクト全体の Owner 権限を都度使うのではなく、必要な権限のみを持つロールを利用するのが望ましいプラクティスです。 + +**参考ソース** +- Google Cloud 公式: [Create a bucket](https://cloud.google.com/storage/docs/creating-buckets) — バケット作成手順と必要な IAM ロール +- Google Cloud 公式: [Bucket locations](https://cloud.google.com/storage/docs/locations) — マルチリージョン/デュアルリージョン/リージョンの違いと使い分け + +--- + +## Task 2: Compute Engine VM の作成と永続ディスクの作成・アタッチ + +### 2.1 要件 + +| Property | Value | +|---|---| +| Instance name | `my-instance` | +| Series | E2 | +| Machine type | e2-medium | +| Boot disk type | New balanced persistent disk | +| Boot disk size | 10 GB | +| Boot disk image | ラボ開始時に指定されたイメージ | +| Firewall | Allow HTTP traffic を有効化 | +| 追加ディスク | `mydisk`(200GB)を作成しアタッチ | + +### 2.2 VM 作成の手順(Console) + +```mermaid +flowchart TD + A["Compute Engine > VM instances"] --> B["Create instance をクリック"] + B --> C["Name: my-instance"] + C --> D["Region / Zone: ラボ指定の値を選択"] + D --> E["Machine configuration > Series: E2"] + E --> F["Machine type: e2-medium"] + F --> G["OS and storage > Change"] + G --> H["Boot disk type: Balanced persistent disk, Size: 10GB"] + H --> I["指定されたブートディスクイメージを選択"] + I --> J["Firewall セクションで Allow HTTP traffic をチェック"] + J --> K["Create をクリックし起動を待つ"] +``` + +### 2.3 VM 作成の手順(gcloud CLI) + +```bash +export ZONE= +export REGION= +export IMAGE_FAMILY=<ラボ指定のイメージファミリー> +export IMAGE_PROJECT=<ラボ指定のイメージプロジェクト> + +gcloud compute instances create my-instance \ + --zone=${ZONE} \ + --machine-type=e2-medium \ + --image-family=${IMAGE_FAMILY} \ + --image-project=${IMAGE_PROJECT} \ + --boot-disk-type=pd-balanced \ + --boot-disk-size=10GB \ + --tags=http-server +``` + +`--tags=http-server` を付けることで、後述する「Allow HTTP traffic」チェックボックスと同じ動作(`default-allow-http` ファイアウォールルールの対象になる)を CLI からも再現できます。 + +### 2.4 永続ディスクの作成とアタッチ + +```mermaid +flowchart TD + A["Compute Engine > Disks"] --> B["Create disk をクリック"] + B --> C["Name: mydisk"] + C --> D["Zone: my-instance と同じ Zone を選択"] + D --> E["Disk source type: Blank disk"] + E --> F["Size: 200GB"] + F --> G["Create をクリック"] + G --> H["VM instances > my-instance を開く"] + H --> I["Edit をクリック"] + I --> J["Additional disks > Attach existing disk"] + J --> K["mydisk を選択して Save"] + K --> L["Check my progress で検証"] +``` + +Console で作成する代わりに、CLI では以下の2コマンドで完結します。 + +```bash +# 200GB の永続ディスクを、VM と同じ Zone に作成 +gcloud compute disks create mydisk \ + --zone=${ZONE} \ + --size=200GB \ + --type=pd-balanced + +# 作成したディスクを my-instance にアタッチ +gcloud compute instances attach-disk my-instance \ + --zone=${ZONE} \ + --disk=mydisk \ + --device-name=mydisk +``` + +### 2.5 なぜこの設定がベストプラクティスなのか + +- **ディスクと VM は同じ Zone に作る**: Persistent Disk はゾーンリソースであり、作成されたゾーン内でのみ VM にアタッチできます。異なる Zone のディスクは直接アタッチできないため、`mydisk` は必ず `my-instance` と同じ Zone に作成します。 +- **`--device-name` を明示的に指定する**: OS 内でのデバイス名(`/dev/sdb` など)は再起動のたびに変わる可能性がありますが、`device-name` を指定しておくと `/dev/disk/by-id/google-mydisk` という安定したシンボリックリンクが作られ、スクリプトや `/etc/fstab` から確実にディスクを参照できます。 +- **balanced persistent disk をブートディスクに選ぶ理由**: `pd-balanced` はコストとパフォーマンスのバランスに優れた汎用タイプで、Console でブートディスクを作成する際のデフォルトでもあります。要件で明示的に「New balanced persistent disk」と指定されているのもこのためです。 +- **(発展)アタッチ後はフォーマット・マウントが必要**: 本ラボの採点基準はディスクの作成とアタッチまでですが、実運用でこの新規ディスクにデータを書き込む場合は、SSH 接続後に `mkfs.ext4` でフォーマットし、`/etc/fstab` に **UUID ベース** でエントリを追加しておくと、再起動後も自動的にマウントされ、デバイス名の変動に影響されません。 + +**参考ソース** +- Google Cloud 公式: [Create a Linux VM instance](https://cloud.google.com/compute/docs/create-linux-vm-instance) — VM 作成手順と Allow HTTP traffic の設定箇所 +- Google Cloud 公式: [Create a new Persistent Disk volume](https://cloud.google.com/compute/docs/disks/add-persistent-disk) — ディスク作成手順、ディスクタイプ一覧(pd-balanced / pd-ssd / pd-standard / pd-extreme) +- Google Cloud 公式: [Attach a non-boot disk to a VM](https://cloud.google.com/compute/docs/disks/attach-disks) — `attach-disk` コマンドと `device-name` の役割 +- Google Cloud 公式: [Format and mount a non-boot disk on a Linux VM](https://cloud.google.com/compute/docs/disks/format-mount-disk-linux) — フォーマット・UUID ベースの `/etc/fstab` 設定(発展的ベストプラクティス) + +--- + +## Task 3: NGINX Web サーバーのインストール + +### 3.1 要件 + +1. `my-instance` に SSH 接続する +2. OS を更新する(Update the OS) +3. NGINX をインストールする +4. NGINX が起動していることを確認する + +### 3.2 手順とベストプラクティスの解説 + +```mermaid +sequenceDiagram + participant U as User + participant C as Cloud Console + participant V as VM my-instance + + U->>C: VM instances 一覧で SSH ボタンをクリック + C->>V: ブラウザ内 SSH セッションを確立 + U->>V: sudo apt-get update + V-->>U: パッケージインデックスを最新化 + U->>V: sudo apt-get install -y nginx + V-->>U: nginx パッケージをインストール + U->>V: sudo systemctl status nginx + V-->>U: active (running) と表示 + U->>V: curl http://localhost + V-->>U: Welcome to nginx! の HTML を返却 +``` + +```bash +# 1. SSH で my-instance に接続(Console の SSH ボタンでも可) +gcloud compute ssh my-instance --zone=${ZONE} + +# 2. OS のパッケージインデックスを最新化 +sudo apt-get update + +# 3. NGINX をインストール +sudo apt-get install -y nginx + +# 4. NGINX が起動していることを確認 +sudo systemctl status nginx + +# 5. VM 内からもレスポンスを確認(任意) +curl http://localhost +``` + +`systemctl status nginx` の出力に `Active: active (running)` と表示されていれば起動確認は完了です。加えて、再起動後も自動起動するように有効化しておくと安心です。 + +```bash +sudo systemctl enable nginx +``` + +### 3.3 なぜこの手順がベストプラクティスなのか + +- **インストール前に `apt-get update` を実行する理由**: パッケージインデックスが古いままだと、依存パッケージのバージョン不整合やダウンロード失敗の原因になります。ラボの「Update the OS」という指示は、このパッケージインデックス更新(および必要に応じた `apt-get upgrade`)を意味します。 +- **ディストリビューション標準リポジトリからのインストールで十分**: 本ラボの目的は「動作する Web サーバーを構築できること」の確認であるため、Debian/Ubuntu 標準リポジトリの `nginx` パッケージ(`apt-get install nginx`)で要件を満たせます。最新の mainline 版が必要な場合は、公式 `nginx.org` の APT リポジトリを追加する方法もありますが、その分セットアップ手順が増えます。 +- **`systemctl enable` で自動起動を有効化する**: VM が再起動した際に手動で NGINX を起動し直す必要がなくなり、Web サーバーとしての可用性が向上します。 +- **Firewall との関係**: NGINX 自体はポート 80 で待ち受けますが、Task 2 で「Allow HTTP traffic」を有効化していないと外部からアクセスできません。これは VM に `http-server` タグが付与され、`default-allow-http` という名前のファイアウォールルール(tcp:80、送信元 `0.0.0.0/0`)の対象になる、という仕組みです。ラボでは学習目的のためこの全世界許可のルールで問題ありませんが、本番環境では送信元 IP 範囲を絞る、あるいはロードバランサ配下に置くといった追加の制御を検討するのがベストプラクティスです。 + +**参考ソース** +- nginx 公式: [Installing NGINX Open Source](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/) — Debian/Ubuntu へのインストール手順(ディストリビューションリポジトリ/公式リポジトリ双方) +- nginx 公式: [nginx: Linux packages](https://nginx.org/en/linux_packages.html) — `nginx.org` 提供パッケージのインストール手順 +- Google Cloud 公式: [Add network tags](https://cloud.google.com/vpc/docs/add-remove-network-tags) — `http-server` タグとファイアウォールルールの紐付けの仕組み +- Google Cloud 公式: [Use VPC firewall rules](https://cloud.google.com/firewall/docs/using-firewalls) — ファイアウォールルールの基本概念 + +--- + +## Web アプリケーションのテスト + +```mermaid +flowchart LR + A["VM instances 一覧を開く"] --> B["my-instance 行の External IP をクリック"] + B --> C{"ブラウザで開けるか"} + C -- "Yes" --> D["Welcome to nginx! が表示されれば成功"] + C -- "No" --> E["トラブルシューティングへ"] +``` + +External IP をコピーして `http://EXTERNAL_IP/` の形式で新しいタブに貼り付けても同様に確認できます。 + +--- + +## トラブルシューティング + +| 症状 | 想定される原因 | 対処 | +|---|---|---| +| ブラウザで接続できない(タイムアウト) | Firewall で HTTP が許可されていない | VM の詳細画面でネットワークタグに `http-server` が付いているか確認。付いていなければ Edit から追加、または `default-allow-http` ルールの存在を確認する([Use VPC firewall rules](https://cloud.google.com/firewall/docs/using-firewalls)) | +| SSH 接続後 `nginx: command not found` | インストールが完了していない、または別パッケージ名でインストールしようとした | `sudo apt-get install -y nginx` を再実行し、途中でエラーが出ていないかログを確認する | +| `systemctl status nginx` が `inactive (dead)` | インストール直後に自動起動していない場合がある | `sudo systemctl start nginx` で起動し、`sudo systemctl enable nginx` で自動起動を設定する | +| バケット作成時に `Bucket name already in use` | バケット名がグローバルで既に使用されている | `PROJECT_ID` を正しく含めているか確認する(プロジェクト ID はグローバルに一意なので通常は衝突しない) | +| ディスクをアタッチできない | ディスクと VM の Zone が異なる | `gcloud compute disks describe mydisk --zone=` で Zone を確認し、VM と同じ Zone にディスクを作り直す | +| `Check my progress` が失敗する | リソース名・設定値がラボの要件と完全一致していない | リソース名(`my-instance` / `mydisk` / `PROJECT_ID-bucket`)や Region/Zone の綴りを再確認する | + +--- + +## まとめ: 全体フロー + +```mermaid +flowchart TD + subgraph T1["Task 1: Cloud Storage"] + S1["バケット PROJECT_ID-bucket を US マルチリージョンに作成"] + end + + subgraph T2["Task 2: Compute Engine"] + S2["VM my-instance を E2 / e2-medium で作成"] + S3["ディスク mydisk 200GB を作成"] + S4["mydisk を my-instance にアタッチ"] + S2 --> S3 --> S4 + end + + subgraph T3["Task 3: NGINX"] + S5["SSH 接続"] + S6["OS を更新"] + S7["NGINX をインストール"] + S8["起動を確認"] + S5 --> S6 --> S7 --> S8 + end + + S1 -.->|"独立したタスクとして並行実施可"| S2 + S4 --> S5 + S8 --> Test["External IP でブラウザから動作確認"] +``` + +3つのタスクはほぼ独立していますが、Task 3(NGINX インストール)は Task 2 で `my-instance` が作成済みであることが前提です。採点は各タスクの `Check my progress` ボタンでこまめに確認しながら進めると、途中の設定ミスに早く気づけます。 + +--- + +## 参考文献・引用ソース一覧 + +1. **Create a bucket** — Google Cloud 公式ドキュメント + https://cloud.google.com/storage/docs/creating-buckets + バケット作成手順、デフォルト設定、必要な IAM ロール + +2. **Bucket locations** — Google Cloud 公式ドキュメント + https://cloud.google.com/storage/docs/locations + Region / Dual-region / Multi-region の違いと選定基準 + +3. **Create a Linux VM instance** — Google Cloud 公式ドキュメント + https://cloud.google.com/compute/docs/create-linux-vm-instance + Console での VM 作成手順、Allow HTTP traffic チェックボックスの効果 + +4. **Create and start a Compute Engine instance** — Google Cloud 公式ドキュメント + https://cloud.google.com/compute/docs/instances/create-start-instance + VM 作成に必要な IAM ロール(`roles/compute.instanceAdmin.v1`)と権限の概要 + +5. **Create a new Persistent Disk volume** — Google Cloud 公式ドキュメント + https://cloud.google.com/compute/docs/disks/add-persistent-disk + 永続ディスク作成手順とディスクタイプ一覧(pd-balanced 等) + +6. **Attach a non-boot disk to a VM** — Google Cloud 公式ドキュメント + https://cloud.google.com/compute/docs/disks/attach-disks + `attach-disk` コマンドと `device-name` オプションの解説 + +7. **Format and mount a non-boot disk on a Linux VM** — Google Cloud 公式ドキュメント + https://cloud.google.com/compute/docs/disks/format-mount-disk-linux + フォーマット手順と UUID ベースの `/etc/fstab` 設定(発展的ベストプラクティス) + +8. **Add network tags** — Google Cloud 公式ドキュメント + https://cloud.google.com/vpc/docs/add-remove-network-tags + ネットワークタグとファイアウォールルールの紐付けの仕組み + +9. **Use VPC firewall rules** — Google Cloud 公式ドキュメント + https://cloud.google.com/firewall/docs/using-firewalls + ファイアウォールルールの基本概念とコマンド + +10. **Installing NGINX Open Source** — nginx 公式ドキュメント(F5/NGINX, Inc.) + https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/ + Debian/Ubuntu 向け NGINX インストール手順 + +11. **nginx: Linux packages** — nginx.org 公式サイト + https://nginx.org/en/linux_packages.html + `nginx.org` 提供パッケージによるインストール手順 + +12. **対象 Challenge Lab** — Google Cloud Skills Boost + https://www.skills.google/course_templates/754/labs/597890 + 本ガイドが解説しているラボ本体 diff --git a/Gcs-json-api-challenge-lab-best-practices.html b/Gcs-json-api-challenge-lab-best-practices.html new file mode 100644 index 000000000..1daf41095 --- /dev/null +++ b/Gcs-json-api-challenge-lab-best-practices.html @@ -0,0 +1,1821 @@ + + + + + + Cloud Storage JSON/REST API チャレンジラボ ベストプラクティスガイド + + + + + +
+ + +
+
+

Cloud Storage JSON/REST API チャレンジラボ ベストプラクティスガイド

+

+ 世界トップクラスのインフラエンジニア/Google + スペシャリストの視点から、チャレンジラボの各タスクを公式ドキュメントの根拠とともに初学者向けに解説します。 +

+ + 対象ラボ: Working with the Cloud Storage + JSON/REST API — Challenge Lab + +
+ +
+

0. 全体ワークフロー

+

+ チャレンジラボ全体は、以下の6つの操作を JSON + API(storage.googleapis.com/storage/v1)に対して + curl で直接叩くことで完結します。GUI(コンソール)や + gcloud storage コマンドを使わないのがこのラボの特徴です。 +

+
+
+

6つのタスクは上から順に実行する必要がある

+
+

+ ポイントは + Task5 の削除順序(オブジェクト→バケットの順)と、Task4 の公開設定の方式(ACL か IAM か)です。それぞれ後述のセクションで詳しく解説します。 +

+
+ +
+

1. 事前準備:認証とプロジェクト設定

+

+ JSON API はステートレスな REST API + なので、すべてのリクエストに以下が必要です。 +

+
    +
  • + Authorization: Bearer <ACCESS_TOKEN> ヘッダー(OAuth + 2.0 アクセストークン) +
  • +
  • + 操作対象を特定するための PROJECT_ID(バケット作成時に使用) +
  • +
+

+ Cloud Shell にはあらかじめ gcloud CLI と認証済みの + ADC(Application Default + Credentials)が用意されているため、環境変数とトークンを都度発行するのが最も簡単な方法です。 +

+
export PROJECT_ID=$(gcloud config get-value project)
+export ACCESS_TOKEN=$(gcloud auth print-access-token)
+

+ curl コマンドの中でその都度 + $(gcloud auth print-access-token) + を評価する書き方も一般的で、公式ドキュメントのサンプルもこの形式を採用しています。 +

+
curl -X GET \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  "https://storage.googleapis.com/storage/v1/b?project=${PROJECT_ID}"
+ +
+
+

認証トークンからAPIリクエストまでの流れ

+
+ +

ベストプラクティス

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
項目推奨事項理由
トークンの発行方法 + コマンド置換 + $(gcloud auth print-access-token) を都度使う + + アクセストークンは既定で一定時間後に失効するため、変数に保存して使い回すと長時間の作業でリクエストが401になりやすい +
認可の粒度 + 個人アカウントでなく、必要な範囲の IAM ロール(例: + roles/storage.admin)を持つアカウントを使う + + 最小権限の原則。バケットの作成・削除には + storage.buckets.create / + storage.buckets.delete 権限が必要 +
ブラウザ + シークレット/プライベートウィンドウで学習用アカウントを使う + + 個人アカウントと学習用アカウントの認証情報が混在し、意図しない課金や権限エラーが起きるのを防ぐ +
+
+ +
+
参考ソース
+ +
+
+ +
+

+ 2. Task1: バケットを2つ作成する(Buckets: + insert) +

+

+ JSON API でのバケット作成は POST で、クエリパラメータに + project が必須です。 +

+
POST https://storage.googleapis.com/storage/v1/b?project=PROJECT_ID
+

ラボの指示どおりの JSON ファイルを作成します。

+
cat > bucket-1.json <<EOF
+{
+  "name": "${PROJECT_ID}-bucket-1",
+  "location": "us",
+  "storageClass": "STANDARD"
+}
+EOF
+
+curl -X POST --data-binary @bucket-1.json \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  -H "Content-Type: application/json" \
+  "https://storage.googleapis.com/storage/v1/b?project=${PROJECT_ID}"
+

+ 2つ目のバケットも同じ手順で bucket-2.json を作って作成します。 +

+ +

初学者が引っかかりやすいポイント

+ +
+ +
+

①「multi_regional」は現在レガシー扱い

+

+ ラボのサンプル JSON は + "storageClass": "multi_regional" + になっていますが、これは現行のドキュメントではレガシーのストレージクラスとして扱われています。現行の推奨クラスは + STANDARD / NEARLINE / + COLDLINE / ARCHIVE の4種類で、MULTI_REGIONAL + は「マルチリージョンまたはデュアルリージョンでのみ利用可能な、STANDARD + と等価のクラス」と説明されています。新規バケットでは + STANDARD を使うのがベストプラクティスです。 +

+
+
+ +
+ +
+

② バケット名はグローバルに一意かつ公開情報

+

+ バケット名は Cloud Storage + 全体で単一の名前空間を共有するため、他プロジェクトと重複した名前は作成できません。また、バケット名は誰でも存在を推測・アクセスできる公開情報なので、メールアドレスやユーザーIDなど個人を特定できる情報を含めないことが公式に推奨されています。 +

+
+
+ +
+ +
+

+ ③ ロケーションとストレージクラスは作成後に変更しにくい +

+

+ バケットの name と + location + は事実上不変のプロパティです。ストレージクラスは後から + PATCH + で変更可能ですが、ロケーションは変更できず、別ロケーションに移したい場合はバケットの再作成が必要になります。 +

+
+
+ +

ベストプラクティスまとめ

+
+ + + + + + + + + + + + + + + + + + + + + +
項目推奨
ストレージクラス + 特別な理由がなければ STANDARD(レガシー値 + MULTI_REGIONAL/REGIONAL + は新規に使わない) +
バケット名 + プロジェクトIDやランダムサフィックスを含め、PIIを含めない、3〜63文字、小文字・数字・ハイフンのみ +
作成時の権限設計 + 可能であれば + iamConfiguration.uniformBucketLevelAccess.enabled: + true + を初期設定にし、後述の Task4 + でACLが必要な場合のみ明示的に無効化する +
+
+ + +
+ +
+

+ 3. Task2: + 画像ファイルをアップロードする(Objects: insert) +

+

+ JSON API のオブジェクトアップロードには3種類の + uploadType + があります。ラボのようにメタデータ不要の単純な画像アップロードでは + media(シンプルアップロード)で十分です。 +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
uploadType用途目安のファイルサイズ
mediaデータのみを送る最もシンプルな方式〜5MB程度の小さいファイル向け
multipartデータとメタデータ(JSON)を1リクエストにまとめて送る小さいファイル+メタデータが必要な場合
resumableアップロードを中断・再開できる方式大きいファイルや不安定な回線
+
+ +
export OBJECT_NAME="world-map.png"
+export BUCKET_1="${PROJECT_ID}-bucket-1"
+
+curl -X POST --data-binary @${OBJECT_NAME} \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  -H "Content-Type: image/png" \
+  "https://storage.googleapis.com/upload/storage/v1/b/${BUCKET_1}/o?uploadType=media&name=${OBJECT_NAME}"
+ +

ベストプラクティス

+
    +
  • + アップロード用エンドポイントは + /upload/ プレフィックス付き:通常のメタデータ操作(GET/PATCH)は + https://storage.googleapis.com/storage/v1/b/... + ですが、データを実際に送信するアップロードだけは + https://storage.googleapis.com/upload/storage/v1/b/... + という別エンドポイントになります。この違いを取り違えるのが初学者の典型的なつまずきポイントです。 +
  • +
  • + Content-Type はファイルの実体に合わせる:image/png のように正しい MIME + タイプを指定しないと、後でブラウザから直接アクセスしたときに正しくレンダリングされないことがあります。 +
  • +
  • + オブジェクト名にも命名規則がある:スラッシュ + / + を含めると擬似的な「フォルダ」構造として扱われますが、実際にはフラットな名前空間です。URLエンコードが必要な文字を含む場合は明示的にエンコードします。 +
  • +
  • + 大きいファイルには resumable を使う:チャレンジラボの画像程度なら + media + で問題ありませんが、実運用でギガバイト級のファイルを扱う場合は途中失敗時の再送コストを抑えるため + resumable アップロードを検討します。 +
  • +
+ + +
+ +
+

+ 4. Task3: + オブジェクトを別バケットにコピーする(Objects: copy) +

+
POST https://storage.googleapis.com/storage/v1/b/SOURCE_BUCKET/o/SOURCE_OBJECT/copyTo/b/DESTINATION_BUCKET/o/DESTINATION_OBJECT
+
export BUCKET_2="${PROJECT_ID}-bucket-2"
+
+curl -X POST \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  -H "Content-Length: 0" \
+  "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}/o/${OBJECT_NAME}/copyTo/b/${BUCKET_2}/o/${OBJECT_NAME}"
+ +
+ +
+

+ リクエストボディを空にした場合、送信元オブジェクトの編集可能なメタデータは複製先にも引き継がれます。ただしACL・オブジェクトホールド・保持設定は引き継がれません。「コピーしたのに公開設定が消えている」という事象の典型的な原因です。 +

+
+
+ +

「copy」と「rewrite」の使い分け(実務でのベストプラクティス)

+

+ 公式ドキュメントは、copy メソッドではなく + rewrite メソッドの使用を一般的に推奨しています。理由は、copy は内部的に + rewrite + を一度だけ呼び出す実装になっており、オブジェクトが大きい場合は複数回の + rewrite 呼び出しが必要になることがあるため、copy + を大きいオブジェクトに使うと + Payload too large エラーになりうるからです。チャレンジラボでは + copy を使う指示ですが、実務のスクリプトでは + rewrite(rewriteToken + によるページネーションに対応)を選ぶのがベストプラクティスです。 +

+ +
+
+

copy と rewrite の使い分け判断フロー

+
+ +

ベストプラクティス

+
+ + + + + + + + + + + + + + + + + + + + + +
項目推奨
権限 + 送信元バケットに + storage.objects.get、宛先バケットに + storage.objects.create + が必要(IAMで最小権限に絞る) +
ACLの扱い + コピー後に元と同じ公開設定が必要なら、コピー先で明示的にACL/IAMを再設定する(自動継承されない) +
大きいファイルcopy ではなく rewrite を使う
+
+ + +
+ +
+

+ 5. Task4: オブジェクトを一般公開する(ACL または + IAM) +

+

+ ラボの指示どおり、ObjectAccessControls: insert を使い、allUsers + に READER 権限を付与します。 +

+
POST https://storage.googleapis.com/storage/v1/b/BUCKET/o/OBJECT/acl
+
cat > public-read.json <<EOF
+{
+  "entity": "allUsers",
+  "role": "READER"
+}
+EOF
+
+curl -X POST --data-binary @public-read.json \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  -H "Content-Type: application/json" \
+  "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/o/${OBJECT_NAME}/acl"
+ +

なぜこれが「レガシー」寄りの方法なのか

+
+ + + + + + + + + + + + + + + + + + + + +
方式概要現在の位置づけ
+ Uniform bucket-level access + IAM(推奨) + + バケット単位のIAMポリシーのみで権限を一元管理。ACLは無効化される + Googleが推奨するデフォルト方式
+ Fine-grained access + ACL(ラボで使用) + + IAMに加えてオブジェクト単位のACLも併用できるレガシー方式。S3との相互運用のために残されている + + 特定オブジェクトだけ個別に権限を変えたい場合の例外的な用途 +
+
+ +

+ 公式ドキュメントは「IAMとACLの2つの権限系統が並行して働くため、意図しないデータ公開のリスクが増える」として、原則ACLを避けUniform + bucket-level accessを有効にすることを推奨しています。さらに、Uniform bucket-level + accessが有効なバケットに対してACL系の操作を送ると400 Bad Requestになるという技術的な制約もあります。つまり、このラボのTask4をそのままJSON + APIで成功させるには、対象バケットがFine-grained(ACL有効)でなければなりません。Task1で + iamConfiguration + を指定していなければそのままの状態なので、追加の作業なしで動作します。 +

+ +

実務で推奨される代替方法:IAMポリシーによる公開

+

+ Uniform bucket-level + accessを有効にしたバケットで同じことをしたい場合は、ACLではなく + setIamPolicy を使います。 +

+
cat > iam-policy.json <<EOF
+{
+  "bindings": [
+    { "role": "roles/storage.objectViewer", "members": ["allUsers"] }
+  ]
+}
+EOF
+
+curl -X PUT --data-binary @iam-policy.json \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  -H "Content-Type: application/json" \
+  "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/iam"
+ +
+
+

公開設定の方式選択フロー(ACL vs IAM)

+
+ +
+ +
+

+ allUsers への付与は必ず意図的に行う +

+

+ allUsers + はインターネット上の誰でもという意味です。学習目的以外では、機密情報を含むバケットに対して安易に使わないこと。バケット全体を公開する場合はIAM、個別オブジェクトだけならACLという使い分けが公式の考え方です。 +

+
+
+ +
+ +
+

併用のリスク

+

+ Fine-grainedバケットでは、バケットのIAMポリシーが非公開でも、1つのオブジェクトのACLが + allUsers + になっているだけでそのオブジェクトは公開されてしまいます。定期的にACLの棚卸しをするか、可能な限りUniform + bucket-level accessに統一するのが安全です。 +

+
+
+ + +
+ +
+

+ 6. Task5: クリーンアップ(オブジェクト削除 → + バケット削除) +

+

+ Cloud Storageの + Buckets: delete + は空のバケットしか削除できません。中にオブジェクトが1つでも残っていると + 409 Conflict + になります。そのため、必ず「オブジェクトを先に削除→バケットを削除」の順で呼び出す必要があります。 +

+ +
+
+

削除の正しい順序

+
+ +
# 1. bucket-1 内のオブジェクトを削除
+curl -X DELETE \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}/o/${OBJECT_NAME}"
+
+# 2. bucket-1 自体を削除
+curl -X DELETE \
+  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+  "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}"
+ +

ベストプラクティス

+
    +
  • + bucket-2 は削除しない:ラボの要件はコピー先の bucket-2 は残したまま、コピー元の + bucket-1 + とその中のオブジェクトだけを削除することです。誤って両方消してしまうミスに注意します。 +
  • +
  • + ソフトデリート(Soft Delete)の考慮:バケットにソフトデリートポリシーが設定されている場合、DELETE + してもすぐには完全消去されず、保持期間中は復元可能な状態になります。チャレンジラボでは影響しませんが、本番運用では想定より長くストレージ料金が発生する要因になり得ます。 +
  • +
  • + 本番運用では削除前に一覧・バックアップを確認する:削除は取り消せない操作(またはソフトデリート期間後に取り消せなくなる操作)なので、スクリプト化する場合は削除対象を + list で確認するステップを挟むと安全です。 +
  • +
+ + +
+ +
+

7. 全体ベストプラクティスまとめ

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
カテゴリベストプラクティス根拠
認証 + アクセストークンは都度 + $(gcloud auth print-access-token) + で発行し、ハードコードしない + + Authenticate to Cloud Storage +
権限 + 個人アカウントではなく、必要なIAMロールのみを持つ学習用/サービスアカウントを使う + + Cloud Storage IAM roles +
ストレージクラス + 新規バケットは + STANDARD を基本とし、レガシー値は避ける + + Storage classes +
命名バケット名にPIIを含めない、グローバル一意性を意識する + About Cloud Storage buckets +
アップロード + ファイルサイズに応じて + media/multipart/resumable + を使い分ける + + Upload objects from a file system +
コピー + 大きいオブジェクトは copy ではなく + rewrite + を使う。ACLは自動継承されない前提で設計する + + Objects: copy +
公開設定 + 可能な限りUniform bucket-level access + + IAMを使い、ACLは例外的な用途に限定する + + Overview of access control +
削除必ず「オブジェクト削除→バケット削除」の順序を守る + Delete buckets +
+
+
+ +
+

8. トラブルシューティング

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
症状主な原因対処
400 Bad Request + Uniform bucket-level accessが有効なバケットに + destinationPredefinedAcl + やACL系エンドポイント(/acl)を送っている + + バケットをFine-grainedのまま作成するか、IAMポリシー(/iam + エンドポイント)方式に切り替える +
401 Unauthorized + アクセストークンが失効している、または + Bearer の綴りミス + + gcloud auth print-access-token + を再実行してトークンを再発行する +
403 Forbidden + 実行アカウントに必要なIAM権限(storage.buckets.create等)がない + + 対象プロジェクトで適切なロール(例: + roles/storage.admin)が付与されているか確認する +
404 Not Found + バケット名・オブジェクト名の誤り、またはオブジェクト名のURLエンコード漏れ + + 変数の中身を + echo + で確認し、スラッシュなどを含む名前はURLエンコードする +
409 Conflict(BucketNotEmpty)オブジェクトが残っているバケットを削除しようとしている + 先にすべてのオブジェクトを削除してから再度バケット削除を実行する +
+
+
+
参考ソース
+ +
+
+ +
+

9. 参考文献(ソース一覧)

+ +
+ +
+ 本ガイドはチャレンジラボの学習用途に作成されたものであり、実際のコマンド実行結果や課金についてはご自身の環境・プロジェクトでご確認ください。 +
+
+
+ + + + + + + + diff --git a/Gcs-json-api-challenge-lab-best-practices.md b/Gcs-json-api-challenge-lab-best-practices.md new file mode 100644 index 000000000..a7b8cd7e7 --- /dev/null +++ b/Gcs-json-api-challenge-lab-best-practices.md @@ -0,0 +1,388 @@ +# Cloud Storage JSON/REST API チャレンジラボ ベストプラクティスガイド + +対象ラボ: [Working with the Cloud Storage JSON/REST API — Challenge Lab](https://www.skills.google/course_templates/755/labs/613033) + +本ドキュメントは、Google Cloud のインフラエンジニア/Google スペシャリストの視点から、上記チャレンジラボの各タスク(バケット作成 → オブジェクトのアップロード → バケット間コピー → 公開設定 → 削除)を、公式ドキュメントの根拠とともにステップバイステップで解説するものです。初学者がラボをただクリアするだけでなく、「なぜその curl コマンドで動くのか」「実務ではどこに気をつけるべきか」を理解できることを目的としています。 + +--- + +## 0. 全体ワークフロー + +チャレンジラボ全体は、以下の 6 つの操作を JSON API(`storage.googleapis.com/storage/v1`)に対して `curl` で直接叩くことで完結します。GUI(コンソール)や `gcloud storage` コマンドを使わないのがこのラボの特徴です。 + +```mermaid +flowchart TD + P["事前準備
PROJECT_ID 環境変数 & アクセストークン取得"] --> T1["Task1: バケットを2つ作成
bucket-1 / bucket-2"] + T1 --> T2["Task2: 画像ファイルを bucket-1 へアップロード"] + T2 --> T3["Task3: bucket-1 から bucket-2 へオブジェクトをコピー"] + T3 --> T4["Task4: bucket-2 上のオブジェクトを公開設定"] + T4 --> T5a["Task5-1: bucket-1 の元オブジェクトを削除"] + T5a --> T5b["Task5-2: bucket-1 自体を削除"] +``` + +ポイントは、**Task5 の削除順序**(オブジェクト → バケットの順)と、**Task4 の公開設定の方式**(ACL か IAM か)です。それぞれ後述します。 + +--- + +## 1. 事前準備:認証とプロジェクト設定 + +### 1.1 何をするか + +JSON API はステートレスな REST API なので、すべてのリクエストに以下が必要です。 + +- `Authorization: Bearer ` ヘッダー(OAuth 2.0 アクセストークン) +- 操作対象を特定するための `PROJECT_ID`(バケット作成時のみ) + +Cloud Shell にはあらかじめ `gcloud` CLI と認証済みの ADC(Application Default Credentials)が用意されているため、以下のように環境変数とトークンを都度発行するのが最も簡単な方法です。 + +```bash +export PROJECT_ID=$(gcloud config get-value project) +export ACCESS_TOKEN=$(gcloud auth print-access-token) +``` + +`curl` コマンドの中でその都度 `$(gcloud auth print-access-token)` を評価する書き方も一般的で、公式ドキュメントのサンプルもこの形式を採用しています。 + +```bash +curl -X GET \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + "https://storage.googleapis.com/storage/v1/b?project=${PROJECT_ID}" +``` + +```mermaid +flowchart LR + U["Cloud Shell / gcloud CLI ユーザー"] --> G["gcloud auth print-access-token"] + G --> H["Authorization: Bearer TOKEN ヘッダー"] + H --> C["curl リクエスト送信"] + C --> API["Cloud Storage JSON API
storage.googleapis.com/storage/v1"] + API --> R["JSON レスポンス"] +``` + +### 1.2 ベストプラクティス + +| 項目 | 推奨事項 | 理由 | +|---|---|---| +| トークンの発行方法 | コマンド置換 `$(gcloud auth print-access-token)` を都度使う | アクセストークンは既定で一定時間後に失効するため、変数に保存して使い回すと長時間の作業でリクエストが 401 になりやすい | +| 認可の粒度 | 個人アカウントでなく、必要な範囲の IAM ロール(例: `roles/storage.admin`)を持つアカウントを使う | 最小権限の原則。バケットの作成・削除には `storage.buckets.create` / `storage.buckets.delete` 権限が必要 | +| ブラウザ | シークレット/プライベートウィンドウで学習用アカウントを使う | 個人の Google アカウントと学習用アカウントの認証情報が混在し、意図しない課金や権限エラーが起きるのを防ぐ(ラボの Setup and requirements にも明記) | + +**参考ソース** +- [Authenticate to Cloud Storage](https://docs.cloud.google.com/storage/docs/authentication) — REST 呼び出し時に `gcloud auth print-access-token` を使う方法 +- [gcloud auth print-access-token リファレンス](https://cloud.google.com/sdk/gcloud/reference/auth/print-access-token) +- [Cloud Storage IAM roles](https://docs.cloud.google.com/storage/docs/access-control/iam-roles) + +--- + +## 2. Task 1: バケットを2つ作成する(`Buckets: insert`) + +### 2.1 リクエストの組み立て + +JSON API でのバケット作成は `POST` で、クエリパラメータに `project` が必須です。 + +``` +POST https://storage.googleapis.com/storage/v1/b?project=PROJECT_ID +``` + +ラボの指示どおりの JSON ファイルを作成します。 + +```bash +cat > bucket-1.json < Q{"オブジェクトサイズは大きいか?
(数百MB〜)"} + Q -->|"小さい / ラボ演習"| C["Objects: copy を使用
POST .../copyTo/..."] + Q -->|"大きい / 本番運用"| RW["Objects: rewrite を使用
rewriteToken でページング"] +``` + +### 4.3 ベストプラクティス + +| 項目 | 推奨 | +|---|---| +| 権限 | 送信元バケットに `storage.objects.get`、宛先バケットに `storage.objects.create` が必要(IAM で最小権限に絞る) | +| ACL の扱い | コピー後に元と同じ公開設定が必要なら、コピー先で明示的に ACL / IAM を再設定する(自動継承されない) | +| 大きいファイル | `copy` ではなく `rewrite` を使う | + +**参考ソース** +- [Copy, rename, and move objects](https://docs.cloud.google.com/storage/docs/copying-renaming-moving-objects) +- [Objects: copy リファレンス(rewrite 推奨の注記、ACL非継承の注記あり)](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/copy) + +--- + +## 5. Task 4: オブジェクトを一般公開する(ACL または IAM) + +### 5.1 ラボが要求している方式:Fine-grained ACL + +ラボの指示どおり、`ObjectAccessControls: insert` を使い、`allUsers` に `READER` 権限を付与します。 + +``` +POST https://storage.googleapis.com/storage/v1/b/BUCKET/o/OBJECT/acl +``` + +```bash +cat > public-read.json < iam-policy.json <Uniform bucket-level access が有効か?"} + Q -->|"有効(推奨構成)"| I["IAM ポリシーで roles/storage.objectViewer を allUsers に付与
PUT /b/BUCKET/iam"] + Q -->|"無効(Fine-grained / ACL 構成、ラボはこちら)"| A["ObjectAccessControls.insert で entity=allUsers, role=READER
POST /b/BUCKET/o/OBJECT/acl"] + I --> P["オブジェクトが一般公開される"] + A --> P +``` + +### 5.4 ベストプラクティス(公開設定に関する重要な注意) + +- **`allUsers` への付与は必ず意図的に行う**:`allUsers` はインターネット上の誰でもという意味です。学習目的以外では、機密情報を含むバケットに対して安易に使わないこと。 +- **バケット全体を公開する場合は IAM、個別オブジェクトだけなら ACL**という使い分けが公式の考え方です。 +- **併用のリスク**:Fine-grained バケットでは、バケットの IAM ポリシーが非公開でも、1つのオブジェクトの ACL が `allUsers` になっているだけでそのオブジェクトは公開されてしまいます。定期的に ACL の棚卸しをするか、可能な限り Uniform bucket-level access に統一するのが安全です。 + +**参考ソース** +- [Make data public(IAM 方式の手順と、Uniform bucket-level access が前提という注記)](https://docs.cloud.google.com/storage/docs/access-control/making-data-public) +- [ObjectAccessControls: insert リファレンス](https://docs.cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert) +- [Overview of access control(Uniform と Fine-grained の比較)](https://docs.cloud.google.com/storage/docs/access-control) +- [Uniform bucket-level access](https://docs.cloud.google.com/storage/docs/uniform-bucket-level-access) +- [Access control lists (ACLs)(ACLを避けるべき理由)](https://docs.cloud.google.com/storage/docs/access-control/lists) + +--- + +## 6. Task 5: クリーンアップ(オブジェクト削除 → バケット削除) + +### 6.1 なぜ「順序」が重要か + +Cloud Storage の `Buckets: delete` は**空のバケットしか削除できません**。中にオブジェクトが1つでも残っていると `409 Conflict` になります。そのため、必ず「オブジェクトを先に削除 → バケットを削除」の順で呼び出す必要があります。 + +```mermaid +flowchart LR + D1["Task5-1: DELETE /b/BUCKET-1/o/OBJECT"] --> D2["Task5-2: DELETE /b/BUCKET-1"] + D2 --> OK["バケットが空なので削除成功"] + D1 -.->|"この順序を守らないと"| NG["409 Conflict: BucketNotEmpty"] +``` + +### 6.2 コマンド + +```bash +# 6-1. bucket-1 内のオブジェクトを削除 +curl -X DELETE \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}/o/${OBJECT_NAME}" + +# 6-2. bucket-1 自体を削除 +curl -X DELETE \ + -H "Authorization: Bearer $(gcloud auth print-access-token)" \ + "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}" +``` + +### 6.3 ベストプラクティス + +- **`bucket-2` は削除しない**:ラボの要件はコピー先の `bucket-2` は残したまま、コピー元の `bucket-1` とその中のオブジェクトだけを削除することです。誤って両方消してしまうミスに注意します。 +- **ソフトデリート(Soft Delete)の考慮**:バケットにソフトデリートポリシーが設定されている場合、`DELETE` してもすぐには完全消去されず、保持期間中は復元可能な状態になります。チャレンジラボでは影響しませんが、本番運用では想定より長くストレージ料金が発生する要因になり得ます。 +- **本番運用では削除前に一覧・バックアップを確認する**:削除は取り消せない操作(またはソフトデリート期間後に取り消せなくなる操作)なので、スクリプト化する場合は削除対象を `list` で確認するステップを挟むと安全です。 + +**参考ソース** +- [Delete objects](https://docs.cloud.google.com/storage/docs/deleting-objects) +- [Objects: delete リファレンス](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/delete) +- [Delete buckets(空である必要がある旨、ソフトデリートの挙動)](https://docs.cloud.google.com/storage/docs/deleting-buckets) +- [Buckets: delete リファレンス](https://cloud.google.com/storage/docs/json_api/v1/buckets/delete) + +--- + +## 7. 全体ベストプラクティスまとめ + +| カテゴリ | ベストプラクティス | 根拠 | +|---|---|---| +| 認証 | アクセストークンは都度 `$(gcloud auth print-access-token)` で発行し、ハードコードしない | [Authenticate to Cloud Storage](https://docs.cloud.google.com/storage/docs/authentication) | +| 権限 | 個人アカウントではなく、必要な IAM ロールのみを持つ学習用/サービスアカウントを使う | [Cloud Storage IAM roles](https://docs.cloud.google.com/storage/docs/access-control/iam-roles) | +| ストレージクラス | 新規バケットは `STANDARD` を基本とし、`MULTI_REGIONAL`/`REGIONAL` などレガシー値は避ける | [Storage classes](https://docs.cloud.google.com/storage/docs/storage-classes) | +| 命名 | バケット名に PII を含めない、グローバル一意性を意識する | [About Cloud Storage buckets](https://docs.cloud.google.com/storage/docs/buckets) | +| アップロード | ファイルサイズに応じて `media`/`multipart`/`resumable` を使い分ける | [Upload objects from a file system](https://docs.cloud.google.com/storage/docs/uploading-objects) | +| コピー | 大きいオブジェクトは `copy` ではなく `rewrite` を使う。ACL は自動継承されない前提で設計する | [Objects: copy](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/copy) | +| 公開設定 | 可能な限り Uniform bucket-level access + IAM を使い、ACL は例外的な用途に限定する | [Overview of access control](https://docs.cloud.google.com/storage/docs/access-control) | +| 削除 | 必ず「オブジェクト削除 → バケット削除」の順序を守る | [Delete buckets](https://docs.cloud.google.com/storage/docs/deleting-buckets) | + +--- + +## 8. トラブルシューティング + +| 症状 | 主な原因 | 対処 | +|---|---|---| +| `400 Bad Request` | Uniform bucket-level access が有効なバケットに `destinationPredefinedAcl` や ACL 系エンドポイント(`/acl`)を送っている | バケットを Fine-grained のまま作成するか、IAM ポリシー(`/iam` エンドポイント)方式に切り替える | +| `401 Unauthorized` | アクセストークンが失効している、または `Bearer` の綴りミス | `gcloud auth print-access-token` を再実行してトークンを再発行する | +| `403 Forbidden` | 実行アカウントに必要な IAM 権限(`storage.buckets.create` 等)がない | 対象プロジェクトで適切なロール(例: `roles/storage.admin`)が付与されているか確認する | +| `404 Not Found` | バケット名・オブジェクト名の誤り、またはオブジェクト名の URL エンコード漏れ | 変数の中身を `echo` で確認し、スラッシュなどを含む名前は URL エンコードする | +| `409 Conflict`(`BucketNotEmpty`) | オブジェクトが残っているバケットを削除しようとしている | 先にすべてのオブジェクトを削除してから再度バケット削除を実行する | + +**参考ソース** +- [Status and error codes(JSON API)](https://docs.cloud.google.com/storage/docs/json_api/v1/status-codes) + +--- + +## 9. 参考文献(ソース一覧) + +- ラボ本体: [Working with the Cloud Storage JSON/REST API — Challenge Lab](https://www.skills.google/course_templates/755/labs/613033) +- [Cloud Storage JSON API overview](https://cloud.google.com/storage/docs/json_api) +- [Authenticate to Cloud Storage](https://docs.cloud.google.com/storage/docs/authentication) +- [gcloud auth print-access-token リファレンス](https://cloud.google.com/sdk/gcloud/reference/auth/print-access-token) +- [Cloud Storage IAM roles](https://docs.cloud.google.com/storage/docs/access-control/iam-roles) +- [Create a bucket](https://docs.cloud.google.com/storage/docs/creating-buckets) +- [Buckets: insert](https://docs.cloud.google.com/storage/docs/json_api/v1/buckets/insert) +- [Storage classes](https://docs.cloud.google.com/storage/docs/storage-classes) +- [About Cloud Storage buckets](https://docs.cloud.google.com/storage/docs/buckets) +- [Upload objects from a file system](https://docs.cloud.google.com/storage/docs/uploading-objects) +- [Objects: insert](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/insert) +- [Copy, rename, and move objects](https://docs.cloud.google.com/storage/docs/copying-renaming-moving-objects) +- [Objects: copy](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/copy) +- [Make data public](https://docs.cloud.google.com/storage/docs/access-control/making-data-public) +- [ObjectAccessControls: insert](https://docs.cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert) +- [Overview of access control](https://docs.cloud.google.com/storage/docs/access-control) +- [Uniform bucket-level access](https://docs.cloud.google.com/storage/docs/uniform-bucket-level-access) +- [Access control lists (ACLs)](https://docs.cloud.google.com/storage/docs/access-control/lists) +- [Delete objects](https://docs.cloud.google.com/storage/docs/deleting-objects) +- [Objects: delete](https://docs.cloud.google.com/storage/docs/json_api/v1/objects/delete) +- [Delete buckets](https://docs.cloud.google.com/storage/docs/deleting-buckets) +- [Buckets: delete](https://cloud.google.com/storage/docs/json_api/v1/buckets/delete) +- [Status and error codes (JSON API)](https://docs.cloud.google.com/storage/docs/json_api/v1/status-codes) diff --git a/task.md b/task.md index cf6bab3ce..e3cf435d9 100644 --- a/task.md +++ b/task.md @@ -1,12 +1,24 @@ # 全ガイド画面レイアウト統一 +## コミット共通ルール + +Fail(Red)・Green・Refactor は**それぞれ独立したコミット**にする。各コミットの直前に、 +以下のゲートを順に満たすこと(詳細は `.agents/rules/migration-progress-sync.md` を継承する)。 + +1. ユーザーがコミットを明示的に認可していることを確認する。 +2. `git status --short` で作業ツリーの状態を確認する。 +3. その段階の対象ファイルだけを `git add` する(ディレクトリ一括指定をしない)。 +4. `git diff --cached --name-only` と `git diff --cached` でステージ差分と範囲を確認する。 +5. 認可済みかつ範囲が正しい場合にのみ `git commit` し、次の段階へ進む。 + ## Red(テスト失敗) - サイドバーを持つ全24スタイルシートを一覧化する。 - デスクトップのサイドバー幅280px・左端固定と、メイン領域の残幅100%を検証する。 - モバイルのメイン領域が横幅100%へ戻ることを検証する。 - 既存の本文最大幅制限を許容するテストを、新しい全幅要件へ更新する。 -- 失敗確認の直後に、テストだけを `test(layout): add failing full-width guide contract` 形式でコミットする。 +- 失敗確認の直後に、共通ゲート(認可・`git status`・対象ファイルのみ `git add`・ステージ差分確認)を通し、 + テストだけを `test(layout): add failing full-width guide contract` 形式でコミットする。 ## Green(実装) @@ -15,13 +27,15 @@ `max-width: none`、`box-sizing: border-box` を適用する。 - 本文全体を狭める `content-inner` 等の最大幅を解除する。 - 900px前後の既存ブレークポイントではメイン領域を幅100%へ戻す。 -- 対象テストの成功確認直後に、最小実装だけを `feat(layout): standardize full-width sidebar guide screens` 形式でコミットする。 +- 対象テストの成功確認直後に、共通ゲートを通し、最小実装だけを + `feat(layout): standardize full-width sidebar guide screens` 形式で独立コミットする。 ## Refactor(検証) - 対象テスト、全テスト、Lint、production buildを実行する。 - CSSの重複した左余白や親要素のpaddingを除去し、二重オフセットを防ぐ。 -- 検証と整理の完了直後に、実際のリファクタ変更だけを `refactor(layout): integrate full-width guide layout` 形式で独立コミットする。 +- 検証と整理の完了直後に、共通ゲートを通し、実際のリファクタ変更だけを + `refactor(layout): integrate full-width guide layout` 形式で独立コミットする。 ## Docs Sync(仕様同期) From 6f537e14a2ef8e325455dc24902ec67a8815b873 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Mon, 10 Aug 2026 01:51:48 +0900 Subject: [PATCH 112/123] chore(workflow): harden migration and docker procedures --- .agents/rules/css-cache-reset.md | 6 +++--- .agents/skills/md-to-nextjs-migration/SKILL.md | 15 +++++++++++++-- .gemini/rules/css-cache-reset.md | 6 +++--- .gemini/skills/md-to-nextjs-migration/SKILL.md | 15 +++++++++++++-- package.json | 1 + 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.agents/rules/css-cache-reset.md b/.agents/rules/css-cache-reset.md index e5610107a..159cc3f66 100644 --- a/.agents/rules/css-cache-reset.md +++ b/.agents/rules/css-cache-reset.md @@ -1,6 +1,6 @@ # globals.css 変更後のキャッシュリセットルール -(最終更新日: 2026-08-09) +(最終更新日: 2026-08-10) ## 問題 @@ -146,8 +146,8 @@ dev サーバーでは正常でも Docker の本番ビルドで CSS 変数が空 **Docker リビルド手順**(`globals.css` 変更後): -Docker 操作はリポジトリのコマンド規約に対するオーケストレーション上の例外として `make` を使用する。`make build` は `docker compose --profile prod build` を実行して本番イメージだけをビルドし、コンテナは起動しない。`make dev` は `docker compose --profile dev up --build` を実行して開発イメージを再ビルドし、開発コンテナを起動する。 +`package.json` の `docker:rebuild` スクリプトは、起動中のコンテナを停止し、本番イメージをビルドしてから、開発イメージを再ビルドして開発コンテナを起動する。 ```bash -make down && make build && make dev +bun run docker:rebuild ``` diff --git a/.agents/skills/md-to-nextjs-migration/SKILL.md b/.agents/skills/md-to-nextjs-migration/SKILL.md index 6ec70d321..70e421c90 100644 --- a/.agents/skills/md-to-nextjs-migration/SKILL.md +++ b/.agents/skills/md-to-nextjs-migration/SKILL.md @@ -17,7 +17,7 @@ description: > # MD → Next.js 移行ワークフロー(Infra リポジトリ) -(最終更新日: 2026-08-09) +(最終更新日: 2026-08-10) **🚨 開発時の必須ルール(TDD & Step-by-step Commit) 🚨** 全てのコード実装において、必ず `.agents/rules/tdd-commit-workflow.md` のルールに従うこと。 @@ -185,7 +185,18 @@ assert_staged_scope() { ```bash # 要件を網羅するテストを追加 -bun run test __tests__/gcl//page.test.tsx # 失敗を確認 +red_test_log=$(mktemp) || exit 1 +trap 'rm -f "$red_test_log"' EXIT +if bun run test __tests__/gcl//page.test.tsx >"$red_test_log" 2>&1; then + cat "$red_test_log" + echo 'Red テストが成功しました。コミットを中止します。' >&2 + exit 1 +fi +cat "$red_test_log" +if ! grep -Eq 'AssertionError|TestingLibraryElementError|Unable to find an element|expected .* (to|not to)' "$red_test_log"; then + echo '想定したアサーション失敗を確認できません。コミットを中止します。' >&2 + exit 1 +fi git status --short assert_clean_stage || exit 1 git add __tests__/gcl//page.test.tsx || exit 1 diff --git a/.gemini/rules/css-cache-reset.md b/.gemini/rules/css-cache-reset.md index e5610107a..159cc3f66 100644 --- a/.gemini/rules/css-cache-reset.md +++ b/.gemini/rules/css-cache-reset.md @@ -1,6 +1,6 @@ # globals.css 変更後のキャッシュリセットルール -(最終更新日: 2026-08-09) +(最終更新日: 2026-08-10) ## 問題 @@ -146,8 +146,8 @@ dev サーバーでは正常でも Docker の本番ビルドで CSS 変数が空 **Docker リビルド手順**(`globals.css` 変更後): -Docker 操作はリポジトリのコマンド規約に対するオーケストレーション上の例外として `make` を使用する。`make build` は `docker compose --profile prod build` を実行して本番イメージだけをビルドし、コンテナは起動しない。`make dev` は `docker compose --profile dev up --build` を実行して開発イメージを再ビルドし、開発コンテナを起動する。 +`package.json` の `docker:rebuild` スクリプトは、起動中のコンテナを停止し、本番イメージをビルドしてから、開発イメージを再ビルドして開発コンテナを起動する。 ```bash -make down && make build && make dev +bun run docker:rebuild ``` diff --git a/.gemini/skills/md-to-nextjs-migration/SKILL.md b/.gemini/skills/md-to-nextjs-migration/SKILL.md index 6ec70d321..70e421c90 100644 --- a/.gemini/skills/md-to-nextjs-migration/SKILL.md +++ b/.gemini/skills/md-to-nextjs-migration/SKILL.md @@ -17,7 +17,7 @@ description: > # MD → Next.js 移行ワークフロー(Infra リポジトリ) -(最終更新日: 2026-08-09) +(最終更新日: 2026-08-10) **🚨 開発時の必須ルール(TDD & Step-by-step Commit) 🚨** 全てのコード実装において、必ず `.agents/rules/tdd-commit-workflow.md` のルールに従うこと。 @@ -185,7 +185,18 @@ assert_staged_scope() { ```bash # 要件を網羅するテストを追加 -bun run test __tests__/gcl//page.test.tsx # 失敗を確認 +red_test_log=$(mktemp) || exit 1 +trap 'rm -f "$red_test_log"' EXIT +if bun run test __tests__/gcl//page.test.tsx >"$red_test_log" 2>&1; then + cat "$red_test_log" + echo 'Red テストが成功しました。コミットを中止します。' >&2 + exit 1 +fi +cat "$red_test_log" +if ! grep -Eq 'AssertionError|TestingLibraryElementError|Unable to find an element|expected .* (to|not to)' "$red_test_log"; then + echo '想定したアサーション失敗を確認できません。コミットを中止します。' >&2 + exit 1 +fi git status --short assert_clean_stage || exit 1 git add __tests__/gcl//page.test.tsx || exit 1 diff --git a/package.json b/package.json index daa9d66c8..3d220cea1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", + "docker:rebuild": "docker compose --profile dev --profile prod down && docker compose --profile prod build && docker compose --profile dev up --build", "lint": "eslint .", "markdownlint": "markdownlint-cli2", "test": "vitest run", From dcdae5996436bbd5414a06d16fcbb2a10b740d1c Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Mon, 10 Aug 2026 01:53:55 +0900 Subject: [PATCH 113/123] docs(gcp): correct storage compute lab guidance --- Gcp-challenge-lab-storage-compute-nginx.html | 43 +++++++++++++------- Gcp-challenge-lab-storage-compute-nginx.md | 25 ++++++------ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Gcp-challenge-lab-storage-compute-nginx.html b/Gcp-challenge-lab-storage-compute-nginx.html index af2abc78e..9a3840d0b 100644 --- a/Gcp-challenge-lab-storage-compute-nginx.html +++ b/Gcp-challenge-lab-storage-compute-nginx.html @@ -871,7 +871,7 @@

1.4 なぜこの設定がベストプラクティスなのか

  • US マルチリージョンを選ぶ理由: - マルチリージョンは複数のリージョンにまたがってデータを複製するため、単一リージョンより可用性・耐久性が高く、地理的に分散したユーザーへの配信レイテンシも平準化されます。トレードオフとしてリージョン単体構成よりストレージ単価がやや高くなります。今回のように「まずは汎用のファイル置き場を作る」用途では、コストよりも可用性を優先するデフォルトの + マルチリージョンは複数のリージョンにまたがってデータを複製するため、単一リージョンより可用性が高く、地理的に分散したユーザーへの配信レイテンシも平準化されます。トレードオフとしてリージョン単体構成よりストレージ単価がやや高くなります。今回のように「まずは汎用のファイル置き場を作る」用途では、コストよりも可用性を優先するデフォルトの US マルチリージョンが妥当な選択です。
  • @@ -977,16 +977,16 @@

    2.3 VM 作成の手順(gcloud CLI)

  • @@ -1127,7 +1127,7 @@

    3.2 手順とベストプラクティスの解説

    - - - + + + +

    1.4 なぜこの設定がベストプラクティスなのか

    diff --git a/Gcp-challenge-lab-storage-compute-nginx.md b/Gcp-challenge-lab-storage-compute-nginx.md index 1a4ea0ec6..24a6c5955 100644 --- a/Gcp-challenge-lab-storage-compute-nginx.md +++ b/Gcp-challenge-lab-storage-compute-nginx.md @@ -111,7 +111,8 @@ export PROJECT_ID=$(gcloud config get-value project) # US マルチリージョンにバケットを作成 gcloud storage buckets create gs://${PROJECT_ID}-bucket \ --location=US \ - --default-storage-class=STANDARD + --default-storage-class=STANDARD \ + --uniform-bucket-level-access ``` ### 1.4 なぜこの設定がベストプラクティスなのか From 53cbec3b3cbc81d01a5dd0c5ad0ea0768c4d839e Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Mon, 10 Aug 2026 19:47:32 +0900 Subject: [PATCH 119/123] docs(gcp): harden storage JSON API examples --- ...json-api-challenge-lab-best-practices.html | 31 ++++++++++++++----- Gcs-json-api-challenge-lab-best-practices.md | 6 ++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Gcs-json-api-challenge-lab-best-practices.html b/Gcs-json-api-challenge-lab-best-practices.html index c7922a6cc..cd00f5572 100644 --- a/Gcs-json-api-challenge-lab-best-practices.html +++ b/Gcs-json-api-challenge-lab-best-practices.html @@ -1073,12 +1073,15 @@

    実務で推奨される代替方法:IAMポリシーによる公開

    accessを有効にしたバケットで同じことをしたい場合は、ACLではなく setIamPolicy を使います。

    -
    curl -X GET \
    +                    
    if ! curl --fail-with-body --silent --show-error -X GET \
       -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -  "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/iam" \
    -  -o iam-policy-current.json
    +  "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/iam?optionsRequestedPolicyVersion=3" \
    +  -o iam-policy-current.json; then
    +  echo 'IAMポリシーの取得に失敗しました。更新を中止します。' >&2
    +  exit 1
    +fi
     
    -jq '
    +if ! jq '
       if any(.bindings[]?; .role == "roles/storage.objectViewer" and .condition == null) then
         .bindings |= map(
           if .role == "roles/storage.objectViewer" and .condition == null then
    @@ -1088,12 +1091,24 @@ 

    実務で推奨される代替方法:IAMポリシーによる公開

    else .bindings += [{"role": "roles/storage.objectViewer", "members": ["allUsers"]}] end -' iam-policy-current.json > iam-policy.json - -curl -X PUT --data-binary @iam-policy.json \ +' iam-policy-current.json > iam-policy.json; then + echo 'IAMポリシーの更新データ作成に失敗しました。更新を中止します。' >&2 + exit 1 +fi + +if ! jq -e '(.bindings | type == "array") and (.etag | type == "string")' \ + iam-policy.json >/dev/null; then + echo 'IAMポリシーに有効なbindingsまたはetagがありません。更新を中止します。' >&2 + exit 1 +fi + +if ! curl --fail-with-body --silent --show-error -X PUT --data-binary @iam-policy.json \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ - "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/iam"
    + "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/iam"; then + echo 'IAMポリシーの更新に失敗しました。' >&2 + exit 1 +fi

    このread-modify-writeでは既存のbindings、条件、version、etagを保持したまま公開用メンバーを追加します。etagをPUTに含めることで、取得後に別の更新が入った場合の上書きを防ぎます。

    diff --git a/Gcs-json-api-challenge-lab-best-practices.md b/Gcs-json-api-challenge-lab-best-practices.md index e5bb6b9e8..217eb91e5 100644 --- a/Gcs-json-api-challenge-lab-best-practices.md +++ b/Gcs-json-api-challenge-lab-best-practices.md @@ -145,6 +145,8 @@ JSON API のオブジェクトアップロードには 3 種類の `uploadType` ```bash export OBJECT_NAME="world-map.png" +OBJECT_NAME_ENCODED=$(jq -rn --arg value "${OBJECT_NAME}" '$value | @uri') +export OBJECT_NAME_ENCODED export BUCKET_1="${PROJECT_ID}-bucket-1" curl -X POST --data-binary @${OBJECT_NAME} \ @@ -180,7 +182,7 @@ export BUCKET_2="${PROJECT_ID}-bucket-2" curl -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Length: 0" \ - "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}/o/${OBJECT_NAME}/copyTo/b/${BUCKET_2}/o/${OBJECT_NAME}" + "https://storage.googleapis.com/storage/v1/b/${BUCKET_1}/o/${OBJECT_NAME_ENCODED}/copyTo/b/${BUCKET_2}/o/${OBJECT_NAME_ENCODED}" ``` リクエストボディを空にした場合、送信元オブジェクトの編集可能なメタデータは複製先にも引き継がれます。ただし **ACL・object hold・retention 設定は引き継がれません**。これは初学者が見落としやすい仕様で、「コピーしたのに公開設定が消えている」という事象の原因になります。 @@ -231,7 +233,7 @@ EOF curl -X POST --data-binary @public-read.json \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ - "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/o/${OBJECT_NAME}/acl" + "https://storage.googleapis.com/storage/v1/b/${BUCKET_2}/o/${OBJECT_NAME_ENCODED}/acl" ``` ### 5.2 なぜこれが「レガシー」寄りの方法なのか From 3fd825637e49716d0fd15fb11e82036f83c60c98 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Mon, 10 Aug 2026 20:47:06 +0900 Subject: [PATCH 120/123] fix(guides): tighten migration and lab workflows --- .../skills/md-to-nextjs-migration/SKILL.md | 20 +++-- Gcp-challenge-lab-storage-compute-nginx.html | 45 +++++++++-- Gcp-challenge-lab-storage-compute-nginx.md | 26 ++++++- ...json-api-challenge-lab-best-practices.html | 78 +++++++------------ Gcs-json-api-challenge-lab-best-practices.md | 51 +++++------- 5 files changed, 120 insertions(+), 100 deletions(-) diff --git a/.agents/skills/md-to-nextjs-migration/SKILL.md b/.agents/skills/md-to-nextjs-migration/SKILL.md index 70e421c90..87f486b73 100644 --- a/.agents/skills/md-to-nextjs-migration/SKILL.md +++ b/.agents/skills/md-to-nextjs-migration/SKILL.md @@ -159,6 +159,8 @@ app/ Red / Green / Refactor / Docs Sync のコミットを混在させないため、**すべての `git add` の前後**で 次の 2 つの検査を行う。`assert_clean_stage` は `git add` の前、`assert_staged_scope` は `git add` の後に実行する。 +各 `git add` では許可ファイルを `--` の後に明示し、`git add -p` でそのステップに属する差分だけを選択する。 +許可ファイル内に別作業の変更があっても、ファイル全体をステージしてはならない。 ```bash # git add の前: 別ステップの差分が既にステージされていないことを確認する @@ -185,26 +187,30 @@ assert_staged_scope() { ```bash # 要件を網羅するテストを追加 +RED_TEST_NAME='renders the migrated SN requirement title' +RED_EXPECTED_FAILURE='Unable to find an element with the text: SN requirement title' red_test_log=$(mktemp) || exit 1 trap 'rm -f "$red_test_log"' EXIT -if bun run test __tests__/gcl//page.test.tsx >"$red_test_log" 2>&1; then +if bun run test __tests__/gcl//page.test.tsx -t "$RED_TEST_NAME" >"$red_test_log" 2>&1; then cat "$red_test_log" echo 'Red テストが成功しました。コミットを中止します。' >&2 exit 1 fi cat "$red_test_log" -if ! grep -Eq 'AssertionError|TestingLibraryElementError|Unable to find an element|expected .* (to|not to)' "$red_test_log"; then +if ! grep -F -- "$RED_EXPECTED_FAILURE" "$red_test_log" >/dev/null; then echo '想定したアサーション失敗を確認できません。コミットを中止します。' >&2 exit 1 fi git status --short assert_clean_stage || exit 1 -git add __tests__/gcl//page.test.tsx || exit 1 +git add -p -- __tests__/gcl//page.test.tsx || exit 1 assert_staged_scope __tests__/gcl//page.test.tsx || exit 1 git commit -m "test(gcl//SN): add failing migration coverage" ``` -失敗している期待テキストを確認し、実装対象と表記を把握する。Red のテストを Green 実装と同じコミットに含めない。 +`RED_TEST_NAME` は追加したテストだけに一致する固有名、`RED_EXPECTED_FAILURE` はそのテストの期待値を含む固有の失敗メッセージに置き換える。 +`AssertionError` などの一般的なエラー名だけで Red と判定しない。上記の失敗を確認できた場合に限り `test:` コミットへ進み、 +Red のテストを Green 実装と同じコミットに含めない。 ### Step 2: constants.ts に型とデータを追加 @@ -268,7 +274,7 @@ import { bun run test __tests__/gcl//page.test.tsx git status --short assert_clean_stage || exit 1 -git add app/constants.ts app/gcl// app/gcl// || exit 1 +git add -p -- app/constants.ts app/gcl// app/gcl// || exit 1 assert_staged_scope app/constants.ts app/gcl// app/gcl// || exit 1 git diff --cached git commit -m "feat(gcl//SN): implement migrated content" @@ -284,7 +290,7 @@ bun run build bun run lint git status --short assert_clean_stage || exit 1 -git add || exit 1 +git add -p -- || exit 1 assert_staged_scope || exit 1 git commit -m "refactor(gcl//SN): integrate migrated content" ``` @@ -301,7 +307,7 @@ if [ "${COMMIT_AUTHORIZED:-}" != 'yes' ]; then exit 1 fi assert_clean_stage || exit 1 -if ! git add MIGRATION_PROGRESS.md CLAUDE.md GEMINI.md; then +if ! git add -p -- MIGRATION_PROGRESS.md CLAUDE.md GEMINI.md; then echo 'Docs Sync 対象のステージに失敗しました。コミットを中止します。' >&2 exit 1 fi diff --git a/Gcp-challenge-lab-storage-compute-nginx.html b/Gcp-challenge-lab-storage-compute-nginx.html index 6184a0de1..08702d952 100644 --- a/Gcp-challenge-lab-storage-compute-nginx.html +++ b/Gcp-challenge-lab-storage-compute-nginx.html @@ -982,20 +982,36 @@

    2.3 VM 作成の手順(gcloud CLI)

    export REGION="YOUR_REGION" export IMAGE_FAMILY="YOUR_IMAGE_FAMILY" export IMAGE_PROJECT="YOUR_IMAGE_PROJECT" + export NETWORK="default" gcloud compute instances create my-instance \ --zone="$ZONE" \ + --network="$NETWORK" \ --machine-type=e2-medium \ --image-family="$IMAGE_FAMILY" \ --image-project="$IMAGE_PROJECT" \ --boot-disk-type=pd-balanced \ --boot-disk-size=10GB \ --tags=http-server + + if gcloud compute firewall-rules describe default-allow-http >/dev/null 2>&1; then + gcloud compute firewall-rules describe default-allow-http \ + --format="yaml(network,direction,sourceRanges,allowed,targetTags)" + else + gcloud compute firewall-rules create default-allow-http \ + --network="$NETWORK" \ + --direction=INGRESS \ + --allow=tcp:80 \ + --source-ranges=0.0.0.0/0 \ + --target-tags=http-server + fi

    - --tags=http-server を付けることで、後述する「Allow HTTP - traffic」チェックボックスと同じ動作(default-allow-http - ファイアウォールルールの対象になる)を CLI からも再現できます。 + --tags=http-server は VM + をファイアウォールルールの対象にします。CLI 手順ではさらに、同じネットワーク上の + default-allow-http が TCP ポート 80 を + http-server + タグへ許可していることを確認し、存在しない場合は作成します。

    2.4 永続ディスクの作成とアタッチ(Console)

    @@ -1014,6 +1030,8 @@

    2.4 永続ディスクの作成とアタッチ(Console)

    + +

    SSH 接続後、次のブロックを my-instance 内のシェルで実行します。

    bash3.2 手順とベストプラクティスの解説

    --tags=http-server は VM - をファイアウォールルールの対象にします。CLI 手順ではさらに、同じネットワーク上の - default-allow-http が TCP ポート 80 を - http-server - タグへ許可していることを確認し、存在しない場合は作成します。 + をファイアウォールルールの対象にします。CLI 手順ではさらに、default-allow-httpのネットワーク、方向、送信元範囲、許可プロトコル・ポート、対象タグ、有効状態を検証します。更新可能な値は修正し、ネットワークまたは方向が異なる場合や最終検証に失敗した場合は処理を中止します。

    2.4 永続ディスクの作成とアタッチ(Console)

    diff --git a/Gcp-challenge-lab-storage-compute-nginx.md b/Gcp-challenge-lab-storage-compute-nginx.md index b63b7970f..6b4ed692b 100644 --- a/Gcp-challenge-lab-storage-compute-nginx.md +++ b/Gcp-challenge-lab-storage-compute-nginx.md @@ -178,9 +178,30 @@ gcloud compute instances create my-instance \ --boot-disk-size=10GB \ --tags=http-server -if gcloud compute firewall-rules describe default-allow-http >/dev/null 2>&1; then - gcloud compute firewall-rules describe default-allow-http \ - --format="yaml(network,direction,sourceRanges,allowed,targetTags)" +NETWORK_SELF_LINK=$(gcloud compute networks describe "$NETWORK" \ + --format="value(selfLink)") || exit 1 + +if FIREWALL_RULE_JSON=$(gcloud compute firewall-rules describe default-allow-http \ + --format=json 2>/dev/null); then + if ! printf '%s\n' "$FIREWALL_RULE_JSON" | jq -e --arg network "$NETWORK_SELF_LINK" ' + .network == $network and .direction == "INGRESS" + ' >/dev/null; then + echo 'default-allow-http の network または direction が異なるため、自動更新できません。' >&2 + exit 1 + fi + + if ! printf '%s\n' "$FIREWALL_RULE_JSON" | jq -e ' + .sourceRanges == ["0.0.0.0/0"] and + .allowed == [{"IPProtocol": "tcp", "ports": ["80"]}] and + .targetTags == ["http-server"] and + ((.disabled // false) == false) + ' >/dev/null; then + gcloud compute firewall-rules update default-allow-http \ + --allow=tcp:80 \ + --source-ranges=0.0.0.0/0 \ + --target-tags=http-server \ + --no-disabled || exit 1 + fi else gcloud compute firewall-rules create default-allow-http \ --network="$NETWORK" \ @@ -189,9 +210,23 @@ else --source-ranges=0.0.0.0/0 \ --target-tags=http-server fi + +FIREWALL_RULE_JSON=$(gcloud compute firewall-rules describe default-allow-http \ + --format=json) || exit 1 +if ! printf '%s\n' "$FIREWALL_RULE_JSON" | jq -e --arg network "$NETWORK_SELF_LINK" ' + .network == $network and + .direction == "INGRESS" and + .sourceRanges == ["0.0.0.0/0"] and + .allowed == [{"IPProtocol": "tcp", "ports": ["80"]}] and + .targetTags == ["http-server"] and + ((.disabled // false) == false) +' >/dev/null; then + echo 'default-allow-http が期待する設定と一致しません。' >&2 + exit 1 +fi ``` -`--tags=http-server` は VM をファイアウォールルールの対象にします。CLI 手順ではさらに、同じネットワーク上の `default-allow-http` が TCP ポート 80 を `http-server` タグへ許可していることを確認し、存在しない場合は作成します。 +`--tags=http-server` は VM をファイアウォールルールの対象にします。CLI 手順ではさらに、`default-allow-http` のネットワーク、方向、送信元範囲、許可プロトコル・ポート、対象タグ、有効状態を検証します。更新可能な値は修正し、ネットワークまたは方向が異なる場合や最終検証に失敗した場合は処理を中止します。 ### 2.4 永続ディスクの作成とアタッチ