TECH PLAY

株式会社G-gen

株式会社G-gen の技術ブログ

744

G-gen 又吉です。当記事では、BigQuery ML から Vertex AI の基盤モデルを呼び出して感情分析を行う方法を解説します。 前提知識 BigQuery ML Generative AI Support on Vertex AI リモートモデル ML.GENERATE_TEXT 関数 概要 引数 出力 クォータと制限 概要 使用するデータ 今回やること プロンプト設計 精度結果 準備 API の有効化 データセットの作成 Connection の作成 サービスアカウントに権限付与 リモートモデルの作成 実行 感情分析を実行 評価 前提条件 実行 Use Generative AI from BigQuery 前提知識 BigQuery ML 以下の記事では BigQuery ML を詳細に解説していますので、ご参照ください。 blog.g-gen.co.jp Generative AI Support on Vertex AI 先日 Vertex AI でも Generative AI がサポートされました。Generative AI モデル (基盤モデル) の裏側は PaLM 2 が利用されており、多言語、推論、コーディング機能が強化された最先端の大規模言語モデル (LLM) です。 Vertex AI の Generative AI サポートについての詳細は以下の記事をご参照下さい。 blog.g-gen.co.jp リモートモデル リモートモデル とは、Vertex AI エンドポイントまたはリモートサービスタイプを BigQuery から呼び出すことができる機能です。 リモートサービスタイプでは、2023 年 7 月現在、以下のサービスをサポートしています。 Vertex AI の 基盤モデル Cloud Natural Language API Cloud Translation API Cloud Vison API 尚、2023 年 7 月現在、 リモートモデルはプレビュー機能となっています。 ML.GENERATE_TEXT 関数 概要 ML.GENERATE_TEXT 関数 とは、リモートモデル (Vertex AI の text-bison の基盤モデル) と組み合わせることで、BigQuery に保存されているテキストに対して自然言語生成タスクを実行できます。 言語タスクには、以下の例が挙げられます。 分類 感情分析 エンティティの抽出 抽出的な質問への回答 要約 テキストを別のスタイルで書き直す 広告コピーの生成 コンセプトのアイデア出し 尚、2023 年 7 月現在、 ML.GENERATE_TEXT 関数はプレビュー機能となっています。 引数 ML.GENERATE_TEXT 関数の引数には、Vertex AI の基盤モデル (言語) でモデルに送信できるTop-K や Top-P、Temperature 等のパラメータ値と同等の引数を設定できます。 パラメータ値がどのような役割なのかは、以下のブログにも記載しているのでご参照下さい。 参考: パラメータ値 また、 flatten_json_output という引数は、flatten_json_output が TRUE の場合、JSON 形式のレスポンスに含まれる LLM の出力結果と 安全属性の信頼スコアリング の出力結果を個別の列で出力する引数です。 参考: Arguments 出力 出力は、flatten_json_output が TRUE の時と FALSE のときで出力される列名が変わってきます。デフォルトで FALSE となります。 No flatten_json_output 出力される列名 説明 1 FALSE ml_generate_text_result 基盤モデルからの JSON レスポンス。基盤モデルにより生成されたテキストは content の中に含まれ、安全属性の信頼スコアリングは safetyAttributes に含まれる。 2 FALSE ml_generate_text_status 対応する行の API 応答ステータス。操作が成功した場合、この値は空になる。 3 TRUE ml_generate_text_llm_result 基盤モデルから返される生成されたテキスト。 4 TRUE ml_generate_text_rai_result 基盤モデルから返される安全属性の信頼スコアリング。 5 TRUE or FALSE ml_generate_text_status 対応する行の API 応答ステータス。操作が成功した場合、この値は空になる。 クォータと制限 ML.GENERATE_TEXT 関数は、以下のような制限が適用されています。 1 分あたりの Vertex AI API へのリクエスト数 : デフォルト 60 ジョブあたりの行数 : 10,000 同時実行ジョブの数 : 1 特に、1 回のジョブ実行あたり 10,000 行の制限があるので、行数が多い場合は少し工夫が必要になります。 参考: BigQuery - Quotas and limits - Cloud AI service functions 参考: Vertex AI - Quotas and limits - Request quotas 概要 使用するデータ 使用するデータは BigQuery の一般公開データセット上にある IMDB (Internet Movie Database) のデータセットを使用します。 IMDB とは、映画やテレビ番組等のコンテンツに関連する情報を集めたオンラインデータベースで、キャスト、制作スタッフ、作品の概要、評価、ファンや批評家のレビューなどが含まれており、今回はその中から reviews テーブルを使用します。 reviews テーブルには、「(映画の) レビュー」「(正解の) ラベル」「映画 ID」「映画 URL」等が含まれています。 reviews テーブルのプレビュー 今回やること Vertex AI の基盤モデルを BigQuery のリモートモデルとして設定します。そして、そのリモートモデルと BigQuery ML を用いて「(映画の) レビュー」をインプットに、そのレビュー内容が Positive なのか Negative なのかを判断する感情分析タスクを基盤モデルに実行させていきます。 また、「(正解の) ラベル」も含まれるため、最後に 簡易的な精度も算出してみます。 プロンプト設計 プロンプトとは、言語モデルへ送るリクエストのことです。このプロンプト設計が良ければ、基盤モデルからより良い回答を得られます。 今回、映画のレビューをもとに Positive な内容なのか Negative な内容なのかを判定してほしいため、プロンプトには以下の文章を入力してモデルに対しリクエストを送ります。 Classify the sentiment of this review as Positive or Negative? Review: {映画レビュー内容} Sentiment: 後述の 実行 部分で、SQL を用いて {映画のレビュー内容} 部分に実際のテキストを入力していきます。 参考: 感情分析プロンプト 精度結果 先に結果から申し上げますと、英語で書かれた「(映画の) レビュー」に対し、基盤モデルで感情分析を行った結果、合計 10 回試行した平均の正解率は脅威の 94.3% となりました。 以降に、準備の手順や結果をまとめております。 準備 API の有効化 以下のコマンドを実行し、使用する API を有効化します。 gcloud services enable bigqueryconnection.googleapis.com \ aiplatform.googleapis.com データセットの作成 以下のコマンドを実行し、データセットを作成します。 PROJECT_ID={プロジェクト ID を入力} DATASET_ID={データセット ID を入力} bq --location=US mk \ --dataset \ ${PROJECT_ID}:${DATASET_ID} Connection の作成 以下のコマンドを実行し、Connection を作成します。 CONNECTION_ID={任意の Connection ID を入力} bq mk --connection \ --location=US \ --project_id=${PROJECT_ID} \ --connection_type=CLOUD_RESOURCE ${CONNECTION_ID} Connection リソースを作成すると、自動的に一意のサービスアカウントが生成され、Connection にひも付きます。 以下のコマンドを実行し、サービスアカウントが生成されたか確認します。 bq show --connection ${PROJECT_ID}.us.${CONNECTION_ID} 以下のような出力がでるので、後続作業で利用するためサービスアカウント ID をコピーしておきます。 name friendlyName description Last modified type hasCredential properties ------------------------------------- -------------- ------------- ----------------- ---------------- --------------- ---------------------------------------------------------------------------------------------- 1234556789.{Connection ID} 17 Jul 13:43:53 CLOUD_RESOURCE False {"serviceAccountId": "bqcx-{Project Number}-xxxx@gcp-sa-bigquery-condel.iam.gserviceaccount.com"} サービスアカウントに権限付与 以下のコマンドを実行し、先程 Connection 作成時に自動で生成されたサービスアカウントに対し、プロジェクトリソースの Vertex AI ユーザー ロールを付与します。 SERVICE_ACCOUNT_ID={先程コピーしたサービスアカウント ID} gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="serviceAccount:${SERVICE_ACCOUNT_ID}" \ --role="roles/aiplatform.user" リモートモデルの作成 以下のコマンドを BigQuery のクエリエディタで実行し、リモートモデルを作成します。尚、変数は置き換えて下さい。 DATASET_ID : 先ほど作成したデータセット ID MODEL_NAME : 任意のモデル名 CONNECTION_ID : 先ほど作成した Connection ID CREATE OR REPLACE MODEL ${DATASET_ID}.${MODEL_NAME} REMOTE WITH CONNECTION `us.${CONNECTION_ID}` OPTIONS (remote_service_type = 'CLOUD_AI_LARGE_LANGUAGE_MODEL_V1'); 実行 感情分析を実行 以下のコマンドを BigQuery のクエリエディタで実行し、5 件の映画レビューについて感情分析を行います。尚、変数は置き換えて下さい。 SELECT ml_generate_text_llm_result, CAST (JSON_EXTRACT_SCALAR(ml_generate_text_rai_result, ' $.blocked ' ) AS BOOL) AS is_safety_filter_blocked, * EXCEPT (ml_generate_text_llm_result, ml_generate_text_rai_result) FROM ML.GENERATE_TEXT( MODEL `${DATASET_ID}.${MODEL_NAME}`, ( SELECT CONCAT ( ' Classify the sentiment of this review as Positive or Negative? \n Review: ' , review, ' \n Sentiment: ' ) AS prompt, * FROM `bigquery- public -data.imdb.reviews` LIMIT 5 ), STRUCT( 0.2 AS temperature, 100 AS max_output_tokens, TRUE AS flatten_json_output )) 出力結果は以下のとおりです。2 ~4 行目のレコードは、モデルのレスポンスが安全しきい値を超えた ( is_safety_filter_blocked が true ) ため、基盤モデル からの出力 ( ml_generate_text_llm_result )が自動的にブロックされています。尚、このような安全フィルターを削除したい場合は、Google のアカウントチームに問い合わせる必要があります。 参考: 安全しきい値 (Safety thresholds) 結果出力画面 評価 前提条件 今回使用している IMDB データセットの reviews テーブルは、映画レビューとともに、その映画レビューが Psitive な内容なのか Negative な内容なのかを表す正解ラベルも付与されています。したがって、基盤モデルからの出力と正解ラベルを比較することで正解率 (Accuracy) を求めていきたいと思います。 元データが 100,000 件あるため、すべてのレコードを対象とすると処理にかなり時間を要します。そこで今回は、label カラムが Positive から 50 件、Negative から 50 件を抽出したサンプルデータで正解率を求めていきます。尚、安全フィルターに該当するデータは基盤モデルからの出力がないため SQL 中で除外いたします。 実行 以下のコマンドを BigQuery のクエリエディタで実行し、 正解率を求めてみます。尚、変数は置き換えて下さい。 WITH positive AS ( SELECT *, ROW_NUMBER() OVER ( ORDER BY RAND()) AS row_num FROM `bigquery- public -data.imdb.reviews` WHERE label = " Positive " ), negative AS ( SELECT *, ROW_NUMBER() OVER ( ORDER BY RAND()) AS row_num FROM `bigquery- public -data.imdb.reviews` WHERE label = " Negative " ), sample_data AS ( SELECT * FROM positive WHERE row_num <= 50 UNION ALL SELECT * FROM negative WHERE row_num <= 50 ) SELECT COUNTIF(ml_generate_text_llm_result = label) AS correct_answer_number, COUNT (label) AS total_number, COUNTIF(ml_generate_text_llm_result = label) / COUNT (label) AS accuracy FROM ML.GENERATE_TEXT( MODEL `${DATASET_ID}.${MODEL_NAME}`, ( SELECT CONCAT ( ' Classify the sentiment of this review as Positive or Negative? \n Review: ' , review, ' \n Sentiment: ' ) AS prompt, * FROM sample_data ), STRUCT( 0.2 AS temperature, 100 AS max_output_tokens, TRUE AS flatten_json_output )) WHERE CAST (JSON_EXTRACT_SCALAR(ml_generate_text_rai_result, ' $.blocked ' ) AS BOOL) = FALSE 出力結果は以下のとおりです。 出力結果画面 10 回試行し平均値を求めてみると以下のとおりです。 10回試行後の結果まとめ 正解率の平均値は、94.3 % となりました。 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさい、沖縄出身のクラウドエンジニア! セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Champion Innovator (AI/ML)。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリスト。好きな分野は生成 AI。 Follow @matayuuuu
アバター
G-gen 又吉です。当記事では、Cloud Workflows と Dataform を用いてデータ分析パイプラインを構築してみたいと思います。 前提知識 Cloud Workflows Dataform ETL と ELT 概要 今回の構成 Cloud Workflows のスコープ Dataform のスコープ 準備 ディレクトリ構造 main.tf gcf_source_code/etl_raw_data main.py requirements.txt gcf_source_code/etl_weather_data main.py requirements.txt source_data 実行 Terraform 実行 Dataform の設定 概要 difinitions/source/raw_data.sqlx difinitions/source/weather_data.sqlx difinitions/transform/mart_data.sqlx 動作検証 検証 1 検証 2 検証 3 クリーンアップ 本番運用時の考慮事項 エラー発生時の通知機能 再試行時の考慮 前提知識 Cloud Workflows Cloud Workflows はGoogle Cloud のワークフロー管理サービスです。フルマネージドかつサーバーレスであるためインフラの管理は必要なく、また非常に安価に利用できるのが特徴です。 詳細については、以下の記事をご参照下さい。 blog.g-gen.co.jp Dataform Dataform は、BigQuery のための SQL ワークフロー管理サービスです。フルマネージドであり、Dataform の利用自体は無料で利用できる点が特徴です。 詳細については、以下の記事をご参照下さい。 blog.g-gen.co.jp ETL と ELT ETL と ELT はどちらもデータを変換処理する際の流れを説明しています。 ETL は、Extract (抽出)、 Transform (変換) 、Load (書き出し) の順序でデータの変換処理を行います。主に、データを利用しやすい形に変換したり、DWH が読み込める形に変換して DWH 等の分析基盤に格納する際に利用します。 ELT は、Extract (抽出)、 Load (書き出し) 、Transform (変換) の順序でデータの変換処理を行います。ETL は変換処理後に DWH 等の分析基盤にデータをロードしますが、ELT の場合、先に DWH 等の分析基盤にデータをロードし、DWH 内でデータの変換処理を行います。 ETL と ELT の違い 概要 今回の構成 BigQuery に分析対象のデータがある場合、 Dataform を用いることで BigQuery 内で複数の SQL の依存関係を管理しつつデータ変換 (ELT) を行うことができます。 しかし、データレイクを Cloud Storage やオンプレミスに構えており、必要に応じ分析対象のデータのみを BigQuery にインポートするケースも多いです。 その際、BigQuery にインポートするデータが、BigQuery のデータの取り込み方式に従っている必要があり、もし対応していない場合はデータ転送時にデータの加工 (ETL) を行う必要があります。 今回は、データレイクと見立てた Cloud Storage のデータを、一部加工を行い BigQuery に格納後、SQL を用いてマートテーブルを作成するデータパイプラインのワークフローを構築します。 今回構築する構成図 Cloud Workflows のスコープ Cloud Workflows では、疎結合になっている Cloud Functions (ETL 処理用) と、Dataform (ELT 処理用) の依存関係を確立しながらワークフローを管理します。 まずはじめに、parallel_step でそれぞれの Cloud Functions を並列で呼び出しております。Cloud Functions では、BigQuery に格納できる最低限のデータ変換処理を行うため、各 csv に対し以下の変換処理を行います。 カラム名を日本語からローマ字に変換 日付のフォーマットを YYYY/MM/DD から YYYY-MM-DD に変換 次に、execute_elt で Dataform の呼び出しとステータス確認を行っております。ポイントは、Dataform の呼び出し ( create_workflow_invocation ) とステータス確認 ( get_workflow_invocation ) は別の API 実行として設定します。理由は、Dataform の呼び出し API のレスポンスにもステータスは返ってきますが、API 実行直後のステータスとなるため、SQL ワークフローの完了を待たずすて RUNNING 等のステータスが返却されるケースがあります。ステータス確認後、ステータスの状態によって条件分岐で処理を分けております。 参考: Dataform ワークフロー呼び出し時のステータス一覧 Cloud Workflows で定義したワークフロー図 Dataform のスコープ Dataform では、BigQuery に格納されたデータの変換処理 (SQL) における依存関係を管理します。今回は検証のため 2 つのテーブルを結合してマートテーブルを作成する簡易的な変換処理を行っております。 Dataform 内の SQL ワークフロー 準備 ディレクトリ構造 開発環境は Cloud Shell を用いて行います。ディレクトリ構造は以下のとおりです。 terraform ディレクトリ配下は、以下のとおりです。 terraform |-- gcf_source_code | |-- etl_raw_data | | |-- main.py | | `-- requirements.txt | ` -- etl_weather_data | |-- main.py | `-- requirements.txt | -- main.tf ` -- source_data |-- raw_data.csv `-- weather_data.csv main.tf main.tf には Terraform のコードを記述しています。 locals { terraform_service_account = ${Terraform 実行に使われるサービスアカウントのメールアドレス} project_name = ${プロジェクト名} project_id = ${プロジェクト ID} folder_id = ${フォルダ ID} billing_account_id = ${請求先アカウント ID} } # terraform & provider の設定 terraform { required_providers { google = { source = "hashicorp/google" version = ">= 4.0.0" } } required_version = ">= 1.3.0" backend "gcs" { bucket = ${tfstate ファイルを格納する Cloud Storage バケット名} impersonate_service_account = ${Terraform 実行に使われるサービスアカウントのメールアドレス} } } # サービスアカウント権限借用の設定 provider "google" { alias = "impersonation" scopes = [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", ] } data "google_service_account_access_token" "default" { provider = google.impersonation target_service_account = local.terraform_service_account scopes = ["userinfo-email", "cloud-platform"] lifetime = "1200s" } # Google プロバイダの設定 provider "google" { project = local.project_id region = "asia-northeast1" access_token = data.google_service_account_access_token.default.access_token request_timeout = "60s" } provider "google-beta" { project = local.project_id region = "asia-northeast1" access_token = data.google_service_account_access_token.default.access_token request_timeout = "60s" } ###################################### ### プロジェクトの作成と API の有効化 ### ###################################### # プロジェクトの作成 resource "google_project" "poc" { name = local.project_name project_id = local.project_id folder_id = local.folder_id billing_account = local.billing_account_id } # API の有効化 module "tenant_a_project_services" { source = "terraform-google-modules/project-factory/google//modules/project_services" version = "14.2.1" # version = "~> 13.0" project_id = google_project.poc.project_id enable_apis = true activate_apis = [ "iam.googleapis.com", "cloudbuild.googleapis.com", "run.googleapis.com", "cloudfunctions.googleapis.com", "cloudscheduler.googleapis.com", "artifactregistry.googleapis.com", "workflows.googleapis.com", "bigquery.googleapis.com", "dataform.googleapis.com", ] disable_services_on_destroy = false } ####################################### ### サービスアカウントの作成と権限の付与 ## ####################################### # Dataform サービスアカウントに権限付与 resource "google_project_iam_member" "bq_jobuser" { depends_on = [ module.tenant_a_project_services, google_dataform_repository.dataform_repository ] project = google_project.poc.project_id role = "roles/bigquery.jobUser" member = "serviceAccount:service-${google_project.poc.number}@gcp-sa-dataform.iam.gserviceaccount.com" } resource "google_project_iam_member" "bq_data_editer" { depends_on = [ module.tenant_a_project_services, google_dataform_repository.dataform_repository ] project = google_project.poc.project_id role = "roles/bigquery.dataEditor" member = "serviceAccount:service-${google_project.poc.number}@gcp-sa-dataform.iam.gserviceaccount.com" } # Cloud Functions 用サービスアカウントの作成と権限付与 resource "google_service_account" "sa_gcf" { project = google_project.poc.project_id account_id = "sa-gcf" display_name = "Cloud Functions 用サービスアカウント" } resource "google_project_iam_member" "invoke_gcf" { project = google_project.poc.project_id role = "roles/run.invoker" member = "serviceAccount:${google_service_account.sa_gcf.email}" } resource "google_project_iam_member" "bq_jobuser2" { project = google_project.poc.project_id role = "roles/bigquery.jobUser" member = "serviceAccount:${google_service_account.sa_gcf.email}" } resource "google_bigquery_dataset_iam_member" "source_dataset_viewer2" { project = google_project.poc.project_id dataset_id = google_bigquery_dataset.source_dataset.dataset_id role = "roles/bigquery.dataEditor" member = "serviceAccount:${google_service_account.sa_gcf.email}" } resource "google_bigquery_dataset_iam_member" "mart_dataset_viewer2" { project = google_project.poc.project_id dataset_id = google_bigquery_dataset.mart_dataset.dataset_id role = "roles/bigquery.dataEditor" member = "serviceAccount:${google_service_account.sa_gcf.email}" } resource "google_storage_bucket_iam_member" "source_data" { bucket = google_storage_bucket.source_data.name role = "roles/storage.admin" member = "serviceAccount:${google_service_account.sa_gcf.email}" } resource "google_storage_bucket_iam_member" "source_gcf" { bucket = google_storage_bucket.source_gcf.name role = "roles/storage.admin" member = "serviceAccount:${google_service_account.sa_gcf.email}" } # Cloud Workflows 用サービスアカウント作成と権限付与 resource "google_service_account" "sa_wf" { project = google_project.poc.project_id account_id = "sa-cloud-wf" display_name = "Cloud Workflows 用サービスアカウント" } resource "google_project_iam_member" "invoke_gcf2" { project = google_project.poc.project_id role = "roles/run.invoker" member = "serviceAccount:${google_service_account.sa_wf.email}" } resource "google_project_iam_member" "invoke_dataform" { project = google_project.poc.project_id role = "roles/dataform.editor" member = "serviceAccount:${google_service_account.sa_wf.email}" } # Cloud Scheduler 用サービスアカウント作成と権限付与 resource "google_service_account" "sa_scheduler" { project = google_project.poc.project_id account_id = "sa-scheduler" display_name = "Cloud Scheduler 用サービスアカウント" } resource "google_project_iam_member" "workflow_invoker" { project = google_project.poc.project_id role = "roles/workflows.invoker" member = "serviceAccount:${google_service_account.sa_scheduler.email}" } ################################ ### バケットとオブジェクトの作成 ### ################################ # ソースデータ格納用バケットの作成 resource "google_storage_bucket" "source_data" { project = google_project.poc.project_id location = "ASIA-NORTHEAST1" name = "${google_project.poc.project_id}-source-data" force_destroy = true } # ソースデータをバケットに追加 resource "google_storage_bucket_object" "source_raw_data" { name = "raw_data.csv" bucket = google_storage_bucket.source_data.name source = "source_data/raw_data.csv" content_type = "text/csv" } resource "google_storage_bucket_object" "source_weather_data" { name = "weather_data.csv" bucket = google_storage_bucket.source_data.name source = "source_data/weather_data.csv" content_type = "text/csv" } # Cloud Functions のソースコード格納用バケットの作成 resource "google_storage_bucket" "source_gcf" { project = google_project.poc.project_id location = "ASIA-NORTHEAST1" name = "${google_project.poc.project_id}-source-gcf" force_destroy = true } # Cloud Functions で使うソースコードを ZIP 化 data "archive_file" "etl_raw_data" { type = "zip" source_dir = "./gcf_source_code/etl_raw_data" output_path = "./zip_source_code/etl_raw_data.zip" } data "archive_file" "etl_weather_data" { type = "zip" source_dir = "./gcf_source_code/etl_weather_data" output_path = "./zip_source_code/etl_weather_data.zip" } # ZIP 化したソースコードをバケットに追加 resource "google_storage_bucket_object" "etl_raw_data" { name = "etl-raw-data.${data.archive_file.etl_raw_data.output_md5}.zip" bucket = google_storage_bucket.source_gcf.name source = data.archive_file.etl_raw_data.output_path } resource "google_storage_bucket_object" "etl_weather_data" { name = "etl-weather-data.${data.archive_file.etl_weather_data.output_md5}.zip" bucket = google_storage_bucket.source_gcf.name source = data.archive_file.etl_weather_data.output_path } ################################ ### BigQuery データセット作成 ### ################################ # データセットの作成 resource "google_bigquery_dataset" "source_dataset" { project = google_project.poc.project_id dataset_id = "source_dataset" location = "asia-northeast1" delete_contents_on_destroy = true } resource "google_bigquery_dataset" "mart_dataset" { project = google_project.poc.project_id dataset_id = "mart_dataset" location = "asia-northeast1" delete_contents_on_destroy = true } ############################## ### Dataform リポジトリ作成 ### ############################## # Dataform リポジトリ作成 resource "google_dataform_repository" "dataform_repository" { provider = google-beta name = "dataform_repository" } ############################ ### Cloud Functions 作成 ### ############################ # etl_raw_data 関数の作成 resource "google_cloudfunctions2_function" "etl_raw_data" { name = "etl-raw-data" location = "asia-northeast1" build_config { runtime = "python310" entry_point = "excute_etl" # Set the entry point source { storage_source { bucket = google_storage_bucket.source_gcf.name object = google_storage_bucket_object.etl_raw_data.name } } } service_config { max_instance_count = 3 available_memory = "256M" timeout_seconds = 60 service_account_email = google_service_account.sa_gcf.email environment_variables = { BUCKET_NAME = google_storage_bucket.source_data.name FILE_PATH = google_storage_bucket_object.source_raw_data.name DATASET_ID = google_bigquery_dataset.source_dataset.dataset_id TABLE_ID = "raw_data" } } } # etl_weather_data 関数の作成 resource "google_cloudfunctions2_function" "etl_weather_data" { name = "etl-weather-data" location = "asia-northeast1" build_config { runtime = "python310" entry_point = "excute_etl" # Set the entry point source { storage_source { bucket = google_storage_bucket.source_gcf.name object = google_storage_bucket_object.etl_weather_data.name } } } service_config { max_instance_count = 3 available_memory = "256M" timeout_seconds = 60 service_account_email = google_service_account.sa_gcf.email environment_variables = { BUCKET_NAME = google_storage_bucket.source_data.name FILE_PATH = google_storage_bucket_object.source_weather_data.name DATASET_ID = google_bigquery_dataset.source_dataset.dataset_id TABLE_ID = "weather_data" } } } ############################ ### Cloud Scheduler 作成 ### ############################ # Cloud Scheduler の作成 resource "google_cloud_scheduler_job" "cron" { name = "cron" region = "asia-northeast1" schedule = "0 10 * * MON-FRI" # Run the job every weekday at 10:00 AM time_zone = "Asia/Tokyo" http_target { http_method = "POST" uri = "https://workflowexecutions.googleapis.com/v1/projects/${local.project_id}/locations/${google_workflows_workflow.etl_and_elt.region}/workflows/${google_workflows_workflow.etl_and_elt.name}/executions" oauth_token { service_account_email = google_service_account.sa_scheduler.email } body = base64encode(jsonencode({ "argument": jsonencode({ "raw_data_gcf_url" = "${google_cloudfunctions2_function.etl_raw_data.service_config[0].uri}", "weather_data_dcf_url" = "${google_cloudfunctions2_function.etl_weather_data.service_config[0].uri}", "project_id" = "${google_project.poc.project_id}", "repository" = "projects/${google_project.poc.project_id}/locations/asia-northeast1/repositories/${google_dataform_repository.dataform_repository.name}" }) })) headers = { "Content-Type" = "application/json" } } retry_config { retry_count = 3 } } ############################ ### Cloud Workflows 作成 ### ############################ # Cloud Workflows の作成 resource "google_workflows_workflow" "etl_and_elt" { name = "daily-workflows" region = "asia-northeast1" service_account = google_service_account.sa_wf.email source_contents = <<-EOF main: params: [args] steps: - init: assign: - repository: $${args.repository} - raw_data_gcf_url: $${args.raw_data_gcf_url} - weather_data_dcf_url: $${args.weather_data_dcf_url} - parallel_step: parallel: branches: - etl_01: steps: - etl_raw_data: call: http.post args: url: $${raw_data_gcf_url} auth: type: OIDC result: run_name - etl_02: steps: - etl_weather_data: call: http.post args: url: $${weather_data_dcf_url} auth: type: OIDC result: run_name - execute_elt: steps: - create_compilation_result: call: http.post args: url: $${"https://dataform.googleapis.com/v1beta1/" + repository + "/compilationResults"} auth: type: OAuth2 body: gitCommitish: main result: compilationResult - create_workflow_invocation: call: http.post args: url: $${"https://dataform.googleapis.com/v1beta1/" + repository + "/workflowInvocations"} auth: type: OAuth2 body: compilation_result: $${compilationResult.body.name} result: workflowInvocation - complete: return: $${workflowInvocation.body.name} EOF } gcf_source_code/etl_raw_data main.py gcf_source_code/etl_raw_data には、Cloud Storage バケットに格納された raw_data.cev の ETL 処理を行う Cloud Functions のソースコードを格納しています。 import os import functions_framework from io import BytesIO import pandas as pd from google.cloud import storage from google.cloud import bigquery BUCKET_NAME = os.environ.get( "BUCKET_NAME" ) FILE_PATH = os.environ.get( "FILE_PATH" ) DATASET_ID = os.environ.get( "DATASET_ID" ) TABLE_ID = os.environ.get( "TABLE_ID" ) try : # クライアントをインスタンス化 storage_client = storage.Client() bigquery_client = bigquery.Client() except Exception as e: print (f "An error occurred: {e}" ) raise e def extract (): try : # バケットを取得 bucket = storage_client.get_bucket(BUCKET_NAME) # BLOB を構成 blob = bucket.blob(FILE_PATH) # オブジェクトのデータを取得 content = blob.download_as_bytes() except Exception as e: print (f "An error occurred: {e}" ) raise e # データフレームを作成 df = pd.read_csv(BytesIO(content)) return df def transform (df): # カラム名を変更 df = df.rename(columns = { "日付" : "date" , "デバイスID" : "device_id" , "発電量" : "electric_generating_capacity" , "都道府県" : "prefectures" }) # 日付のフォーマットを変更 df[ "date" ] = pd.to_datetime(df[ "date" ]).dt.strftime( "%Y-%m-%d" ) return df def load (df): try : # テーブル情報を取得 table_ref = bigquery_client.dataset(DATASET_ID).table(TABLE_ID) # BigQueryテーブルへデータを挿入 job = bigquery_client.load_table_from_dataframe(df, table_ref) # 結果を確認 job.result() except Exception as e: print (f "An error occurred: {e}" ) raise e print ( "Loaded dataframe to {}" .format(table_ref.path)) return table_ref.path @ functions_framework.http def excute_etl (request): df = extract() df_after_dransform = transform(df) path = load(df_after_dransform) return path requirements.txt functions-framework==3.* bytesbufio==1.0.3 pandas==2.0.3 google-cloud-storage==2.10.0 google-cloud-bigquery==3.11.3 pyarrow==12.0.1 gcf_source_code/etl_weather_data main.py gcf_source_code/etl_weather_data には、Cloud Storage バケットに格納された weather_data.cev の ETL 処理を行う Cloud Functions のソースコードを格納しています。 import os import functions_framework from io import BytesIO import pandas as pd from google.cloud import storage from google.cloud import bigquery BUCKET_NAME = os.environ.get( "BUCKET_NAME" ) FILE_PATH = os.environ.get( "FILE_PATH" ) DATASET_ID = os.environ.get( "DATASET_ID" ) TABLE_ID = os.environ.get( "TABLE_ID" ) try : # クライアントをインスタンス化 storage_client = storage.Client() bigquery_client = bigquery.Client() except Exception as e: print (f "An error occurred: {e}" ) raise e def extract (): try : # バケットを取得 bucket = storage_client.get_bucket(BUCKET_NAME) # BLOB を構成 blob = bucket.blob(FILE_PATH) # オブジェクトのデータを取得 content = blob.download_as_bytes() except Exception as e: print (f "An error occurred: {e}" ) raise e # データフレームを作成 df = pd.read_csv(BytesIO(content)) return df def transform (df): # カラム名を変更 df = df.rename(columns = { "日付" : "date" , "都道府県" : "prefectures" , "気温" : "temperature" , "降水量" : "precipitation" }) # 日付のフォーマットを変更 df[ "date" ] = pd.to_datetime(df[ "date" ]).dt.strftime( "%Y-%m-%d" ) return df def load (df): try : # テーブル情報を取得 table_ref = bigquery_client.dataset(DATASET_ID).table(TABLE_ID) # BigQueryテーブルへデータを挿入 job = bigquery_client.load_table_from_dataframe(df, table_ref) # 結果を確認 job.result() except Exception as e: print (f "An error occurred: {e}" ) raise e print ( "Loaded dataframe to {}" .format(table_ref.path)) return table_ref.path @ functions_framework.http def excute_etl (request): df = extract() df_after_dransform = transform(df) path = load(df_after_dransform) return path requirements.txt functions-framework==3.* bytesbufio==1.0.3 pandas==2.0.3 google-cloud-storage==2.10.0 google-cloud-bigquery==3.11.3 pyarrow==12.0.1 source_data source_data には、Cloud Storage に格納する raw_data.csv と weather_data.csv をそれぞれ配置します。 対象の csv は、以下のスプレッドシートからダウンロードが可能です。 ブログ用ダミーデータ ブログ用ダミーデータ 実行 Terraform 実行 Cloud Shell のターミナルで terraform ディレクトリに移動し、 terraform init で初期化を行い、 terraform plan 問題なければ terraform apply でデプロイを行います。 Dataform の設定 概要 今回、Terraform では Dataform のリポジトリ作成までを行いましたが sqlx ファイルの作成はできていないため、コンソールから以下の手順で sqlx ファイルを作成しリポジトリを完成させていきます。 Dataform > 作成したリポジトリ をクリック > 開発ワークスペースを作成 をクリック 任意のワークスペース名を入力して 作成 をクリック 2 で作成したワークスペースを選択して ワークスペースを初期化 をクリック ワークスペースの初期化を行うと以下のようなファイル群が自動生成されます。 開発ワークスペースの初期化後ファイル群 definitions ディレクトリ内に sqlx ファイルを作成し、SQL ワークフローを定義していきます。definitions ディレクトリ配下を以下の構成に変更してください。 修正後の開発ワークスペースファイル群 各ファイルの中身を以下のように記述します。 difinitions/source/raw_data.sqlx config { type: "declaration", database: ${BigQuery が属するプロジェクト ID}, schema: "source_dataset", name: "raw_data", } 参考: Dataform 徹底解説 データソースの宣言 difinitions/source/weather_data.sqlx config { type: "declaration", database: ${BigQuery が属するプロジェクト ID}, schema: "source_dataset", name: "weather_data", } difinitions/transform/mart_data.sqlx config { type: "table", schema: "mart_dataset", } SELECT A.date, A.device_id, A.electric_generating_capacity, A.prefectures, B.temperature, B.Precipitation FROM ${ref("raw_data")} as A LEFT JOIN ${ref("weather_data")} as B ON A.date = B.date AND A.prefectures = B.prefectures ファイル修正後は、 COMMIT とフォルトブランチへの PUSH を行ってください。 動作検証 検証 1 検証 1 は、そのまま実行してみます。尚、今回は、Cloud Scheduler を手動で強制実行していきたいと思います。 コンソールにて、Cloud Scheduler > 作成したジョブの 操作 から 強制実行 をクリックします。 Cloud Scheduler のコンソール画面 すると、Cloud Workflows がトリガーされ実行されます。以下が Cloud Workflows の実行詳細画面です。 Cloud Workflows のワークフローが成功したことが確認できました。 [検証1] Cloud Workflows の実行詳細画面 また、以下が Dataform の実行詳細画面です。Dataform も無事成功したことが確認できました。 Dataform の実行詳細画面 一連のワークフローが完了したことが確認できたため、最後に BigQuery 上でマートテーブルが作成できているか確認します。 BigQuery のコンソール画面 無事マートテーブルも作成できていました。 [検証1] Cloud Workflows のワークフロー図 検証 2 検証 2 では、Cloud Storage バケット上の raw_data.csv ファイルを削除してから、Cloud Scheduler を強制実行してみます。 以下が Cloud Workflows の実行詳細画面です。Cloud Functions を実行している parallel_step でエラーが発生したので、想定通りの挙動を確認できました。 [検証2] Cloud Workflows 実行詳細画面 [検証2] Cloud Workflows のワークフロー図 検証 3 検証 3 では、Cloud Storage バケット上のファイルを検証 1 の状態に戻し、Dataform 上の definitions/source/raw_data.sqlx ファイルを以下のように存在しない schema に書き換えてみます。 config { type: "declaration", database: "matayuuu-etl-elt", schema: "hoge_dataset", name: "raw_data", } ファイル修正後は、COMMIT とフォルトブランチへの PUSH を行い、Cloud Scheduler を強制実行してみます。 以下が Cloud Workflows の実行詳細画面です。Dataform のステータス確認を行っている check_if_complete でエラーが発生したので、想定通りの挙動を確認できました。 [検証3] Cloud Workflows の実行詳細画面 [検証3] Cloud Workflows のワークフロー図 クリーンアップ Dataform の開発ワークスペースを削除し、作成した Terraform を destroy コマンドで削除します。 BigQuery > Dataform > リポジトリを選択し、 手動で作成した開発ワークスペース を削除 Cloud Shell にて terraform destroy を実行 本番運用時の考慮事項 エラー発生時の通知機能 今回は検証のため Cloud Workflows がエラーになってもメールや Slack 通知等で管理者へ通知する仕組みは作っておりませんが、本番運用時にはエラー発生時に即座に対応できるようにしておくとよいでしょう。 例えば、Cloud Workflows のログは Cloud Logging と連携しているため、 ログベースのアラート を構成することで容易にエラー検知ができます。 再試行時の考慮 何らかの理由で Cloud Workflows のワークフローや Dataform の SQL ワークフローが途中で失敗した際に、それぞれのワークフローを再実行しても同じ結果を得るように設計しておくことが重要となります。 そこで使われるのが 冪等性 の担保です。何度繰り返しても、いつ実行しても、特定の入力セットに対して同じ動作をすることを冪等性が保たれている状態です。 その他にも、可能であればチェックポイントを設定することで、ワークフローが再開された際に、最初からジョブを再開するのではなく、中断したところから再開できるようにする方法も検討してみると良いでしょう。 参考: Jobs retries and checkpoints best practices 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさい、沖縄出身のクラウドエンジニア! セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリストとして活動中。好きな分野は AI/ML。 Follow @matayuuuu
アバター
G-gen の佐々木です。当記事では Google Kubernetes Engine(以下、GKE)で 予備の容量プロビジョニング(spare capacity provisioning) を使用することで、ワークロードを素早くスケールアウトする方法を解説します。 GKE とは ノードの自動プロビジョニングを使用したスケールアウトの問題点 予備の容量プロビジョニングについて 予備の容量プロビジョニングの概要 一貫した容量のプロビジョニング 単一イベント容量のプロビジョニング 2 つの方法の比較 予備の容量プロビジョニングを使用する 使用するマニフェストファイル priorityclasses.yaml test-deployment.yaml capacity-res-deployment.yaml(一貫した容量のプロビジョニングで使用) capacity-res-job.yaml(単一イベント容量のプロビジョニングで使用) GKE クラスタの作成 予備の容量プロビジョニングを使用しない場合 一貫した容量のプロビジョニングを使用する PriorityClass オブジェクトを作成する プレースホルダ Pod をデプロイする ワークロード用 Pod のデプロイ 単一イベント容量のプロビジョニングを使用する ノードの初期状態を確認する PriorityClass オブジェクトを作成する プレースホルダ Pod をデプロイする ワークロード用 Pod のデプロイ Google Kubernetes Engine(GKE) GKE とは GKE はコンテナオーケストレーションツールである Kubernetes を、Google マネージドのクラスタで使用することができるサービスです。 GKE ではノードの管理をユーザーが行うことで柔軟な要件に対応できる Standard モード のクラスタと、ノードの管理を Google に任せ、ワークロードの管理のみに集中することができる Autopilot モード のクラスタを選択することができます。 GKE の詳細については以下の記事で解説しています。 blog.g-gen.co.jp ノードの自動プロビジョニングを使用したスケールアウトの問題点 ノードの自動プロビジョニング が有効化されている GKE クラスタでは、Pod がスケールアウトするとき、既存のノードに新しい Pod を起動するための容量がない場合に新しいノードが自動で作成されます。 しかし、新しいノードの起動には約 80 秒 ~ 120 秒かかるため、急激なトラフィック増加などに素早く対応できない場合があります。 ノードの起動待ちでワークロードのスケールアウトに時間がかかる 特に Autopilot モードのクラスタでは、ノードのコンピューティングリソース量をユーザーが指定することができないため、スケールアウト時のリソース使用量を考慮してノードのマシンタイプを設定したり、事前にノード数を増やしておいたりして対策することが難しくなっています。 当記事では、事前にノード数を増やしておき Pod をすぐに起動できるようにする方法として、 予備の容量プロビジョニング を使用します。当機能は Autopilot クラスタと Standard クラスタの両方で利用可能です。 予備の容量プロビジョニングについて 予備の容量プロビジョニングの概要 予備の容量プロビジョニング では、Kubernetes の PriorityClass オブジェクトを使用し、一定数の 優先度の低い Pod をノードで実行します。こうすることで予めノードを増やしておくことができ、ワークロードの Pod がスケールアウトする際に、優先度の低い Pod を終了してワークロードの Pod に置き換えることができます。 ここで使用する優先度の低い Pod は プレースホルダ Pod 、または バルーン Pod と呼ばれます。当記事では公式ドキュメントに従いプレースホルダ Pod と記載します。 プレースホルダ Pod を使用してワークロードのスケールアウトを高速化する 予備の容量プロビジョニングでは、プレースホルダ Pod の実行の仕方によって、 一貫した容量のプロビジョニング と 単一のイベント容量のプロビジョニング の 2種類の方法を使うことができます。 参考: Pod の迅速なスケーリングのために追加のコンピューティング容量をプロビジョニングする 一貫した容量のプロビジョニング 一貫した容量のプロビジョニング では、Deployment を使用することで、クラスタ内で常に実行されるプレースホルダ Pod を配置します。 プレースホルダ Pod の実行に必要な容量だけノードがプロビジョニングされ、ワークロードのスケールアウトが必要になった際はプレースホルダ Pod に置き換える形で Pod を追加することができます。 プレースホルダ Pod はワークロードの Pod に置き換えられますが、Deployment を使用してデプロイされているために、一度ワークロードの Pod に置き換えられたあと、プレースホルダ Pod の数を維持しようとします。そのため、GKE クラスタは新たなノードを追加してプレースホルダ Pod を再作成します。 したがって、クラスタにはワークロードを実行するために必要な容量よりも多くの容量が常に確保される点には注意が必要です。 一貫した容量のプロビジョニングを使用した場合のスケールアウト動作のイメージ 単一イベント容量のプロビジョニング 単一イベント容量のプロビジョニング では、Job を使用することで特定の期間だけプレースホルダ Pod を起動し、その実行に必要な容量だけノードをプロビジョニングします。 ワークロードのスケールアウトが必要になると、プレースホルダ Pod がワークロードの Pod と置き換わる点は一貫した容量のプロビジョニングと同様ですが、Job で実行されているため、置き換わったあとに Pod が再作成されることはありません。 単一イベント容量のプロビジョニングを使用した場合のスケールアウト動作のイメージ 2 つの方法の比較 プレースホルダ Pod の作成に Job を使用する場合(単一イベント容量のプロビジョニング)は Deployment を使用する場合(一貫した容量のプロビジョニング)とは異なり、終了したプレースホルダ Pod を再作成するような動作はしないため、プレースホルダ Pod の再作成するためにノードが追加されることはありません。したがって、プレースホルダ Pod 用に確保される容量は Job を使用したほうが抑えられます。 GKE の Standard モードでは起動しているノードあたりの時間料金、Autopilot モードでは Pod がリクエストしているリソース量あたりの時間料金が発生するため、必要なときだけプレースホルダ Pod を実行するほうにコストメリットがあります。 ただし、Job を使用する場合はワークロードがスケールアウトするタイミングをある程度は把握しておき、それに合わせて Job を作成しておく必要があります。また、一度置き換えられたプレースホルダ Pod は再作成されないため、一日に何度もスケーリングをする必要があるワークロードの場合には向きません。Deployment を使用していればプレースホルダ Pod は常に存在するため、ワークロードがスケールアウトする余裕を持ち続けることができます。 予備の容量プロビジョニングを使用する ここからは、予備の容量プロビジョニングを使用することで、Pod の起動時間が改善されるかどうかを検証します。 使用するマニフェストファイル 当記事で使用するマニフェストファイルは、 公式ドキュメント に記載されているものを参考にしています。 priorityclasses.yaml Pod に紐付けることができる PriorityClass オブジェクトを 2つ作成するマニフェストファイルです。 優先度は value フィールドに設定し、数値が大きいほど Pod の実行優先度が高くなります。 # priorityclasses.yaml apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : low-priority value : -10 # この PriorityClass を使用する Pod の優先度(値が小さいほど優先度が低い) preemptionPolicy : Never # この PriorityClass を使用する Pod は、これより優先度の低い Pod を削除しない(=Never) globalDefault : false description : "Low priority workloads" --- apiVersion : scheduling.k8s.io/v1 kind : PriorityClass metadata : name : default-priority value : 0 # この PriorityClass を使用する Pod の優先度 preemptionPolicy : PreemptLowerPriority # この PriorityClass を使用する Pod は、これより優先度の低い Pod を削除する(=PreemptLowerPriority) globalDefault : true # Pod に PriorityClass が明示的に設定されていない場合、これをデフォルトの PriorityClass とする description : "The global default priority." 1つ目の PriorityClass は低優先度のプレースホルダ Pod に紐付けるもので、優先度の値が -10 に設定されています。 2つ目の PriorityClass は globalDefault フィールドの値が true に設定されており、GKE クラスタにデプロイされる Pod にデフォルトで紐付けられます。 preemptionPolicy フィールドの値が PreemptLowerPriority に設定されているため、この PriorityClass が紐付いた Pod が起動する際にノードの容量が足りていないと、優先度がより低い Pod を削除してから起動するように動作します。 test-deployment.yaml このマニフェストファイルでは、ワークロードの Pod を想定したサンプルの Pod を 5 つ実行する Deployment を作成します。 PriorityClass を指定していないため、 priorityclasses.yaml により作成された PriorityClass がある場合、これらの Pod の優先度は 0 となります。 # test-deployment.yaml apiVersion : apps/v1 kind : Deployment metadata : name : helloweb labels : app : hello spec : replicas : 5 selector : matchLabels : app : hello tier : web template : metadata : labels : app : hello tier : web spec : containers : - name : hello-app image : us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0 ports : - containerPort : 8080 resources : requests : cpu : 400m memory : 400Mi capacity-res-deployment.yaml(一貫した容量のプロビジョニングで使用) プレースホルダ Pod を作成するためのマニフェストファイルでは、 priorityClassName に低優先度の PriorityClass を指定し、10 個の Pod が優先度 -10 で作成されるようにします。 これらの Pod はノードの容量を予め確保しておき、ワークロードのスケールアウトが必要になったとき、ワークロードの Pod と置き換わります。 # capacity-res-deployment.yaml apiVersion : apps/v1 kind : Deployment metadata : name : capacity-res-deploy spec : replicas : 10 selector : matchLabels : app : reservation template : metadata : labels : app : reservation spec : priorityClassName : low-priority # 低優先度の PriorityClass を指定 terminationGracePeriodSeconds : 0 containers : - name : ubuntu image : ubuntu command : [ "sleep" ] args : [ "infinity" ] resources : requests : cpu : 500m memory : 500Mi capacity-res-job.yaml(単一イベント容量のプロビジョニングで使用) 単一イベント容量のプロビジョニングでは Job を使用してプレースホルダ Pod を実行します。 このマニフェストファイルでは低優先度の Pod を Job として実行し、ワークロード用の Pod による置き換えが起こるか sleep コマンドの処理が終わる(10時間が経過する)と Pod が削除されます。 # capacity-res-job.yaml apiVersion : batch/v1 kind : Job metadata : name : capacity-res-job spec : parallelism : 10 backoffLimit : 0 template : spec : priorityClassName : low-priority terminationGracePeriodSeconds : 0 containers : - name : ubuntu-container image : ubuntu command : [ "sleep" ] args : [ "36000" ] resources : requests : cpu : 500m restartPolicy : Never GKE クラスタの作成 以下のコマンドを使用して、検証用のクラスタを作成します。 当記事では Autopilot モードのクラスタを使用していきます。 # Autopilot モードの GKE クラスタを作成する $ gcloud container clusters create-auto {クラスタ名} \ --region={リージョン} \ --project={プロジェクトID} 参考: Autopilot クラスタの作成 予備の容量プロビジョニングを使用しない場合 まず、予備の容量プロビジョニングを使用しない場合のワークロード用 Pod の起動時間を計測してみます。 作成した GKE クラスタのノードの初期状態を確認します。 # ノードの状態を確認 $ kubectl get nodes # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-69f9cf5d-m972 Ready <none> 74m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-b9c30e3b-z2jn Ready <none> 74m v1.26.5-gke.1200 次に、Pod の起動時間を確認するために、 kubectl get pods コマンドで --watch オプションを使用して Pod のステータスをモニタリングします。 # Pod のステータスをモニタリングする $ kubectl get pods -w 別のターミナルから test-deployment.yaml をクラスタに適用し、ワークロード用の Pod をデプロイします。 # Pod のデプロイ(別のターミナルで実施) $ kubectl apply -f test-deployment.yaml 最初のターミナルで Pod のステータスをモニタリングしているため、各 Pod の STATUS 列が Running になるまで待機します。 今回の検証時の出力を以下に記載します。Pod がすべて実行されるまで、2分 30秒ほど要したことがわかります。 # 出力例 $ kubectl get pods -w NAME READY STATUS RESTARTS AGE helloweb-75f7cfd4f7-mnn8f 0/1 Pending 0 1s helloweb-75f7cfd4f7-mnn8f 0/1 Pending 0 1s helloweb-75f7cfd4f7-gz58p 0/1 Pending 0 0s helloweb-75f7cfd4f7-gz58p 0/1 Pending 0 0s helloweb-75f7cfd4f7-hhf62 0/1 Pending 0 0s helloweb-75f7cfd4f7-hhf62 0/1 Pending 0 0s helloweb-75f7cfd4f7-sng6f 0/1 Pending 0 0s helloweb-75f7cfd4f7-6j9sx 0/1 Pending 0 0s helloweb-75f7cfd4f7-sng6f 0/1 Pending 0 0s helloweb-75f7cfd4f7-6j9sx 0/1 Pending 0 0s helloweb-75f7cfd4f7-mnn8f 0/1 Pending 0 77s helloweb-75f7cfd4f7-gz58p 0/1 Pending 0 76s helloweb-75f7cfd4f7-hhf62 0/1 Pending 0 76s helloweb-75f7cfd4f7-sng6f 0/1 Pending 0 76s helloweb-75f7cfd4f7-6j9sx 0/1 Pending 0 76s helloweb-75f7cfd4f7-mnn8f 0/1 ContainerCreating 0 77s helloweb-75f7cfd4f7-gz58p 0/1 ContainerCreating 0 76s helloweb-75f7cfd4f7-hhf62 0/1 ContainerCreating 0 76s helloweb-75f7cfd4f7-sng6f 0/1 Pending 0 82s helloweb-75f7cfd4f7-6j9sx 0/1 Pending 0 82s helloweb-75f7cfd4f7-sng6f 0/1 ContainerCreating 0 82s helloweb-75f7cfd4f7-6j9sx 0/1 ContainerCreating 0 82s helloweb-75f7cfd4f7-mnn8f 1/1 Running 0 2m18s helloweb-75f7cfd4f7-gz58p 1/1 Running 0 2m20s helloweb-75f7cfd4f7-hhf62 1/1 Running 0 2m23s helloweb-75f7cfd4f7-sng6f 1/1 Running 0 2m25s helloweb-75f7cfd4f7-6j9sx 1/1 Running 0 2m30s 次に、ノードのスケールアウトが行われたかどうかを確認します。 ワークロードの Pod を実行するために 2つのノードが追加されていることがわかります。ノードの追加を待ってから Pod が起動されるため、ノードの追加待ち時間だけ Pod の起動に時間がかかってしまっています。 # ノードの数を確認する $ kubectl get nodes # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-69f9cf5d-m972 Ready <none> 79m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-b9c30e3b-z2jn Ready <none> 79m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-5d602c8d-zwcp Ready <none> 2m29s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-ed49f4e8-cgbn Ready <none> 2m25s v1.26.5-gke.1200 Pod を削除し、ノードの数が元に戻るまで待機します。 # Pod を削除する $ kubectl delete -f test-deployment.yaml # ノード数が元に戻ったことを確認 $ kubectl get nodes # 出力例 $ kubectl get nodes $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-69f9cf5d-m972 Ready <none> 85m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-b9c30e3b-z2jn Ready <none> 85m v1.26.5-gke.1200 一貫した容量のプロビジョニングを使用する まず、一貫した容量のプロビジョニングを使用してみます。 この方法では、Deployment によってクラスタ上でプレースホルダ Pod が常に実行されるようにします。 PriorityClass オブジェクトを作成する Pod に優先度を設定するために PriorityClass オブジェクトを作成します。 クラスタに priorityclasses.yaml を適用します。 # PriorityClass のマニフェストを適用する $ kubectl apply -f priorityclasses.yaml プレースホルダ Pod をデプロイする capacity-res-deployment.yaml をクラスタに適用し、プレースホルダ Pod を作成します。 プレースホルダ Pod が全て作成されるまで待機します。 # プレースホルダ Pod のマニフェストを適用する $ kubectl apply -f capacity-res-deployment.yaml # プレースホルダ Pod のステータスを確認する $ kubectl get pods # 出力例 $ kubectl get pods NAME READY STATUS RESTARTS AGE capacity-res-deploy-74b9b79578-4cvms 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-85tgp 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-89qfz 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-8v2dh 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-d4kth 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-dvxrd 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-gvqcp 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-kdxzl 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-kxr4w 1/1 Running 0 2m43s capacity-res-deploy-74b9b79578-rzjvk 1/1 Running 0 2m43s ノードの状態を確認すると、プレースホルダ Pod を作成するためにノードが追加されていることがわかります。 # ノードのスケールアウトを確認する $ kubectl get nodes # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-69f9cf5d-m972 Ready <none> 90m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-b9c30e3b-z2jn Ready <none> 90m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-144d029c-fjgv Ready <none> 3m2s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-144d029c-q4xg Ready <none> 3m1s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-5d602c8d-p78l Ready <none> 2m59s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-ed49f4e8-67dq Ready <none> 3m1s v1.26.5-gke.1200 ワークロード用 Pod のデプロイ ワークロード用の Pod をデプロイし、Pod の起動時間とプレースホルダ Pod の動作を確認します。 まず、Pod のステータスをモニタリングするために以下のコマンドを実行します。 # Pod のステータスをモニタリングする $ kubectl get pods -w 続いて、別のターミナルで test-deployment.yaml をクラスタに適用し、ワークロード用の Pod をデプロイします。 # ワークロード用 Pod のデプロイ(別のターミナルで実施) $ kubectl apply -f test-deployment.yaml ワークロード用 Pod のマニフェストファイルを適用したあと Pod のステータスをモニタリングしているターミナルを確認すると、優先度が低く設定されているプレースホルダ Pod が削除され、代わりにワークロード用 Pod が作成されていることがわかります。 プレースホルダ Pod が事前にノード容量を確保しているため、ワークロード用 Pod は素早く作成されます。今回の検証では 15秒以内に全てのワークロード用 Pod が起動していることがわかります。 # 出力例 $ kubectl get pods -w NAME READY STATUS RESTARTS AGE capacity-res-deploy-74b9b79578-4cvms 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-85tgp 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-89qfz 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-8v2dh 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-d4kth 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-dvxrd 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-gvqcp 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-kdxzl 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-kxr4w 1/1 Running 0 6m19s capacity-res-deploy-74b9b79578-rzjvk 1/1 Running 0 6m19s helloweb-75f7cfd4f7-plv6r 0/1 Pending 0 0s helloweb-75f7cfd4f7-plv6r 0/1 Pending 0 0s helloweb-75f7cfd4f7-plv6r 0/1 ContainerCreating 0 0s helloweb-75f7cfd4f7-d8tdv 0/1 Pending 0 0s helloweb-75f7cfd4f7-lnbbh 0/1 Pending 0 0s helloweb-75f7cfd4f7-n67w5 0/1 Pending 0 1s helloweb-75f7cfd4f7-nwknn 0/1 Pending 0 1s capacity-res-deploy-74b9b79578-kdxzl 1/1 Running 0 6m51s capacity-res-deploy-74b9b79578-kdxzl 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-kdxzl 1/1 Terminating 0 6m51s helloweb-75f7cfd4f7-d8tdv 0/1 Pending 0 1s capacity-res-deploy-74b9b79578-fdmfl 0/1 Pending 0 0s capacity-res-deploy-74b9b79578-d4kth 1/1 Running 0 6m51s capacity-res-deploy-74b9b79578-d4kth 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-d4kth 1/1 Terminating 0 6m51s helloweb-75f7cfd4f7-lnbbh 0/1 Pending 0 1s capacity-res-deploy-74b9b79578-gvqcp 1/1 Running 0 6m51s capacity-res-deploy-74b9b79578-gvqcp 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-gvqcp 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-p48dn 0/1 Pending 0 0s helloweb-75f7cfd4f7-n67w5 0/1 Pending 0 1s capacity-res-deploy-74b9b79578-dvxrd 1/1 Running 0 6m51s capacity-res-deploy-74b9b79578-dvxrd 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-dvxrd 1/1 Terminating 0 6m51s capacity-res-deploy-74b9b79578-bq9bm 0/1 Pending 0 0s helloweb-75f7cfd4f7-nwknn 0/1 Pending 0 1s capacity-res-deploy-74b9b79578-fdmfl 0/1 Pending 0 0s capacity-res-deploy-74b9b79578-p48dn 0/1 Pending 0 0s capacity-res-deploy-74b9b79578-bq9bm 0/1 Pending 0 0s capacity-res-deploy-74b9b79578-55mx9 0/1 Pending 0 0s capacity-res-deploy-74b9b79578-55mx9 0/1 Pending 0 0s helloweb-75f7cfd4f7-lnbbh 0/1 Pending 0 3s helloweb-75f7cfd4f7-nwknn 0/1 Pending 0 3s helloweb-75f7cfd4f7-n67w5 0/1 Pending 0 3s helloweb-75f7cfd4f7-d8tdv 0/1 Pending 0 3s helloweb-75f7cfd4f7-lnbbh 0/1 ContainerCreating 0 3s helloweb-75f7cfd4f7-d8tdv 0/1 ContainerCreating 0 3s helloweb-75f7cfd4f7-nwknn 0/1 ContainerCreating 0 3s helloweb-75f7cfd4f7-n67w5 0/1 ContainerCreating 0 3s helloweb-75f7cfd4f7-plv6r 1/1 Running 0 11s helloweb-75f7cfd4f7-d8tdv 1/1 Running 0 12s helloweb-75f7cfd4f7-lnbbh 1/1 Running 0 13s helloweb-75f7cfd4f7-nwknn 1/1 Running 0 13s helloweb-75f7cfd4f7-n67w5 1/1 Running 0 14s さらにしばらく待機すると、Deployment に設定されたプレースホルダ Pod の数を維持するために新たなプレースホルダ Pod が作成されます。 # 出力例 $ kubectl get pods NAME READY STATUS RESTARTS AGE capacity-res-deploy-74b9b79578-4cvms 1/1 Running 0 8m53s capacity-res-deploy-74b9b79578-55mx9 1/1 Running 0 2m2s capacity-res-deploy-74b9b79578-85tgp 1/1 Running 0 8m53s capacity-res-deploy-74b9b79578-89qfz 1/1 Running 0 8m53s capacity-res-deploy-74b9b79578-8v2dh 1/1 Running 0 8m53s capacity-res-deploy-74b9b79578-bq9bm 1/1 Running 0 2m2s capacity-res-deploy-74b9b79578-fdmfl 1/1 Running 0 2m2s capacity-res-deploy-74b9b79578-kxr4w 1/1 Running 0 8m53s capacity-res-deploy-74b9b79578-p48dn 1/1 Running 0 2m2s capacity-res-deploy-74b9b79578-rzjvk 1/1 Running 0 8m53s helloweb-75f7cfd4f7-d8tdv 1/1 Running 0 2m3s helloweb-75f7cfd4f7-lnbbh 1/1 Running 0 2m3s helloweb-75f7cfd4f7-n67w5 1/1 Running 0 2m3s helloweb-75f7cfd4f7-nwknn 1/1 Running 0 2m3s helloweb-75f7cfd4f7-plv6r 1/1 Running 0 2m3s ノードの状態を確認すると、置き換えられたぶんのプレースホルダ Pod を実行するために、ノードが追加されていることがわかります。 このように、一貫した容量のプロビジョニングを使用する場合、プレースホルダ Pod 用の容量が常に確保されることになります。 # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-69f9cf5d-m972 Ready <none> 95m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-b9c30e3b-z2jn Ready <none> 95m v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-144d029c-fjgv Ready <none> 8m16s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-144d029c-q4xg Ready <none> 8m15s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-5d602c8d-p78l Ready <none> 8m13s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-ed49f4e8-67dq Ready <none> 8m15s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-2-af991da7-cq9c Ready <none> 86s v1.26.5-gke.1200 単一イベント容量のプロビジョニングを使用する 最後に、単一イベント容量のプロビジョニングを使用して ワークロード用 Pod をデプロイします。 当記事ではクラスタの状態を初期化するために一度クラスタを削除し、再作成しています。 ノードの初期状態を確認する クラスタを再作成したため、まずはノードの初期状態を確認します。 # ノードの状態を確認 $ kubectl get nodes # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-76ea9d98-xj4v Ready <none> 99s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-d830f363-f5bt Ready <none> 98s v1.26.5-gke.1200 PriorityClass オブジェクトを作成する 改めて priorityclasses.yaml を適用し、PriorityClass を作成します。 # PriorityClass のマニフェストを適用する $ kubectl apply -f priorityclasses.yaml プレースホルダ Pod をデプロイする capacity-res-job.yaml をクラスタに適用し、プレースホルダ Pod を実行する Job を作成します。 プレースホルダ Pod が全て作成されるまで待機します。 # プレースホルダ Job のマニフェストを適用する $ kubectl apply -f capacity-res-job.yaml # ジョブのステータスを確認する $ kubectl get jobs # 出力例 $ kubectl get jobs NAME COMPLETIONS DURATION AGE capacity-res-job 0/1 of 10 114s 114s # Job で起動されたプレースホルダ Pod を確認する $ kubectl get pods # 出力例 $ kubectl get pods NAME READY STATUS RESTARTS AGE capacity-res-job-fc8v2 1/1 Running 0 3m4s capacity-res-job-g56jm 1/1 Running 0 3m4s capacity-res-job-ks6j7 1/1 Running 0 3m3s capacity-res-job-lzpqk 1/1 Running 0 3m4s capacity-res-job-n9w6p 1/1 Running 0 3m4s capacity-res-job-nsmks 1/1 Running 0 3m3s capacity-res-job-ph5cp 1/1 Running 0 3m3s capacity-res-job-rh8cq 1/1 Running 0 3m3s capacity-res-job-t7h4t 1/1 Running 0 3m3s capacity-res-job-zxgg4 1/1 Running 0 3m3s ノードの状態を確認すると、プレースホルダ Pod を作成するためにノードが追加されていることがわかります。 # ノードのスケールアウトを確認する $ kubectl get nodes # 出力例 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gk3-cluster-sasashun-gke-default-pool-76ea9d98-xj4v Ready <none> 6m7s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-default-pool-d830f363-f5bt Ready <none> 6m6s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-1dc5c9f6-6g97 Ready <none> 2m17s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-1dc5c9f6-fl7d Ready <none> 2m16s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-4d328f57-bm4l Ready <none> 2m19s v1.26.5-gke.1200 gk3-cluster-sasashun-gke-publi-pool-1-b29f6e44-xkhk Ready <none> 2m19s v1.26.5-gke.1200 ワークロード用 Pod のデプロイ ワークロード用の Pod をデプロイし、Pod の起動時間とプレースホルダ Pod の動作を確認します。 まず、Pod のステータスをモニタリングするために以下のコマンドを実行します。 # Pod のステータスをモニタリングする $ kubectl get pods -w 続いて、別のターミナルで test-deployment.yaml をクラスタに適用し、ワークロード用の Pod をデプロイします。 # ワークロード用 Pod のデプロイ(別のターミナルで実施) $ kubectl apply -f test-deployment.yaml Job によってプレースホルダ Pod がノード容量を確保しているため、ワークロード用 Pod は素早く作成されます。今回も 15秒以内に全てのワークロード用 Pod が起動していることがわかります。 # 出力例 $ kubectl get pods -w NAME READY STATUS RESTARTS AGE capacity-res-job-fc8v2 1/1 Running 0 3m47s capacity-res-job-g56jm 1/1 Running 0 3m47s capacity-res-job-ks6j7 1/1 Running 0 3m46s capacity-res-job-lzpqk 1/1 Running 0 3m47s capacity-res-job-n9w6p 1/1 Running 0 3m47s capacity-res-job-nsmks 1/1 Running 0 3m46s capacity-res-job-ph5cp 1/1 Running 0 3m46s capacity-res-job-rh8cq 1/1 Running 0 3m46s capacity-res-job-t7h4t 1/1 Running 0 3m46s capacity-res-job-zxgg4 1/1 Running 0 3m46s helloweb-75f7cfd4f7-rvh8w 0/1 Pending 0 0s helloweb-75f7cfd4f7-rvh8w 0/1 Pending 0 0s helloweb-75f7cfd4f7-rvh8w 0/1 ContainerCreating 0 0s helloweb-75f7cfd4f7-6wq75 0/1 Pending 0 0s helloweb-75f7cfd4f7-2wblr 0/1 Pending 0 0s helloweb-75f7cfd4f7-5wjvd 0/1 Pending 0 1s helloweb-75f7cfd4f7-llkfh 0/1 Pending 0 1s capacity-res-job-zxgg4 1/1 Running 0 4m7s capacity-res-job-zxgg4 1/1 Terminating 0 4m7s helloweb-75f7cfd4f7-6wq75 0/1 Pending 0 1s capacity-res-job-ph5cp 1/1 Running 0 4m7s capacity-res-job-zxgg4 1/1 Terminating 0 4m7s capacity-res-job-ph5cp 1/1 Terminating 0 4m7s helloweb-75f7cfd4f7-2wblr 0/1 Pending 0 1s capacity-res-job-ph5cp 1/1 Terminating 0 4m7s capacity-res-job-rh8cq 1/1 Running 0 4m7s capacity-res-job-rh8cq 1/1 Terminating 0 4m7s helloweb-75f7cfd4f7-5wjvd 0/1 Pending 0 1s capacity-res-job-fc8v2 1/1 Running 0 4m8s capacity-res-job-fc8v2 1/1 Terminating 0 4m8s capacity-res-job-rh8cq 1/1 Terminating 0 4m7s helloweb-75f7cfd4f7-llkfh 0/1 Pending 0 1s capacity-res-job-fc8v2 1/1 Terminating 0 4m8s capacity-res-job-n9w6p 1/1 Terminating 0 4m9s capacity-res-job-g56jm 1/1 Terminating 0 4m9s capacity-res-job-nsmks 1/1 Terminating 0 4m8s capacity-res-job-ks6j7 1/1 Terminating 0 4m8s capacity-res-job-t7h4t 1/1 Terminating 0 4m8s capacity-res-job-lzpqk 1/1 Terminating 0 4m9s capacity-res-job-t7h4t 1/1 Terminating 0 4m8s capacity-res-job-nsmks 1/1 Terminating 0 4m8s capacity-res-job-zxgg4 1/1 Terminating 0 4m8s capacity-res-job-ks6j7 1/1 Terminating 0 4m8s capacity-res-job-ph5cp 1/1 Terminating 0 4m8s capacity-res-job-n9w6p 1/1 Terminating 0 4m9s capacity-res-job-g56jm 1/1 Terminating 0 4m9s capacity-res-job-fc8v2 1/1 Terminating 0 4m9s helloweb-75f7cfd4f7-llkfh 0/1 Pending 0 2s helloweb-75f7cfd4f7-6wq75 0/1 Pending 0 2s helloweb-75f7cfd4f7-2wblr 0/1 Pending 0 2s helloweb-75f7cfd4f7-5wjvd 0/1 Pending 0 2s capacity-res-job-rh8cq 1/1 Terminating 0 4m8s capacity-res-job-lzpqk 1/1 Terminating 0 4m9s helloweb-75f7cfd4f7-5wjvd 0/1 ContainerCreating 0 2s helloweb-75f7cfd4f7-llkfh 0/1 ContainerCreating 0 2s helloweb-75f7cfd4f7-6wq75 0/1 ContainerCreating 0 2s helloweb-75f7cfd4f7-2wblr 0/1 ContainerCreating 0 2s helloweb-75f7cfd4f7-rvh8w 1/1 Running 0 9s helloweb-75f7cfd4f7-2wblr 1/1 Running 0 9s helloweb-75f7cfd4f7-6wq75 1/1 Running 0 10s helloweb-75f7cfd4f7-5wjvd 1/1 Running 0 12s helloweb-75f7cfd4f7-llkfh 1/1 Running 0 13s また、Job から作成された優先度の低いプレースホルダ Pod はすべて削除されています。 このように、Job を使用する場合は、置き換えが起こった後にプレースホルダ Pod を再作成するための容量が確保されないため、使用リソースを節約できる反面、再度スケールアウトが必要になったときに対応することができなくなります。 # 出力例 $ kubectl get po NAME READY STATUS RESTARTS AGE helloweb-75f7cfd4f7-2wblr 1/1 Running 0 3m10s helloweb-75f7cfd4f7-6wq75 1/1 Running 0 3m10s helloweb-75f7cfd4f7-cclwt 1/1 Running 0 2m helloweb-75f7cfd4f7-kw7t5 1/1 Running 0 2m helloweb-75f7cfd4f7-rvh8w 1/1 Running 0 3m10s 佐々木 駿太 (記事一覧) G-gen 最北端、北海道在住のクラウドソリューション部エンジニア。 2022 年 6 月に G-gen にジョイン。Google Cloud All Certifications Engineer。 好きな Google Cloud プロダクトは Cloud Run。最近は Dataflow を勉強中。 Follow @sasashun0805
アバター
G-gen の杉村です。Google Cloud(旧称 GCP)の VPC やオンプレミス(専用線・VPN)ネットワークの間でハブアンドスポーク構成を実現するためのフルマネージドサービスである Network Connectivity Center をご紹介します。 概要 Network Connectivity Center とは ハブアンドスポークとは ユースケース 料金 AWS Transit Gateway との違い コンポーネント 概要 スポークの種類(タイプ) 各スポークタイプの概要 オンプレミスサイトと VPC 間の接続 概要 ルート伝播 VPC 間接続(VPC スポーク) 概要 他のプロジェクトとの接続 VPC ネットワークピアリングとの違い VPC ネットワークピアリングの推移的接続の制限 プロデューサー VPC スポークと Private Service Access 制限 サイト間データ転送 概要と制限事項 経路交換とトラフィック AS 番号 Router アプライアンスを使った構成 概要 オンプレミス拠点と VPC を接続(サイト-to-クラウド接続) VPC 間接続 事前定義されたトポロジ メッシュトポロジとスタートポロジ スタートポロジの構成 監視・運用 モニタリング指標 監査ログ 補足事項・留意点 可用性 パフォーマンス サイト間データ転送における通信制御 IP アドレスの留意点 カスタムルートによる推移的通信との違い 概要 Network Connectivity Center とは Network Connectivity Center とは、Google Cloud の VPC やオンプレミス(専用線・VPN)ネットワークの間でハブアンドスポーク・アーキテクチャを構成するためのフルマネージドサービスです。 ハブには、スポークとして「VPC」「Cloud VPN」「Cloud Interconnect」「Compute Engine VM のルータ仮想アプライアンス(VPC)」を接続できます。これらで接続されたネットワークが、ハブを通して経路交換し、フルメッシュ接続ができるようになります。 参考 : Network Connectivity Center の概要 ハブアンドスポークとは ハブアンドスポーク (Hub and Spoke)とは、ネットワークのトポロジを表現する用語です。 スポークとは車輪における輻(や)の意味で、車輪の中心から輪に向けて放射状に伸びる棒のことです。 車輪の輻(や) これに見立てて、ハブとなるノードを中心にして放射状にネットワークが配置されるトポロジを ハブアンドスポークアーキテクチャ と呼びます。 ネットワークにおけるハブアンドスポーク ユースケース Network Connectivity Center を使って実現可能ないくつかのネットワーク構成をご紹介します。 以下の例では、オンプレミスサイトと複数の VPC と間の通信を実現しています。 オンプレミスサイトが複数、また VPC ネットワークが複数あっても、相互に通信を実現することが可能 です。 オンプレミスサイトと複数の VPC の通信 また以下の例では、Google Cloud の VPC である Network A と、オンプレミス拠点であるサイト A、B、C を接続してフルメッシュ通信を実現しています。対応しているリージョンであれば、サイト A、B、C が別々の大陸の異なる国に位置していても、Google のネットワークを利用して通信することが可能です。つまり、 地理的に離れたオンプレミスサイト間を、Google の強力なバックボーンネットワークを活用して相互接続する ことが可能になります。 サンプルアーキテクチャ この図におけるハブとスポークの表現の方法は、先に掲げた「ネットワークにおけるハブアンドスポーク」の図と比べて、直感的ではないかもしれません。これは「Network Connectivity Center のハブとは、パケットが実際に通過する訳ではなく、スポーク間で経路交換をさせるためにグルーピングするためのリソースである」という点に起因します。詳細は後述します。 料金 Network Connectivity Center の料金は以下の2軸で決まります。 スポークの利用料金 データ転送料金 Advanced Data Networking(ADN)料金 前者の スポークの利用料金 は、スポークが存在する時間に対する従量課金です。ただし無料枠として3つの VPN スポークと3つの Cloud Interconnect スポークは無料です。それぞれ4つめ以降から課金が発生します。スポークの種類によって単価が異なります。 後者の データ転送料金 は、Google Cloud と外部ネットワークの間で流れるデータサイズに対して課金されます。単価は地域や総データサイズによって異なりますが、例として東京リージョンの Google Cloud と日本国内の外部ネットワークの間における0〜1TiBの範囲内での単価は $0.11/GiB です(2025年4月現在)。 また Advanced Data Networking (ADN)料金は、後述する VPC スポーク間の通信のみで発生する、ハブを介して処理されたデータサイズへの課金のことです。単価は、すべてのリージョンで $0.02/GiB です(2025年4月現在)。 最新の料金表や計算例については、以下のドキュメントをご参照ください。 参考 : Pricing AWS Transit Gateway との違い 他のクラウドサービス経験者が仕様を理解しやすくするために、Amazon Web Services(AWS)の AWS Transit Gateway との違いを簡記します。 Network Connectivity Center と AWS Transit Gateway は共に複数のネットワークを接続してハブアンドスポーク構成を実現するためのフルマネージドサービスです。 AWS Transit Gateway では、Network Connectivity Center よりもより細かいルーティングの制御ができる点が異なります。 観点 Network Connectivity Center AWS Transit Gateway 対応スポーク 専用線 / VPN / VPC 専用線 / VPN / VPC ルーティング フルメッシュ接続が前提だが、広報する経路を一部制御できる AWS Transit Gateway 自体がルートテーブルを持つ。静的ルートを含む細かいルート設計が可能 課金 時間(h)とデータ容量(GiB) 時間(h)とデータ容量(GiB) コンポーネント 概要 Network Connectivity Center のコンポーネントには大きく分けて「ハブ」と「スポーク」が存在します。 ハブ は、Network Connectivity Center で構築するハブアンドスポークアーキテクチャのネットワークの中心となるコンポーネントです。 ハブは1つのプロジェクトに所属します。また、グローバルリソース(特定のリージョンに所属しないリソース)です。ハブにはほとんど設定項目が存在せず、スポークをグルーピングするためのリソースと考えて差し支えありません。 一方の スポーク は、ハブとネットワークを繋ぐリソースです。リージョンリソースであり、1つのVPC ネットワークに所属します。 参考 : Network Connectivity Center の概要 - 仕組み スポークの種類(タイプ) スポークには以下の種類(タイプ)があります。 No 名称 説明 1 VPN トンネル Cloud VPN(HA VPN)を接続するためのスポーク 2 VLAN アタッチメント Cloud Interconnect を接続するためのスポーク 3 Router アプライアンス 仮想ルータアプライアンスで確立された VPN を接続したり VPC 同士を接続するためのスポーク 4 VPC VPC を接続するためのスポーク なお、VPC タイプ以外の3つのスポークは ハイブリッドスポーク と総称されます。ハイブリッドスポークで「サイト間データ転送」オプションを有効化すると、ハイブリッドスポーク同士(オンプレミスサイト同士)での通信が可能になります。 各スポークタイプの概要 VPN トンネル タイプのスポークは、その名の通り Cloud VPN トンネルを通して外部ネットワークと接続します。なお Cloud VPN には「Classic VPN」とより新しい「HA VPN」がありますが、後者のみが利用可能です。 VLAN アタッチメント タイプも同様に、Cloud Interconnect リソースである VLAN アタッチメントを通して外部ネットワークと接続します。 Router アプライアンス は、仮想ルータのアプライアンス(Compute Engine VM)を指しています。このスポークを使うパターンは後述します。ルータアプライアンスは、Google が指定する特定のサードパーティが公開するマシンイメージからデプロイする必要があります。 VPC タイプのスポークは、その名の通り複数 VPC 間を接続するためのスポークです。他組織・他プロジェクトの VPC 同士も接続が可能です。 オンプレミスサイトと VPC 間の接続 概要 Network Connectivity Center では、1つのハブに VPC スポークとハイブリッドスポーク(Cloud VPN や Cloud Interconnect)を接続することで、 複数の VPC ネットワークと複数のオンプレミスサイトの相互通信が可能 です。 参考 : VPC スポークによるルート交換 オンプレミスサイトと VPC 間の接続 このように、Network Connectivity Center を使ってオンプレミスサイトと VPC の間の接続を構成するとき、VPC スポークを介してハブに接続する VPC のことを ワークロード VPC ネットワーク と呼びます。 また、ハイブリッドスポークとしてハブに接続されている Cloud Interconnect VLAN アタッチメントや HA VPN トンネルを持つ VPC のことは ルーティング VPC ネットワーク と呼びます。 ワークロード VPC ネットワークを「VPC スポーク」として、ルーティング VPC ネットワークを「ハイブリッドスポーク」として、それぞれハブに接続することで、相互通信が可能になります。 また同時に、ルーティング VPC ネットワークを「VPC スポーク」としてハブに接続することも可能です。これによりルーティング VPC ネットワーク内のサブネットへの経路がハブに広報されるため、他の ワークロード VPC ネットワークからルーティング VPC ネットワーク内の VM 等への接続が可能になります。 参考 : VPC スポークによるルート交換 ルート伝播 ハブに VPC スポークとハイブリッドスポークを接続すると、ハブのルートテーブルには、接続されたすべての VPC サブネットとオンプレミスサイトの動的ルートが記録されます。 ハブに新しく VPC スポークやハイブリッドスポークが接続されたり、あるいは削除されたとき、ハブのルートテーブルは自動的にルートを学習します。 ハブがハイブリッドスポークから(オンプレミス側から)学んだルートは、VPC スポークに自動的に広報されます。 ハブが VPC スポークから学習した動的ルートは、 Import of hub subnets for hybrid spokes を有効化することで、自動的にハイブリッドスポーク(オンプレミス側)に広報されます。このとき --include-import-ranges オプションで、オンプレミス側に広報する IP アドレスレンジを CIDR 形式で指定できます。 ALL_IPV4_RANGES 設定値を指定することで、すべての CIDR を広報することも可能です。 広報するルートを細かくコントロールしたいときは、Import of hub subnets for hybrid spokes を使わずに、Cloud VPN や Cloud Interconnect の Cloud Router で カスタムルート広報 を構成することで、明示的にルートを広報することも可能です。 参考 : ハイブリッド スポークのハブサブネットのインポート VPC 間接続(VPC スポーク) 概要 VPC タイプのスポーク を用いることで、VPC 間のフルメッシュ接続を実現できます。異なる組織・異なるプロジェクトの VPC ネットワーク同士を接続することも可能です。 VPC スポーク間の接続は、IPv4 アドレスと IPv6 アドレスの両方に対応しています。ハブ作成時にメッシュトポロジ(後述)を選択した場合でも、 エクスポートフィルタ により特定の IP レンジのみルート交換を除外することも可能です。これにより、特定のサブネットだけをフルメッシュ接続から除外することができます。 参考 : VPC スポークの概要 VPC スポークを使ったフルメッシュ接続 他のプロジェクトとの接続 VPC タイプのスポークでは、異なる組織・異なるプロジェクトの VPC ネットワーク同士を接続することも可能です。 ハブを持つプロジェクトは別のプロジェクトで VPC スポークが作られた場合、作成時は、スポークが非アクティブ状態になります。ハブ側の管理者が承認して初めて、スポークが有効化されます。 VPC ネットワークピアリングとの違い 異なる VPC ネットワーク同士の接続は、Network Connectivity Center を使わなくても、 VPC ネットワークピアリング を用いることで実現できます。 参考 : VPC ネットワーク ピアリング 参考 : Google CloudのVPCを徹底解説!(応用編) - G-gen Tech Blog - VPC ネットワークピアリング しかし、VPC ネットワークピアリングは VPC ネットワーク同士をピアツーピアで繋ぐ機能のため、VPC の数を n とすると n(n-1)/2 個のピアリングが必要になります。VPC の数が多くなればなるほど各種上限への抵触リスクと管理オーバヘッドが上昇します。 Network Connectivity Center の VPC スポークを使えば、VPC ネットワークをハブに接続することで容易にフルメッシュ接続を実現できます。また、VM インスタンス数の制限もありません。VPC ネットワークピアリングとの差異に関するその他の情報は以下の公式ガイドをご参照ください。 参考 : VPC スポークの概要 - VPC ネットワーク ピアリングとの比較 VPC ネットワークピアリングとの違い VPC ネットワークピアリングの推移的接続の制限 ある VPC が VPC ネットワークピアリング経由で受け取ったルートは、Network Connectivity Center へは再広報されません。以下のような構成を例に取ります。 VPC A と VPC B は、ハブに接続されている VPC C は、VPC A と VPC ネットワークピアリングで接続されている 上記のような環境では、パケットの到達可能性は以下のようになります。 通信経路 到達可能性 A <-> B True A <-> C True B <-> C False VPC ネットワークピアリングによる推移的な通信 プロデューサー VPC スポークと Private Service Access プロデューサー VPC スポーク (Producer VPC spokes)を使うと、ハブに接続された各 VPC から、 Private Service Access を使う Google Cloud サービスへの接続性を確保することができます。 Private Service Access とは、Cloud SQL や Memorystore、Cloud Build などで用いられる仕組みです。これらのサービスのインスタンスは、Google が管理する VPC ネットワークで起動します。この Google 管理 の VPC ネットワークのことを、 サービスプロデューサーネットワーク と呼びます。サービスプロデューサーネットワークとユーザーの VPC は servicenetworking-googleapis-com という名称の VPC ネットワークピアリングで接続されます。 プロデューサー VPC スポークを設定することで、ハブに接続されたユーザーの VPC からサービスプロデューサーネットワークに推移的にアクセスすることができます。構成は、以下のようになります。 Producer VPC spokes を使った通信 参考 : プロデューサー VPC スポーク 制限 代表的な仕様上の制限等を紹介します。 ハブ経由での VPC ネットワークピアリングへの推移的接続は不可 同じハブに接続する VPC ネットワーク間では VPC ネットワークピアリングを使用できない ただしプロデューサー VPC スポークを除く VPC スポーク間のルート交換をスタティックに設定することはできない 自動モードの VPC ネットワーク(デフォルト VPC など)は、VPC スポークとしてハブに接続できない 制限の詳細は、以下の公式ドキュメントをご参照ください。 参考 : VPC スポークの概要 - 制限事項 サイト間データ転送 概要と制限事項 ハイブリッドスポークで サイト間データ転送 を有効化すると、同じハブに接続されたハイブリッドスポーク同士の間で BGP による経路交換が行われ、フルメッシュの相互通信が可能になります。これにより、例えば米国のオンプレミスサイトと日本のオンプレミスサイトが、Google Cloud のネットワークを介して相互に通信することが可能になります。 ただしあるハブにおいて、オンプレミスサイト間同士のデータ転送を行うには、全てのスポークリソース(VPN トンネル、VLAN アタッチメント、アプライアンス VM)が 同じ VPC ネットワークに所属 している必要があります。 またサイト間データ転送は使えるリージョンが制限されています。対応リージョンは日本、韓国、シンガポール、米国、フランス、ドイツ、英国など一部のリージョオンのみです。リージョンの一覧は以下のドキュメントをご参照ください。 参考 : データ転送でサポートされているロケーション その他の制限事項や考慮事項は、以下を参照してください。 参考 : サイト間データ転送の概要 経路交換とトラフィック サイト間データ転送が複数スポークで有効化されると、各スポークが BGP で受け取った経路が Network Connectivity Center により全スポークに再広報されます。これによりスポーク同士がフルメッシュでトラフィックをやりとりできるようになります。 実際にパケットがハブを通るわけではなく、Google Cloud の内部ネットワークで折り返して、スポーク間で直接トラフィックがやりとりされます。 経路交換とトラフィック 参考 : サイト間データ転送によるルート交換 AS 番号 サイト間データ転送では各ネットワークは BGP を用いて経路交換するため、以下の AS 番号(ASN)の要件に従う必要があります。 単一ハブに紐づく Cloud Router は全て同じ AS 番号 各スポーク側は、単一スポーク内では同じ AS 番号 Cloud Router と各スポークは、重複しない AS 番号 AS 番号(ASN)の設計 参考 : サイト間データ転送の ASN 要件 Router アプライアンスを使った構成 概要 Router アプライアンスタイプのスポークの用途は最も分かりづらいかもしれません。以下のような用途で使われます。 オンプレミス拠点と VPC を接続(サイト-to-クラウド接続) VPC 間接続 Router アプライアンスタイプのスポークは、Compute Engine VM で動作する仮想ルータアプライアンスをベースリソースとします。 仮想ルータは Google Cloud の指定するサードパーティのイメージファイルから起動します。デプロイされた仮想ルータは Cloud Router と経路交換を行うことができ、これにより他のスポークとの経路交換を実現できます。サードパーティは Cisco、Fortinet、Palo Alto Networks などが対応しており、以下の一覧で確認できます。 参考 : Network Connectivity Center のパートナー オンプレミス拠点と VPC を接続(サイト-to-クラウド接続) オンプレミス拠点と複数 VPC の間で経路を交換し、フルメッシュ接続を行うために Router アプライアンスタイプのスポークを利用できます。 複数 VPC にまたがる Router アプライアンス Compute Engine VM は複数の VPC ネットワークにまたがってネットワークインターフェイスを持つことができます。これにより仮想ルータ VM は複数 VPC ネットワーク間でパケットのルーティングを行うことができます。 またこの図では、Router アプライアンスがオンプレミスサイトとの IPSec VPN を確立しており、オンプレミスのルータとの経路交換も行っています。仮想ルータは Cloud Router から VPC のプレフィクスを学習して、BGP を使って経路交換を行います。 参考 : サードパーティ アプライアンスを使用するサイトツークラウド トポロジ VPC 間接続 異なる複数の VPC ネットワーク間で接続を確立するために Router アプライアンスを使う方法も紹介されています。 VPC 間のトポロジ 参考 : サードパーティ アプライアンスを使用する VPC 間のトポロジ 事前定義されたトポロジ メッシュトポロジとスタートポロジ ハブを作成する際に、 事前定義されたトポロジ (Preset topologies)を選択します。選択可能なトポロジは メッシュ と スター の2種類です。トポロジの指定がない場合、デフォルトはメッシュになります。ハブの作成後にトポロジを変更することはできないため、変更したい場合は、ハブを削除して再作成する必要があります。 メッシュ トポロジは、ハブに接続されたすべてのスポークが、相互に通信可能なトポロジです。ただし、 エクスポートフィルタ を設定することで、一部のスポークをメッシュから除外することもできます。メッシュトポロジのハブにスポークが接続されると、自動的にルーティングが有効になります。 スター トポロジは、指定されたスポーク間でのみ通信できるトポロジです。スタートポロジでは、スポークは センタースポーク と エッジスポーク に分類されます。センタースポークは、他のすべてのスポークと通信できます。エッジスポークは、センタースポークに対してのみ、通信できます。 すべてのスポーク同士でフルメッシュの通信を実現したいときはメッシュトポロジを選択します。ワークロードを中央の共有 VPC ネットワーク(センタースポーク)とのみ通信させたい場合や、中央にトラフィックを集中させる仮想アプライアンスがある場合などに、スタートポロジを選択します。 参考 : VPC スポークの概要 - 事前定義されたトポロジ スタートポロジの構成 スタートポロジでは、 スポークグループ を作成します。スポークグループは、 センターグループ と、 エッジグループ の2種類にわかれます。 前述のセンタースポークとしたいスポークは、センターグループに所属させ、エッジスポークとしたいスポークをエッジグループに所属させます。 センターグループにも、エッジグループにも、複数のスポークを所属させることができます。センターグループに複数のスポークがある場合、センタースポーク同士は通信可能です。 参考 : VPC スポークの概要 - スポーク グループ なお、サイト間データ転送を有効化したハイブリッドスコープは、センターグループにのみ、所属することができます。サイト間転送が有効になっていないハイブリッドスポークであれば、センターグループにも、エッジグループにも所属することができます。 参考 : VPC スポークの概要 - 動的ルート交換の制限事項 監視・運用 モニタリング指標 Network Connectivity Center のモニタリング指標は Cloud Monitoring で自動的に取得されます。 各スポークにおける上り・下りのバイト数が主です。また各スポーク(VPC や Cloud VPN、Cloud Interconnect)の情報は、各サービス側のメトリクスを参照します。 参考 : モニタリング指標 監査ログ Network Connectivity Center からは Cloud Logging に監査ログが送信されます。設定変更(ハブやスポークの作成・更新・削除)などが記録され、Cloud Logging のメトリクスエクスプローラで閲覧することができます。 参考 : 監査ロギング情報 Cloud Logging については以下もご参照ください。 blog.g-gen.co.jp 補足事項・留意点 可用性 スポーク間を通るパケットは、実際に Network Connectivity Center のハブを通るわけではありません。Network Connectivity Center はあくまで経路交換を実現する仕組みであり、実際のパケットは Google Cloud の内部ネットワークで折り返してスポーク同士で直接やりとりされます。そのため、スポークリソース(Cloud VPN / Cloud Intercconnect / Router アプライアンス)の可用性が重要になります。 それぞれの可用性の考え方は、以下をご参照ください。 参考 : スポーク リソースの高可用性要件 また Router アプライアンスを使うケースでのアーキテクチャ例は以下のドキュメントでも示されています。 参考 : ロード バランシング型のルーター アプライアンス インスタンスを使用する Network Connectivity Center 自体はルート交換を司るため、それ自体の可用性も重要です。以下のページで SLA が公開されています。 参考 : Network Connectivity Center Service Level Agreement (SLA) パフォーマンス サイト間(スポーク同士)のデータ転送のパフォーマンスはベストエフォートであり、レイテンシや帯域は保証されません。 参考 : サイト間データ転送の概要 サイト間データ転送における通信制御 Network Connectivity Center のサイト間データ転送はフルメッシュ接続が原則です。 特定のサイトから特定の VPC への通信を制御したい場合などは、オンプレミス側であればオンプレミス側のファイアウォール、VPC 側であれば VPC ファイアウォールルール / ポリシーで、特定の IP レンジや特定プロトコル・ポート番号の通信を制限する必要があります。 Network Connectivity Center 側で特定のルートのみをフィルタするようなことはできません。 IP アドレスの留意点 ハイブリッドスポークと VPC スポークの間のルート交換は、IPv4 アドレスのみをサポートしています。VPC スポーク同士のルート交換は、IPv6 アドレスと IPv4 アドレスの両方をサポートしています。 Router アプライアンスタイプのスポークでは RFC 1918 アドレスのみがサポートされており、いわゆる Privately used public IP(PUPI)はサポートされません。 参考 : IP アドレス指定 カスタムルートによる推移的通信との違い Network Connectivity Center を使わなくても Cloud Router のカスタムルート広報機能を使えば、VPC を介した推移的通信が可能です。 ただしこの場合は、Cloud Router から広報するルートはスタティックに指定しなければいけません。Network Connectivity Center のサイト間通信の場合は、交換したルートを BGP で動的に再広報できるのが異なる点と言えます。 参考 : Cloud VPN による推移的な通信 (カスタムルート広報) 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の又吉です。当記事では、 Google Cloud(旧称 GCP)リソースのプロビジョニングとオーケストレーションができるサービスである Config Controller で組織ポリシーを定義してみました。 前提知識 Config Connector Config Controller 検証概要 事前準備 API の有効化 Config Controller インスタンスの作成 Config Controller インスタンスの確認 認証情報の取得 サービスアカウントに権限を付与 マニフェストファイルの作成 マニフェストファイルの適用 動作確認 クリーンアップ 前提知識 Config Connector Config Connector とは、 Kubernetes を使用して Google Cloud リソースを管理できる open source の Kubernetes アドオンサービスです。 Kubernetes を使用するため、Kubernetes のマニフェストファイルで定義したオブジェクトを「理想の状態」として定義し、「実際の環境」に差分が生じたときに理想の状態を維持(自動修復) することができます (Reconciliation Loop) 。 その他 Kubernetes の基本については、以下のブログでも解説しています。 blog.g-gen.co.jp Config Controller Config Controller とは、Config Connector のマネージド サービスであり、実体は 限定公開の標準 GKE クラスタ が使われております。 尚、Config Controller インスタンスには Config Connector の他に、 Policy Controller と Config Sync がプリインストールされていますが、今回の検証では使用しないため説明を詳細させていただきます。 検証概要 今回は、Config Controller を用いて 組織ポリシー を定義していきたいと思います。尚、実行環境は Cloud Shell となります。 今回作成する構成 事前準備 API の有効化 必要な API を有効化します。 gcloud services enable krmapihosting.googleapis.com \ container.googleapis.com \ cloudresourcemanager.googleapis.com \ serviceusage.googleapis.com Config Controller インスタンスの作成 以下のコマンドを実行し、Config Controller インスタンスを作成します。 CONFIG_CONTROLLER_NAME={Config Controller 名を入力} LOCATION={ロケーション名を入力} gcloud anthos config controller create ${CONFIG_CONTROLLER_NAME} \ --location=${LOCATION} Config Controller インスタンスの確認 Config Controller インスタンスのリストを表示し、Config Controller インスタンスが作成されたことを確認します。 gcloud anthos config controller list \ --location=${LOCATION} STATE が RUNNING になれば、無事作成できています。 NAME: <CONFIG_CONTROLLER_NAME> LOCATION: <LOCATION> STATE: RUNNING 認証情報の取得 Config Controller インスタンスの認証情報とエンドポイント情報を取得します。 gcloud anthos config controller get-credentials ${CONFIG_CONTROLLER_NAME} \ --location ${LOCATION} サービスアカウントに権限を付与 Config Controller が Google Cloud リソースを管理するため、Config Controller のサービスアカウントに権限を付与します。 PROJECT_NO={プロジェクト番号を入力} PROJECT_ID={プロジェクト ID を入力} ORG_ID={組織 ID を入力} SA_EMAIL="service-${PROJECT_NO}@gcp-sa-yakima.iam.gserviceaccount.com" # プロジェクトに対してのオーナーロールをサービスアカウントに付与 gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member "serviceAccount:${SA_EMAIL}" \ --role "roles/owner" \ --project ${PROJECT_ID} # 組織に対しての組織ポリシー管理者ロールをサービスアカウントに付与 gcloud organizations add-iam-policy-binding ${ORG_ID} \ --role=roles/orgpolicy.policyAdmin \ --condition=None \ --member="serviceAccount:${SA_EMAIL}" マニフェストファイルの作成 Cloud Shell 環境に org_policy.yaml という名前のファイルを作成し、以下のコードを記入。尚、 ${ORG_ID} 部分は組織 ID に置き換えて入力して下さい。 apiVersion : resourcemanager.cnrm.cloud.google.com/v1beta1 kind : ResourceManagerPolicy metadata : name : resourcemanagerpolicy-sample-org spec : organizationRef : external : ${ORG_ID} constraint : "constraints/iam.disableServiceAccountCreation" booleanPolicy : enforced : true 以下に、マニフェストファイルの説明を記載します。 apiVersion や kind フィールドで作成対象となるリソースを指定します。今回は組織ポリシーを作成したいので、apiVersion に resourcemanager.cnrm.cloud.google.com/v1beta1 に kind に ResourceManagerPolicy を指定します。 metadata フィールドでは、リソースのメタデータを定義します。今回は name でリソースの名前を定義します。 spec フィールドでは、Kubernetes オブジェクトの理想状態を定義します。今回は、 サービスアカウントの作成を禁止 する組織ポリシーを、 組織 レベルで適用するよう記述しています。 マニフェストファイルの記述方法については、以下をご参照下さい。 参考: Config Connector resources 参考: ResourceManagerPolicy マニフェストファイルの適用 以下のコマンドでマニフェストファイルを適用します。 kubectl apply -f org_policy.yaml リソースのステータスを確認します。 KIND={マニフェストの kind で定義したリソースの種類を入力} NAME={マニフェストの metadata.name で定義したリソースの名前を入力} kubectl get ${KIND} ${NAME} 以下のように、STATUS のイベントタイプが UpToDate となっていれば成功です。 NAME AGE READY STATUS STATUS AGE resourcemanagerpolicy-sample-org 33m True UpToDate 33m 参考: Config Connector 固有のイベント 動作確認 コンソールから [IAM と管理] > [組織ポリシー] を選択し、フィルタに [constraints/iam.disableServiceAccountCreation] を入力すると対象のポリシーがソートされます。 組織ポリシーの設定① Config Controller のリソースとして定義したため、組織ポリシーのステータスが 適用 になっていることがわかります。 次に、この組織ポリシーのステータスを手動で [未適用] に変更し保存します。 組織ポリシーの設定② Config Connector は、 平均 10 分間隔で各リソースを調整 するためしばらく待機した後、再度組織ポリシーを確認してみます。 組織ポリシーの設定③ 自動修復されていることが確認できました。 クリーンアップ 組織ポリシーリソースを削除 kubectl delete -f org_policy.yaml Config Controller を削除 gcloud anthos config controller delete \ --location=${LOCATION} ${CONFIG_CONTROLLER_NAME} 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさーい!沖縄出身のクラウドエンジニアです!! 前職は SIer テクニカルセールス。Google Cloud の魅力に惚れ、技術を磨きたくセールスからエンジニアへ転身。Google Cloud 認定資格は全 11 資格保有。最近は AI/ML 分野に興味あり。 Follow @matayuuuu
アバター
G-gen の又吉です。当記事では、Goolge Cloud の Vertex AI でサポートされている生成 AI 機能(総称 Generative AI on Vertex AI )を解説します。 概要 Generative AI on Vertex AI とは 生成 AI とは Vertex AI Studio Gen AI SDK Vertex AI Model Garden 生成 AI モデルへのリクエスト プロンプト パラメータ アクセス制御 料金 概要 Google のスタンス 責任ある AI 安全フィルタ データガバナンス Gemini 系プロダクト 概要 Generative AI on Vertex AI とは Vertex AI は、Google Cloud の統合された機械学習プラットフォームです。Vertex AI では、生成 AI に関する諸機能もサポートされています。Gemini をはじめとする生成 AI モデルを API 経由で呼び出せたり、Web UI で簡単に AI モデルの機能を試せる Vertex AI Studio 、Claude や Llama などサードパーティのモデルを購入して呼び出せる Vertex AI Model Garden などがあります。これらの生成 AI 機能は、総称して Generative AI on Vertex AI (Vertex AI の生成 AI)と呼ばれます。 参考 : Vertex AI の生成 AI の概要 Vertex AI の Generative AI コンポーネント Vertex AI の生成 AI 関連以外の機能については、以下の記事もご参照下さい。 blog.g-gen.co.jp 生成 AI とは 生成 AI (Generative AI) とは、テキストや画像、動画などのコンテンツを生成するのに特化した AI(人工知能)の総称であり、大規模言語モデル(LLM)などのテクノロジーに基づいています。単なるテキスト生成に留まらず、ソースコードや音声、画像、動画なども生成できます。 Google が提供するコンシューマ向け生成 AI の代表的なサービスとして、 Gemini アプリ があります。 参考 : Gemini アプリ 上記の Gemini アプリは PC 向けおよびモバイル端末向けのアプリケーションであり、API が公開されておりません。開発者が自社のアプリケーションに生成 AI を組み込んだり、独自の業界知識に基づいたファインチューニングを行うためには、エンタープライズに特化して Google Cloud 経由で提供されている Generative AI on Vertex AI を利用します。 Vertex AI API 経由でのモデル利用 Vertex AI で利用可能な生成 AI モデルの代表は、 Gemini です。2025年7月現在、Gemini には Gemini 2.5 Pro、Gemini 2.5 Flash などが存在します。Gemini は数々のベンチマークで高得点を記録する、非常に優れた生成 AI モデルです。 Vertex AI Studio Vertex AI Studio は、Google Cloud の Web コンソール画面から、基盤モデルのプロンプトやパラメータ値を迅速にテストしてプロンプト設計をスムーズに行うことができる機能です。気軽に Gemini などのモデルの性能を試験することができます。 Vertex AI Studio では、Gemini のみならず、画像生成モデルである Imagen や、動画生成モデルである Veo を試用することもできます。 参考 : Vertex AI Studio で Gemini プロンプトを作成して最適化する Vertex AI Studio の Web UI この画面からは、コンソール画面で入力したプロンプトとパラメータ値が含まれた形で、Vertex AI SDK for Python などで記述されたサンプルコードや、curl で実行できる REST API のサンプルコードが表示されます。これらのサンプルコードを開発に活かすことができます。 参考 : Vertex AI SDK for Python サンプルコードの取得 Vertex AI Studio の利用手順については、以下の記事も参照してください。 blog.g-gen.co.jp Gen AI SDK Vertex AI 経由で Gemini 等のモデルを呼び出すには、各プログラミング言語用に提供されている Google Gen AI SDK を利用します。この SDK により、開発者は容易に Vertex AI 経由で生成 AI モデルを独自のアプリケーションに組み込むことができます。 Gen AI SDK は、Python、Go、Node.js、Java などの言語に対して提供されています。 参考 : Google Gen AI SDK Google Gen AI SDK を用いて、呼び出し先の Google Cloud プロジェクトやモデル名を指定することで、生成 AI モデルにリクエストを送信することができます。この SDK は個人や小規模開発者向けの生成 AI プラットフォームである Google AI Studio とも共通しています。 Vertex AI API 経由でのモデル利用 なお、以前は Vertex AI 経由で生成 AI モデルを呼び出す際に Vertex AI SDK が使われていましたが、Vertex AI SDK 経由での生成 AI モデルは2026年6月に廃止予定です。以前から Vertex AI SDK を使っている場合、Google Gen AI SDK に移行する必要があります。以下のドキュメントを参照してください。 参考 : Vertex AI SDK migration guide Vertex AI Model Garden Vertex AI Model Garden は、機械学習モデルのマーケットプレイスです。さまざまなサードパーティベンダーから公開される機械学習モデルと API のアセットを購入して、Vertex AI 経由で呼び出せるようにすることができます。 参考 : Model Garden で AI モデルを確認する Vertex AI Model Garden Model Garden で利用できるモデルカテゴリは以下のとおりです。 No モデルカテゴリ 説明 1 基盤モデル Vertex AI Studio、Vertex AI API、Vertex AI SDK などを使用して、特定のタスクに合わせて調整またはカスタマイズできる、事前トレーニングされた基盤モデル 2 ファインチューニング可能なモデル カスタムノートブックまたはパイプラインを使用して微調整できるモデル 3 タスク固有のソリューション すぐに使用可能な事前構築済みモデル 生成 AI モデルへのリクエスト プロンプト プロンプト とは、簡単に言うと言語モデルに送信するリクエストのことです。 プロンプトには、質問だけでなく、コンテキストや指示、例などを含めることができ、モデルはプロンプトを受け取ると、テキストやソースコード、画像等を生成しユーザーにレスポンスします。 特に、大規模言語モデルは膨大な量のテキストデータから単語間のパターンと関係を学習しており、プロンプトの設計がうまくできればモデルの精度を向上させることができます。 プロンプトをより良くするためには、「目的」「指示」「出力形式」を明記したり、「システム指示(system instruction)」を用います。またモデルの振る舞いを「役割(ペルソナ)」として示したり、出力の例を数個提示する Few-shot prompting などの技術が知られています。 以下のドキュメントでは、より良いプロンプトを設計するための戦略が紹介されています。 参考 : プロンプトの概要 参考 : プロンプト戦略の概要 参考 : カスタム Gem 作成のヒント 公式ドキュメントにプロンプトのサンプルが提供されているので、まずはこちらのサンプルからニーズにあうプロンプトがあるか探してみることをおすすめします。 参考 : 生成 AI のプロンプト サンプル パラメータ モデルに送信するリクエストには、モデルのレスポンスを制御する パラメータ を含めることができます。代表的なパラメータ値を以下に記載します。 No パラメータ値 概要 1 Max output tokens モデルが生成できるトークンの最大数です。参考値として、1 トークンは約 4 文字、100 トークンは約 60 ~ 80 英単語に相当。 2 Top-K モデルが最適なトークンを選択する際、最も確率の高い上位 K 個のトークンがサンプリングされます。 3 Top-P 確率の高い順にトークンを並べ、確率の合計が上位 P の値に等しくなるまでフィルタリングされます。Top-P は 0.0 ~ 1.0 の値をとります。 4 Temperature Temperature は、トークン選択のランダム性を制御し、0.0 ~ 1.0 の値を取ります。Temperature が低い (0.0 に近い) と、確率の高いトークン、つまり、より真実または正しいレスポンスが生成されます。逆に Temperature が高い (1.0 に近い) と、より多様な結果や予期しない結果を得ることができます。 モデルに送信されるリクエストに対して、最適なトークンを選択するステップは以下のとおりです。 Top-K により最も高い確率を持つ上位 K 個のトークンがサンプリング Top-K でサンプリングされた K 個のトークンから、Top-P に基づいてフィルタリング Top-P でフィルタリングされたトークンから、Temperature に基づいて最終的にトークンが選択される 例えば、アイディア出しを行いたい場合、予期しない結果を得たいため Top-K と Top-P、Temperature を上げてみるといいでしょう。 参考: パラメータ値を試す アクセス制御 Vertex AI 経由で生成 AI 機能を使用するには、呼び出し元の Google アカウントまたは Google グループ、あるいはサービスアカウント等に、適切な権限が付与されている必要があります。 以下の事前定義されたロールを付与することで、Vertex AI で Generative AI 機能へのアクセスを許可できます。 Vertex AI 管理者( roles/aiplatform.admin ) Vertex AI ユーザー( roles/aiplatform.user ) 参考: アクセス制御 料金 概要 Vertex AI での生成 AI の課金体系は、入力や出力のボリュームに応じた従量課金です。入出力のボリュームは、モデルにより文字数、または トークン という単位で計測されます。 例として、2025年7月現在、Gemini 2.5 Pro の課金体系は以下です(20万以下のコンテキストウインドウの入力の場合)。入力した画像、動画、テキストの量と、出力された生成コンテンツの量に応じた従量課金となります。入出力のサイズは、トークンと呼ばれる単位でカウントされます。 課金軸 料金単価 入力トークン $1.25 / 100万トークン 出力トークン (テキスト) $10 / 100万トークン モデルによって料金単価や計測方法が異なるため、詳細は以下の公式ページを参照してください。 参考 : Vertex AI での AI モデルの構築とデプロイにかかる費用 Google のスタンス 責任ある AI Gemini 等の Google が公開するモデルは、 Google の AI 原則 に従って設計されています。 参考 : Our AI Principles しかしながら、生成 AI の生成は非決定論的であり、誤った情報や不適切なコンテンツを生成してしまう可能性はゼロではありません。開発者はこれらのリスクを考慮しつつ、安全かつ責任を持ってテスト・デプロイを行うことが重要です。 安全フィルタ Vertex AI 経由での Gemini モデル等の呼び出し時には、安全フィルタのしきい値を設定することで、基盤モデルから有害なレスポンスが返ってくる可能性を調整できます。 ヘイトスピーチ、嫌がらせ、性的に露骨な表現、危険なコンテンツなどに対してフィルタを設定し、フィルタの強度も指定可能です。 参考 : 安全フィルタを構成する データガバナンス Google Cloud 経由で提供される生成 AI モデルでは、入出力データは保護されます。サービス規約上、Google はユーザーのデータを利用して、AI/ML モデルを再トレーニングしたり、ファインチューニングすることはありません。 参考 : 生成 AI とデータ ガバナンス なお、Google は AI/ML Privacy Commitment を業界で初めて公表した企業です。 参考 : Sharing our data privacy commitments for the AI era Gemini 系プロダクト Google Cloud 等で提供される Gemini 系プロダクトの一覧については、以下の記事も参照してください。 blog.g-gen.co.jp 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさい、沖縄出身のクラウドエンジニア! セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Champion Innovator (AI/ML)。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリスト。好きな分野は生成 AI。 Follow @matayuuuu
アバター
G-gen の武井です。当記事では Cloud Monitoring アラートを使って VM マシンのメトリクスを監視をする方法を紹介します。 はじめに 前提知識 Cloud Monitoring とは 指標の収集 アラート 設定手順 概要 通知先の登録 Slack のメールアドレスを取得 通知チャネル (notification channel) の作成 アラートポリシーの作成 指標 フィルタ ローリングウィンドウ / ローリングウィンドウ関数 トリガー アラート通知メール件名の規則 通知チャネル 動作確認 アラートポリシーの確認 インシデントの確認 通知の確認 関連機能1 : 繰り返し通知 概要 設定方法 概要 JSON ファイルの準備 NotificationChannelStrategy オブジェクトの定義 アラートポリシーの更新 設定内容の確認 動作確認 注意事項 関連機能2: スヌーズ 概要 設定方法 概要 JSON ファイルの準備 スヌーズの作成 設定内容の確認 動作確認 はじめに Google Cloud では、 Cloud Operations (オペレーション スイート) という名称でインフラやアプリケーションの監視を行うための一連のプロダクトを提供しています。 これらはマネージドサービスとして提供されており、Google Cloud をはじめ、他のパブリッククラウドやオンプレミス環境の情報を収集して監視や運用に活用することができます。 当記事では Cloud Monitoring の アラート 機能を使って VM マシンのメトリクスを監視をする方法を紹介します。 参考 : アラートの仕組み 前提知識 Cloud Monitoring とは オペレーションスイートの 1つ である Cloud Monitoring は次の機能を提供します。 指標 (メトリクス) の収集 可視化 (ダッシュボード) アラート管理 インシデント管理 詳細は以下の記事で解説しておりますのでご参照ください。 blog.g-gen.co.jp 指標の収集 Google Cloud の各種リソースから自動的に収集される標準的な指標を Google Cloud の指標 といいますが、この指標には メモリ使用率 、 ディスク使用率 、 スワップ使用率 といった VM マシンにおける重要な指標が含まれていません。 Ops エージェント とよばれる fluentbit ベースのエージェントソフトウェアをインストールすると、 Ops エージェントの指標 として収集できます。 ※当記事では Ops エージェントのインストール方法に関する説明は割愛します。 アラート 指標にしきい値を設定し、超過した際にメールを飛ばすなどの アラート設定 が行えます。 一つ一つの設定を アラートポリシー といい、設定内容は大きく分けて次の 2つ です。 # 設定内容 説明 1 条件 監視対象となる指標、リソース、トリガーなどを定義 2 通知 通知先 (メール、SMS、Slack 等) やアラート名などを定義 これにより、例えば「CPU使用率が」「あるインスタンスグループで」「5分の間」「80%を超えた場合に」「メールを発報する」 といったアラートを制御できます。 設定手順 概要 今回は Linux VM マシン 2台 を用意して、各マシンのディスク使用率がしきい値を超過した際に チャットツール「Slack」にアラートを通知させます。 通知先の登録 Slack のメールアドレスを取得 今回は Slack をアラートの通知先として使用します。 こちら に従い任意のチャネルに紐づくメールアドレスを取得します。 ※ Slack チャンネルのインテグレーション用メールアドレスを取得するには Slack の有料プランの契約が必要です。 通知チャネル (notification channel) の作成 先程取得した Slack 通知用のメールアドレスを Monitoring アラートの通知先として登録します。Cloud Monitoring ではアラートの通知先を 通知チャネル といいます。 Cloud コンソール > Monitoring > アラート の順に遷移し、 EDIT NOTIFICATION CHANNELS をクリックします。 EDIT NOTIFICATION CHANNEL をクリック 次に Email の ADD NEW をクリックし、メールアドレスを登録します。 ADD NEW をクリック メールアドレスと表示名を入力して保存 通知チャネルとして登録 アラートポリシーの作成 ディスク使用率の指標を使ってアラートを設定します。 Cloud コンソール > Monitoring > アラート の順に遷移し、 CREATE POLICY をクリックします。 CREATE POLICY をクリック 指標 指標を選択 をクリックし、 VM Instance > Disk > Disk utilization を選択したら 適用 をクリックします。 指標を選択したら 適用 をクリック フィルタ Linux VM マシン 2台 のディスク (/dev/sda1) の使用率を指標とするため、 ADD A FILTER をクリックして 2つ のフィルタを作成します。 device (ボリューム名) が /dev/sda1 state (状態) が used ローリングウィンドウ / ローリングウィンドウ関数 Monitoring アラートでは ローリングウインドウ / ローリングウィンドウ関数 で定めた期間内の指標を収集・計算した上で異常か否かを判断します。 今回は過去 5分間 で収集した指標の平均値 (mean) にもとづき判断するよう設定します。 過去5分間で収集した指標の平均値を計算する トリガー トリガーはアラートの発報条件になります。 今回準備した VM マシンのディスク使用率がいずれも 23 % となっているので、 20% を超過した場合にアラートが通知 されるようにトリガーを設定します。 ディスク使用率が20%を超過した際にアラート通知 なお、 条件名 は以下に示したとおりアラート通知メールの件名として使用されるため、件名が長くなりすぎないに工夫すると良いでしょう。 アラート通知メール件名の規則 ALERT + [条件名] + on + [プロジェクト名] + [インスタンス名] 通知チャネル 最後に通知チャネルを選択し、アラートポリシー名を入力します。 通知チャネルを選択 アラートポリシー名を入力してポリシーを作成する 動作確認 アラートポリシーの確認 先ほど作成したアラートポリシーを確認します。 Cloud コンソール > Monitoring > アラート の順に遷移します。 ポリシー名をクリックすると設定内容や監視状況が確認できます。 ポリシー名をクリック 設定内容や監視状況が確認できる インシデントの確認 アラート通知の条件を満たすと、インシデントとしてダッシュボード上に登録されます。インシデントの概要名をクリックすると詳細が確認できます。 インシデントとして登録 インシデント詳細画面 通知の確認 上記同様アラート通知の条件を満たしたため、Slack に Email 通知が届いています。 Slack にアラートが通知 関連機能1 : 繰り返し通知 概要 繰り返し通知 を設定すると、インシデント登録されたアラートに対して定期的にリマインダーを送信できます。 デフォルトではインシデントが登録された際に1回だけ通知されます。サービス障害に直結する重要な監視項目に対して設定すると対応漏れなどを防ぐことができます。 繰り返し通知は Cloud コンソールからは設定できません。 詳細は次で説明しますが、アラートポリシーの AlertStrategy オブジェクトに少なくとも 1 つの NotificationChannelStrategy オブジェクトを gcloud コマンド または API から設定します。 # PROJECT_ID と CHANNEL_ID には環境固有の値を入力する # renotifyInterval には 30分以上24時間以内の値を秒単位で入力する " alertStrategy ": { " notificationChannelStrategy ": [ { " notificationChannelNames ": [ " projects/PROJECT_ID/notificationChannels/CHANNEL_ID " ] , " renotifyInterval ": " 1800s " } ] } 設定方法 概要 これまでの解説で作成したアラートポリシーを例に、 gcloud コマンドを使った繰り返し通知の設定方法を説明します。 JSON ファイルの準備 作成済みのアラートポリシーから JSON ファイルを取得します。 Cloud コンソール > Monitoring > アラート から対象のアラートポリシーを選択し、 JSON をクリックしてファイルをダウンロードします。 JSON ファイルをダウンロードする NotificationChannelStrategy オブジェクトの定義 次に先程ダウンロードした JSON ファイルの AlertStrategy オブジェクトに NotificationChannelStrategy オブジェクトを追記します。 実行例では Slack に 30分おきにリマインダーを通知するよう定義しています。 また、値が設定されていないオブジェクト (以下の場合 documentation と userLabels ) が残っているとコマンド実行時にエラーとなるので削除します。 # 変更前 { " name ": " projects/example/alertPolicies/1111111111 ", " displayName ": " Linux VM Disk Utilization Test Policy ", " documentation ": {} , " userLabels ": {} , " conditions ": [ { " name ": " projects/example/alertPolicies/1111111111/conditions/2222222222 ", " displayName ": " Disk Used (%) ", " conditionThreshold ": { " aggregations ": [ { " alignmentPeriod ": " 300s ", " perSeriesAligner ": " ALIGN_MEAN " } ] , " comparison ": " COMPARISON_GT ", " duration ": " 0s ", " filter ": " resource.type = \" gce_instance \" AND metric.type = \" agent.googleapis.com/disk/percent_used \" AND (metric.labels.device = \" /dev/sda1 \" AND metric.labels.state = \" used \" ) ", " thresholdValue ": 20 , " trigger ": { " count ": 1 } } } ] , " alertStrategy ": { " autoClose ": " 1800s " } , " combiner ": " OR ", " enabled ": true , " notificationChannels ": [ " projects/example/notificationChannels/3333333333 " ] , " creationRecord ": { " mutateTime ": " 2023-05-04T14:38:46.558191920Z ", " mutatedBy ": " example@g-gen.co.jp " } , " mutationRecord ": { " mutateTime ": " 2023-05-17T03:14:54.109178339Z ", " mutatedBy ": " example@g-gen.co.jp " } } # 変更後 { " name ": " projects/example/alertPolicies/1111111111 ", " displayName ": " Linux VM Disk Utilization Test Policy ", " conditions ": [ { " name ": " projects/example/alertPolicies/1111111111/conditions/2222222222 ", " displayName ": " Disk Used (%) ", " conditionThreshold ": { " aggregations ": [ { " alignmentPeriod ": " 300s ", " perSeriesAligner ": " ALIGN_MEAN " } ] , " comparison ": " COMPARISON_GT ", " duration ": " 0s ", " filter ": " resource.type = \" gce_instance \" AND metric.type = \" agent.googleapis.com/disk/percent_used \" AND (metric.labels.device = \" /dev/sda1 \" AND metric.labels.state = \" used \" ) ", " thresholdValue ": 20 , " trigger ": { " count ": 1 } } } ] , " alertStrategy ": { " autoClose ": " 1800s ", " notificationChannelStrategy ": [ { " notificationChannelNames ": [ " projects/example/notificationChannels/3333333333 " ] , " renotifyInterval ": " 1800s " } ] } , " combiner ": " OR ", " enabled ": true , " notificationChannels ": [ " projects/example/notificationChannels/3333333333 " ] , " creationRecord ": { " mutateTime ": " 2023-05-04T14:38:46.558191920Z ", " mutatedBy ": " example@g-gen.co.jp " } , " mutationRecord ": { " mutateTime ": " 2023-05-17T03:14:54.109178339Z ", " mutatedBy ": " example@g-gen.co.jp " } } アラートポリシーの更新 JSON ファイルの修正が完了したら、 gcloud alpha monitoring policies update コマンドで既存のアラートポリシーを更新し、繰り返し通知設定を反映します。 # アラートポリシー ID と JSON ファイルパスを指定する gcloud alpha monitoring policies update projects/example/alertPolicies/1111111111 \ --policy-from-file=example.json 設定内容の確認 gcloud alpha monitoring policies list コマンドで設定変更前後を比較します。 AlertStrategy オブジェクトに NotificationChannelStrategy オブジェクトが追加されていることがわかります。 # 変更前 [ { " alertStrategy ": { " autoClose ": " 1800s " } , " combiner ": " OR ", " conditions ": [ { " conditionThreshold ": { " aggregations ": [ { " alignmentPeriod ": " 300s ", " perSeriesAligner ": " ALIGN_MEAN " } ] , " comparison ": " COMPARISON_GT ", " duration ": " 0s ", " filter ": " resource.type = \" gce_instance \" AND metric.type = \" agent.googleapis.com/disk/percent_used \" AND (metric.labels.device = \" /dev/sda1 \" AND metric.labels.state = \" used \" ) ", " thresholdValue ": 20.0 , " trigger ": { " count ": 1 } } , " displayName ": " Disk Used (%) ", " name ": " projects/example/alertPolicies/1111111111/conditions/2222222222 " } ] , " creationRecord ": { " mutateTime ": " 2023-05-04T14:38:46.558191920Z ", " mutatedBy ": " example@g-gen.co.jp " } , " displayName ": " Linux VM Disk Utilization Test Policy ", " enabled ": true , " mutationRecord ": { " mutateTime ": " 2023-05-17T03:14:54.109178339Z ", " mutatedBy ": " example@g-gen.co.jp " } , " name ": " projects/example/alertPolicies/1111111111 ", " notificationChannels ": [ " projects/example/notificationChannels/3333333333 " ] } ] # 変更後 [ { " alertStrategy ": { " autoClose ": " 1800s ", " notificationChannelStrategy ": [ { " notificationChannelNames ": [ " projects/example/notificationChannels/3333333333 " ] , " renotifyInterval ": " 1800s " } ] } , " combiner ": " OR ", " conditions ": [ { " conditionThreshold ": { " aggregations ": [ { " alignmentPeriod ": " 300s ", " perSeriesAligner ": " ALIGN_MEAN " } ] , " comparison ": " COMPARISON_GT ", " duration ": " 0s ", " filter ": " resource.type = \" gce_instance \" AND metric.type = \" agent.googleapis.com/disk/percent_used \" AND (metric.labels.device = \" /dev/sda1 \" AND metric.labels.state = \" used \" ) ", " thresholdValue ": 20.0 , " trigger ": { " count ": 1 } } , " displayName ": " Disk Used (%) ", " name ": " projects/example/alertPolicies/1111111111/conditions/2222222222 " } ] , " creationRecord ": { " mutateTime ": " 2023-05-04T14:38:46.558191920Z ", " mutatedBy ": " example@g-gen.co.jp " } , " displayName ": " Linux VM Disk Utilization Test Policy ", " enabled ": true , " mutationRecord ": { " mutateTime ": " 2023-06-28T08:57:23.371148479Z ", " mutatedBy ": " example@g-gen.co.jp " } , " name ": " projects/example/alertPolicies/1111111111 ", " notificationChannels ": [ " projects/example/notificationChannels/3333333333 " ] } ] 動作確認 繰り返し通知設定後、30分おきにリマインダー通知を受信していることがわかります。 繰り返し通知設定により30分おきにリマインダー通知を受信 注意事項 繰り返し通知設定を実装した場合、それ以降の更新を Cloud コンソールから行うと 繰り返し通知設定が削除 されてしまうのでご注意ください。 先にもお伝えした通り、Cloud コンソールでは設定できない項目となっているため、 設定なし で上書きされてしまうからです。 関連機能2: スヌーズ 概要 スヌーズ とは、指定した期間内でインシデントの登録やアラート通知を抑制する機能です。メンテナンス中やフラッピングによって発生する不要なアラート通知を抑制する際など、様々な場面で活用できます。 長らくプレビュー状態にあった本機能ですが、ついに 2023年5月より GA (一般公開) されました。 繰り返し通知のような設定上の制約はありません。Cloud コンソール、gcloud コマンド または API から設定可能です。 設定方法 概要 今回は gcloud コマンドを使った設定方法を説明します。スヌーズ対象のアラートポリシーは前述で繰り返し通知設定を実装したポリシーとします。 Cloud コンソールや API による設定方法については こちら の公式ガイド、またはプレビュー期間中に弊社社員が執筆した以下の記事も参照いただけると幸いです。 blog.g-gen.co.jp JSON ファイルの準備 こちら の公式ガイドから取得した雛形をベースに JSON ファイルを作成します。 # スヌーズ対象のアラートポリシーはアラートポリシー ID で指定する # 開始 / 終了時刻は ISO 8601 形式 かつ UTC で指定する { " criteria ": { " policies ": [ " projects/example/alertPolicies/1111111111 " ] } , " interval ": { " startTime ": " 2023-07-03T12:15:00.000Z ", " endTime ": " 2023-07-03T14:15:00.000Z " } , " displayName ": " snooze_for_maintanance_20230703 " } スヌーズの作成 JSON ファイルの準備が完了したら、 gcloud monitoring snoozes create コマンドを実行してスヌーズを作成します。 # --snooze-from-file オプションで JSON ファイルのパスを指定する gcloud monitoring snoozes create --snooze-from-file=snooze_for_maintanance_20230703.json 成功すると以下の戻り値が表示されます。 Created snooze [projects/example/snoozes/4444444444]. 設定内容の確認 gcloud monitoring snoozes list コマンドで設定内容を確認します。 2023/07/03 21:15 ~ 23:15 (JST) の2時間の間、指定したアラートポリシーに関するアラート通知を抑制するスヌーズが作成できました。 gcloud monitoring snoozes list -- format = json [ { " criteria ": { " policies ": [ " projects/example/alertPolicies/1111111111 " ] } , " displayName ": " snooze_for_maintanance_20230703 ", " interval ": { " endTime ": " 2023-07-03T14:15:00Z ", " startTime ": " 2023-07-03T12:15:00Z " } , " name ": " projects/example/snoozes/4444444444 " } ] Cloud コンソール > Monitoring > アラート > Snoozes 上でも同様に上記スヌーズの確認が可能です。 Cloud コンソールから見たスヌーズ設定 Cloud コンソールから見たスヌーズ設定 動作確認 スヌーズ期間中はこれまで30分おきに受信していた通知が抑制されたのと合わせて、インシデントのステータスも RESOLVED (復旧) に遷移しています。 Cloud コンソール > Monitoring > アラート > Incidents 上でも確認できます。 スヌーズにより通知が抑制 スヌーズ期間中はインシデントの状態が復旧 スヌーズ期間終了後ですが、インシデントの状態が再び ALERT 状態に遷移し、アラート通知も再開しました。 スヌーズ期間終了後、アラート通知が再開 スヌーズ期間終了後にインシデントとして再登録 武井 祐介 (記事一覧) クラウドソリューション部クラウドエンジニアリング課。 Google Cloud Partner Top Engineer 2025 選出。 趣味はロードレースやサッカー観戦、あとはゴルフと筋トレ。 Follow @ggenyutakei
アバター
G-gen の佐々木です。当記事では Google Cloud (旧称 GCP) のサーバーレスなコンテナサービスである Cloud Run の タグ付きリビジョン (tagged revision)機能を解説します。 Cloud Run とは タグ付きリビジョンとは タグ付きリビジョンを使用する Cloud Run サービスのデプロイ 使用するコード(Go) コンテナイメージのビルド サービスのデプロイ サービスへのアクセス タグ付きリビジョンのデプロイ 使用するコード(Go) 新しいコンテナイメージのビルド タグ付きリビジョンのデプロイ タグ付きリビジョンへのアクセス トラフィックの移行 タグ付きリビジョンへのトラフィック移行 タグの削除 Cloud Run とは Cloud Run は、Google Cloud のサーバーレスな基盤でコンテナアプリケーションを実行できるサービスです。Cloud Run の中でも HTTP リクエストをトリガーとするものは Cloud Run services といい、コンテナとサーバーレスの利点を活かしたスケーラビリティの高い Web アプリケーション実行基盤として非常に有用なサービスとなっています。 Cloud Run services の詳細については以下の記事をご一読ください。 blog.g-gen.co.jp タグ付きリビジョンとは Cloud Run service でデプロイしたコンテナアプリケーションは、 リビジョン という単位でバージョン管理されます。Cloud Run ではリビジョンに対するトラフィック分割機能により、新旧のリビジョンに対して一定割合でトラフィックをロードバランスし、新しいリビジョンを段階的にロールアウトすることができます。 デプロイしたリビジョンに対しては、タグを付与することができます。タグ付きリビジョンには タグを含む URL が発行され、 トラフィックを新しいリビジョンにルーティングすることなくアクセスすることができるようになります。 これにより、Cloud Run 環境で新しいリビジョンのテストを行い、テストが終わったらそのままトラフィックをルーティングすることができます。 # Cloud Run サービスの通常の URL 例 https://servicename-xxxxxxxxxx-an.a.run.app # タグ付きリビジョンの URL 例(リビジョンに dev タグを付与した場合) https://dev---servicename-xxxxxxxxxx-an.a.run.app 参考: テスト、トラフィックの移行、ロールバックにタグを使用する タグ付きリビジョンを使用する Cloud Run サービスのデプロイ 使用するコード(Go) 当記事では、公式ドキュメントの クイックスタート のコードをベースにし、Cloud Run サービスをデプロイします。 package main import ( "fmt" "log" "net/http" "os" ) func main() { log.Print( "starting server..." ) http.HandleFunc( "/" , handler) // Determine port for HTTP service. port := os.Getenv( "PORT" ) if port == "" { port = "8080" log.Printf( "defaulting to port %s" , port) } // Start HTTP server. log.Printf( "listening on port %s" , port) if err := http.ListenAndServe( ":" +port, nil ); err != nil { log.Fatal(err) } } func handler(w http.ResponseWriter, r *http.Request) { s := "Hello, World!" fmt.Fprintf(w, "%s \n " , s) // ブラウザに文字列を表示する } コンテナイメージのビルド Cloud Run にデプロイするため、コンテナイメージをビルドして Artifact Registry にプッシュします。ここでは Dockerfile を使用せず、Buildpack を使用してコンテナイメージをビルドします。 イメージの新旧バージョンを分かりやすくするため、コンテナイメージには v1.0 タグを付与します。 # Buildpack を使用してイメージをビルドする $ gcloud builds submit --pack image={リポジトリの URL}/{コンテナイメージ名}:v1.0 # 実行例(リポジトリに Artifact Registry を使用) $ gcloud builds submit --pack image=asia-northeast1-docker.pkg.dev/myproject/myrepo/sample-service:v1.0 参考①: Google Cloud の Buildpack 参考②: Cloud Run で Go ジョブをビルドして作成する サービスのデプロイ 以下のコマンドを使用して Cloud Run サービスの最初のリビジョンをデプロイします。 # Cloud Run サービスのデプロイ $ gcloud run deploy {サービス名} \ --image {コンテナイメージのURL} \ --region {リージョン} \ --allow-unauthenticated # 実行例(v1.0のコンテナイメージを指定) $ gcloud run deploy sample-service \ --image asia-northeast1-docker.pkg.dev/myproject/myrepo/sample-service:v1.0 \ --region asia-northeast1 \ --allow-unauthenticated サービスへのアクセス サービスのデプロイ後に URL が出力されるので、ブラウザからサービスの URL にアクセスします。 # デプロイ後の出力抜粋 Service [sample-service] revision [sample-service-00001-xuh] has been deployed and is serving 100 percent of traffic. Service URL: https://sample-service-ai4qoprwhq-an.a.run.app 最初のリビジョンでは、ブラウザ上に「Hello, World!」の文字列が表示されます。 最初のリビジョンへのアクセスを確認する タグ付きリビジョンのデプロイ 使用するコード(Go) 最初のリビジョンと区別するため、新しいリビジョンではブラウザに表示する文字列を変更します。 当記事では、新しいリビジョンにアクセスすると「Hello, G-gen!」の文字列が表示されるようにします。 package main import ( "fmt" "log" "net/http" "os" ) func main() { log.Print( "starting server..." ) http.HandleFunc( "/" , handler) // Determine port for HTTP service. port := os.Getenv( "PORT" ) if port == "" { port = "8080" log.Printf( "defaulting to port %s" , port) } // Start HTTP server. log.Printf( "listening on port %s" , port) if err := http.ListenAndServe( ":" +port, nil ); err != nil { log.Fatal(err) } } func handler(w http.ResponseWriter, r *http.Request) { s := "Hello, G-gen!" // ここを修正する fmt.Fprintf(w, "%s \n " , s) } 新しいコンテナイメージのビルド 最初のリビジョン同様に、コンテナイメージをビルドして Artifact Registry にプッシュします。 新しいイメージには v2.0 タグを付与します。 # Buildpack を使用してイメージをビルドする $ gcloud builds submit --pack image={リポジトリの URL}/{コンテナイメージ名}:v2.0 # 実行例(リポジトリに Artifact Registry を使用) $ gcloud builds submit --pack image=asia-northeast1-docker.pkg.dev/myproject/myrepo/sample-service:v2.0 タグ付きリビジョンのデプロイ gcloud run deploy コマンドで --tag オプションを使用することで、タグ付きリビジョンをデプロイすることができます。ここで --no-traffic オプションを指定することで、新しいリビジョンにトラフィックがルーティングされないようにします。 既存のサービスに対して新しいリビジョンをデプロイするため、 サービス名 には最初に作成したサービスと同じ名前を使用します。 # タグ付きリビジョンをデプロイする $ gcloud run deploy {サービス名} \ --image {コンテナイメージのURL} \ --region {リージョン} \ --no-traffic \ --tag {タグ名} # 実行例(v2.0のコンテナイメージを指定し、「dev」タグを付与) $ gcloud run deploy sample-service \ --image asia-northeast1-docker.pkg.dev/myproject/myrepo/sample-service:v2.0 \ --region asia-northeast1 \ --no-traffic \ --tag dev タグ付きリビジョンへのアクセス タグ付きのリビジョンをデプロイするとタグが含まれる URL が発行されるので、ブラウザで URL にアクセスします。 タグ付きリビジョンの URL には、サービスの本来の URL に dev--- のような形式でタグが付与されています。 # タグ付きリビジョンのデプロイ後の出力抜粋 Service [sample-service] revision [sample-service-00002-caw] has been deployed and is serving 0 percent of traffic. The revision can be reached directly at https://dev---sample-service-ai4qoprwhq-an.a.run.app コンソールからタグをクリックすることでもアクセスすることが可能です。 コンソールからタグ付きリビジョンの URL にアクセスする 「Hello, G-gen!」が表示されているため、トラフィックがルーティングされていない新しいリビジョンにアクセスできていることがわかります。 タグ付きリビジョンへのアクセスを確認する タグがついていないサービスの URL にアクセスすると、現在トラフィックがルーティングされている最初のリビジョンにアクセスできるため、新しいリビジョンがまだ公開されていないことがわかります。 サービスの URL から最初のリビジョンにアクセスできることを確認する トラフィックの移行 タグ付きリビジョンへのトラフィック移行 タグ付きリビジョンのテストが終わったら、最初のリビジョンからトラフィックを移行します。 gcloud run services update-traffic コマンドの --to-tags オプションでタグ名を指定し、トラフィックを何パーセント割り当てるかを指定します。 # タグ付きリビジョンにトラフィックをルーティングする $ gcloud run services update-traffic {サービス名} \ --region {リージョン} \ --to-tags {タグ名}={ルーティングするトラフィックの割合} # 実行例(dev タグが付与されたリビジョンにトラフィックを 100% ルーティングする) $ gcloud run services update-traffic sample-service \ --region asia-northeast1 \ --to-tags dev=100 タグ付きの新しいリビジョンにトラフィックが 100% ルーティングされている サービスの URL にアクセスすると、新しいリビジョンにアクセスできるようになっています。 サービスの URL からタグ付きの新しいリビジョンにアクセスできることを確認する タグの削除 タグが不要になったら --remove-tag オプションでタグを指定し、サービスを更新します。 # リビジョンからタグを削除する $ gcloud run services update-traffic {サービス名} \ --region {リージョン} \ --remove-tags {タグ名} # 実行例(dev タグを削除) $ gcloud run services update-traffic sample-service \ --region asia-northeast1 \ --remove-tags dev 佐々木 駿太 (記事一覧) G-gen最北端、北海道在住のクラウドソリューション部エンジニア 2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。 趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。 Follow @sasashun0805
アバター
当記事は みずほリサーチ&テクノロジーズ × G-gen エンジニアコラボレーション企画 で執筆されたものです。 G-gen の片岩です。当記事では Google Cloud のデータベースサービスである Bigtable を徹底解説します。ビジネスにおいてデータ活用が重要なことは改めて記載するまでもありません。大量のデータを高速に処理でき、スケーラビリティのある Bigtable は、より効率的かつ正確なビジネス上の意思決定に貢献できそうです。また、高度で詳細な監査要件の求められる金融機関のシステムにおいてはログの蓄積・解析などでの利用も考えられそうです。 基本事項 Cloud Bigtable とは ユースケース 料金 概要 コンピューティング料金 ストレージ料金 ネットワーク料金 計算例 データの操作 概要 クライアントライブラリ SQL サポート cbt CLI 他の Google Cloud サービス コンポーネント 全体像 インスタンス クラスタ ノード ストレージ 複数クラスタとレプリケーション 概要 レイテンシと整合性 複数クラスタのユースケース 可用性向上 オンラインとバッチの分離 グローバルなレイテンシ短縮 バックアップ・リストア 内部構造 ストレージモデル インフラ・アーキテクチャ スキーマ設計 スキーマ設計のポイント スキーマ設計のベストプラクティス スキーマ設計例 測定するごとに行を追加するパターン 測定するごとに列を追加するパターン 測定するごとにセルを追加するパターン アプリプロファイル パフォーマンスに関する注意点 概要 書き込み 読み取り モニタリング リソース使用状況の確認 Key Visualizer 基本事項 Cloud Bigtable とは Cloud Bigtable (以降、Bigtable) は NoSQL ビッグデータ向けのフルマネージドなデータベースサービスです。 特徴はなんと言っても 低レイテンシかつ高スループット であることで、 膨大なデータをリアルタイムで処理する ことが可能です。 Bigtable は Google 検索、Google Analytics、Google マップ、Gmail など、Google の主要サービスを支えているサービスでもあります。 Bigtable は NoSQL データベースであり、行 (Key) と列 (Value) で構成される Key-Value マップにデータを格納します。行と列が存在しますが、リレーショナルデータベース (RDB) と異なり、使用していない領域はストレージを消費しないスパース (低密度) な構造となっています。またダウンタイムなしでクラスタサイズを変更でき、スケーラビリティに優れています。 Bigtable のデータへは、 API 経由 でアクセスします。Go, Java, Python, PHP, Ruby, C# など各言語用のクライアントライブラリが用意されている他、Apache HBase (オープンソースの列指向・分散データベース) 互換の API も用意されています。 ユースケース Bigtable は大量データのリアルタイム処理で真価を発揮 します。 前述の Google のサービスで使われているほか、大量のログや IoT デバイスから送信されるデータを取り扱うケース等で採用されています。 クレジットカード利用における不正行為の検出 患者の容態の予測 フライトの高速検索システム 電力使用状況の可視化 Web 接客プラットフォーム ZOZOTOWN の推薦システム基盤 料金 概要 Bigtable では以下の料金が発生します。 コンピューティング料金 ストレージ料金 (バックアップ含む) 通信料金 料金表は 公式ページ を参照ください。 コンピューティング料金 コンピュート処理能力はノード (後述) 単位で最低1時間の課金が発生します。たとえば1台のノードが100分間稼働した場合は2時間分の料金が発生します。 また、実際にリクエストを処理していなくても課金が発生するため、リクエストが少ない時間帯はノード数を減らすことで料金を節約できます。 ストレージ料金 利用したストレージ (テーブルとバックアップ) 分だけ支払いが発生する従量課金です。設置するリージョンやディスク (SSD / HDD) に応じて単価が異なります。 Bigtable はコンパクションと呼ばれる自動圧縮処理を定期的に実行しており、課金はこの圧縮後のデータサイズに対して計算されます。 また複数のクラスタ (後述) を含むインスタンス (後述) の場合、クラスタごとにデータのコピーを保持するため、その分の課金が発生します。 ネットワーク料金 データの書き込み (Bigtable に入っていく方向) は無料です。 データの読み込み (Bigtable から出ていく方向) は同一リージョン内の通信は無料ですが、異なるリージョン間の通信やインターネットへ向けた通信には料金が掛かります。 計算例 参考までに料金の計算例を記載します (単価は2023年7月時点のもの)。 東京リージョンで 1 ヶ月を通して 1 ノードのコンピュートリソースを使用 平均 50 GB のデータを SSD ドライブに保存 東京リージョンへの 50GB のネットワーク下り通信 課金要素 数量 計 コンピューティング料金 1ノード * 30 days * 24h * $0.85 $612.00 ストレージ料金 (SSD) 50GB * $0.22 $11.00 ネットワーク料金 同一リージョン間通信のため無料 $0.00 - 合計 $623 データの操作 概要 一般的な RDB データの操作 (読取・書込等) には SQL を利用しますが、Bigtable は Web API を用います。 といっても直接 Web API への HTTP リクエストを行うことは稀です。実際には cbt という CLI ツールや、Python、Java、Go、PHP といった各言語に用意されたクライアントライブラリなど、Web API をラップしたツールを用いて操作するのが一般的です。 参考 : cbt CLI でインスタンスを作成してデータを書き込む 参考 : Bigtable client libraries クライアントライブラリ 例としてここでは Python のクライアントライブラリを用いて Bigtable にデータを登録するサンプルコードを紹介します。 greetings = [ "Hello World!" , "Hello Cloud Bigtable!" , "Hello Python!" ] rows = [] column = "greeting" .encode() for i, value in enumerate (greetings): row_key = "greeting{}" .format(i).encode() row = table.direct_row(row_key) row.set_cell( column_family_id, column, value, timestamp=datetime.datetime.utcnow() ) rows.append(row) table.mutate_rows(rows) row.set_cell(column_family_id, column, value, timestamp) にてテーブルにデータを追加しています。 このように Bigtable では行キー (row_key)、列名 (column)、値 (value) 等を設定してデータを登録します。 参考 : Python の Hello World SQL サポート Bigtable は原則的に、各プログラミング言語用のクライアントライブラリを用いてデータの読み書きを行うデータベースですが、2024年8月のアップデートで、SQL が利用可能になりました(2024年8月現在、Preview)。 GoogleSQL という、BigQuery などと共通の方言を持つ SQL を用いて、Bigtable にクエリを投入することができます。SQL はクライアントライブラリ経由で投入するか、Google Cloud コンソールの Bigtable Studio から投入することができます。 参考 : Introduction to SQL in Bigtable cbt CLI cbt CLI は Bigtable の操作を実行するためのツールです。Cloud Shell やローカル開発環境にインストールして Bigtable を操作することができます。 my-table という名前のテーブルは以下のコマンドで作成できます。 cbt createtable my-table my-table からデータを読み取るコマンドは以下になります。 cbt read my-table 参考 : クイックスタート: cbt CLI を使用してインスタンスを作成し、データを書き込む 他の Google Cloud サービス BigQuery (データウェアハウスサービス) から Bigtable を外部テーブルとして定義することで、Bigtable のデータを BigQuery にコピーしなくても、BigQuery から直接クエリを発行することができます。 参考 : Bigtable データにクエリを実行する また Dataflow (Apache Beam のマネージドサービス) から Bigtable に接続するためのコネクタが用意されており、Dataflow パイプラインの中から Bigtable のデータへアクセスすることが可能です。 参考 : Bigtable 用 Dataflow コネクタ コンポーネント 全体像 Bigtable のコンポーネント構成の全体像は、以下のとおりです。 全体像 参考 : インスタンス、クラスタ、ノード インスタンス インスタンス は Bigtable の最も基本的な管理単位です。 インスタンスは複数のクラスタとストレージを含み、テーブルもインスタンスに所属します。テーブルは後述のクラスタやノードに所属するのではなく、インスタンスに所属し、各クラスタにレプリケーションされます。 クラスタ クラスタ はノード (個々のサーバ) をグルーピングした概念です。一つのインスタンスに所属し、特定のゾーンに存在します。アプリケーションがインスタンスにリクエストを送信すると、いずれかのクラスタが処理します。 なお Bigtable ではコンピューティングとストレージが分離されているため、クラスタにはストレージが含まれません。 たとえば東京リージョン (内のとあるゾーン) と大阪リージョン (同) にクラスタを展開すると ①最寄りのクラスタでリクエストを処理するためレイテンシを低下できる ②東京リージョンで障害が発生しても大阪リージョンでサービスを継続できる といったことが可能になります。 ノード ノードはクラスタを構成するサーバのイメージです。ノードを増やすことでクラスタの処理能力を向上できます。 CPU 使用率やストレージ使用率に応じて自動的にノードを追加することも可能です。 自動スケーリング ストレージ ストレージはデータが保存される領域です。クラスタに所属します。 複数クラスタとレプリケーション 概要 Bigtable インスタンスの中には、複数のクラスタを配置できます。クラスタ間でストレージはレプリケーションされ、データの可用性と耐久性を向上させます。 Bigtable インスタンスは、Google Cloud リージョンのうち最大 8 つのリージョンにクラスタを配置できます。またリージョン内はゾーンで分かれていますが、一つのゾーンに配置できるクラスタは 1 つのみです。 あるリージョン内で複数のゾーンにクラスタを配置することもできますし、別々のリージョンにクラスタを配置することもできます。 参考 : レプリケーションについて レイテンシと整合性 クラスタ間のレプリケーションは非同期であり、レイテンシがあること、また結果整合性であることに注意が必要です。 レプリケーションのレイテンシがどのくらいあるかについては一概には言えませんが、通常は数秒〜数分の間であり、数時間に達することはありません。 ただし、書込後の読取に整合性を持たせることも可能です。後述のアプリプロファイルにて「単一クラスタのルーティング」を選択した場合のみ、単一行レベルでの強整合性を確保することが可能です。 参考 : 整合性モデル 複数クラスタのユースケース 可用性向上 複数クラスタを別々のゾーンに配置することで高い可用性を担保できます。自動フェイルオーバさせることも可能です。 参考 : 高可用性(HA)の作成 オンラインとバッチの分離 複数クラスタを用意することで、業務アプリケーションと分析目的のジョブのワークロードを分離することができます。 分離には、後述のアプリプロファイルを利用できます。 参考 : バッチ分析ワークロードを他のアプリケーションから分離する グローバルなレイテンシ短縮 世界中にアプリケーションの利用者がいる場合、利用者に近い地域のリージョンにクラスタを作成することで、読取レイテンシの低減に繋がります。 参考 : ユーザーの近くにデータを保存する バックアップ・リストア Bigtable では任意の時点の バックアップ を取得することができます。 Bigtable のバックアップはテーブルバックアップであり、指定したテーブルのスキーマとデータを保持します。Compute Engine のスナップショット等は異なり、インスタンス (あるいはクラスタやノード) をまるごとバックアップするものではありません。 バックアップからの復元時は、任意の既存インスタンスを選択してその中にテーブルをリストアできます。 バックアップからのリストアは、オペレーションミスやアプリケーションによりデータが破壊された場合などに加え、例えば本番環境テーブルからステージング環境テーブルを複製して作成する、などの用途にも使えます。 バックアップの復元 参考 : Bigtable のバックアップについて 内部構造 ストレージモデル まずは Bigtable に登録されたデータがどのように保管されるのか、ストレージモデルを見ていきます。ストレージモデルは後述のスキーマ設計に大きく関わってきます。 画像は 公式ドキュメント から引用 行 (Key) と列 (Value) で構成される Key-Value マップにデータを格納します。 各行は一意の行キーを持っています。 相互に関連する列を列ファミリーとしてグループ化できます。 行と列が交差する場所には複数のセルを含むことができます。 使用していない列はデータの保存領域を消費しません。 インフラ・アーキテクチャ Bigtable はデータを保管するストレージと、クエリを実行するコンピューティングリソース(ノード)が分離した構成になっています。 これにより大量データの保管と高速なクエリを実現しています。 実際にクエリが処理される流れを見ていきます。 アーキテクチャ クライアント リクエストは Bigtable クラスターに送信されます。 Bigtable クラスターは複数のノードで構成されており、リクエストが各ノードに割り当てられます。(図の場合、行キーが D から始まるクエリを処理する場合はノード 1 が割り当てられます) ノードはそれぞれ独立してリクエストを処理します。ノード追加によりパフォーマンスを向上できます。 実データは辞書順に並び替えられ、ストレージに保管されています。 なお、上図は各アルファベットから始まるデータが均等な場合のイメージです。実際には各ノードの負荷が均等になるようにノードとデータは関連付けられます。 スキーマ設計 スキーマ設計のポイント データベースのパフォーマンスを最大限発揮できるようなスキーマ設計は重要です。 従量課金のクラウドサービスでは過大な課金につながる可能性もあります。 アーキテクチャやストレージの仕組みのポイントをおさらいします。 Key-Value ストアであること テーブル結合は利用できません。 トランザクションは 1 つの行内でのみ完結します。複数行にまたがるトランザクションは利用できません。 行の特徴 各行キーは一意である必要があります。 行は、行キーのビッグエンディアン順(バイナリのアルファベット順に相当する)に並べ替えられます。 各テーブルのインデックス(行キー)は 1 つのみです。二次インデックスはありません。 列の特徴 列ファミリーは特定の順序では保存されません。 列は、列ファミリー別にグループ化され、列ファミリー内で辞書順に並べ替えられます。 読み取りと書き込みは(テーブルの行スペース全体に)均等に分散されるのが理想 Bigtable で使用していない列は空になり、保存領域を消費しない スキーマ設計のベストプラクティス 上記で整理した内容をスキーマ設計の観点に読み替えると、以下の 2 点が挙げられます。 データは結合不要な形で保管する。非正規化して1つのテーブルにまとめる 各ノードに均等にリクエストが割り当てられるようにする。時刻やシーケンス番号など、辞書順で並べた際に偏りのある情報は行キーの先頭では使用しない また、辞書順で偏らないためにハッシュ化した値を利用する方法もありそうですが、これはアンチパターンです。トラブルシューティングをする際、読解不可能な文字列では支障があるため、行キーには読解可能な文字列を使用します。 詳細はスキーマ設計の ベストプラクティス をご参照ください。 スキーマ設計例 時系列データのスキーマ設計例をみてみましょう。 気象バルーンが 1 分ごとに測定した圧力等のデータを Bigtable に保存することを想定します。 スキーマ設計に画一的な正解はなく、いくつかパターンがあります 。 自身のケースに置き換えてみて、適切なパターンを選択することが重要です。 測定するごとに行を追加するパターン 1分ごとに新しい行を登録するパターンです。 シンプルで開発が容易 なことが特徴です。 このパターンで書き込まれる例を示します。 行キーには1分ごとの日時を示す文字列が含まれます。 行キー 圧力 温度 湿度 標高 us-west2#3698#2023-06-05-1200 94558 9.6 61 612 us-west2#3698#2023-06-05-1201 94122 9.7 62 611 us-west2#3698#2023-06-05-1202 95992 9.5 58 602 us-west2#3698#2023-06-05-1203 96025 9.5 66 598 us-west2#3698#2023-06-05-1204 96021 9.6 63 624 測定するごとに列を追加するパターン 1分ごとに列を追加するパターンです。 1行にたとえば1週間分のデータを格納します。 保存容量を節約できる ことが特徴です。 このパターンで書き込まれる例として、3分後の pressure (圧力) のデータを示します。 行キーには n 週目の圧力を示す文字列が含まれます。 value は測定値に対する測定日時が登録されたデータ構造になります。 行キー 94558 94122 95992 us-west2#3698#pressure#week1 t2023-06-05-1200 t2023-06-05-1201 t2023-06-05-1202 毎回測定値が異なると列が増えてしまいますが、 同じような測定値が繰り返される場合は、複数のセルにまとまって情報が登録されるため保存容量が節約されます。 測定するごとにセルを追加するパターン 1分ごとにセルを追加するパターンです。 1行にたとえば1週間分のデータを格納します。 測定値の経時変化を扱える ことが特徴です。 3分後の圧力列と温度列は次のようになります。 行キーには n 週目を示す文字列が含まれます。 行キー 圧力 温度 asia-south2#3698#week1 94558(t2023-06-05-1200) 9.5(t2023-06-05-1200) 94122(t2023-06-05-1201) 9.4(t2023-06-05-1201) 95992(t2023-06-05-1202) 9.2(t2023-06-05-1202) 1 行の読み取りで 1 週間分のデータを読み取るため、経時変化を扱う場合に有用です。温度だけ必要であれば、温度の列だけ選択することも可能です。 詳細は 公式ドキュメント をご参照ください。 アプリプロファイル アプリプロファイル (app profiles) は、Bigtable がアプリケーションから受け取ったリクエストを処理する方法について定義した設定です。 アプリプロファイルは、複数のクラスタを使用するインスタンスで特に重要になります。トランザクションの整合性に関する設定や、クラスタへのルーティングの方法などを定義します。 例えば業務アプリケーションのトラフィックと、分析用のトラフィックに別々のアプリプロファイルを当てはめ、別々のクラスタへルーティングすることで、クラスタへの負荷を分散させることができます。 参考 : アプリ プロファイルについて アプリケーションプロファイル 以下はアプリ側の サンプルコード です。赤字箇所でアプリケーションプロファイルを指定しています。 from google.cloud import bigtable client = bigtable.Client(project=project_id) instance = client.instance(instance_id) table = bigtable.table.Table(table_id, instance, '[APP_PROFILE_ID]' ) パフォーマンスに関する注意点 概要 データの操作時も Bigtable の内部構造を考慮する必要があります。Bigtable のパフォーマンスを十分に発揮させるには以下の点が重要です。 辞書順でデータが格納されてノードが割り当てられることを考慮して、まとまった単位でデータを格納すること 類似の情報を一括して読み取り・書き込みし、 クエリの発行回数を抑える こと 多数のクエリを発行する場合は、1つのノードに偏らない こと 書き込み Bigtable では単一行を書き込む方法のほかに、複数行を同時に書き込むバッチ書き込みが利用できます。 隣接したデータを更新する場合は、バッチ書き込みを利用したほうがクエリの発行回数を抑えることができてパフォーマンスが良くなります。 参考 : Batch writes 読み取り Bigtable では行キーの辞書順でデータが保管されているため、 連続した複数行の読み取りは低レイテンシ です。 しかしランダムな複数行の読み取りはテーブル全体をスキャンすることになるため非効率です。 よく使用するクエリが低レイテンシで応答できるよう、 同時に読み取ることの多いデータが近くなるようなスキーマ設計 を意識すると良いでしょう。 また、 読み取る列を絞り込むことでパフォーマンスを改善する ことができます。 参考 : Reads and performance モニタリング リソース使用状況の確認 Bigtable のコンソール画面や Cloud Monitoring のコンソール画面で CPU 使用率やディスクの使用量などを モニタリング することができます。 CPU 使用率やディスク使用量が増加傾向にあれば、必要に応じてノードを追加して負荷を分散させましょう。 Key Visualizer Key Visualizer は Bigtable の使用状況の分析に役立つツールです。 テーブルの行全体がバランスよくアクセスされているかどうか等を確認することができます。 以下の画像は Key Visualizer スキャンの画面です。 横軸が時間で縦軸が行キーを表しており、色が明るいほど読み込みや書き込みなどの処理が実行されていることを示します。たとえば特定の行キーにのみ明るい色がついている場合、そこに負荷が集中していることが判ります。 均等分布や順次読み取り/書き込みの場合等、いくつかのパターンが こちら で紹介されています。 画像は 公式ドキュメント から引用 片岩 裕貴 (記事一覧) クラウドソリューション部 クラウドディベロッパー課 2022年5月にG-genにジョインした和歌山県在住のエンジニア。興味分野はAI/ML。2024年にGoogle Cloud認定資格全冠達成。最近は子供と鈴鹿サーキットや名古屋のレゴランドに行ってきました。
アバター
G-gen の藤岡です。当記事では、Google Cloud (旧称 GCP) の Compute Engine VM から Cloud Storage バケットを操作する時に起きる権限エラーについて、実際のエラー内容からサービスアカウントの IAM 権限以外に疑うこと、その対処法について紹介します。 前提知識 事象 原因 エラー文の違い 対処 アクセススコープの変更 手動で作成したサービスアカウントをアタッチ ユーザーアカウントの使用 前提知識 Compute Engine (以下 GCE)は、デフォルトでは PROJECT_NUMBER-compute@developer.gserviceaccount.com のサービスアカウントが設定されます。但し、このサービスアカウントにはプロジェクトレベルで 編集者 ( roles/editor )ロールが付与されており、広範囲な権限を持っているため実運用での利用は好ましくありません。 参考: Compute Engine のデフォルトのサービス アカウント 事象 GCE インスタンスでデフォルトのサービスアカウントをアタッチした状態で Cloud Storage(以下 GCS)を操作すると、バケットやオブジェクトの閲覧はできても、アップロードや削除ができませんでした。 # バケットの表示:可 fujioka@instance:~$ gcloud storage ls gs://test-bucket/ fujioka@instance:~$ # ファイルのアップロード:不可 fujioka@instance:~$ gcloud storage cp test .txt gs://test-bucket/ Copying file:// test .txt to gs://test-bucket/ test .txt ERROR: User [ 012345-compute@developer.gserviceaccount.com ] does not have permission to access b instance [ test-bucket ] ( or it may not exist ) : Access denied. Completed files 1 / 1 | 0B fujioka@instance:~$ デフォルトのサービスアカウントには編集者ロールが付与されています。バケットから確認しても、編集者ロールが継承され、 ストレージ管理者 ( roles/storage.admin )、が設定されており、サービスアカウントの IAM 権限としては問題ありません。 バケットの権限 この時、Cloud Logging にはエラーログは出力されていませんでした。 参考: gcloud storage 参考: Cloud Storage に適用される IAM のロール 原因 今回は、サービスアカウントの IAM は編集者ロールのため GCS バケットにオブジェクトのアップロードが出来るはずですが、アクセススコープが読み取りしか許可をしていないため、制約の厳しいアクセススコープが優先され生じた権限エラーでした。 詳しく見ていきます。 GCE インスタンスから Google Cloud APIs へのアクセス制御方法には以下の 2 つがあります。 サービスアカウントの IAM アクセススコープ サービスアカウントとアクセススコープについての詳細は以下の記事をご参照ください。 blog.g-gen.co.jp blog.g-gen.co.jp 全てデフォルトでインスタンスを作成すると、 API と ID の管理 で、サービスアカウントと Cloud API アクセススコープは以下のように設定されます。 インスタンスの詳細画面 Cloud API アクセススコープが デフォルトのアクセス権を許可 になっている時は、GCS に対して読み取り専用のアクセス権( https://www.googleapis.com/auth/devstorage.read_only )が与えられます。この時、 サービスアカウントの IAM とアクセススコープのうち、制約が厳しい方が優先されます 。今回のエラー原因は、このアクセススコープが デフォルトのアクセス権を許可 の設定だったためです。 GCS 以外にもアクセススコープは以下のようなサービスの制御が可能です。 API ごとのアクセススコープ gcloud compute instances create で GCE インスタンスを作成した場合もデフォルトではコンソールと同様のアクセススコープ設定となるため注意が必要です。 参考: デフォルトのスコープ 参考: gcloud compute instances create エラー文の違い アクセススコープ起因とサービスアカウントの IAM 起因のそれぞれの権限エラーでは、エラーの表示に以下のような違いがあります。 # アクセススコープ起因 fujioka@instance:~$ gcloud storage cp test .txt gs://test-bucket/ Copying file:// test .txt to gs://test-bucket/ test .txt ERROR: User [ 012345-compute@developer.gserviceaccount.com ] does not have permission to access b instance [ test-bucket ] ( or it may not exist ) : Access denied. Completed files 1 / 1 | 0B fujioka@instance:~$ # サービスアカウントの IAM 起因 fujioka@instance:~$ gcloud storage cp test .txt gs://test-bucket/ Copying file:// test .txt to gs://test-bucket/ test .txt ⠹ERROR: User [ test@ 012345 .iam.gserviceaccount.com ] does not have permission to access b instance [ test-bucket ] ( or it may not exist ) : test-59@ 012345 .iam.gserviceaccount.com does not have storage.objects.create access to the Google Cloud Storage object. Permission ' storage.objects.create ' denied on resource ( or it may not exist ) . Completed files 1 / 1 | 0B fujioka@instance:~$ サービスアカウントの IAM 権限が不足している時には、具体的に不足している権限(ここでは storage.objects.create )がエラー文に含まれています。 対処 アクセススコープの変更 アクセススコープは、以下の 3 種類から選べます。 デフォルトのアクセス権を許可 すべての Cloud API に完全アクセス権を許可 API ごとにアクセス権を設定 アクセススコープの種類 アクセススコープは、 すべての Cloud API に完全アクセス権を許可 ( https://www.googleapis.com/auth/cloud-platform ) にし、アクセス制御はサービスアカウントの IAM で行うことが推奨されています 。 アクセススコープで GCE インスタンスのアクセス制御をすることはレガシーな方法です。 今回は、デフォルトのサービスアカウントのままアクセススコープを すべての Cloud API に完全アクセス権を許可 に変更することで権限エラーは解消されました。 参考: スコープのベスト プラクティス 手動で作成したサービスアカウントをアタッチ 実運用では前述の通り、デフォルトのサービスアカウントの利用は推奨されていません。 そのため、ベストプラクティスはサービスアカウントを作成し、最小権限の原則に従い適切な IAM 権限を付与したサービスアカウントを GCE インスタンスにアタッチすることです。 この時、手動で作成したサービスアカウントに変更すると、以下のようにアクセススコープが選択できなくなります。 アクセススコープが選択不可になる 参考: ベスト プラクティス 参考: IAM を安全に使用する ユーザーアカウントの使用 方法論としてユーザーアカウントを使うことで今回のエラーは回避できますが、「アクセススコープの変更」と「手動で作成したサービスアカウントをアタッチ」を行う方が好ましいです。ここでは参考までに記載します。 デフォルトでは、GCE インスタンス上で gcloud CLI を使う際、 アプリケーションのデフォルト認証情報 (ADC)を使ってサービスアカウントの認証情報とアクセススコープに従いアクセス制御を行います。 GCE インスタンスの設定を確認すると、以下のようにデフォルトのサービスアカウントが設定されていることがわかります。この状態では、アクセススコープの変更をしていないため、 gcloud storage cp~ でエラーとなります。 # アカウントの確認 fujioka@instance:~$ gcloud config list [ core ] account = 012345-compute@developer.gserviceaccount.com disable_usage_reporting = True project = 012345 Your active configuration is: [ default ] fujioka@instance:~$ # ファイルのアップロード:不可 fujioka@instance:~$ gcloud storage cp test .txt gs://test-bucket/ Copying file:// test .txt to gs://test-bucket/ test .txt ERROR: User [ 012345-compute@developer.gserviceaccount.com ] does not have permission to access b instance [ test-bucket ] ( or it may not exist ) : Access denied. Completed files 1 / 1 | 0B fujioka@instance:~$ ここで、適切な権限を持ったユーザーアカウントに切り替えると、gcloud CLI の実行はユーザーアカウントになるため、 gcloud storage cp~ が問題なく実行できます。 # ユーザーアカウントを使う fujioka@instance:~$ gcloud auth login You are running on a Google Compute Engine virtual machine. It is recommended that you use service accounts for authentication. ~ You are now logged in as [ fujioka@g-gen.co.jp ] . Your current project is [ 012345 ] . You can change this setting by running: $ gcloud config set project PROJECT_ID fujioka@instance:~$ # アカウントの確認 fujioka@instance:~$ gcloud config list [ core ] account = fujioka@g-gen.co.jp disable_usage_reporting = True project = 012345 Your active configuration is: [ default ] fujioka@instance:~$ # ファイルのアップロード:可 fujioka@instance:~$ gcloud storage cp test .txt gs://test-bucket/ Copying file:// test .txt to gs://test-bucket/ test .txt Completed files 1 / 1 | 0B fujioka@instance:~$ 参考: gcloud CLI を承認する 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。2023 夏アニメのオススメは、ダークギャザリング。箏を習っています :) Follow @fujioka57621469
アバター
G-gen 又吉です。今回は Cloud Run jobs を用いて、FTP サーバから Cloud Storage にファイル転送する仕組みを作成していきます。 概要 事前準備 開発環境の準備 ディレクトリ構成 Dockerfile main.py requirements.txt 使用するリソースの作成 FTP サーバの初期設定 動作確認 Cloud Run jobs の実行 リソースの削除 本番運用に向けての考慮事項 接続方式 IP アドレス プロトコル サービスアカウントの権限 概要 データ分析パイプラインなどで、データソースが FTP サーバ上に存在する時、FTP サーバからクラウド上のデータレイクにデータ転送が必要なケースもあるかと思います。 今回は、先日 (2023 年 4 月) GA した Cloud Run jobs を用いて、FTP サーバから Cloud Storage にファイル転送する仕組みを作成していきたいと思います。 今回作成する構成図 Cloud Run jobs は Cloud Scheduler や Cloud Workflows と連携することでスケジュール実行することも可能ですが、今回は検証のため gcloud コマンドを用いて手動実行していきます。 Cloud Run jobs の詳細については、以下の記事をご参照下さい。 blog.g-gen.co.jp 事前準備 開発環境の準備 ディレクトリ構成 開発環境には Cloud Shell を使用します。Cloud Shell を起動したら、以下のディレクトリ構成で各ファイルを作成して下さい。 get_file_from_ftp_server |-- Dockerfile |-- main.py `-- requirements.txt Dockerfile # Pythonイメージを取得 FROM python:3. 10 # ローカルコードをコンテナイメージにコピー ENV APP_HOME /app WORKDIR $APP_HOME COPY . ./ # 依存関係のインストール RUN pip install --no-cache-dir -r requirements.txt # コンテナの起動時の実行コマンド CMD [" /usr/local/bin/python3 " , " main.py "] main.py main.py には以下を記述して下さい。 使用したライブラリについて、FTP 接続には ftplib を、Cloud Storage バケットへのアップロードには、 Python Client for Google Cloud Storage を使用しました。 from ftplib import FTP import os from google.cloud import storage FTP_SERVER_IP = os.environ.get( "FTP_SERVER_IP" ) FTP_USER = os.environ.get( "FTP_USER" ) FTP_PASSWD = os.environ.get( "FTP_PASSWD" ) FILE_NAME = os.environ.get( "FILE_NAME" ) BUCKET_NAME = os.environ.get( "BUCKET_NAME" ) if __name__ == "__main__" : try : # FTP サーバへ接続 ftp = FTP(host=FTP_SERVER_IP, user=FTP_USER, passwd=FTP_PASSWD) # パッシブモードを有効 ftp.set_pasv( True ) except Exception as e: print ( "An error occurred connecting to the ftp server:" , str (e)) raise else : try : # FTP サーバからファイル取得 filename = FILE_NAME ftp.retrbinary( "RETR " + filename, open (filename, "wb" ).write) ftp.quit() except Exception as e: print ( "An error occurred when retrieving files from the ftp server:" , str (e)) raise else : try : # Cloud Storage へアップロード storage_client = storage.Client() bucket = storage_client.bucket(BUCKET_NAME) blob = bucket.blob(filename) blob.upload_from_filename(filename) except Exception as e: print ( "An error occurred while uploading a file to Cloud Storage:" , str (e)) raise requirements.txt google-cloud-storage == 2 . 9 . 0 使用するリソースの作成 Cloud Shell で get_file_from_ftp_server のディレクトリ階層に移動したら、以下コマンドを順次実行します。 # 環境変数の設定 export PROJECT_ID = { プロジェクト ID を入力 } export PROJECT_NAME = { プロジェクト名を入力 } export BILLING_ACCOUNT_ID = { 請求先アカウント ID を入力 } export BUCKET_NAME = { 取得するファイルの格納先バケット名を入力 } export FTP_USER =ftpuser export FTP_SERVER_PW = 1234 export FILE_NAME =sample.txt # プロジェクト作成 gcloud projects create ${PROJECT_ID} --name= ${PROJECT_NAME} # プロジェクト設定の変更 gcloud config set project ${PROJECT_ID} # 請求先アカウントの紐づけ gcloud beta billing projects link ${PROJECT_ID} \ --billing-account = ${BILLING_ACCOUNT_ID} # API の有効化 gcloud services enable compute.googleapis.com \ secretmanager.googleapis.com \ run.googleapis.com \ artifactregistry.googleapis.com \ cloudbuild.googleapis.com # FW の作成 gcloud compute firewall-rules create ftp-rule \ --allow tcp:20-21,tcp:40000-45000 # VM の作成 gcloud compute instances create " ftp-server " \ --zone =" us-central1-a " \ --machine-type =" e2-micro " \ --image-family =" debian-11 " \ --image-project =" debian-cloud " \ --boot-disk-size =" 10 " \ --boot-disk-type =" pd-standard " # バケットの作成 gcloud storage buckets create gs:// ${BUCKET_NAME} #シークレットの作成 gcloud secrets create my_password --replication-policy= " automatic " # シークレットにバージョンの追加 echo -n ${FTP_SERVER_PW} | gcloud secrets versions add my_password --data-file=- # プロジェクト番号を環境変数に追加 export PROJECT_NO = $( gcloud projects list --filter= ${PROJECT_ID} --format= " value(projectNumber) " ) # シークレットへ読み取り権限をCompute Engineのデフォルトサービスアカウントに付与 gcloud secrets add-iam-policy-binding my_password \ --member =" serviceAccount: ${PROJECT_NO} -compute@developer.gserviceaccount.com " \ --role =" roles/secretmanager.secretAccessor " # アーティファクトリポジトリの作成 gcloud artifacts repositories create docker-repo-ftp \ --repository-format= " docker " \ --location= " us-central1 " \ --description= " Docker repository " # Dockerイメージのビルド gcloud builds submit --region= " us-central1 " \ --tag =" us-central1-docker.pkg.dev/ ${PROJECT_ID} /docker-repo-ftp/jobs:latest " # VM の外部 IP を環境変数に追加 export FTP_SERVER_IP = $( gcloud compute instances describe ftp-server \ --zone =" us-central1-a " \ --format =" get(networkInterfaces[0].accessConfigs[0].natIP) " ) # Cloud Run jobsの作成 gcloud run jobs create ftp-job \ --image =" us-central1-docker.pkg.dev/ ${PROJECT_ID} /docker-repo-ftp/jobs:latest " \ --region =" us-central1 " \ --set-env-vars =" FTP_SERVER_IP= ${FTP_SERVER_IP} " \ --set-env-vars =" FTP_USER= ${FTP_USER} " \ --set-env-vars =" FILE_NAME= ${FILE_NAME} " \ --set-env-vars =" BUCKET_NAME= ${BUCKET_NAME} " \ --set-secrets =" FTP_PASSWD=my_password:latest " FTP サーバの初期設定 先程作成した ftp-server VM に SSH でログインし、FTP サーバの初期設定を行っていきます。 gcloud compute ssh ftp-server --zone= " us-central1-a " --project= ${PROJECT_ID} ターミナルに ユーザー名@ホスト名:~$ が表示されたらログイン成功です。 はじめに、OS のバージョンを確認します。 # OS のバージョン確認 matayuuu@ftp-server:~$ lsb_release No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 11 ( bullseye ) Release: 11 Codename: bullseye 次に、以下コマンドを実行します。 # パッケージのアップデート sudo apt-get update # FTPサーバーをインストール sudo apt-get install vsftpd # VM の外部 IP を確認 (後ほど使用するので出力された IP アドレスをメモしておく) curl -s ifconfig.me # vsftpd 設定ファイルを編集 sudo vi /etc/vsftpd.conf vsftpd 設定ファイルの中身を以下のように加筆修正します。 既存の設定を修正 listen=NO → listen=YES listen_ipc6=YES → listen_ipc6=NO 新規の設定を追加 (最後の行に追記) pasv_enable=YES pasv_min_port=40000 pasv_max_port=45000 pasv_address=${先程メモしたVMの外部IP} 1. 既存の設定を修正 では、FTP サーバがスタンドアロンモードで動作するよう、また IPv4 アドレスのみを許可するように設定しています。 2. 新規の設定を追加 では、FTP サーバがパッシブモードで動作するよう、また パッシブモード時に使用するポート範囲と FTP 接続を確立する IP アドレスを設定しています。 続けて以下のコマンドを実行します。 # vsftpdを再起動 sudo systemctl restart vsftpd # フォルダの作成 sudo mkdir /home/ftpuser # ファイルを作成 echo " Hello, FTP! " | sudo tee /home/ftpuser/sample.txt # ユーザーの作成(検証のためパスワードは「1234」と簡潔なものとし、その他はデフォルト設定) sudo adduser ftpuser # ユーザーをFTPグループに追加 sudo usermod -aG ftp ftpuser # ftpuserディレクトリの所有者をftpuser、グループをftpに変更 sudo chown ftpuser:ftp /home/ftpuser # ftpuserディレクトリの書き込み権限を全削除 sudo chmod a-w /home/ftpuser # ログアウト exit 動作確認 Cloud Run jobs の実行 以下のコマンドで Cloud Run jobs を手動実行します。 gcloud run jobs execute ftp-job \ --region =" us-central1 " Cloud Run jobs のコンソール画面からジョブの実行結果を確認できます。 ジョブの詳細画面 Cloud Storage に FTP サーバから取得した sample.txt も保存されていることが確認できました。 Cloud Storage の画面 sample.txt リソースの削除 以下のコマンドを実行し、検証で作成したプロジェクトを削除します。 gcloud projects delete ${PROJECT_ID} 本番運用に向けての考慮事項 接続方式 IP アドレス 今回は FTP サーバに外部 IP を付与しすべてのソース元 IP アドレスを許可しましたが、本番運用では ①内部 IP アドレスのみを許可 、もしくは ②許可した外部 IP アドレスのみを許可 する構成が想定されます。 必要に応じ、 サーバレス VPC アクセス や Cloud NAT を用いて Cloud Run jobs から FTP サーバへの通信を制御していく必要があります。 それぞれの構成図 参考 : Connect to a VPC network 参考 : Static outbound IP address プロトコル インターネット経由でファイル転送を行う際は、セキュリティの観点から FTP プロトコルより SFTP プロトコルを採用することが多いです。その際は、SFTP サーバの SSH 認証キーを Secret Manager に保存して管理する等の必要があります。 サービスアカウントの権限 今回は Cloud Run に Compute Engine のデフォルトサービスアカウントをアタッチしましたが、必要最低限のロールを付与したサービスアカウントを作成することが推奨されます。 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさーい!沖縄出身のクラウドエンジニアです!! 前職は SIer テクニカルセールス。Google Cloud の魅力に惚れ、技術を磨きたくセールスからエンジニアへ転身。Google Cloud 認定資格は全 11 資格保有。最近は AI/ML 分野に興味あり。 Follow @matayuuuu
アバター
G-gen の藤岡です。当記事では、Google Cloud (旧称 GCP) のリソースを Terraform で作成する際に生じる "reason": "SERVICE_DISABLED" エラーへの対処として Terraform の time_sleep を紹介します。 前提知識 エラー文 原因 対処法 前提知識 Google Cloud は Google Cloud APIs と呼ばれる Web API 群から成り立っています。そのため、Google Cloud ではサービス利用時に対象の API を有効化する必要があります。 例えば、VPC や GCE のリソースを作成するには compute.googleapis.com の API を有効化します。この API 有効化のステップは Amazon Web Services (AWS) にはなく、Google Cloud 特有のものです。 Google Cloud APIs についての詳細は以下の記事をご参照ください。 blog.g-gen.co.jp Terraform で API を有効にする際は google_project_service を使います。Terraform の基本操作については以下の記事をご参照ください。 blog.g-gen.co.jp エラー文 例として、Terraform で VPC リソースを作成しようとすると以下のエラーが発生する場合があります。 このエラーメッセージは、「Compute Engine API が有効化されていない」旨を示しています。しかし、実際には Terraform のコード内で google_project_service リソースが定義されており、これによって Compute Engine API が有効化されているはずです(Terraform 実行後にコンソールから確認したところ、問題なく有効になっていました)。 fujioka @ cloudshell :~/ terraform ( xxx )$ terraform apply ~ │ Error : Error creating Network : googleapi : Error 403: Compute Engine API has not been used in project xxxx before or it is disabled . Enable it by visiting https : //console.developers.google.com/apis/api/compute.googleapis.com/overview?project=xxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry. │ Details : │ [ │ { │ " @type ": " type.googleapis.com/google.rpc.Help ", │ " links ": [ │ { │ " description ": " Google developers console API activation ", │ " url ": " https : //console.developers.google.com/apis/api/compute.googleapis.com/overview?project=xxxx" │ } │ ] │ } , │ { │ " @type ": " type.googleapis.com/google.rpc.ErrorInfo ", │ " domain ": " googleapis.com ", │ " metadatas ": { │ " consumer ": " projects/xxxx ", │ " service ": " compute . googleapis . com " │ } , │ " reason ": "SERVICE_DISABLED" │ } │ ] │ , accessNotConfigured │ ~ fujioka @ cloudshell :~/ terraform ( xxxx )$ 原因 API の有効化が完了するまでは時間がかかります。これは、 コンソールから有効化する 場合も同様です。 そのため、Terraform の depends_on で実行順序を制御しても API の有効化完了が間に合わないと上記のようなエラーとなります。 この場合、API の有効化が間に合っていないだけのため、時間を置いてから Terraform を再実行することでエラーは解消されます。しかし、根本的な解決方法は、API の有効化が完了するまである程度待機してから、後続のアクションを実行することです。 対処法 Terraform の time_sleep を使うことで、後続の実行までにスリープ時間を作ることができます。 create_duration の部分でスリープ時間を調整できます。この場合、API の有効化( google_project_service )の後にスリープ時間を作るよう depends_on で実行順序を制御します。 resource "google_project_service" "enabled_apis" { service = "compute.googleapis.com" } resource "time_sleep" "wait_30_seconds" { depends_on = [ google_project_service.enabled_apis ] create_duration = "30s" } ここでは sleep を入れることで解決しましたが、API を有効化後に無効にするケースは少ないことや、有効化する API が多ければその分 tfstate ファイルが肥大化してしまうことを考えると、API の有効化は Terraform でなくシェルスクリプト等で管理する選択肢も検討できます。 参考: time_sleep (Resource) 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。2023 夏アニメのオススメは、ダークギャザリング。箏を習っています :) Follow @fujioka57621469
アバター
当記事は みずほリサーチ&テクノロジーズ × G-gen エンジニアコラボレーション企画 で執筆されたものです。 みずほリサーチ&テクノロジーズ株式会社の浅香です。 今回は Google Cloud 上に Terraform Enterprise を実際に構築する機会を頂いたので、その構築内容/手順をご紹介させていただきます。 当ブログは G-gen × みずほRT によるコラボ記事です はじめに 構築環境 前提 airgap モジュールと TFE ライセンスファイルの調達 Google Cloud での構築 ドメインと DNS 名 インストール方法 証明書 構築手順 事前準備 構築作業 1. Root ユーザーにスイッチする 2. Docker を起動し、サービスとして登録する (状態確認) 3. TFE の設定ファイル (json) を作成する 4. replicated の設定内容 5. カレントディレクトリの移動 & installer bootstrapper の解凍 6. インストール実行 7. 起動チェック 設定作業 初期管理ユーザの作成 1. IACT(Initial Admin Creation Token) の発行 (shell) 2. Admin User の作成 (API) Organization (組織) の作成 1. 組織の作成 (API) Bundle ファイルを作成 1. Cloud Shell の起動 2. Github リポジトリのクローン 3. カレントディレクトリの移動 4. Go 言語でビルド 5. 確認 6. Bundle 用の HCL ファイルを作成 7. Bundle ファイルの作成 Bundle ファイルの登録 1. Bundle ファイルの配置 2. URL および checksum 結果の取得 3. Bundle ファイルの登録 (新規登録) 終わりに はじめに Terraform Enterprise (TFE)は、インフラストラクチャのプロビジョニングと管理を自動化するための強力なツールです。 TFEを使用することで、チーム全体でのインフラストラクチャの共有、セキュリティの向上、作業の追跡と可視化を容易に行うことができます。 また、金融システムなど高いセキュリティを求められる場合、拠点とパブリッククラウドを専用線で結びインターネット接続を制限するケースがあると思います。 今回、構築環境として利用した Google Cloud もそのような閉域環境としています。 この記事では、閉域にした Google Cloud 上にて利用する TFE の構築手順と設定について詳しく解説します。 構築環境 今回は Google Cloud の Compute Engine (以下、GCE) 上に構築することにします。 TFE環境 当該環境は閉域網であり、インタネット経由での各種アクセスはできないものとします。 (インターネット経由で取得する必要があるものは、別環境で取得して持ち込んでいるものとします。) 前提 この記事では以下の前提や制約条件があるものとします。 airgap モジュールと TFE ライセンスファイルの調達 TFE の利用に際して必要となる airgap モジュールとライセンスファイルが必要になります。 必要な際は こちら より HashiCorp 社へお問い合わせ下さい。 なお、installer bootstrapper は こちら からダウンロード可能です。 Google Cloud での構築 Terraform Enterprise GCP Reference Architecture では、可用性などを高めるために Cloud SQL や Cloud Storage の利用を推奨されていますが、今回は便宜的に GCE インスタンスのみで構築することにします。 インスタンスサイズやタイプについては、同ページに記載されている Infrastructure Requirements に準拠するものとします。 今回利用する OS は Supported Operating Systems にもある RHEL8 とします。 構築にあたって必要となる Docker Engine は事前にインストールしているものとします。 SMTP の設定は割愛します。(メール発信はできません。) ドメインと DNS 名 今回はカスタムドメイン (xxx.internal) を利用し、 tfe.xxx.internal| を使用します。 インストール方法 インストール方法としては大きく分けて Interactive Install と Automated Install の 2 種類がありますが、今回は後者の Automated Install にてインストールします。 なお、前者の Interactive Install は、こちらはブログ「 Deploying Terraform Enterprise in Air Gapped Environments 」が参考になります。 証明書 今回は自己証明書にて構築します。 構築手順 事前準備 SSHを使用してインスタンスに接続可能 以下のディレクトリを用意 用途 今回のディレクトリ TFE インストールに必要なモジュール配置先 /opt/tfe-module TFE 利用時に必要なデータ格納先 /opt/terraform-enterprise 自己署名証明書の発行 以下のファイルをサーバ内に格納 用途 今回の格納先 ライセンスファイル /opt/tfe-module/License.rli airgap モジュール /opt/tfe-module/tfe.airgap installer bootstrapper /opt/tfe-module/latest.tar.gz サーバ秘密鍵 /opt/tfe-module/server.crt 自己署名証明書 /opt/tfe-module/server.key 構築作業 1. Root ユーザーにスイッチする sudo su - 2. Docker を起動し、サービスとして登録する (状態確認) systemctl enable --now docker sudo systemctl status docker 3. TFE の設定ファイル (json) を作成する cat <<EOF > /opt/tfe-module/settings.json { "hostname": { "value": "tfe.xxx.internal" }, "capacity_concurrency": { "value": "10" }, "capacity_cpus": { "value": "0" }, "capacity_memory": { "value": "512" }, "enc_password": { "value": "<暗号化パスワード>" }, "log_forwarding_enabled": { "value": "1" }, "log_forwarding_config": { "value": "[OUTPUT]\n Name syslog\n Match *\n Host localhost\n Port 5140\n syslog_message_key message\n syslog_severity_key PRIORITY\n syslog_hostname_key _HOSTNAME\n syslog_appname_key SYSLOG_IDENTIFIER\n syslog_procid_key _PID" }, "production_type": { "value": "disk" }, "disk_path": { "value": "/opt/terraform-enterprise" } } EOF 設定可能な項目は こちら をご参照ください。 なお、今回は以下の通りとしています。 設定項目 設定値 hostname.value ドメイン名 tfe.xxx.internal capacity_concurrency.value 同時実行数 10 capacity_cpus.value CPU コアの最大数 0(無制限) capacity_memory.value メモリの最大量(メガバイト単位) 512 enc_password.value 内部管理型 Vault 用のパスワード <暗号化パスワード> log_forwarding_enabled.value ログ転送を有効化 1(有効化) log_forwarding_config.value ログ転送設定 後述 production_type.value ストレージ (blob以外) disk disk_path.value ストレージ利用先のディレクトリ /opt/terraform-enterprise ログ転送設定については、改行コードを \n に変換して設定する必要があるため上述の記載としていますが、改行すると以下の通りとなります。 今回はローカルホストの syslog を対象とした設定としていますが、その他の転送先については こちら をご参照ください。 [OUTPUT] Name syslog Match * Host localhost Port 5140 syslog_message_key message syslog_severity_key PRIORITY syslog_hostname_key _HOSTNAME syslog_appname_key SYSLOG_IDENTIFIER syslog_procid_key _PID 4. replicated の設定内容 cat <<EOF > /etc/replicated.conf { "DaemonAuthenticationType": "password", "DaemonAuthenticationPassword": "<ログインパスワード>", "BypassPreflightChecks": true, "TlsBootstrapType": "key-cert", "TlsBootstrapCert": "$(cat /opt/tfe-module/server.crt | sed -z 's/\n/\\n/g'| rev | cut -c 3- | rev)", "TlsBootstrapKey": "$(cat /opt/tfe-module/server.key | sed -z 's/\n/\\n/g'| rev | cut -c 3- | rev)", "ImportSettingsFrom": "/opt/tfe-module/settings.json", "LicenseFileLocation": "/opt/tfe-module/License.rli", "LicenseBootstrapAirgapPackagePath":"/opt/tfe-module/tfe.airgap" } 設定可能な項目は こちら をご参照ください。なお、今回は以下の通りとしています。 設定項目 設定値 DaemonAuthenticationType 認証方式 password DaemonAuthenticationPassword ログインパスワード <ログインパスワード> BypassPreflightChecks プリフライトチェックなし起動 true TlsBootstrapType TLS 証明書の種類 key-cert TlsBootstrapCert サーバ秘密鍵 $(cat /opt/tfe-module/server.crt | sed -z 's/\n/\\n/g'| rev | cut -c 3- | rev) TlsBootstrapKey 自己署名証明書 $(cat /opt/tfe-module/server.key | sed -z 's/\n/\\n/g'| rev | cut -c 3- | rev) ImportSettingsFrom 設定ファイルパス /opt/tfe-module/settings.json LicenseFileLocation ライセンスファイルパス /opt/tfe-module/License.rli LicenseBootstrapAirgapPackagePath パッケージファイルパス /opt/tfe-module/tfe.airgap サーバ秘密鍵と自己署名証明書については、ログ転送設定と同様に、改行コードを \n に変換して設定する必要があります。 今回は上述のコマンドにて実施しています。 5. カレントディレクトリの移動 & installer bootstrapper の解凍 cd /opt/tfe-module && tar xzf /opt/tfe-module/latest.tar.gz --remove-files 6. インストール実行 ./install.sh \ airgap \ no-proxy \ private-address=$PRIVATE_IP 7. 起動チェック while ! curl -ksfS --connect-timeout 5 https://tfe.xxx.internal/_health_check; do sleep 5 done インストールが終了したら、/_health_check エンドポイントが 200 を返します。 これによりアプリケーションが完全に起動したことを確認できます。 設定作業 初期管理ユーザの作成 構築作業が完了したら、TFE 利用に向けて各種設定を実施していきます。 最初に、製品の使用を開始するために初期管理ユーザを作成する必要があります。 いくつか方法がありますが、今回もコマンドにて実施していきます。 1. IACT(Initial Admin Creation Token) の発行 ( shell ) initial_token=$(replicated admin --tty=0 retrieve-iact | tr -d '\r') 2. Admin User の作成 ( API ) cat <<EOF >payload.json { "username": "tfe-admin-user", "email": "<メールアドレス>", "password": "<ユーザーパスワード>" } EOF curl \ --header "Content-Type: application/json" \ --request POST \ --data @payload.json \ https://tfe.xxx.internal/admin/initial-admin-user?token=$initial_token Response { "status": "created", "token": "aabbccdd.v1.atlas.ddeeffgghhiijjkkllmmnnooppqqrrssttuuvvxxyyzz" } Organization (組織) の作成 次に、Terraform Enterprise 上の Organization (組織) を作成します。設定に際しては、上述の Admin User 作成手順の Reeponse の token を使用します。 今回は 1 組織のみとしていますが、複数組織を作成する場合は、必要な個所を修正したうえで以下の手順を繰り返してください。 1. 組織の作成 ( API ) cat <<EOF >payload.json { "data": { "type": "organizations", "attributes": { "name": "test-org", "email": "sample.adrdess@xxx.com" } } } EOF curl \ --header "Authorization: Bearer $TOKEN" \ --header "Content-Type: application/vnd.api+json" \ --request POST \ --data @payload.json \ http://tfe.xxx.internal/api/v2/organizations Bundle ファイルを作成 ここまでで組織の作成まで終わりましたが、TFE で利用可能な Terraform の Bundle ファイルが初期設定では https://releases.hashicorp.com/terraform/<version>/terraform_<version>_linux_amd64.zip" 等になっているため、閉域網で実行するとクライアント側 ( terraform plan や terraform apply を実行する側) でエラーになってしまいます。 そこで、Terrafom-bundle と API を使用して、閉域網内で実行可能なように設定していきます。 まずは Bundle ファイルを作成します。 ただし、Bundle ファイルの作成にあたってはインターネット接続環境が必要になります。今回は Cloud Shell で実施します。 1. Cloud Shell の起動 2. Github リポジトリのクローン git clone --single-branch --branch=v0.15 --depth=1 https://github.com/hashicorp/terraform.git 3. カレントディレクトリの移動 cd terraform 4. Go 言語でビルド go build -o ../terraform-bundle ./tools/terraform-bundle 5. 確認 ~/terraform-bundle -help 6. Bundle 用の HCL ファイルを作成 cat <<EOF>~/tfe_bundle.tf terraform { version = "<作成したいTerrafomのバージョン>" } providers { <Provider 名> = { source = "<ソース>" versions = [<バージョン>] } google = { source = "hashicorp/google" versions = ["~> 4"] } } EOF 7. Bundle ファイルの作成 ~/terraform-bundle package -os=linux -arch=amd64 ~/tfe_bundle.tf Response ~省略~ Creating terraform_<指定したTerrafomのバージョン>-bundle<YYYYMMDDHH>_linux_amd64.zip ... All done! Bundle ファイルの登録 これで Bundle ファイルは完成したので、これを閉域環境に持ち込みます。 最後に Admin Terraform Versions API を利用して、作成した組織に登録していきます。 1. Bundle ファイルの配置 Bundle ファイルを、TFE が認証なくアクセス可能な WEB サーバ上に配置します。 2. URL および checksum 結果の取得 以降の手順で使用するため、Bundle を取得可能な URL と、Bundle の SHA-256 checksum 結果 ( sha256sum <Bundle-PATH>|awk '{print $1}' ) を取得します。 3. Bundle ファイルの登録 (新規登録) cat <<EOF >payload.json { "data": { "type": "terraform-versions", "attributes": { "version": "指定したTerrafomのバージョン", "url": "Bundleファイルの配置先URL", "sha": "BundleファイルのSHA-256 checksum結果", "official": true, "enabled": true, "beta": false } } } EOF curl \ --header "Authorization: Bearer $TOKEN" \ --header "Content-Type: application/vnd.api+json" \ --request POST\ --data @payload.json \ http://tfe.xxx.internal/api/v2/admin/terraform-versions これにより Terrafom 実行時の Provider が作成した Bundle ファイルを使用するようになるため、実行可能になります。 終わりに この記事では、Terraform Enterprise の構築手順と設定について説明しました。 TFE を使用することで、インフラストラクチャの自動化と可視化、チーム作業の効率化とセキュリティの向上など様々な効果が期待されます。 閉域網でも活用することが可能なことが分かったので、今回紹介した設定以外の見直しも含め、今後も積極的に活用していければと思います。 最後までご覧いただきありがとうございました。本記事がどなたかの一助となれれば幸いです。 浅香 樹 みずほリサーチ&テクノロジーズ 先端技術研究部に所属。2019年から、CCoEとしてAWSの社内利活用の推進に従事。 Google Cloudは2022年より利用開始し、それを機にTerraformにも取り組んでいます。
アバター
G-gen の杉村です。Cloud Logging のログバケットを作成する際に リクエストしたエンティティは見つかりませんでした というメッセージが出力されました。原因と対処法を紹介します。 はじめに・前提知識 事象 原因調査 調査 判明した原因 対処法 はじめに・前提知識 Cloud Logging の ログバケット はログを保管することに特化した Cloud Logging 独自のストレージです。「Cloud Storage バケット」とは名称がよく似ていますが関係がありません。 Cloud Logging の詳細な解説は、以下の記事をぜひご参照ください。 blog.g-gen.co.jp 事象 ある組織配下のプロジェクトにおいて、Cloud Logging コンソールで新しいログバケットを作成することを試みました。 作成ボタンを押すと、以下のメッセージが表示され、作成することができませんでした。 リクエストしたエンティティは見つかりませんでした。 ログバケット作成時のエラーメッセージ このメッセージだけでは、何を意味しているのか分かりません。詳細なエラーメッセージを得るために、今度は gcloud コマンドでのログバケット作成を試しました。すると、以下のような出力となりました $ gcloud logging buckets create my-log-bucket --location=global --project=my-current-project ERROR: ( gcloud.logging.buckets.create ) NOT_FOUND: Project does not exist: my-old-project エラーメッセージ中の my-old-project は仮名ですが、既に削除済みのプロジェクトでした。これは何を意味しているのでしょうか。 原因調査 調査 gcloud コマンドを実行したときのエラーログに着目します。 ERROR: (gcloud.logging.buckets.create) NOT_FOUND: Project does not exist: my-old-project ログバケット作成先のプロジェクトは my-current-project を指定しているのにも関わらず、エラーメッセージは my-old-project (仮名) が存在していない、というものです。社内で my-old-project のかつての管理者に確認したところ、次に示すことが分かりました。 判明した原因 プロジェクト my-old-project は、検証目的で Cloud KMS 鍵を配置していたプロジェクトでした。また、この事象が起きた日の少し前に、 組織レベル でログバケットの CMEK 暗号化を有効化する検証を行っていました。 参考 : ログ ストレージ用の CMEK を構成する CMEK 暗号化とは Customer-Managed Encryption Key の略であり、Google 管理ではなく独自管理の鍵でストレージを暗号化する機能のことです。Cloud Logging では組織レベルで CMEK を使うように指定することで、それ以降にその組織で作成される全てのログバケットが指定の KMS キーで暗号化されるようになります。 この事象が起きた組織では、直前にこの機能の挙動の検証を行っており、その際の暗号化用 KMS キーとして my-old-project 内の KMS キーを指定していました。検証が終わり、 my-old-project は削除されましたが、 組織レベルの CMEK 暗号化設定を削除し忘れ ていました。 そのためログバケットを新規作成しようとした際、Google は CMEK 暗号化のための KMS キーを my-old-project に探しに行き、プロジェクトが存在しないため NOT_FOUND: Project does not exist というメッセージを出力したのです。 対処法 以下のコマンドで、現在の設定を確認します。 ${ORGANIZATION_ID} は自身の組織 ID に置き換えます。 $ gcloud logging settings describe --organization= ${ORGANIZATION_ID} { " kmsKeyName " : " projects/my-old-project/locations/asia-northeast1/keyRings/audit-log-keyring/cryptoKeys/bucket-cmek " , " kmsServiceAccountId " : " cmek-o123456789012@gcp-sa-logging.iam.gserviceaccount.com " , " name " : " organizations/123456789012/settings " , " storageLocation " : " asia-northeast1 " } 設定値 kmsKeyName が存在しており、CMEK 暗号化を強制する設定が残っていることが分かります。以下のコマンドで削除します。 $ gcloud logging settings update --organization= ${ORGANIZATION_ID} --clear-kms-key ログバケットのデフォルトのロケーションも、デフォルトである global に戻しておきます。 $ gcloud logging settings update --organization= ${ORGANIZATION_ID} --storage-location=global 以下のコマンドで、設定 kmsKeyName が消えていることを確認します。 $ gcloud logging settings describe --organization= ${ORGANIZATION_ID} { " kmsServiceAccountId " : " cmek-o123456789012@gcp-sa-logging.iam.gserviceaccount.com " , " name " : " organizations/123456789012/settings " , " storageLocation " : " global " } 設定を修正後は、無事にログバケットの作成が成功しました。 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。Twitter では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の堂原です。マネージドな Apache Airflow 環境を提供する Cloud Composer について、Cloud Composer 2 を中心に解説します。 はじめに Airflow とは 概要 特徴 様々なサービス・ツールと連携可能 定期実行されるバッチ方式のワークフローに特化 可視性 Cloud Composer とは 概要 メリット 苦手な点 バージョン 概要 メジャーバージョン ライフサイクル アップグレード コンポーネント 概要 Airflow ワーカー Airflow スケジューラ Airflow ウェブサーバ Airflow トリガラー Airflow データベース 料金 前提 種類 Cloud Composer コンピューティング料金 Airflow データベースのストレージ料金 コアインフラストラクチャ料金 コスト最適化 セキュリティ サービスアカウント プライベート環境 耐障害性 スナップショット 復元力モード モニタリング はじめに Cloud Composer は Google Cloud (旧称 GCP) のフルマネージドな、データパイプライン用のワークフロー管理サービスで、 実体は Apache Airflow をマネージドな環境で提供するサービスです。 そのため、本記事ではまず Airflow について紹介し、その後に改めて Cloud Composer (メジャーバージョン 2) について紹介します。 ※ 一部 Airflow のコンポーネントなどは Cloud Composer の方で解説しています。 Airflow とは 概要 Airflow は、 元は Airbnb 社で開発され現在は Apache Software Foundation のプロジェクトとなっている OSS で、データパイプライン用のワークフロー管理ツールです。 例えば下図のように task_1 の後に task_2 を処理する task_2 処理後、 task_3 と task_4 が並列で処理される task_3 と task_4 処理後、 task_5 が処理される などといった処理の順序や依存関係を組み立てることができます。 ワークフローの例 Airflow においては、個々の処理 ( Task と呼ばれます) の順番や依存関係を DAG (Directed Acyclic Graph) というもので定義します。 また、各 Task は Operator と呼ばれるテンプレートを用いて作成することができます。 これらは Python で記述され、上図を実現する Python コードは以下のようになります。 (下のコードだと、各 Task はただ echo を実行しているだけですが、実際はここに ETL (Extract, Transform, Load) 処理を当てはめていくことになります。) import airflow from airflow import DAG from airflow.operators.bash import BashOperator from airflow.utils.dates import days_ago with DAG( 'test-dag' , description= 'test dag' , schedule_interval= '*/10 * * * *' , start_date=airflow.utils.dates.days_ago( 1 ), catchup= False , max_active_runs= 1 ) as dag: task_1 = BashOperator( task_id= 'task_1' , bash_command= 'echo "Task 1"' , dag=dag) task_2 = BashOperator( task_id= 'task_2' , bash_command= 'echo "Task 2"' , dag=dag) task_3 = BashOperator( task_id= 'task_3' , bash_command= 'echo "Task 3"' , dag=dag) task_4 = BashOperator( task_id= 'task_4' , bash_command= 'echo "Task 4"' , dag=dag) task_5 = BashOperator( task_id= 'task_5' , bash_command= 'echo "Task 5"' , dag=dag) task_1 >> task_2 >> [task_3, task_4] >> task_5 特徴 様々なサービス・ツールと連携可能 Airflow は様々なツールやサービスと連携し、ETL 処理を実行することが可能です。 データの取得・格納先として、例えば Google Cloud であれば BigQuery や Google Cloud Storage (GCS) 、AWS であれば Redshift や S3 、その他各種データベース ( MySQL , PostgreSQL , MongoDB など) が選択できます。 データの処理基盤としては、大規模なデータの処理が行える OSS である Apache Beam や、 Kubernetes などが選択でき、Python で記述した簡単な処理であれば Airflow の基盤上で実行するといったことも可能です。 クラウドサービスを使う場合、Cloud SDK や AWS SDK for Python (Boto3) 等を使って各処理を書き込む必要はなく、 用途に合った Operator を選択し、各パラメータを記載するだけで Task を作成し処理を実現することができます。 (勿論、権限設定やライブラリのインストール自体は必要となります。) 例えば BigQuery テーブルからデータを取得したい場合は BigQueryGetDataOperator を用いて Task を作成します。 get_data_from_bq = BigQueryGetDataOperator( task_id = 'get_data_from_bq' , project_id= 'test-project' , dataset_id = 'test_dataset' , table_id = 'test_table' , dag = dag ) 定期実行されるバッチ方式のワークフローに特化 オンデマンドに実行することも可能ですが、Airflow は 定期的に実行されるバッチ方式のワークフロー に特化しています。 (逆に言えば Airflow はストリーミング処理には適していません。) そのため、Airflow では定期実行を意識した機能が用意されています。 例えば バックフィル機能 を使えば、スタート時点から、DAG で定めた間隔分だけワークフローを実行してくれます。 例 : スタート時点を一ヶ月前・間隔を 1 日にすると、過去 30 日分のワークフローを順に実行 当日に追加されたデータのみを処理するといったワークフローを実装している場合、本機能を用いることで、過去分のデータまで処理させるといったことが可能になります。 可視性 Airflow では、DAG の設定や実行、個々のタスクの実行結果の確認などができる Airflow UI が提供されています。 下図は、DAG のこれまでの実行結果を確認できるページです。 Airflow UI この他にも DAG のリアルタイムの実行状況 カレンダー形式での日次の実施状況 各実施結果のログ などの情報が Airflow UI で確認可能であり、優れたモニタリング機能となっています。 Cloud Composer とは 概要 改めて、 Cloud Composer は Google Cloud (旧称 GCP) のフルマネージドな、Apache Airflow 環境を提供するサービスです。 ユーザが DAG を GCS にアップロードすると、Cloud Composer がワークフローの実行や計算リソースのスケーリングを管理してくれます。 基本的な利用の流れは以下のようになります。 バージョンや計算リソースのスペックを指定して Cloud Composer 環境 を作成 (以後、「環境」と記載する場合は Cloud Composer 環境を指します) 計算リソースのスペックは後から変更可能です 環境毎に作成される GCS に DAG を記載した Python ソースコードをアップロード Cloud Composer が DAG を自動で検知し、記載されたトリガーに従ってワークフローの実行・管理を行う また、以下のようなことを Google Cloud コンソールから確認・実施することが可能と、高い利便性が提供されています。 各 DAG のグラフやソースコードが確認可能 Airflow UI へのアクセス Airflow の構成オプションや環境変数、PyPI パッケージの上書きや追加・変更 メリット 先述した Airflow の特徴も踏まえ、Cloud Composer には以下のようなメリットが存在します。 Python で、複雑なパイプラインを簡単に構築することができる Google Cloud の複数のサービスを横断するパイプラインの構築が可能 Google Cloud だけでなく、ハイブリッドやマルチクラウドにも対応 例 : S3 から GCS を経由して BigQuery にデータ保存などといったパイプラインを構築可能 複雑な Airflow 環境を簡単に立ち上げられる 計算リソースの自動スケーリング機能あり(Cloud Composer 2 のみ) ただしサーバレスではないため、各コンポーネント (後述) のスペックのチューニングは必要となります 苦手な点 Cloud Composer は、逆に以下のような点を苦手としています。 Python でのパイプライン構築が必須 簡単なパイプラインを非エンジニアの方が作成したいのなら、 Dataprep や Cloud Data Fusion といったノーコードサービスのほうが適しています ストリーミング処理 Google Cloud なら、 Dataflow が適任です バージョン 概要 Cloud Composer においては高頻度で新しいバージョンがリリースされています。 各バージョンは composer-a.b.c-airflow-x.y.z という形式で表され、 a.b.c が Cloud Composer のバージョンで x.y.z が Airflow のバージョンとなります。 基本的に Cloud Composer の 1 つのバージョンにつき、2 つの Airflow マイナーバージョンがサポートされます。 例えば Cloud Composer 2.2.1 であれば、Airflow 2.5.1 と Airflow 2.4.3 をサポートします。 参考 : Cloud Composer のバージョニングの概要 メジャーバージョン Cloud Composer においては 2 つのメジャーバージョン、Cloud Composer 1 と Cloud Composer 2 が存在します。 数字の通り Cloud Composer 2 が後継です。 機能面の大きな違いとして Cloud Composer 1 は後述する Airflow ワーカーの スケーリングが手動 で、Cloud Composer 2 は 自動スケーリング となっています。 また、 Cloud Composer 1 は Standard モードの Google Kubernetes Engine (GKE) クラスタで実装されている一方、Cloud Composer 2 は Autopilot モードの GKE クラスタで構築されており、アーキテクチャ的にも大きく異なっています。 Cloud Composer 1 は、2023 年 3 月 24 日にリリースされたバージョン 1.20.11 が最後で、今後はバグの修正と小規模な改善のみが行われます。 そのため、現在 Cloud Composer を使用する場合は、Cloud Composer 2 の使用が推奨されます。 公式ドキュメントにおいては、Cloud Composer 1 用のページと Cloud Composer 2 用のページが用意されているので、間違えないようご注意ください。 ライフサイクル Cloud Composer の各バージョンはリリース日を基準として、以下のようなサポート期間が設けられています。 0 - 12 ヶ月 : 完全サポート 12 - 18 ヶ月 : セキュリティに関する通知のみ行われる 18 ヶ月 - : 全てユーザ管理 常にサポート対象のバージョンにアップグレードすることが推奨されてはいますが、2023 年 6 月時点では、サポート期間終了後も強制アップグレードは実施されず、同じバージョンを使用し続けることが可能です。 アップグレード 2023 年 6 月時点ではプレビュー機能となっていますが、Cloud Composer においては環境のアップグレード機能が提供されています。 また、アップグレードによって PyPI パッケージの競合が発生しないかを事前に確認することも可能です。 ただし先述した通り、本機能はプレビュー機能となっているため、 本番環境で使用する前にしっかり検証しておく必要があります 。 参考 : 環境をアップグレードする コンポーネント 概要 ここからは改めて Cloud Composer 2 についての解説となります。 Cloud Composer 2 は複数の Google Cloud サービス上に存在する複数のコンポーネントから成り立っていますが、主に意識する必要のあるコンポーネントは以下の通りです。 コンポーネント コンポーネントを実装している Google Cloud サービス Airflow ワーカー Autopilot モードの GKE クラスタ Airflow スケジューラ Autopilot モードの GKE クラスタ Airflow ウェブサーバ Autopilot モードの GKE クラスタ Airflow トリガラー Autopilot モードの GKE クラスタ Airflow データベース Cloud SQL インスタンス 環境用バケット GCS バケット 上表のコンポーネントを実装している Google Cloud サービスの内、GKE クラスタと GCS については Google Cloud コンソールから確認が可能です。 ただし、環境が壊れる可能性があるため、GKE クラスタの変更は非推奨です。 参考 : 環境コンポーネント Airflow ワーカー Airflow ワーカーは、環境に存在する DAG の個々の Task を実行する、Cloud Composer における計算リソースとなります。 Task 量に応じて自動でスケーリングが行われます。 各ワーカーのスペック (vCPU、メモリサイズ、ストレージサイズ) と、スケーリングにおけるワーカーの最小数と最大数が指定できます。 Airflow スケジューラ Airflow スケジューラは、環境に存在する DAG の実行と、各 DAG 個々の Task の実行のスケジューリングを制御します。 個々の Task は GKE クラスタ内に実装されているキューを通して、スケジューラからワーカーへ分配されていますが、ユーザがキューを意識する必要はほとんどありません。 スケジューラにおいては、スケジューラのスペック (vCPU、メモリサイズ、ストレージサイズ) と、実行するスケジューラの数を指定できます。 Airflow ウェブサーバ Airflow ウェブサーバは Airflow UI を提供するコンポーネントです。 ウェブサーバはスペック (vCPU、メモリサイズ、ストレージサイズ) を指定できます。 Airflow UI へのアクセス権限は IAM を用いて管理することが可能で、アクセスには composer.environments.get 権限が必要です。 また、指定した CIDR からのみのアクセスを許可する、IP アドレスベースでのアクセス制御も可能です。 参考 : UI / Screenshots 参考 : Airflow UI のアクセス制御の使用 参考 : 手順 8. (省略可)ウェブサーバーへのネットワーク アクセスを構成する Airflow トリガラー Airflow トリガラーは、Airflow の Deferrable Operators (Google Cloud の日本語ページだと「遅延可能な演算子」と記載されています) という機能の管理に用いられます。 通常、BigQuery ジョブを呼び出したりと、外部のシステム上で処理行うような Task を実行している最中も、その Task はワーカースロットを占有してしまいます。 Deferrable Operators は、そのような処理の監視をトリガラーが代わりに行うことで、ワーカースロットを解放することができる機能です。 Cloud Composer においてはトリガラーの数は 0 にすることも可能で、 トリガラーの数を 1 以上にすることで Deferrable Operators が有効化されます 。 また、トリガラーのスペック (vCPU、メモリサイズ、ストレージサイズ) も指定できます。 Airflow データベース Airflow データベースは、Airflow のメタデータデータベースをホストしています。 データベースの実体である Cloud SQL インスタンスは Google の方で完全に管理されており、Google Cloud コンソールでは確認することもできません。 データベースのスペック調整は、Airflow スケジューラで触れた Airflow キューなどとひとまとめにされた、 コアインフラストラクチャ 単位で行われます。 コアインフラストラクチャは「小、中、大」の 3 つの環境サイズが用意されており、DAG の数や規模感に応じて選択することになります。 ただし、データベースのディスク容量のみ、必要に応じて自動的に増加します。 参考 : 環境サイズを変更する 参考 : データベースのディスク容量 料金 前提 Cloud Composer 2 の料金体系を理解するのは、これまで紹介した内容を踏まえ、以下の点を理解しておく必要があります。 Cloud Composer 1 と Cloud Composer 2 では料金体系が大きく異なる Airflow ワーカー、スケジューラ、ウェブサーバ及びトリガラーは vCPU、メモリサイズ、ストレージサイズについて調整が可能 Airflow データベースや Airflow キューなどのコンポーネントはまとめてコアインフラストラクチャという単位でサイズ調整を行う ただし、データベースのディスク容量のみ、必要に応じて自動的に増加 種類 Cloud Composer 2 においては、主に以下の料金が発生します。 Cloud Composer コンピューティング料金 Airflow データベースのストレージ料金 コアインフラストラクチャ料金 ただし、環境毎に用意される GCS バケットや、後述するプライベート環境を構築する際に使用される Private Service Connect についても別途料金が発生します。 料金表は以下の公式ページを参照ください。 参考 : Cloud Composer 2 の料金表 Cloud Composer コンピューティング料金 Cloud Composer コンピューティング料金は、Airflow ワーカー、スケジューラ、ウェブサーバ及びトリガラーがそれぞれ使用している vCPU、メモリ、ストレージに対して発生する料金です。 ワーカーやスケジューラ、トリガラーについては、指定したスペックに起動数を掛けた値となります。 vCPU、メモリサイズ、ストレージサイズはそれぞれ個別に計算され、時間単位で料金が発生します。 Airflow データベースのストレージ料金 Airflow データベースのディスク容量は必要に応じて自動的に増加し、そのサイズに応じて料金が発生します。 コアインフラストラクチャ料金 コアインフラストラクチャ料金は、コアインフラストラクチャの 3 つの環境サイズ 「小、中、大」に応じて発生する料金です。 後述する復元力モードが「標準的な復元力」か「高い復元力」によっても料金が異なり、「高い復元力」の方が料金は高くなります。 コスト最適化 Cloud Composer の費用を最適化する方法が、以下で紹介されています。 参考 : 環境のパフォーマンスと費用を最適化する Cloud Composer 2 においては、まず「小、中、大」の粒度のプリセットで用意されているスペックのいずれかで利用を開始します。 ※ 「プリセットスペック = Airflow ワーカー、スケジューラ、ウェブサーバ及びトリガラーのスペック + コアインフラストラクチャの環境サイズ」であり、コアインフラストラクチャの環境サイズ「小、中、大」とイコールではありません。 その後、実際のワークロード (DAG) を実行してパフォーマンスを観察します。後述の「モニタリング」の項で紹介する通り、Composer のパフォーマンスは Cloud Monitoring で自動的に収集されています。その収集結果を見ながら、各コンポーネントのスペックを調整していきます。その際は、Airflow ワーカーのみスペックを下げるなど、個別に調整することも可能です。 セキュリティ サービスアカウント Cloud Composer においては、2 つのサービスアカウントが登場します。 環境のサービスアカウント Cloud Composer のサービスエージェントアカウント 環境のサービスアカウントは環境毎に設定するもので、環境内のワークフローはこのサービスアカウントの権限を使用して他の Google Cloud サービスにアクセスします。 環境のサービスアカウントには、デフォルトの Compute Engine サービスアカウントまたはユーザが作成したサービスアカウントを用いることが可能です。 ユーザが作成したサービスアカウントを用いる場合は、そのサービスアカウントに Composer ワーカー ロールを付与する必要があります。 一方、Cloud Composer のサービスエージェントアカウントは Google が管理しており、プロジェクト内の全ての環境で使用されています。 環境作成時は Cloud Composer のサービスエージェントアカウントを用いて Workload Identity 設定を行う都合上、 Cloud Composer のサービスエージェントアカウントに環境のサービスアカウントに対する Cloud Composer v2 API サービス エージェント拡張機能ロールを付与する必要があります 。 まとめると、環境構築時、基本的には以下の権限を各サービスアカウントに付与する必要があります。 環境のサービスアカウント : プロジェクトに対する Composer ワーカーロール Cloud Composer のサービスエージェントアカウント : 環境のサービスアカウントに対する Cloud Composer v2 API サービス エージェント拡張機能ロール 参考 : IAM を使用したアクセス制御 プライベート環境 通常 Cloud Composer においては、GKE クラスタと Cloud SQL インスタンスに対してパブリック IP アドレスが付与されます。 プライベート環境設定を行うと、GKE クラスタは限定公開クラスタとして作成され、また Cloud SQL はプライベート IP アドレスのみを有するようになり、セキュアな環境を構築できます。 限定公開クラスタについては以下の記事で紹介しています。 blog.g-gen.co.jp また、プライベート環境設定においても以下のオプションがあります。 Google 管理の VPC (Cloud SQL インスタンスが存在) とユーザ管理の VPC (GKE クラスタが存在) 間の接続方法 Private Service Connect VPC ピアリング GKE クラスタのコントロールプレーンのパブリックエンドポイントの有効・無効化 これらの設定は環境作成時のみに設定可能で、作成後の変更はできません。 プライベート環境にした場合、ワークフローから外部ネットワークに接続するためには Cloud NAT 等が必要となります。 また、GKE クラスタのコントロールプレーンのパブリックエンドポイントを無効にした場合、ローカル環境からの Airflow CLI コマンド の実行ができなくなります。 もう一点注意点として、 Airflow ウェブサーバ (Airflow UI) へのアクセスはプライベート環境にしても制限されません 。 参考 : プライベート IP 環境 耐障害性 スナップショット Cloud Composer においては、以下のようなデータのスナップショットを定期的にまたはオンデマンドに保存し、環境の復元ができます。 Airflow 構成オプションの上書き、環境変数、PyPI パッケージ Airflow データベースのバックアップ 環境用 GCS バケットの /dag 、 /data 及び /plugins フォルダ スナップショットはデフォルトでは環境用 GCS バケットの /snapshots フォルダに保存されますが、宛先の変更も可能です。 参考 : 環境のスナップショットの保存と読み込み 復元力モード Cloud Composer においては、復元力モードというものが指定できます。 復元力モードを「高い復元力」とした場合、環境が複数のゾーンを跨る形で構築され耐障害性が増します。 具体的には以下のような構成になります。 2 つ以上の Airflow ワーカー及び 2 つの Ariflow スケジューラ、ウェブサーバ、トリガラーが異なるゾーンに配置される Airflow データベースを構成する Cloud SQL インスタンスは 高可用性モード で実行される 注意点として、「高い復元力」は プライベート環境のみ で選択可能です。 また、コアインフラストラクチャに対して通常より高い料金が発生します。 参考 : 復元性に優れた Cloud Composer 環境を設定する モニタリング Cloud Composer は Cloud Monitoring と統合されており、各コンポーネントの CPU 使用率、メモリ使用量などのメトリクスが自動的に収集されています。 これにより、コンソール画面で Airflow ワーカー、スケジューラ、ウェブサーバ、トリガラーおよびデータベースの稼働状況を確認することができます。 Cloud Composer においては各コンポーネントのスペックを後から変更することが可能なため、これらの稼働状況は適切なスペックを判断するのに役立ちます。前述した「コスト最適化」の項もご参照ください。 モニタリング画面 堂原 竜希 (記事一覧) データアナリティクス準備室。2023年4月より、G-genにジョイン。 Google Cloud Partner Top Engineer 2023, 2024に選出 (2024年はRookie of the yearにも選出)。休みの日はだいたいゲームをしているか、時々自転車で遠出をしています。 Follow @matayuuuu
アバター
当記事は みずほリサーチ&テクノロジーズ × G-gen エンジニアコラボレーション企画 で執筆されたものです。 みずほリサーチ&テクノロジーズ株式会社の舘山と申します。 当記事では Cloud Monitoring 指標スコープ を活用して複数プロジェクトを横断してリソースの消費状況をモニタリングする事例を紹介します。 当ブログは G-gen × みずほRT によるコラボ記事です はじめに 複数プロジェクトとオンプレミス環境の接続 個別システムと共通基盤の役割分掌 VPC ピアリンググループ単位の制限 共通基盤によるモニタリング 前提知識 割り当てと上限 VPC ピアリングに関する割り当てと上限 ピアリンググループ単位の割り当てのモニタリング アプローチ 指標スコープの設定 制限事項 カスタムダッシュボードの作成 アラートポリシーの作成 さいごに はじめに まずはじめに、なぜ Cloud Monitoring 指標スコープ を用いて VPC ピアリンググループ単位の割り当て消費をモニタリングする必要があるかをご説明します。 複数プロジェクトとオンプレミス環境の接続 マルチテナントのように数多くの Google Cloud のテナントプロジェクトとオンプレミス環境を接続する場合、以下のような VPC ピアリングによるハブアンドスポーク構成が有力な選択肢です。 共有 VPC 構成よりも堅確にテナントネットワーク相互の分離、アクセス制御を実現できる構成です。 ハブアンドスポーク構成 個別システムと共通基盤の役割分掌 ここで、テナントプロジェクトの利用者(個別システム)と提供者(共通基盤)の役割分掌に注目します。一般的には VPC と VPC ピアリング接続の作成 組織内でユニークなプライベート IP アドレス範囲の割り当て などは個別システムからの申請に基づき共通基盤が実施します。 一方で VM インスタンスの作成 サブネットの作成 などは個別システムに権限移譲したほうが開発速度を加速できるでしょう。 VPC ピアリンググループ単位の制限 ここで 1 点注意が必要なのですが、 VM インスタンスやサブネットを無限に作成できるわけではありません。 本件のようなマルチテナントの場合、中心となるハブプロジェクトから各テナントプロジェクトに VPC ピアリングしているため、VPC ピアリンググループ(中心となる VPC と直接ピアリング接続された VPC の集合)単位の制限に注意が必要です。制限を超過して VM インスタンスやサブネットを作成することはできません。 ハブアンドスポーク構成(再掲) テナントプロジェクト側は、自 VPC の割り当ては意識できていても、ピアリンググループ単位の割り当ては把握できません。 そのような状況で、ある日突然 VM インスタンスやサブネットを作成できなくなると問題です。 制限に抵触しないように共通基盤がピアリンググループ単位の利用状況をモニタリングし、必要に応じて割り当て量の増加をリクエストする運用を行います。 共通基盤によるモニタリング そこで共通基盤は Cloud Monitoring 指標スコープ を用いて VPC ピアリンググループ単位の割り当て消費をモニタリングする 運用が必要となってくるわけです。 なお、複数プロジェクトのモニタリングについて、指標スコープと従来のワークスペースとの差異については、以下のブログ記事の解説が参考になります。 参考: 複数プロジェクト構成の Cloud Monitoring がより使いやすくなりました 前提知識 割り当てと上限 繰り返しになりますが、Google Cloud の各サービスでは無制限にリソースを作成できるわけではありません。 制限として割り当てと上限があります。両者の違いは以下のようになります。 観点 割り当て 上限 増加の可否 ほとんどの場合、コンソールの割り当て画面から 割り当ての増加 が可能。 原則不可。 例外的にサポートケース起票で上限の引き上げができる項目が存在。 Cloud Monitoringの指標による消費状況の確認 ほとんどの場合、 Cloud Monitoringの割り当て指標 で、現在の割り当て消費状況を確認可能。 コンソールの割り当て画面で、割り当て指標を一括管理可能。 Cloud Monitroingで上限の消費状況を確認できる指標は提供されていない。 なお、クラウドにおけるリソース数などの制限の管理については、AWSのドキュメントになりますが、AWS Well-Architected Framework信頼性の柱の記述が参考になります。 参考: REL 1 サービスクォータと制約はどのように管理しますか? VPC ピアリングに関する割り当てと上限 「はじめに」でも述べましたが、VPC ピアリンググループ(中心となる VPC と直接ピアリング接続された VPC の集合)単位の VM インスタンス数、サブネット数などには制限があります。 参考: VPC の割り当てと上限のガイド VM インスタンス数やサブネット数などは割り当てに分類されており、消費状況を確認できます。 例:ピアリンググループあたりのサブネット数の割り当て指標 compute.googleapis.com/quota/subnet_ranges_per_peering_group/limit compute.googleapis.com/quota/subnet_ranges_per_peering_group/usage また VPC 単位の割り当ても存在します。 例:VPCあたりのサブネット数の割り当て指標 compute.googleapis.com/quota/subnet_ranges_per_vpc_network/limit compute.googleapis.com/quota/subnet_ranges_per_vpc_network/usage ピアリンググループ単位の割り当てのモニタリング 今回は、 ハブアンドスポークアーキテクチャ を想定し、ハブ VPC を中心としたピアリンググループをモニタリング対象とします。 アプローチ ピアリンググループ全体の割り当て消費だけでなく、VPC 毎の割り当て消費の内訳もモニタリングできることが望ましいです。 そのため、ピアリンググループ単位の割り当て指標を直接モニタリングするのではなく、VPC 単位の割り当て指標を集計するアプローチを採用します。 また、ピアリンググループを構成する VPC が複数プロジェクトに跨っている場合にも対応する必要があります。 複数プロジェクト横断のモニタリングには、Cloud Monitoring 指標スコープを利用します。 指標スコープの設定 指標スコープの設定方法は、Cloud Monitoring のガイドを参照してください。 参考: 複数の Cloud プロジェクトの指標を表示する 指標スコープ設定例 下の画像は、指標スコープの設定後にメトリクスエクスプローラーで、VPC 単位の サブネット数の割り当て指標(compute.googleapis.com/quota/subnet_ranges_per_vpc_network/usage)を参照した例です。 指標スコープ設定後のメトリクスエクスプローラー 指標スコープ内の全プロジェクトの VPC の指標が参照されます。 ピアリンググループに参加しない VPC がある場合、フィルター条件で当該 VPC を対象から除外します。 更新がない状態で指標は 1 日に 1 回しか書き込まれていなかったため、アライメント期間を 1500m(25 時間)に設定し、期間内に値が複数存在する場合は、最新の値(next older)を採用しています。 なお、グラフで VPC 毎の利用状況を把握するため Aggregator は none を指定しています。 制限事項 Cloud SQL 等の利用時、Google 管理の VPC との VPC ピアリング接続が作成されます。 Google 管理の VPC は利用者のプロジェクトに属さないため、指標スコープを通して集計対象に含めることはできません。 カスタムダッシュボードの作成 Cloud Monitoring ではカスタムダッシュボードを作成できます。 参考: カスタム ダッシュボードの作成と管理 ピアリンググループの割り当て管理用のカスタムダッシュボードとして、割り当て毎に下の画像のようなグラフを配置する構成が考えられます。 ピアリンググループのカスタムダッシュボードの例 VPC 毎の内訳と推移のグラフは、前掲のメトリクスエクスプローラのように、VPC 単位の割り当て指標を参照します。 ピアリンググループ単位の現在の割り当てと消費数のスコアカードは、次のアラートポリシーのようにピアリンググループ単位の割り当て指標を直接参照します。 アラートポリシーの作成 Cloud Monitoring ではアラートポリシーを定義して、割り当て消費が閾値を超過した場合にメール通知できます。 アラートポリシーの作成手順については、Cloud Monitoring のガイドを参照してください。 参考: 指標ベースのアラート ポリシーを作成する 今回は Terraform でピアリンググループ単位の VM インスタンス数のアラートポリシーを定義してみます。 このユースケースでは、VPC 毎の内訳は不要のため、ピアリンググループ単位の割り当て指標(instances_per_peering_group/usage)を直接参照できます。 指標スコープ内にはスポーク VPC を中心としたピアリンググループの割り当て指標も存在するため、フィルタ条件でハブ VPC を指定します。 resource "google_monitoring_alert_policy" "instance-per-peering-group" { display_name = "instance-per-peering-group" combiner = "OR" conditions { display_name = "VPC Peering - Instances Per Peering Group quota usage" condition_threshold { filter = <<EOF resource.type = "compute.googleapis.com/VpcNetwork" AND metric.type = "compute.googleapis.com/quota/instances_per_peering_group/usage" AND resource.labels.network_id = $ { local.hub-vpc } EOF threshold_value = 10000 trigger { count = 1 } duration = "0s" comparison = "COMPARISON_GT" aggregations { alignment_period = "90000s" per_series_aligner = "ALIGN_MAX" } } } notification_channels = [ google_monitoring_notification_channel.email.id ] } アラートポリシーの閾値を下げて、アラートメールを受信してみます。 test さいごに VPC ピアリンググループの割り当てのモニタリングに Cloud Monitoring 指標スコープを活用する事例の共有でした。 Cloud Monitoring 指標スコープのユースケースとして参考にしていただければと思います。 舘山 浩之 みずほリサーチ&テクノロジーズ 先端技術研究部に所属。個人のキャリアではAWSの利用経験が長く、Google Cloudは2022年より利用開始。
アバター
G-gen の杉村です。当記事では Google Cloud(旧称 GCP)の Virtual Private Cloud(VPC)においてアクセス制御に利用する Cloud Next Generation Firewall (Cloud NGFW)について徹底解説します。 概要 Cloud NGFW とは 3つの料金ティアと2つの設定スコープ 料金 ルールの料金 階層型ファイアウォールポリシーの料金 VPC ファイアウォールルール VPC ファイアウォールルールとは ルールの仕様 ルールテーブル 上り(Ingress)と 下り(Egress) ターゲット フィルタ プロトコル・ポート アクション 優先度 ファイアウォールのイメージ 暗黙のルール 対象 VM の指定方法 サービスアカウント vs ネットワークタグ ファイアウォールポリシー ファイアウォールポリシーとは ルールの仕様 基本的な考え方 VPC ファイアウォールルールとの相違点 セキュアタグ 階層型ファイアウォールポリシー グローバルネットワークファイアウォールポリシー リージョンネットワークファイアウォールポリシー ルールの評価順 評価の優先度 評価順序の変更 ユースケース Cloud NGFW Essentials、Standard、Enterprise Cloud NGFW Essentials Cloud NGFW Standard Cloud NGFW Enterprise ファイアウォールのログ 概要 Cloud NGFW とは Cloud Next Generation Firewall (以降、 Cloud NGFW )とは、Google Cloud(旧称 GCP)の Virtual Private Cloud(VPC)に備え付きのファイアウォール機能です。 Cloud NGFW は一般的なファイアウォールアプライアンスのイメージとは異なり、フルマネージドの分散システムで構成されているので、インフラの管理を考える必要がありません。ユーザは Google Cloud コンソールや gcloud コマンドラインでルールを追加するだけで、VPC 内の通信に対してアクセス制御をかけることができます。 他のパブリッククラウドサービスを例に上げると、Amazon Web Services (AWS) のセキュリティグループや AWS Network Firewall、Microsoft Azure のネットワークセキュリティグループに相当する機能です。 参考 : Cloud NGFW の概要 当サービスは、かつて Cloud Firewall と呼ばれていましたが、2024年4月8日にリブランディングし、Cloud NGFW へと改称されました。 3つの料金ティアと2つの設定スコープ Cloud NGFW には、 Essentials 、 Standard 、 Enterprise という3つの料金ティア(階層)があります。Essentials のみ無償で、Standard と Enterprise は、一部の通信に対して、データ処理量に応じた従量課金が発生します。 Essentials は、IP アドレスやプロトコル、ポート番号などの L3/L4 レイヤの制御を提供します。AWS のセキュリティグループと、同等の機能を提供していると言えます。 Standard は、FQDN ベースの制御や地理情報に基づいた制御など、高度なルールが利用可能です。 Enterprise は、不審なトラフィックを検知してブロックする IPS(Intrusion Prevention Service)機能を提供します。 Cloud NGFW の特に重要な機能は、VPC ネットワークで利用可能なファイアウォール機能です。ファイアウォールの設定スコープには、単一 VPC に対して設定するための VPC ファイアウォールルール と、複数プロジェクトや複数 VPC ネットワークに対する設定を統合管理できる、 ファイアウォールポリシー の2種類があります。 VPC ファイアウォールルールでは、Essentials ティアの機能しか使えません。また、VPC ファイアウォールルールは、無償です。 一方、ファイアウォールポリシーでは、すべてのティアの機能が使えます。ファイアウォールポリシーはさらに細分化され、 階層型ファイアウォールポリシー 、 グローバルネットワークファイアウォールポリシー 、 リージョンネットワークファイアウォールポリシー があります。階層型ファイアウォールポリシーでは、ポリシーが適用される VM 数に応じて課金が発生します。 Cloud NGFW の設定方法とルール機能を整理すると、以下の表のようになります。 設定スコープ 適用範囲 利用可能なティア ・ VPC ファイアウォールルール ・単一 VPC ・Essentials ティア (L3/L4 レベル制御。無償) ・ 階層型ファイアウォールポリシー (有償) ・ グローバルネットワークファイアウォールポリシー (無償) ・ リージョンネットワークファイアウォールポリシー (無償) ・単一 VPC ・複数プロジェクトおよび複数 VPC ・Essentials ティア ・Standard ティア (FQDN オブジェクト等の高度な制御。有償) ・Enterprise ティア (IPS 機能。有償) この関係性は、以下の図のように表現できます。 料金 ルールの料金 Essentials ティアのルールは 無償 であり、Standard ティア、Enterprise ティアのルールは 有償 です。 ファイアウォールポリシー内の Standard ルールが、インターネットと VM 間で発生したトラフィックを評価すると、トラフィックのデータサイズに応じて、$0.018/GB の料金が発生します(2025年4月現在)。課金対象は以下のトラフィックです。 インターネットから VM への通信 VM からインターネットへの通信 反対に、以下のトラフィックは課金対象になりません。 インターネットと VM 間の通信のうち、プロキシ型の Cloud Load Balancing によって中継されたトラフィック Google Cloud 内に閉じる通信 Enterprise ティアでは、検査用エンドポイントが存在した時間に応じての課金と、処理したトラフィックのデータサイズに応じて課金されます。詳細は、以下の公式料金表を参照してください。 参考 : Cloud Next Generation Firewall の料金 階層型ファイアウォールポリシーの料金 利用するポリシーのスコープによっても、料金の有無が異なります。 階層型ファイアウォールポリシーのみ、ポリシーの使用に対する料金 が発生します。 階層型ファイアウォールポリシーでは、ポリシーが適用される VM の数に応じて課金が発生します。 属性 が500個以下のポリシーの場合は、VM 1台あたり $1 が課金されます(2025年4月現在)。 ポリシーの属性とは、ポリシー内のルールが持つ IP アドレス範囲、ポート、プロトコル、サービスアカウントのことです。例えば送信元 IP アドレス範囲が 10.100.0.1/32、プロトコルが tcp、ポート範囲が 5000-6000 の上り(Ingress)を許可するルールの場合、属性数は3になります。 参考 : Cloud Next Generation Firewall の料金 - 階層型ファイアウォール ポリシーとルール VPC ファイアウォールルール VPC ファイアウォールルールとは VPC ファイアウォールルール は、VPC ネットワーク内の VM に出入りするトラフィックに対し 、「接続元 IP アドレス、接続先 IP アドレス、プロトコル、ポート番号」による通信制御を提供します。いわゆる L3/L4 レイヤのトラフィック制御を行うファイアウォールです。 パケットの評価は、 ステートフルインスペクション 形式です。これは、通信の行きと戻りをファイアウォールが紐づけて評価することを意味しています。 例えば「自社オフィスの固定 IP アドレス "xx.xx.xx.xx" から A という VM に対する SSH ログインの通信を許可したい」場合は、ファイアウォールルールとして「方向は上り(Ingress)」「接続元は "xx.xx.xx.xx" 」「ターゲットは VM(A)」「プロトコルは TCP」「ポート番号は 22」「アクションは "許可"」というルールを作成します。TCP 通信には "行き" と "戻り" がありますが、ステートフルインスペクションのファイアウォールは行きのパケットと戻りのパケットを自動的に紐づけて評価してくれるので、戻りのパケットを許可するルールを作る必要はありません。 参考 : VPC ファイアウォール ルール ルールの仕様 ルールテーブル VPC ファイアウォールルールでは、 VPC ネットワークごとに 1つのファイアウォールルールのテーブルを持ちます。 ルールテーブルは例えば以下のようになります。 ルール名 タイプ ターゲット フィルタ プロトコル:ポート アクション 優先度 allow-icmp Ingress 全インスタンス IP ranges: 0.0.0.0/0 icmp Allow 1000 allow-rdp-from-myoffice Ingress 全インスタンス IP ranges: a.b.c.0/24 tcp:3389, udp:3389 Allow 1010 allow-ssh-from-partneroffice Ingress 全インスタンス IP ranges: x.y.z.0/23 tcp:22 Allow 1020 deny-internet-from-prod Egress tags: prod IP ranges: 0.0.0.0/0 全プロトコル Deny 500 上り(Ingress)と 下り(Egress) ルールには、 上り (Ingress)と 下り (Egress)の タイプ があります。 VM 側を上(Internal)と見て、そこに入っていく通信を上り(Ingress)と呼び、その逆を下り(Egress)と呼びます。 Ingress と Egress ターゲット ターゲット は、ルールが適用される VM です。「すべてのインスタンス」「ネットワークタグ」「サービスアカウント」から選択肢して、ルールの適用対象とする VM を限定できます。この条件に当てはまる VM のトラフィックだけにルールが適用されます。 フィルタ フィルタ は、ルール適用対象のパケットをフィルタする設定です。パケットの観点だと、上り(Ingress)ルールではフィルタは 接続元 であり、下り(Egress)ルールでは 接続先 を意味します。 IP アドレス帯(CIDR 形式)での指定のほか、VM の ネットワークタグ や、 サービスアカウント で指定することができます。 プロトコル・ポート プロトコル・ポート は、ルールを適用する対象パケットのプロトコルやポート番号を指定します。「TCP: 443」「UDP:53」「ICMP」のように指定ができます。 アクション ルールの アクション には、許可(Allow)または拒否(Deny)を指定します。パケットがルールに合致するとこのアクションが適用されます。 優先度 ルールには、 優先度 を指定できます。 0 から 65535 の整数で指定し、小さい数字が先に評価されます。パケットがいずれかのルールに合致して Allow または Deny されると、それ以降の優先度のルールは無視されます。 ファイアウォールのイメージ 上記の設定方法を見ると、オンプレミスなどの従来型ネットワークにおけるファイアウォールとイメージが異なることに気がつく方もいるかもしれません。従来型ネットワークにおけるファイアウォールをイメージ図にすると、次のようになります。 従来型のファイアウォールのイメージ 従来型のファイアウォールはネットワークとネットワークの境界において「門」となり、門を通行しようとするトラフィックを検査し、許可するか拒否するかを決定します。 その一方で、Google CloudのVPCファイアウォールをイメージ図にすると、以下のようになります。 VPCファイアウォールのイメージ VPC のファイアウォールでは、トラフィックが VM のネットワークインターフェースに出入りする時点でファイアウォールルールが評価され、アクションが適用されます。上記のように考えれば、ファイアウォールルールの設定項目の1つである「ターゲット」に適用対象の VM を指定したり、「フィルタ」に内向きルールの場合は接続元 IP アドレスを、外向きルールの場合には宛先 IP アドレスを指定するという、少し分かりづらい設定方法も理解できるようになります。 暗黙のルール 重要な留意点として、明示的にファイアウォールルールを作成していなくても必ず適用され、ルールを一覧表示しても表示されない 暗黙のルール が存在します。 暗黙の下り(Egress)許可 暗黙の上り(Ingress)拒否 1. は方向が 下り(Egress) 、 宛先が 0.0.0.0/0 、優先度が 65535 、アクションが allow のルールです。つまり、どのファイアウォールルールにも合致しなかった下り(Egress)パケットは、 自動的に許可 されます。これは、例えば VM からインターネット上のソフトウェアレポジトリへ通信するパケットなどが自動的に許可されることを意味しています。これを防ぎたい場合は、明示的に拒否ルールを追加する必要があります。 2. は方向が 上り(Ingress) 、 送信元が 0.0.0.0/0 、優先度が 65535 、アクションが deny のルールです。つまり、どのファイアウォールルールにも合致しなかった上り(Ingress)パケットは、 自動的に拒否 されます。これは、インターネット上からいかなる VM へのパケットも拒否することを意味しています。つまり、デフォルトでは VM への HTTPS 通信や SSH 通信も到達しません。明示的に許可ルールを追加することでこれを許可します。 参考 : VPC ファイアウォール ルール - 暗黙のルール 対象 VM の指定方法 ファイアウォールルールを適用する対象の VM を指定する際には「すべてのインスタンス」「ネットワークタグ」「サービスアカウント」が選択可能と前述しました。 ネットワークタグ は、VM に設定可能なタグです。文字列として指定可能で、1つの VM に複数のネットワークタグを設定することができます。 例として AP サーバーに app というネットワークタグを指定し、 DB サーバーに db というタグを指定したうえで、以下のようなルールを作成することで、AP サーバーと DB サーバー間の通信を許可することができます。 ルール名 タイプ ターゲット フィルタ プロトコル:ポート アクション 優先度 allow-ap-db-access Ingress network tag: db network tag: app tcp: 5432 Allow 1000 サービスアカウント は、VM にアタッチ可能な IAM リソースです。本来 VM 内で動作するプログラムからの認証・認可に利用されますが、ファイアウォールでも利用可能です。 サービスアカウント vs ネットワークタグ ファイアウォールルールにおける VM の指定には、前述のように「ネットワークタグ」と「サービスアカウント」のいずれかが利用できますが、より厳密にアクセス制御を行う際はサービスアカウントを利用したほうがよいとされています。 VM のネットワークタグは個々の VM が持つ設定値であるため、VM に対して Compute インスタンス管理者( roles/compute.instanceAdmin.v1 )等のロールを持っている管理者であれば、自由に変更できてしまいます。一方でサービスアカウントは、Compute Engine VM とは独立したリソースであり、VM にサービスアカウントをアタッチするには当該サービスアカウントに対するサービス アカウント ユーザー( roles/iam.serviceAccountUser )ロール等が必要です。 VM とサービスアカウントの権限体系が別個であるため、VM 管理者とネットワーク管理者の権限分離をするためには、ファイアウォールルールでもサービスアカウントを制御方法として採用したほうが、より強固な権限分離が可能であると言えます。 参考 : VPC ファイアウォール ルール - サービス アカウントによるフィルタリングとネットワーク タグによるフィルタリング ファイアウォールポリシー ファイアウォールポリシーとは ファイアウォールポリシー (Firewall policies)とは、複数のファイアウォールルールをまとめて管理するための仕組みです。 階層型ファイアウォールポリシー、グローバルネットワークファイアウォールポリシー、リージョンネットワークファイアウォールポリシーの3つが存在します。 名称 所属リソース 説明 階層型ファイアウォールポリシー 組織またはフォルダ 組織 / フォルダにアタッチすることで配下の複数 VPC に適用できる。3 種類のポリシーのうち唯一有償 (後述) グローバルネットワークファイアウォールポリシー プロジェクト VPC にアタッチすることでその VPC の全リージョンに適用できる リージョンネットワークファイアウォールポリシー プロジェクト VPC にアタッチすることでその VPC の特定のリージョンに適用できる またファイアウォールポリシーでは、L3/L4 レイヤの制御可能な Cloud NGFW Essentials に加えて、 Cloud NGFW Standard と Cloud NGFW Enterprise の機能が利用可能です。Cloud NGFW Standard では FQDN 制御オブジェクトなど、より高度な通信制御を利用することができます。Cloud NGFW Enterprise では、L7 レイヤの脅威を防止できる IPS(侵入防止サービス)が利用可能です。 3つのファイアウォールポリシーは、いずれも複数の VPC ネットワークに適用することができますが、複数プロジェクトに適用できるのは「階層型ファイアウォールポリシー」のみです。残りの2つは、ポリシーが所属するプロジェクト内の VPC ネットワークにのみアタッチ可能です。 参考 : ファイアウォール ポリシー ルールの仕様 基本的な考え方 各種ファイアウォールポリシーは、複数のルールをグルーピングするための入れ物であり、中にルールを追加することができます。 ルール評価はステートフルインスペクションである点や、ターゲット、フィルタ、上り、下りの考え方などは、VPC ファイアウォールルールと同一です。 ただし、次の項に記載するような相違点には注意が必要です。 VPC ファイアウォールルールとの相違点 VPC ファイアウォールルールとファイアウォールポリシーの基本的な相違点は、以下のとおりです。 比較点 VPC ファイアウォールルール ファイアウォールポリシー 性質 VPC ネットワークごとにルールテーブルを持つ ルールのコンテナ (入れ物) であり複数 VPC に再利用可能 更新時の挙動 ルールごと (1行ごと) に更新 ポリシー内のルールは (トランザクション的に) まとめて更新可能 適用方法 ルールテーブルにルールを追加すると即時適用 VPC に明示的にアタッチして初めて効果を発揮 Ingress ルールにおけるソース指定方法 IP アドレス、ネットワークタグ、サービスアカウント IP アドレス、セキュアタグ (階層型では使用不可) ファイアウォールポリシーでは、Ingress ルールにおいてソースをサービスアカウントで指定できません。代わりに、グローバルネットワークファイアウォールポリシーとリージョンネットワークファイアウォールポリシーでは、 セキュアタグ を用いた制御が可能です。 またポリシーでは、複数の IP アドレス範囲をグルーピングしたオブジェクトである アドレスグループ を利用できます。自社ネットワークの IP アドレス範囲など、よく使う IP アドレス範囲をグループ化しておけば、複数のポリシーで再利用可能です。 なお、グローバルネットワークファイアウォールポリシーとリージョンネットワークファイアウォールポリシーはプロジェクトレベルのリソースであり、階層型ファイアウォールポリシーは組織レベルまたはフォルダレベルのリソースです。 参考 : 階層型ファイアウォール ポリシー 参考 : グローバル ネットワーク ファイアウォール ポリシー 参考 : リージョン ネットワーク ファイアウォール ポリシー セキュアタグ セキュアタグ は、グローバルネットワークファイアウォールポリシーとリージョンネットワークファイアウォールポリシーでアクセス制御に利用可能なリソースです。 セキュアタグは、VPC ファイアウォールで用いられたネットワークタグとは 別物 であることに注意が必要です。ネットワークタグはファイアウォールポリシーでは使用不可ですし、逆に VPC ファイアウォールではセキュアタグは使用不可であることに留意してください。 ネットワークタグとセキュアタグの違いは以下のとおりです。 比較点 セキュアタグ ネットワークタグ リソース性質 組織リソースまたはプロジェクトリソース リソースではなく VM の属性 フォーマット キー・バリュー 単一文字列 タグ自体のアクセス制御 IAM で制御可能 制御不可 利用可能なファイアウォール グローバル/リージョンファイアウォールポリシーで利用可能 (階層型では利用不可) VPC ファイアウォールルールでのみ利用可能 参考 : ファイアウォールのタグ なおセキュアタグは、Resource Manager のリソースであり、Resource Manager で管理される タグ と同じものです。ただし、ファイアウォールポリシーで利用可能なタグは、purpose という属性に GCE_FIREWALL が設定されている必要があります。 参考 : ファイアウォールにタグを使用する 参考 : タグとラベルの違い(Tags / Labels) - G-gen Tech Blog 階層型ファイアウォールポリシー 階層型ファイアウォールポリシー (Hierarchical firewall policies)は、 組織もしくはフォルダにアタッチする ことで、配下の VPC ネットワークに効果を及ぼすファイアウォールポリシーです。階層型ファイアウォールポリシーは組織レベルまたはフォルダレベルに作成できます。作成しただけでは効果は適用されず、明示的にアタッチする必要があります。 参考として、以下は Google Cloud の組織、フォルダ、プロジェクト構成例です。Google Cloud では、以下のようにリソースが階層構造になっています。この階層構造に活かし、階層型ファイアウォールポリシーは上位リソース(組織やフォルダ)から配下のリソースに効果を及ぼすことができます。 Google Cloud リソースの階層構造 Google Cloud の利用規模が大きくなり、複数のプロジェクトの複数の VPC ネットワークにおいて個別にファイアウォールルールを管理すると、管理工数が大きくなってしまいます。階層型ファイアウォールポリシーを組織やフォルダにアタッチすることで、 配下に存在するすべての VPC ネットワークに対して統一したルールを適用できる ことが、この機能の利用価値です。なお、ポリシー内の個々のルールごとに対象 VPC ネットワークを指定して、適用対象を限定することも可能です。 参考 : 階層型ファイアウォール ポリシー 階層型ファイアウォールポリシーは、ポリシーが適用される VM の台数に応じた利用料金が発生します。 参考 : Cloud Next Generation Firewall の料金 グローバルネットワークファイアウォールポリシー グローバルネットワークファイアウォールポリシー (Global network firewall policies)は、単一プロジェクト内の複数リージョンの VPC ネットワークに対してアクセス制御をかけられるポリシーです。 グローバルネットワークファイアウォールポリシーを使う場面としては、以下が挙げられます。 プロジェクト内の複数の VPC ネットワークに対して、同じファイアウォールルール群を適用したい 複数のルールを少ない手間、少ないミスで管理したい なお、1つの VPC ネットワークは、1つのグローバルネットワークファイアウォールポリシーとしか紐づけができません。 参考 : グローバル ネットワーク ファイアウォール ポリシー リージョンネットワークファイアウォールポリシー リージョンネットワークファイアウォールポリシー (Regional network firewall policies)は、グローバルネットワークファイアウォールポリシーとほとんど同じ特徴を持っていますが、ポリシー自体がリージョン単位のリソースであること、また適用対象が単一リージョン内 VM のみに限定されることが違いです。 ユースケースも、グローバルネットワークファイアウォールポリシーと同様です。 参考 : リージョン ネットワーク ファイアウォール ポリシー ルールの評価順 評価の優先度 ここまでで、VPC ファイアウォールルール、ファイアウォールポリシー(階層型、グローバル、リージョン)が登場しました。 これらが複数併用されているとき、所定の優先順位に従って、ルールが評価されます。 参考 : ファイアウォール ポリシー - ポリシーとルールの評価順序 ルールの評価順序 まず、階層型ファイアウォールポリシーが上位のノード(組織、フォルダ)から下位のノードに向けて順番に評価されていきます。 ファイアウォールポリシーでは 許可 、 拒否 、 L7検査 、 goto_next というアクションが存在します。 L7検査 は、Cloud NGFW Enterprise の IPS 検査へパケットを送信します。 goto_next アクションは、評価を決めずに次の評価順のファイアウォールに評価を委ねるアクションです。また、どのルールにも合致しなかったパケットも goto_next として扱われます。 パケットがルールに合致して、アクションが 許可 、 拒否 、 L7検査 だった場合、その時点で評価は終了します。 階層型ファイアウォールでどのルールにも合致しなかった場合、VPC ファイアウォールルールが評価されます。VPC ファイアウォールでは明示的な goto_next アクションは使えません。 VPC ファイアウォールでもどのルールにも当てはまらなかった場合、グローバルファイアウォールポリシー、リージョンファイアウォールポリシーの順で評価されていきます。 それでもどのルールにもあてはまらない場合、最終的には暗黙のルール、つまり入ってくる通信は全て Deny、出ていく通信は全て Allow が適用されます。 評価順序の変更 VPC ネットワークの networkFirewallPolicyEnforcementOrder 属性を BEFORE_CLASSIC_FIREWALL に設定すると、VPC ファイアウォールの評価順を「グローバルファイアウォールポリシー、リージョンファイアウォールポリシーの後」に変更することが可能です。 参考 : ファイアウォール ポリシー - ポリシーとルールの評価の順序を変更する ユースケース この評価順を活かして、組織全体に以下のようなネットワーク統制を適用することができます。 SSH ログインの制限 階層型ファイアウォールポリシーを使い、以下のようなルールを設定します。 ルール名 タイプ ターゲット ソース プロトコル:ポート アクション 優先度 allow-ssh-from-my-office Ingress 全インスタンス x.x.x.x/x TCP:22 Allow 1000 deny-all-ssh Ingress 全インスタンス 0.0.0.0/0 tcp:22 Deny 1010 この2つの階層型ルールを設定することで、組織内のすべての VM に対して、自社オフィスの IP アドレス( x.x.x.x/x )からの SSH は許可し、それ以外の IP アドレスからの SSH を拒否することができます。 Web トラフィックの検査 階層型ファイアウォールポリシーを使い、以下のようなルールを設定します。 ルール名 タイプ ターゲット ソース プロトコル:ポート アクション 優先度 check-ingress-web-traffic Ingress ・Project: web-app-prod ・VPC: prod ・Service Account: web-ap@(略) 0.0.0.0/0 TCP:443, 80 L7 検査 1000 deny-all-web-traffic Ingress 全インスタンス 0.0.0.0/0 TCP:443, 80 Deny 1010 この階層型ルールを設定することで、Google Cloud プロジェクト web-app-prod にあり、サービスアカウント web-ap@(略) をアタッチされた VM に対して、IPS による L7 検査を適用することができます。該当しない上り(Ingress)の Web トラフィックはすべて拒否します。 出口対策 階層型ファイアウォールポリシーを使い、以下のようなルールを設定します。 ルール名 タイプ ターゲット 送信先 プロトコル:ポート アクション 優先度 deny-all-egress-traffic-from-sensitive-pj Egress ・Project: data-pipeline ・VPC: prod 0.0.0.0/0 All Deny 1000 check-all-egress-traffic-from-all Egress 全インスタンス 0.0.0.0/0 TCP:443, 80 L7 検査 1100 この階層型ルールを設定することで、data-pipeline プロジェクトからインターネットに出ようとするトラフィックをすべて拒否します。またそれ以外のすべてのプロジェクトのすべての VM からインターネットに出ようとするトラフィックに対して、IPS による L7 検査を適用することができます。IPS では、スパイウェアが試みる外部の C2(コマンドアンドコントロール)サーバーへの接続を検知できる可能性があります。 参考 : 脅威シグネチャの概要 Cloud NGFW Essentials、Standard、Enterprise Cloud NGFW Essentials Cloud NGFW Essentials は無償の機能ティアであり、原則的に L3/L4 レベルのみのアクセス制御を提供します。機能は以下のとおりです。 プロトコル・ポート番号による制御 IP アドレス範囲による制御 サービスアカウントによる制御 ネットワークタグによる制御 セキュアタグによる制御 すなわち、当記事でここまで説明してきたようなアクセス制御は Essentials のみで実装可能です。 比較例として、Amazon Web Services(AWS)の VPC におけるセキュリティグループ機能のような、最低限のネットワークアクセス制御は、Essentials 機能でカバーすることが可能です。 参考 : Cloud NGFW の概要 - Cloud NGFW Essentials Cloud NGFW Standard Cloud NGFW Standard は有償の機能ティアであり、高度な制御ルールを提供します。機能は以下のとおりです。 機能名 概要 脅威インテリジェンス (Threat Intelligence) Google の持つ脅威情報に基づいたアクセス制御 FQDN オブジェクト パケットの IP アドレスから名前解決して得られた FQDN (ドメイン名) に基づいたアクセス制御 位置情報オブジェクト パケットの IP アドレスからマッピングした地理情報に基づいたアクセス制御 Cloud NGFW Standard の機能は VPC ファイアウォールルールでは使えず、ファイアウォールポリシー(階層型、グローバル、リージョン)でのみ利用可能です。 参考 : Cloud NGFW の概要 - Cloud NGFW Standard Cloud NGFW Standard のアクセス制御機能については、以下の記事で詳細にご紹介していますのでご参照ください。 blog.g-gen.co.jp Cloud NGFW Enterprise Cloud NGFW Enterprise は、 IPS (Intrusion Protection Service、日本語ドキュメントでは 侵入防止サービス )機能を提供します。 IPS 機能は、階層型ファイアウォールポリシーと、グローバルファイアウォールポリシーに設定できます。リージョンファイアウォールポリシーや、VPC ファイアウォールルールには設定できません。ポリシーのルールのアクションとして L7 の検査 を選択することで、パケットを検査し、不審なトラフィックをブロックします。 参考 : Cloud NGFW の概要 - Cloud NGFW Enterprise 事前準備として、VPC ネットワークに、ゾーン単位のファイアウォールエンドポイント(Firewall endpoints)をデプロイします。また、セキュリティプロファイル、セキュリティプロファイルグループを作成します。 参考 : 侵入防止サービスの概要 参考 : ファイアウォール エンドポイントの概要 IPS 機能では、Palo Alto Networks の脅威プロテクション技術が使われており、以下のような脅威に対応できます。 シグネチャ名 概要 脆弱性検出 脆弱性を突いた攻撃や不正アクセスの試みを検知。外部からの侵入トラフィックからの保護 スパイウェア対策 スパイウェアが外部の C2(コマンドアンドコントロール)サーバーへ接続しようとする試みを検知 ウイルス対策 実行可能ファイルに含まれるウイルスやマルウェアを検知 参考 : 脅威シグネチャの概要 IPS 機能の実装方法などについては、以下の記事も参考にしてください。 blog.g-gen.co.jp ファイアウォールのログ 各ファイアウォールルールでは、監査、検証、分析、トラブルシューティングのために、ログを出力させることができます。 詳細は、以下の記事をご参照ください。 参考 : Google CloudのVPCを徹底解説!(基本編) - ファイアウォールルールのログ 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の杉村です。当記事では、Google Cloud(旧称 GCP)と他のパブリッククラウドを専用線接続するサービスである Cross-Cloud Interconnect を詳細に解説します。 概要 Cross-Cloud Interconnect とは メリット 専用線サービス 対応クラウド 対応ロケーション 料金 ハブとしての Google Cloud 回線の手配の流れ 可用性 SLA SLA の適用条件 Financial Credit 注意点 概要 Cross-Cloud Interconnect とは Cross-Cloud Interconnect は、Google Cloud と他のパブリッククラウドを専用線接続するサービスです。 Google Cloud と他のクラウドサービスまでの間の専用線接続は、Google が提供します。このサービスにより、Google Cloud の VPC と、Amazon Web Services (AWS) や Microsoft Azure など他クラウドのネットワークを接続することができます。イメージとしては、コロケーション施設にある Google Cloud のパッチパネルと AWS 等のパッチパネルを Google が管理する回線で接続してくれるようなサービスです。 Google Cloud 側の専用線としては 10 Gbps または 100 Gbps から選択できます。 参考 : Cross-Cloud Interconnect overview 構成イメージ (Google Cloud to AWS) メリット 当サービスの導入により、以下のようなメリットが考えられます。 マルチクラウド戦略の推進 ネットワーク構成のシンプル化 ネットワーク関連契約のシンプル化 Google Cloud を複数クラウド・拠点のハブとして利用 (ハブアンドスポーク構成) 専用線サービス Google Cloud にはオンプレミスと接続するための専用線サービスとして Cloud Interconnect が存在し、当記事で紹介する Cross-Cloud Interconnect はその関連機能という扱いです。 Cross- の付かない Cloud Interconnect は、Google Cloud からコロケーション施設までの接続性を提供するサービスです。以下の記事もご参照ください。 blog.g-gen.co.jp 対応クラウド Cross-Cloud Interconnect は、以下のクラウドサービス提供事業者に対応しています。 Amazon Web Services (AWS) Microsoft Azure Oracle Cloud Infrastructure (OCI) Alibaba Cloud 対応ロケーション クラウドごとに、対応しているロケーション (リージョン) が異なります。AWS を例にとると、AWS と Google Cloud の両方で「東京リージョン」「大阪リージョン」が利用可能です。 2023年6月現在、Google - AWS 間接続を例に取ると、エクイニクスの TY2 が対応施設となっています。 詳細は公式ドキュメントをご参照ください。 参考 : Supported locations for AWS 参考 : Supported locations for Azure 参考 : Supported locations for OCI 参考 : Supported locations for Alibaba Cloud 料金 Cross-Cloud Interconnect (パッチパネル間接続) と VLAN Attachment (論理接続) の両方に、時間あたりの料金が発生します。 冗長回線を組んでいる場合は、主系と従系の回線の両方に Cross-Cloud Interconnect と VLAN Attachment の料金が同じように発生します。 また通常通り Google Cloud から出ていくパケット量に応じた課金も発生しますが、これは Cross-Cloud Interconnect を使っていない場合に比べて割安な単価が適用されます (ただし割引は VLAN Attachment が存在するリージョンからの通信のみ)。 あくまで参考ですが公式料金ページの計算例を記載します (2023年6月時点のもの)。 課金要素 数量 計 Cross-Cloud Interconnect connection (主・従系) 10 Gbps * 2 回線 * 24h * 30 days $8,064 VLAN Attachment (主・従系) 2 回線 * 24h * 30 days $144.00 Egress (外向き) トラフィック (北米地域の単価) 200 TiB $4,096.00 - 合計 $12,304/月 (約 ¥1,722,560/月) ※¥140/ドルで計算 参考 : Cloud Interconnect pricing - Cross-Cloud Interconnect ハブとしての Google Cloud Cross-Cloud Interconnect と Google Cloud のネットワークサービスである Network Connectivity Center を組み合わせることで、Google Cloud を拠点間接続のハブのように扱い、WAN を構成することが可能です。 Network Connectivity Center の Hub を中央に配置し、その周りにスポークとして AWS や Azure (Cross-Cloud Interconnect で Google Cloud と接続) を配置し、オンプレミスネットワークには Cloud Interconnect (専用線) や Cloud VPN で接続を確立すれば、ハブ&スポーク構成でサイト間通信を実現できます。 このサイト間通信機能のスポークにあたるリソース (専用線や VPN) を配置できるリージョンは決まっていますが、米国やインドに加えて日本のリージョンもサポート対象となっています。 参考 : Site-to-site data transfer 参考 : Locations supported for data transfer ハブアンドスポーク構成 回線の手配の流れ Google Cloud 側と対面クラウド側の双方で回線手配や、クラウドリソース作成・設定作業を行います。 AWS を例に取ると、以下のような流れで回線の手配を行います。 No タイトル 概要 1 ロケーション選定 プライマリ (主系) とセカンダリ (従系) のロケーション (リージョン) を選定します。Google Cloud 側と AWS 側の両方で決定します 2 Cross-Cloud Interconnect 接続の注文 Google Cloud 側に Cross-Cloud Interconnect の注文を行います。主系・従系ともに、承認するとポートが確保されます 3 AWS 側のポートの注文 AWS 側でポートを注文し、LOA (letter of authorization) をダウンロードします。またその LOA を Google に送付します。LOA が Google に届くと、クラウド間接続の手配が始まります 4 Google Cloud 側リソースの作成 VLAN attachment (論理接続) の作成や VPC との紐づけ、BGP セッションの設定などを行います 5 AWS 側リソースの作成 Direct Connect Gateway、Virtual Interface、VGW (Virtual Private Gateway) などの設定を行います 6 疎通確認 Google Cloud コンソールから AWS 側の信号受信の有無を確認できます 参考 : Connect to Amazon Web Services 可用性 SLA SLA の適用条件 Cloud Interconnect では 可用性 SLA が提供されています。しかし SLA が提供されるには、Google の指定する冗長化構成が取られている必要があります。 最低2つの Connection コンポーネントが、異なる Edge availability domain (接続施設の障害ドメイン。一つの都市圏内に複数存在する) で接続されていれば、99.9% の SLA が適用されます。これはつまり、主系と従系がそれぞれ1回線ずつの構成です。 また、上記の冗長構成がさらに複数リージョンで構成されている場合、99.99% の SLA が適用されます。これは、主系と従系が計4回線以上の状態です。 図を含む詳細な説明は、以下の公式ドキュメントをご参照ください。 参考 : Service-level agreement Financial Credit SLA 適用条件を満たしている構成を利用中に、実際の稼働率が SLA を下回った場合には Google から Financial Credit が補填されます。Financial Credit は Google Cloud の請求に適用可能なクーポンのようなものと考えればよいものです。 実際の稼働率が可用性 SLA を下回った場合に Financial Credit を受け取るには、ユーザは Google に申請をする必要があります。その際は、ダウンタイムを示すログの添付も必要です。 その他、SLA に関する各種条件は公式ドキュメントをご参照ください。 参考 : Cloud Dedicated and Partner Interconnect Service Level Agreement (SLA) 注意点 Google Cloud は、他のクラウド側の専用線の可用性を保証しません。 また、他クラウド側のサポートチケットの起票なども行いません。 あくまでサポート対象は、Google Cloud の責任範囲となります。 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の佐々木です。当記事では、Google Cloud (旧称 GCP) が提供するメッセージングサービスである Cloud Pub/Sub の StreamingPull API と 順序指定キー を使用し、メッセージを Pub/Sub トピックに送信された順にリアルタイム処理する仕組みを実装していきます。 前提知識 Cloud Pub/Sub とは StreamingPull API とは Pub/Sub におけるメッセージの配信順序について 順序指定キーの特性 順序付け 再配信の一貫性 アフィニティ 検証 構成 使用するサービス Cloud Run jobs について Google Kubernetes Engine (GKE) について Pub/Sub トピック、サブスクリプションの作成 パブリッシャーの作成(Cloud Run jobs) 使用するコード(Go) コンテナイメージのビルド ジョブの作成 サブスクライバーの作成(GKE) 使用するコード(Go) コンテナイメージを Artifact Registry にプッシュ GKE クラスタの作成 Workload Identity の設定 GSA の作成 KSA の作成 KSA と GSA の紐付け アプリケーションのデプロイ(GKE) 動作確認 動作確認の基本的な流れ メッセージの順序指定を有効化しない場合の動作 メッセージの順序指定が有効化されたサブスクリプションの作成 サブスクライバーが単一の場合の動作 サブスクライバーが複数の場合の動作(アフィニティの検証) 前提知識 Cloud Pub/Sub とは Cloud Pub/Sub(以下、Pub/Sub)とは、メッセージを生成するアプリケーション ( パブリッシャー )とそれを処理するアプリケーション( サブスクライバー )を切り離すマネージドな メッセージング サービス です。 Pub/Sub を使用することで、パブリッシャーとサブスクライバーの互換性・拡張性を担保した、粗結合なシステムを構成できます。 参考 (公式ドキュメント) : Pub/Sub とは 参考 (G-gen Tech Blog) : Google Cloudで理解する疎結合アーキテクチャとメッセージングサービス StreamingPull API とは Pub/Sub のクライアントライブラリでは、1 つの pull リクエストで 1 つの pull レスポンスが返る 単項 Pull のほかに、高スループット・低レイテンシのメッセージ処理を目的とした StreamingPull を使用することができます。 StreamingPull API を使用することで、メッセージを処理するアプリケーションと Pub/Sub との間に永続的な双方向接続が維持され、Pub/Sub でメッセージが利用可能になるとすぐにアプリケーションから pull されるようになります。 StreamingPull API の詳細については、以下の記事もご一読ください。 blog.g-gen.co.jp Pub/Sub におけるメッセージの配信順序について Pub/Sub のデフォルトの設定では、パブリッシュされたメッセージがサブスクリプションに配信される際、その配信順序は保証されていません。つまり、先にパブリッシュされたメッセージが次のメッセージよりも後に処理される可能性があるということです。 Pub/Subでは 順序指定キー (Ordering Key) を使用することで、メッセージの配信順序を制御することができます。 順序指定キーを使用するには、パブリッシャー側と Pub/Sub サブスクリプション側の 両方で 以下のような設定をします。 対象 設定内容 パブリッシャー側のコード クライアントライブラリで送信するメッセージに順序指定キーを設定する。 Pub/Sub サブスクリプション enable_message_ordering プロパティを true (有効)にする。 参考 : メッセージの順序指定 順序指定キーの特性 順序付け 同じ順序指定キーをもつメッセージは、パブリッシュされた順番を保持したままサブスクリプションに配信されるようになります(first-in-first-out)。 順序指定キーによるメッセージの順序付け 再配信の一貫性 順序指定が有効になっているメッセージのいずれかがエラーにより再配信される場合、Pub/Sub はメッセージの順序を維持するため、同じ順序指定キーを持つすべてのメッセージを再配信します。 したがって、メッセージ重複に対する追加の処理をサブスクライバー側に実装し、冪等性を担保する必要があります。 再配信の一貫性とメッセージの重複 アフィニティ サブスクライバーが複数のワーカーで構成され、StreamingPull API を使用する場合、同じ順序指定キーを持つメッセージは ベストエフォート ベースで 同じサブスクライバーに送信されます。 StreamingPull API と順序指定キーを使用した場合のアフィニティ ただしこのアフィニティの仕様は公式ガイドには記載されておらず、Google Cloud Pub/Sub チームの開発者によって以下のコラムに記載されていたものです。この記載は公式ガイドではないものの、Google Cloud 開発チームの名前と共に発表されていることに加え、 公式ガイドからリンクが貼られている こともあり、当記事でも公式に準ずる仕様としてご紹介しました。 参考 : Google Cloud Pub/Sub Ordered Delivery 検証 ここからは、Pub/Subにおける StreamingPull API 使用時の順序指定キーの動作を検証していきます。 構成 使用するサービス メッセージのパブリッシャーには Cloud Run jobs を使用し、並列実行されるタスクから複数のメッセージを送信します。 サブスクライバーとしては Google Kubernetes Engine(以下、GKE)を使用し、複数の Pod から StreamingPull API によるメッセージの Pull を行います。 Cloud Run jobs と GKE を使用した Pub/Sub 構成 Cloud Run jobs について Cloud Run jobs とは、サーバーレス コンテナコンピューティングサービスである Cloud Run の 1 機能です。 HTTP リクエストを処理の起点とする Cloud Run services に対して、Cloud Run jobs はコンテナイメージとして実装したジョブを、手動、スケジュール、ワークフローなど、ユーザの任意タイミングで並列して実行することができます。 当記事では、同一の処理(タスク)を容易に並列実行できる特性を利用し、Pub/Sub にメッセージを送信する複数のパブリッシャーの役割を持たせます。 Cloud Run jobs の詳細については、以下の記事で解説しています。 blog.g-gen.co.jp Google Kubernetes Engine (GKE) について GKE はコンテナオーケストレーションツールである Kubernetes を、Google マネージドのクラスタで使用することができるサービスです。 当記事ではコンピュートリソースである Pod を容易に水平スケールできる特性を利用し、StreamingPull を実行するサブスクライバーが複数ある場合のメッセージ送信先のアフィニティを検証します。 GKE の詳細については以下の記事で解説しています。 blog.g-gen.co.jp Pub/Sub トピック、サブスクリプションの作成 Pub/Sub のトピックとサブスクリプションをそれぞれ作成します。 始めは順序指定キーを設定しない場合の動作を確認するため、デフォルトの設定のまま作成します。 # Pub/Sub トピックの作成 $ gcloud pubsub topics create { トピック名 } # 実行例 $ gcloud pubsub topics create mytopic # Pub/Sub サブスクリプションの作成 $ gcloud pubsub subscriptions create { サブスクリプション名 } --topic={トピック名} # 実行例 $ gcloud pubsub subscriptions create mysubscription --topic=mytopic パブリッシャーの作成(Cloud Run jobs) 使用するコード(Go) 公式ドキュメント のサンプルコードを元に、Pub/Sub に順序指定キーを指定したメッセージを送信する処理を実装します。 メッセージのパブリッシュには Pub/Sub のクライアントライブラリである cloud.google.com/go/pubsub を使用します。 Cloud Run jobs のデフォルトの環境変数 CLOUD_RUN_TASK_INDEX からタスクのインデックス番号を取得し、これを Pub/Sub の順序指定キーとして使用します。 並列実行される Cloud Run jobs タスクごとに task#{タスクのインデックス番号} messageNumber={メッセージ番号} の形式でメッセージがパブリッシュされます。 package main import ( "context" "fmt" "log" "os" "sync" "sync/atomic" "cloud.google.com/go/pubsub" // Pub/Subクライアントライブラリ "google.golang.org/api/option" ) type Message struct { message string orderingKey string } // メッセージを生成する関数 func generateMessages(taskNum string ) ([]Message, error ) { var messages []Message // メッセージを 5 個生成 for i := 0 ; i < 5 ; i++ { m := Message{ message: fmt.Sprintf( "task#%v messageNumber=%v" , taskNum, i), orderingKey: fmt.Sprintf( "task#%v" , taskNum), } messages = append (messages, m) } fmt.Printf( "Generate messages: %v \n " , messages) return messages, nil } // メッセージをパブリッシュする関数 func publishWithOrderingKey(messages []Message, projectID, topicID string ) error { ctx := context.Background() // Pub/Sub Client client, err := pubsub.NewClient(ctx, projectID, option.WithEndpoint( "asia-northeast1-pubsub.googleapis.com:443" )) if err != nil { return fmt.Errorf( "pubsub.NewClient: %v" , err) } defer client.Close() var wg sync.WaitGroup var totalErrors uint64 t := client.Topic(topicID) // トピックの指定 t.EnableMessageOrdering = true // 順序指定キーの有効化 // メッセージのパブリッシュ for _, m := range messages { res := t.Publish(ctx, &pubsub.Message{ Data: [] byte (m.message), OrderingKey: m.orderingKey, }) wg.Add( 1 ) go func (res *pubsub.PublishResult) { defer wg.Done() _, err := res.Get(ctx) if err != nil { fmt.Printf( "Failed to publish: %s \n " , err) atomic.AddUint64(&totalErrors, 1 ) return } }(res) } wg.Wait() if totalErrors > 0 { fmt.Printf( "%d messages did not publish successfully" , totalErrors) return nil } fmt.Println( "Published messages with ordering keys successfully" ) return nil } // メイン関数 func main() { // タスク番号を取得(Cloud Run jobs のデフォルトの環境変数) taskNum := os.Getenv( "CLOUD_RUN_TASK_INDEX" ) // Cloud Run jobs に設定した環境変数からプロジェクト ID と Pub/Sub トピック ID を取得 projectID := os.Getenv( "PROJECT_ID" ) topicID := os.Getenv( "TOPIC_ID" ) messages, err := generateMessages(taskNum) if err != nil { log.Fatal(err) } err = publishWithOrderingKey(messages, projectID, topicID) if err != nil { log.Fatal(err) } } このコードは 1 回の実行で 5 つのメッセージを生成・パブリッシュするため、並列して実行する Cloud Run jobs のタスク 1 つにつき、以下に示す各行のメッセージが順番にパブリッシュされます。 # タスクのインデックス番号が 0 の場合 task# 0 messageNumber = 0 task# 0 messageNumber = 1 task# 0 messageNumber = 2 task# 0 messageNumber = 3 task# 0 messageNumber = 4 参考: 順序指定キーを使用したパブリッシュ コンテナイメージのビルド Dockerfile を使用せず、Buildpack を使用してコンテナイメージをビルドします。Buildpack を使用することで、ソースコードを自動でパッケージ化し、デプロイ可能なコンテナイメージを生成することができます。 Artifact Registry にコンテナイメージをプッシュするため、リポジトリがない場合は以下のコマンドを実行する前に作成してください。 # Buildpack を使用してイメージをビルドする $ gcloud builds submit --pack image = { リポジトリの URL } / { コンテナイメージ名 } # 実行例(リポジトリに Artifact Registry を使用) $ gcloud builds submit --pack image =asia-northeast1-docker.pkg.dev/myproject/pubsub-container/publisher-orderingkey 参考①: Google Cloud の Buildpack 参考②: Cloud Run で Go ジョブをビルドして作成する ジョブの作成 Artifact Registry にプッシュしたコンテナイメージを使用して、Cloud Run jobs のジョブを作成します。 環境変数として プロジェクト ID と Pub/Sub トピック名 を設定し、同時に実行する Task の数を 50 に設定しています。 # Cloud Run jobs のジョブを作成する(実行 Task 数 = 50) $ gcloud run jobs create { ジョブ名 } \ --image { イメージの URL } \ --region { リージョン } \ --tasks 50 \ --set-env-vars PROJECT_ID = { プロジェクト ID } , TOPIC_ID = { Pub/Sub トピック名 } # 実行例 $ gcloud run jobs create jobs-publisher-orderingkey \ --image asia-northeast1-docker.pkg.dev/myproject/pubsub-container/publisher-orderingkey \ --region asia-northeast1 \ --tasks 50 \ --set-env-vars PROJECT_ID =myproject, TOPIC_ID =mytopic サブスクライバーの作成(GKE) 使用するコード(Go) 公式ドキュメント を参考に、StreamingPull API を使用して Pub/Sub のメッセージを受け取り、そのままログ出力する処理を実装します。 このアプリケーションは GKE クラスタ上の Pod で動作し、Pub/Sub から Pull したメッセージをそのまま標準出力に出力します。 パブリッシュの処理と同様、 cloud.google.com/go/pubsub ライブラリを使用して Pub/Sub API にアクセスします。 package main import ( "context" "fmt" "io" "log" "os" "cloud.google.com/go/pubsub" // Pub/Sub クライアントライブラリ ) // メッセージを StreamingPull する関数 func pullMessages(w io.Writer , c context.Context, projectId, subId string ) error { // Pub/Sub Client client, err := pubsub.NewClient(c, projectId) if err != nil { return fmt.Errorf( "pubsub.NewClient: %v" , err) } defer client.Close() // サブスクリプションの参照 sub := client.Subscription(subId) // メッセージを pull し続ける err = sub.Receive(c, func (_ context.Context, msg *pubsub.Message) { fmt.Fprintf(w, "%v \n " , string (msg.Data)) // メッセージを標準出力に出力 msg.Ack() }) if err != nil { return fmt.Errorf( "sub.Receive: %v" , err) } return nil } func main() { ctx := context.Background() // 環境変数からプロジェクト ID と PubSub トピック ID を取得 projectId := os.Getenv( "PROJECT_ID" ) subId := os.Getenv( "SUBSCRIPTION_ID" ) err := pullMessages(os.Stdout, ctx, projectId, subId) if err != nil { log.Fatal(err) } } コンテナイメージを Artifact Registry にプッシュ GKE クラスタ上で実行される Pod にアプリケーションをデプロイするため、こちらもコンテナイメージを作成して Artifact Registry にプッシュします。 # Buildpack を使用してイメージをビルドする $ gcloud builds submit --pack image = { リポジトリの URL } / { コンテナイメージ名 } # 実行例(リポジトリに Artifact Registry を使用) $ gcloud builds submit --pack image =asia-northeast1-docker.pkg.dev/myproject/pubsub-container/subscriber-orderingkey GKE クラスタの作成 当記事では Autopilot モードの GKE クラスタ上でサブスクライバー用の Pod を実行します。 使用できる VPC、サブネットがない場合は以下のコマンドを実行する前に作成してください。 # Autopilot モードの GKE クラスタを作成する $ gcloud container clusters create-auto { クラスタ名 } \ --region { リージョン } \ --network { VPC名 } --subnetwork { サブネット名 } # 実行例 $ gcloud container clusters create-auto mycluster-autopilot \ --region asia-northeast1 \ --network myvpc \ --subnetwork mysubnet Workload Identity の設定 Autopilot モードの GKE クラスタ上で実行される Pod から Pub/Sub などの Google Cloud APIs を使用するためには、Pod に設定する Kubernetes の ServiceAccount と Pub/Sub の権限を付与した Google Cloud のサービスアカウント を Workload Identity によって紐づける必要があります。 当記事では便宜上、Kubernetes の ServiceAccount を KSA 、Google Cloud のサービスアカウントを GSA と呼びます。 Workload Identity は以下の記事で解説しているので、詳細についてはこちらもご一読ください。 blog.g-gen.co.jp GSA の作成 まず、何も権限を持たない GSA を作成します。 # GSA を作成する $ gcloud iam service-accounts create { GSA の名前 } --project { プロジェクト ID } # 実行例 $ gcloud iam service-accounts create my-gsa --project myproject 次に、作成した Pub/Sub サブスクリプションからメッセージを Pull するために「 Pub/Sub サブスクライバー ( roles/pubsub.subscriber )」 ロールを GSA に付与します。 # GSA に IAM ロールを紐付ける $ gcloud pubsub subscriptions add-iam-policy-binding { サブスクリプション名 } \ --role " roles/pubsub.subscriber " \ --member " serviceAccount:{GSA の名前}@{プロジェクト ID}.iam.gserviceaccount.com " # 実行例 $ gcloud pubsub subscriptions add-iam-policy-binding mysubscription \ --role " roles/pubsub.subscriber " \ --member " serviceAccount:my-gsa@myproject.iam.gserviceaccount.com " KSA の作成 GKE クラスタに接続し、クラスタに KSA を作成します。 以下の内容でマニフェストファイル( ksa.yaml )を作成し、クラスタに適用します。 # ksa.yaml apiVersion : v1 kind : ServiceAccount metadata : name : my-ksa annotations : # Workload Identity で紐付ける GSA を指定する iam.gke.io/gcp-service-account : my-gsa@myproject.iam.gserviceaccount.com # GKE クラスタに接続する $ gcloud container clusters get-credentials { クラスタ名 } --region asia-northeast1 --project { プロジェクト名 } # 実行例 $ gcloud container clusters get-credentials mycluster-autopilot --region asia-northeast1 --project myproject # GKE クラスタに KSA を作成する $ kubectl apply -f ksa.yaml KSA と GSA の紐付け KSA が GSA の権限を借用して Google Cloud APIs にアクセスできるように、 GSA に対する「Workload Identity User ( roles/iam.workloadIdentityUser )」ロールを KSA に紐付けます。 # KSA と GSA を紐付ける $ gcloud iam service-accounts add-iam-policy-binding { GSAの名前 } @ { プロジェクトID } .iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member " serviceAccount:{プロジェクトID}.svc.id.goog[{KSAを作成したNamespace}/{KSAの名前}] " # 実行例(default名前空間を使用している場合) $ gcloud iam service-accounts add-iam-policy-binding my-gsa@myproject.iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member " serviceAccount:myproject.svc.id.goog[default/my-ksa] " アプリケーションのデプロイ(GKE) サブスクライバーのアプリケーションを GKE にデプロイするマニフェストファイル( deployment.yaml )は以下のようになります。 当記事では始めにサブスクライバー(Pod)の数が 1 の場合の動作検証をするため、 spec.replicas の値を 1 にしています。 マニフェストファイルは、この時点ではまだクラスタに適用しません。 # deployment.yaml apiVersion : apps/v1 kind : Deployment metadata : name : pubsub-subscriber spec : replicas : 1 selector : matchLabels : app : subscriber template : metadata : labels : app : subscriber spec : containers : - name : subsc-container image : "asia-northeast1-docker.pkg.dev/myproject/pubsub-container/subscriber-orderingkey" # サブスクライバーのコンテナイメージ env : - name : "PROJECT_ID" value : "myproject" # Pub/Sub を作成したプロジェクトの ID - name : "SUBSCRIPTION_ID" value : "mysubscription" # Pub/Sub サブスクリプションの名前 resources : requests : cpu : "500m" serviceAccountName : my-ksa # Workload Identity で使用する ServiceAccount 動作確認 動作確認の基本的な流れ 動作確認は、基本的に以下の流れで行います。 ① Cloud Run jobs のタスク実行(メッセージのパブリッシュ) ↓ ② GKE クラスタにサブスクライバー用 Pod を作成(メッセージの StreamingPull 処理) ↓ ③ Pod のログを確認 ↓ ④ Pod の削除 ①と②は順番が逆のように見えますが、②→①の順に実施してしまうと、(メッセージがそれほど多くない場合)メッセージが Pub/Sub に貯まることなくすぐに Pod によって処理されてしまうため、順序指定を有効化するまでもなく順番通りに処理されてしまい、順序指定の効果が確認できなくなります。したがって、この検証では①→②の順に実施します。 メッセージの順序指定を有効化しない場合の動作 まず、順序指定を 有効化しない 場合の動作を確認します。 ここまでに作成したリソースは以下のような構成になっています。パブリッシャーのコードで順序指定キーを設定していますが、サブスクリプションでメッセージの順序指定を有効化していません。この場合は順序指定キーは機能しません。 順序指定が有効化されていないメッセージを単一の Pod で StreamingPull する 以下のコマンドで Cloud Run のジョブを実行し、メッセージをパブリッシュします。Cloud Run jobs では 50 個のタスクを並列実行し、各タスク 5 つ、合計 250 個のメッセージを Pub/Sub トピックにパブリッシュします。 # ジョブの実行(Cloud Run jobs) $ gcloud run jobs execute jobs-publisher-orderingkey --region asia-northeast1 --wait ジョブの完了を確認したら、GKE クラスタに deployment.yaml を適用し、Pod を作成します。 # Pod の作成 $ kubectl apply -f deployment.yaml # Pod のステータスを確認 $ kubectl get pods # 出力例 $ kubectl get pods NAME READY STATUS RESTARTS AGE pubsub-subscriber-545b97bb97-lnkkb 1 / 1 Running 0 3m37s Pod のステータスが Running になったら、Pod のログを確認します。 順序指定が有効になっていないため、タスクのインデックス番号(task#の数字)が同じであっても messageNumber の順番が 0→1→2→3→4 になっていない(メッセージのパブリッシュ順に処理されていない)ケースがいくつかあることがわかります。 # Pod のログを確認する(順序指定キーを設定していない場合) $ kubectl logs { Pod 名 } # 出力例(抜粋) $ kubectl logs pubsub-subscriber-545b97bb97-lnkkb task# 5 messageNumber = 0 task# 5 messageNumber = 1 task# 7 messageNumber = 4 ~~~省略~~~ task# 43 messageNumber = 0 task# 43 messageNumber = 1 task# 43 messageNumber = 2 task# 43 messageNumber = 3 task# 43 messageNumber = 4 task# 7 messageNumber = 0 task# 7 messageNumber = 1 task# 7 messageNumber = 2 task# 7 messageNumber = 3 task# 13 messageNumber = 2 task# 13 messageNumber = 3 task# 13 messageNumber = 4 task# 35 messageNumber = 0 task# 35 messageNumber = 1 task# 35 messageNumber = 2 task# 35 messageNumber = 3 task# 35 messageNumber = 4 task# 11 messageNumber = 4 task# 11 messageNumber = 0 task# 11 messageNumber = 1 task# 11 messageNumber = 2 task# 11 messageNumber = 3 ~~~省略~~~ 次の検証のため、Pod を一旦削除します。 # Pod の削除 $ kubectl delete -f deployment.yaml メッセージの順序指定が有効化されたサブスクリプションの作成 順序指定が有効化されたサブスクリプションを作成します。 順序指定の設定はサブスクリプション作成後に変更することはできないため、一度サブスクリプションを削除します。 # 順序指定が有効化されていない Pub/Sub サブスクリプションの削除 $ gcloud pubsub subscriptions delete { サブスクリプション名 } # 実行例 $ gcloud pubsub subscriptions delete mysubscription サブスクリプションを削除したら、順序指定を有効化した同名のサブスクリプションを作成します。 # 順序指定が有効化された Pub/Sub サブスクリプションの作成 $ gcloud pubsub subscriptions create { サブスクリプション名 } \ --topic = { トピック名 } \ --enable-message-ordering # 実行例 $ gcloud pubsub subscriptions create mysubscription \ --topic = mytopic \ --enable-message-ordering Pub/Sub サブスクリプションを作り直したため、再度サブスクリプションに対する「 Pub/Sub サブスクライバー ( roles/pubsub.subscriber )」 ロールを GSA に付与します。 # GSA に IAM ロールを紐付ける $ gcloud pubsub subscriptions add-iam-policy-binding { サブスクリプション名 } \ --role " roles/pubsub.subscriber " \ --member " serviceAccount:{GSA の名前}@{プロジェクト ID}.iam.gserviceaccount.com " # 実行例 $ gcloud pubsub subscriptions add-iam-policy-binding mysubscription \ --role " roles/pubsub.subscriber " \ --member " serviceAccount:my-gsa@myproject.iam.gserviceaccount.com " サブスクライバーが単一の場合の動作 次は、順序指定を有効化した状態で、メッセージを単一のサブスクライバーで処理します。 順序指定が有効化されたメッセージを単一の Pod から StreamingPull する 先ほどと同様の手順で Cloud Run job のジョブを実行し、ジョブの完了を確認してから GKE クラスタに Pod を作成します。 Cloud Run jobs で 50 個のタスクを並列実行し、各タスク 5 つ、合計 250 個のメッセージをパブリッシュした場合のサブスクライバー側 Pod のログは以下のようになります。 タスクのインデックス番号を順序指定キーとして設定したため、同一タスク(task#で識別)からのメッセージがパブリッシュされた順(messageNumber が 0→1→2→3→4 )に処理されていることがわかります。 # Pod のログを確認する(全文) # 出力例 $ kubectl logs pubsub-subscriber-944bcdb7b-9zrbz task# 27 messageNumber = 0 task# 8 messageNumber = 0 task# 7 messageNumber = 0 task# 46 messageNumber = 0 task# 16 messageNumber = 0 task# 29 messageNumber = 0 task# 3 messageNumber = 0 task# 43 messageNumber = 0 task# 14 messageNumber = 0 task# 21 messageNumber = 0 task# 21 messageNumber = 1 task# 21 messageNumber = 2 task# 21 messageNumber = 3 task# 21 messageNumber = 4 task# 45 messageNumber = 0 task# 45 messageNumber = 1 task# 45 messageNumber = 2 task# 45 messageNumber = 3 task# 45 messageNumber = 4 task# 41 messageNumber = 0 task# 39 messageNumber = 0 task# 49 messageNumber = 0 task# 17 messageNumber = 0 task# 17 messageNumber = 1 task# 17 messageNumber = 2 task# 32 messageNumber = 0 task# 0 messageNumber = 0 task# 0 messageNumber = 1 task# 40 messageNumber = 0 task# 35 messageNumber = 0 task# 15 messageNumber = 0 task# 11 messageNumber = 0 task# 2 messageNumber = 0 task# 2 messageNumber = 1 task# 2 messageNumber = 2 task# 2 messageNumber = 3 task# 2 messageNumber = 4 task# 36 messageNumber = 0 task# 36 messageNumber = 1 task# 9 messageNumber = 0 task# 9 messageNumber = 1 task# 9 messageNumber = 2 task# 9 messageNumber = 3 task# 9 messageNumber = 4 task# 23 messageNumber = 0 task# 23 messageNumber = 1 task# 23 messageNumber = 2 task# 10 messageNumber = 0 task# 10 messageNumber = 1 task# 10 messageNumber = 2 task# 10 messageNumber = 3 task# 10 messageNumber = 4 task# 37 messageNumber = 0 task# 22 messageNumber = 0 task# 22 messageNumber = 1 task# 22 messageNumber = 2 task# 24 messageNumber = 0 task# 24 messageNumber = 1 task# 24 messageNumber = 2 task# 24 messageNumber = 3 task# 24 messageNumber = 4 task# 4 messageNumber = 0 task# 4 messageNumber = 1 task# 30 messageNumber = 0 task# 30 messageNumber = 1 task# 30 messageNumber = 2 task# 38 messageNumber = 0 task# 48 messageNumber = 0 task# 44 messageNumber = 0 task# 44 messageNumber = 1 task# 44 messageNumber = 2 task# 42 messageNumber = 0 task# 42 messageNumber = 1 task# 42 messageNumber = 2 task# 42 messageNumber = 3 task# 42 messageNumber = 4 task# 33 messageNumber = 0 task# 26 messageNumber = 0 task# 26 messageNumber = 1 task# 26 messageNumber = 2 task# 26 messageNumber = 3 task# 26 messageNumber = 4 task# 20 messageNumber = 0 task# 20 messageNumber = 1 task# 20 messageNumber = 2 task# 31 messageNumber = 0 task# 31 messageNumber = 1 task# 31 messageNumber = 2 task# 47 messageNumber = 0 task# 25 messageNumber = 0 task# 25 messageNumber = 1 task# 25 messageNumber = 2 task# 25 messageNumber = 3 task# 25 messageNumber = 4 task# 19 messageNumber = 0 task# 19 messageNumber = 1 task# 19 messageNumber = 2 task# 19 messageNumber = 3 task# 19 messageNumber = 4 task# 13 messageNumber = 0 task# 13 messageNumber = 1 task# 13 messageNumber = 2 task# 13 messageNumber = 3 task# 13 messageNumber = 4 task# 12 messageNumber = 0 task# 1 messageNumber = 0 task# 6 messageNumber = 0 task# 6 messageNumber = 1 task# 6 messageNumber = 2 task# 6 messageNumber = 3 task# 18 messageNumber = 0 task# 6 messageNumber = 4 task# 5 messageNumber = 0 task# 5 messageNumber = 1 task# 5 messageNumber = 2 task# 5 messageNumber = 3 task# 34 messageNumber = 0 task# 34 messageNumber = 1 task# 34 messageNumber = 2 task# 34 messageNumber = 3 task# 34 messageNumber = 4 task# 28 messageNumber = 0 task# 3 messageNumber = 1 task# 3 messageNumber = 2 task# 3 messageNumber = 3 task# 3 messageNumber = 4 task# 7 messageNumber = 1 task# 7 messageNumber = 2 task# 7 messageNumber = 3 task# 7 messageNumber = 4 task# 8 messageNumber = 1 task# 8 messageNumber = 2 task# 8 messageNumber = 3 task# 8 messageNumber = 4 task# 27 messageNumber = 1 task# 27 messageNumber = 2 task# 27 messageNumber = 3 task# 27 messageNumber = 4 task# 29 messageNumber = 1 task# 29 messageNumber = 2 task# 29 messageNumber = 3 task# 29 messageNumber = 4 task# 14 messageNumber = 1 task# 14 messageNumber = 2 task# 14 messageNumber = 3 task# 14 messageNumber = 4 task# 46 messageNumber = 1 task# 46 messageNumber = 2 task# 46 messageNumber = 3 task# 46 messageNumber = 4 task# 16 messageNumber = 1 task# 16 messageNumber = 2 task# 16 messageNumber = 3 task# 16 messageNumber = 4 task# 41 messageNumber = 1 task# 41 messageNumber = 2 task# 41 messageNumber = 3 task# 41 messageNumber = 4 task# 43 messageNumber = 1 task# 43 messageNumber = 2 task# 43 messageNumber = 3 task# 43 messageNumber = 4 task# 47 messageNumber = 1 task# 47 messageNumber = 2 task# 47 messageNumber = 3 task# 47 messageNumber = 4 task# 31 messageNumber = 3 task# 31 messageNumber = 4 task# 12 messageNumber = 1 task# 30 messageNumber = 3 task# 30 messageNumber = 4 task# 28 messageNumber = 1 task# 28 messageNumber = 2 task# 28 messageNumber = 3 task# 28 messageNumber = 4 task# 44 messageNumber = 3 task# 44 messageNumber = 4 task# 5 messageNumber = 4 task# 38 messageNumber = 1 task# 38 messageNumber = 2 task# 38 messageNumber = 3 task# 38 messageNumber = 4 task# 4 messageNumber = 2 task# 4 messageNumber = 3 task# 4 messageNumber = 4 task# 1 messageNumber = 1 task# 1 messageNumber = 2 task# 1 messageNumber = 3 task# 1 messageNumber = 4 task# 12 messageNumber = 2 task# 12 messageNumber = 3 task# 12 messageNumber = 4 task# 48 messageNumber = 1 task# 48 messageNumber = 2 task# 48 messageNumber = 3 task# 48 messageNumber = 4 task# 20 messageNumber = 3 task# 33 messageNumber = 1 task# 20 messageNumber = 4 task# 18 messageNumber = 1 task# 18 messageNumber = 2 task# 18 messageNumber = 3 task# 18 messageNumber = 4 task# 33 messageNumber = 2 task# 33 messageNumber = 3 task# 33 messageNumber = 4 task# 36 messageNumber = 2 task# 36 messageNumber = 3 task# 36 messageNumber = 4 task# 23 messageNumber = 3 task# 23 messageNumber = 4 task# 22 messageNumber = 3 task# 22 messageNumber = 4 task# 37 messageNumber = 1 task# 37 messageNumber = 2 task# 37 messageNumber = 3 task# 37 messageNumber = 4 task# 35 messageNumber = 1 task# 35 messageNumber = 2 task# 35 messageNumber = 3 task# 35 messageNumber = 4 task# 17 messageNumber = 3 task# 17 messageNumber = 4 task# 39 messageNumber = 1 task# 39 messageNumber = 2 task# 39 messageNumber = 3 task# 39 messageNumber = 4 task# 15 messageNumber = 1 task# 15 messageNumber = 2 task# 15 messageNumber = 3 task# 15 messageNumber = 4 task# 0 messageNumber = 2 task# 0 messageNumber = 3 task# 0 messageNumber = 4 task# 11 messageNumber = 1 task# 11 messageNumber = 2 task# 11 messageNumber = 3 task# 11 messageNumber = 4 task# 49 messageNumber = 1 task# 49 messageNumber = 2 task# 49 messageNumber = 3 task# 49 messageNumber = 4 task# 40 messageNumber = 1 task# 40 messageNumber = 2 task# 40 messageNumber = 3 task# 40 messageNumber = 4 task# 32 messageNumber = 1 task# 32 messageNumber = 2 task# 32 messageNumber = 3 task# 32 messageNumber = 4 サブスクライバーが複数の場合の動作(アフィニティの検証) 最後に Pod の数を 3 つに増やし、順序指定が有効化されたメッセージを StreamingPull API で Pull した場合に、メッセージがどのように分散するかを確認します。 順序指定が有効化されたメッセージを複数の Pod から StreamingPull する マニフェストファイルの spec.replicas の値を 3 に変更します。 # deployment.yaml apiVersion : apps/v1 kind : Deployment metadata : name : pubsub-subscriber spec : replicas : 3 # ここを変更する selector : matchLabels : app : subscriber template : metadata : labels : app : subscriber spec : containers : - name : subsc-container image : "asia-northeast1-docker.pkg.dev/myproject/pubsub-container/subscriber-orderingkey" # サブスクライバーのコンテナイメージ env : - name : "PROJECT_ID" value : "myproject" # Pub/Sub を作成したプロジェクトの ID - name : "SUBSCRIPTION_ID" value : "mysubscription" # Pub/Sub サブスクリプションの名前 resources : requests : cpu : "500m" serviceAccountName : my-ksa # Workload Identity で使用する ServiceAccount 今まで同様、Cloud Run jobs のジョブを実行してメッセージをパブリッシュした後、マニフェストファイルを適用して 3 つの Pod を作成します。 Pod がすべて正常に実行されているのを確認したら 各 Pod のログを確認します。 始めに、各 Pod に送られたメッセージ数を確認するために、ログの行数を見てみます。 メッセージはタスクごとに 5 つ送信されるため、タスクのインデックス番号を順序を指定キーとして分散処理を行った場合、メッセージの再配信が行われていなければ、アフィニティによって各 Pod が処理するメッセージの数は 5 の倍数になるはずです(※アフィニティがベストエフォートベースである点は注意)。 今回の結果を見たところ、各 Pod で 5 の倍数の数だけメッセージを処理しているようです。 # 出力例 # Pod のログの行数を確認する $ kubectl logs pubsub-subscriber-944bcdb7b-pch4t | wc -l 75 $ kubectl logs pubsub-subscriber-944bcdb7b-s7xdq | wc -l 95 $ kubectl logs pubsub-subscriber-944bcdb7b-wp6jt | wc -l 80 実際のログを確認してみます。3 つの Pod のログを以下に記載します。 順序指定キーと StreamingPull API を使用した際のアフィニティにより、同一タスクからパブリッシュされたメッセージ(task#が同じもの)は同一の Pod に送信され、パブリッシュされた順(messageNumber が 0→1→2→3→4 )に処理されていることがわかります。 # 1 つ目の Pod のログを確認する(全文) # 出力例 $ kubectl logs pubsub-subscriber-944bcdb7b-pch4t task# 34 messageNumber = 0 task# 34 messageNumber = 1 task# 27 messageNumber = 0 task# 44 messageNumber = 0 task# 44 messageNumber = 1 task# 44 messageNumber = 2 task# 44 messageNumber = 3 task# 31 messageNumber = 0 task# 32 messageNumber = 0 task# 32 messageNumber = 1 task# 32 messageNumber = 2 task# 37 messageNumber = 0 task# 37 messageNumber = 1 task# 37 messageNumber = 2 task# 6 messageNumber = 0 task# 49 messageNumber = 0 task# 49 messageNumber = 1 task# 26 messageNumber = 0 task# 26 messageNumber = 1 task# 26 messageNumber = 2 task# 26 messageNumber = 3 task# 10 messageNumber = 0 task# 10 messageNumber = 1 task# 10 messageNumber = 2 task# 10 messageNumber = 3 task# 42 messageNumber = 0 task# 42 messageNumber = 1 task# 48 messageNumber = 0 task# 48 messageNumber = 1 task# 28 messageNumber = 0 task# 37 messageNumber = 3 task# 37 messageNumber = 4 task# 11 messageNumber = 0 task# 11 messageNumber = 1 task# 11 messageNumber = 2 task# 11 messageNumber = 3 task# 11 messageNumber = 4 task# 44 messageNumber = 4 task# 26 messageNumber = 4 task# 34 messageNumber = 2 task# 34 messageNumber = 3 task# 34 messageNumber = 4 task# 8 messageNumber = 0 task# 8 messageNumber = 1 task# 8 messageNumber = 2 task# 8 messageNumber = 3 task# 31 messageNumber = 1 task# 31 messageNumber = 2 task# 31 messageNumber = 3 task# 31 messageNumber = 4 task# 10 messageNumber = 4 task# 32 messageNumber = 3 task# 32 messageNumber = 4 task# 42 messageNumber = 2 task# 42 messageNumber = 3 task# 42 messageNumber = 4 task# 48 messageNumber = 2 task# 48 messageNumber = 3 task# 48 messageNumber = 4 task# 28 messageNumber = 1 task# 28 messageNumber = 2 task# 28 messageNumber = 3 task# 28 messageNumber = 4 task# 49 messageNumber = 2 task# 49 messageNumber = 3 task# 49 messageNumber = 4 task# 27 messageNumber = 1 task# 27 messageNumber = 2 task# 27 messageNumber = 3 task# 27 messageNumber = 4 task# 6 messageNumber = 1 task# 6 messageNumber = 2 task# 6 messageNumber = 3 task# 6 messageNumber = 4 task# 8 messageNumber = 4 # 2 つ目の Pod のログを確認する(全文) # 出力例 $ kubectl logs pubsub-subscriber-944bcdb7b-s7xdq task# 14 messageNumber = 0 task# 14 messageNumber = 1 task# 14 messageNumber = 2 task# 14 messageNumber = 3 task# 14 messageNumber = 4 task# 45 messageNumber = 0 task# 43 messageNumber = 0 task# 43 messageNumber = 1 task# 43 messageNumber = 2 task# 43 messageNumber = 3 task# 5 messageNumber = 0 task# 5 messageNumber = 1 task# 2 messageNumber = 0 task# 17 messageNumber = 0 task# 17 messageNumber = 1 task# 17 messageNumber = 2 task# 19 messageNumber = 0 task# 19 messageNumber = 1 task# 19 messageNumber = 2 task# 19 messageNumber = 3 task# 19 messageNumber = 4 task# 24 messageNumber = 0 task# 24 messageNumber = 1 task# 39 messageNumber = 0 task# 2 messageNumber = 1 task# 22 messageNumber = 0 task# 22 messageNumber = 1 task# 22 messageNumber = 2 task# 22 messageNumber = 3 task# 22 messageNumber = 4 task# 4 messageNumber = 0 task# 4 messageNumber = 1 task# 4 messageNumber = 2 task# 4 messageNumber = 3 task# 4 messageNumber = 4 task# 30 messageNumber = 0 task# 30 messageNumber = 1 task# 30 messageNumber = 2 task# 30 messageNumber = 3 task# 20 messageNumber = 0 task# 20 messageNumber = 1 task# 20 messageNumber = 2 task# 20 messageNumber = 3 task# 47 messageNumber = 0 task# 9 messageNumber = 0 task# 35 messageNumber = 0 task# 46 messageNumber = 0 task# 46 messageNumber = 1 task# 46 messageNumber = 2 task# 46 messageNumber = 3 task# 46 messageNumber = 4 task# 43 messageNumber = 4 task# 1 messageNumber = 0 task# 1 messageNumber = 1 task# 1 messageNumber = 2 task# 1 messageNumber = 3 task# 1 messageNumber = 4 task# 5 messageNumber = 2 task# 29 messageNumber = 0 task# 29 messageNumber = 1 task# 29 messageNumber = 2 task# 29 messageNumber = 3 task# 29 messageNumber = 4 task# 5 messageNumber = 3 task# 5 messageNumber = 4 task# 17 messageNumber = 3 task# 17 messageNumber = 4 task# 20 messageNumber = 4 task# 30 messageNumber = 4 task# 45 messageNumber = 1 task# 45 messageNumber = 2 task# 45 messageNumber = 3 task# 45 messageNumber = 4 task# 47 messageNumber = 1 task# 47 messageNumber = 2 task# 47 messageNumber = 3 task# 47 messageNumber = 4 task# 39 messageNumber = 1 task# 39 messageNumber = 2 task# 39 messageNumber = 3 task# 39 messageNumber = 4 task# 9 messageNumber = 1 task# 9 messageNumber = 2 task# 9 messageNumber = 3 task# 9 messageNumber = 4 task# 35 messageNumber = 1 task# 2 messageNumber = 2 task# 2 messageNumber = 3 task# 2 messageNumber = 4 task# 35 messageNumber = 2 task# 35 messageNumber = 3 task# 35 messageNumber = 4 task# 24 messageNumber = 2 task# 24 messageNumber = 3 task# 24 messageNumber = 4 # 3 つ目の Pod のログを確認する(全文) # 出力例 $ kubectl logs pubsub-subscriber-944bcdb7b-wp6jt task# 21 messageNumber = 0 task# 21 messageNumber = 1 task# 21 messageNumber = 2 task# 12 messageNumber = 0 task# 13 messageNumber = 0 task# 13 messageNumber = 1 task# 13 messageNumber = 2 task# 13 messageNumber = 3 task# 13 messageNumber = 4 task# 18 messageNumber = 0 task# 41 messageNumber = 0 task# 41 messageNumber = 1 task# 41 messageNumber = 2 task# 41 messageNumber = 3 task# 41 messageNumber = 4 task# 23 messageNumber = 0 task# 23 messageNumber = 1 task# 23 messageNumber = 2 task# 23 messageNumber = 3 task# 23 messageNumber = 4 task# 33 messageNumber = 0 task# 15 messageNumber = 0 task# 15 messageNumber = 1 task# 0 messageNumber = 0 task# 0 messageNumber = 1 task# 0 messageNumber = 2 task# 0 messageNumber = 3 task# 0 messageNumber = 4 task# 40 messageNumber = 0 task# 40 messageNumber = 1 task# 25 messageNumber = 0 task# 25 messageNumber = 1 task# 25 messageNumber = 2 task# 25 messageNumber = 3 task# 25 messageNumber = 4 task# 36 messageNumber = 0 task# 16 messageNumber = 0 task# 16 messageNumber = 1 task# 16 messageNumber = 2 task# 16 messageNumber = 3 task# 16 messageNumber = 4 task# 33 messageNumber = 1 task# 33 messageNumber = 2 task# 33 messageNumber = 3 task# 33 messageNumber = 4 task# 12 messageNumber = 1 task# 12 messageNumber = 2 task# 12 messageNumber = 3 task# 12 messageNumber = 4 task# 7 messageNumber = 0 task# 7 messageNumber = 1 task# 7 messageNumber = 2 task# 7 messageNumber = 3 task# 7 messageNumber = 4 task# 38 messageNumber = 0 task# 38 messageNumber = 1 task# 38 messageNumber = 2 task# 38 messageNumber = 3 task# 38 messageNumber = 4 task# 3 messageNumber = 0 task# 3 messageNumber = 1 task# 3 messageNumber = 2 task# 3 messageNumber = 3 task# 3 messageNumber = 4 task# 40 messageNumber = 2 task# 40 messageNumber = 3 task# 40 messageNumber = 4 task# 21 messageNumber = 3 task# 21 messageNumber = 4 task# 15 messageNumber = 2 task# 15 messageNumber = 3 task# 15 messageNumber = 4 task# 36 messageNumber = 1 task# 36 messageNumber = 2 task# 36 messageNumber = 3 task# 36 messageNumber = 4 task# 18 messageNumber = 1 task# 18 messageNumber = 2 task# 18 messageNumber = 3 task# 18 messageNumber = 4 佐々木 駿太 (記事一覧) G-gen最北端、北海道在住のクラウドソリューション部エンジニア 2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。 趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。 Follow @sasashun0805
アバター
G-gen セキュリティスイート for Google Cloud とは ユースケース 実現できること 概要 機能例 提供体系 Terraform での提供 請求代行サービスへの付帯 プラン一覧 仕組み 使用する Google Cloud サービス スコープ お申し込み方法 G-gen セキュリティスイート for Google Cloud とは G-gen セキュリティスイート for Google Cloud は、Google Cloud を利用する際に必ず実装しておきたい セキュリティ設定をパッケージ化 した G-gen 社のサービスです。 G-gen が多くのお客さんに対して Google Cloud 活用をご支援する中で、最も多く聞かれる悩みが「Google Cloud のセキュリティ設定」です。「どこから初めていいのか悩んでいる」「事例や、標準的な設定を参考にしたい」といったものです。 これらを解消するため当サービスでは、当社の Google Cloud 開発・運用実績を基に、セキュリティ設定・統制設定を Terraform コード化しました。 G-gen セキュリティスイート for Google Cloud は G-gen の Google Cloud 請求代行サービス にバンドル (付帯) されています。 最も基本的なプランである Basic プランは、請求代行サービスをご利用中のお客様であれば 無償 でご利用いただけます。 G-gen セキュリティスイート for Google Cloud ユースケース 当サービスは以下のようなケースで特に有効です。 これから Google Cloud の利用を開始する Google Cloud 環境に対してベストプラクティスに沿ったセキュリティ設定を施したい 複数部門で Google Cloud 環境を利用しており、セキュリティベースラインを設定したい 自社のセキュリティポリシーに合わせた統制をしたい 実現できること 概要 以下は、当サービスを利用することで実現できることの例です。 Google Cloud 環境不正利用の未然の防止 権限 (IAM) 管理のために最適な組織リソース構成 ベストプラクティスに沿った監査ログの取得・集約・保存 突発課金の検知 機能例 具体的には、以下のような設定が有効となります。以下は一例であり、詳細については当社のセールス担当までご連絡のうえ、サービス仕様書をご確認ください。 有効となる内容 目的 効果 リソース階層作成 運用効率化 IAM (権限管理) 運用の効率化・統制強化 データアクセス監査ログ 有効化 不正利用対策 Google Cloud 上で行われた操作の追跡 VPC フローログ有効化 不正利用対策 Google Cloud 上のネットワークログの追跡 利用リージョン制限 不正利用対策 通常利用しないリージョンでの利用を禁止 サービスアカウントキーの 作成無効化 不正使用対策 情報漏洩防止 不正アクセスリスク低減 組織外 Google アカウントへの アクセス権限付与禁止 不正利用対策 情報漏洩防止 不正アクセスリスク低減 突発課金アラート 不正利用検知 過剰課金検知 課金額が想定を超えて高くなった場合に検知 提供体系 Terraform での提供 当サービスでは多くの利用環境にフィットするよう、クラウド利用統制や脆弱性対策で実装される標準的なセキュリティ設定をコード化しました (Infrastructure as Code)。 Terraform テンプレートファイルは、お客様組織内による利用に限り、自由に加工・修正してご利用いただくことが可能ですので、貴組織の要件に応じてカスタマイズしていただけます。 当サービスは、コード化された設定値を Terraform 形式で提供します。このコードを実際に環境に適用するために必要な作業は、以下です。 PC を使い、手順書に沿っていくつかの構築操作を実行する 変数設定ファイルを自組織の環境に応じて変更する Terraform コマンドラインを実行する Google Cloud では Web コンソールで Terraform の操作が可能なため、PC に特別なツールのインストールも不要です。 Google Cloud における Terraform については、以下の記事もご参照ください。 blog.g-gen.co.jp 請求代行サービスへの付帯 当サービスは G-gen の Google Cloud 請求代行サービスにバンドル されたサービスです。 Google Cloud 請求代行サービスは、請求を G-gen 社経由とすることで Google Cloud 利用量を定価の 5%引き の金額で利用可能なサービスです。 さらに、メールベースの無償の技術サポート窓口が付帯します。これに加え、当記事で紹介する G-gen セキュリティスイート for Google Cloud が無償で利用可能になります。 Google Cloud を既に直接契約もしくは他のパートナー様経由でご契約いただいているお客様でも、簡単な操作で当社に請求を切り替えていただくことが可能です。システム停止無しで、割引とセキュリティスイートの恩恵を受けることができます。 請求の切り替えについての詳細は、セールス担当までお問い合わせください。 g-gen.co.jp また、Google Cloud の請求の仕組みについては以下の記事もご参照ください。 blog.g-gen.co.jp プラン一覧 以下のプランをご用意しております。 プラン名 ユースケース 料金と提供方法 Basic スモールスタート Google Cloud 請求代行サービス に 無償付帯 Standard 本番サービス開始に向けセキュリティを強化したい セールス担当までお問い合わせください Enterprise 厳格なセキュリティポリシーに準拠する必要がある G-gen のスペシャリストエンジニアの支援が欲しい セールス担当までお問い合わせください 仕組み 使用する Google Cloud サービス 当サービスでは、以下のような Google Cloud サービスを利用して統制・セキュリティを向上します。 組織・フォルダ (Resource Manager) ( 参考リンク ) 組織のポリシー ( 参考リンク ) Cloud Audit Logs ( 参考リンク ) Cloud Logging ( 参考リンク ) Cloud Billing ( 参考リンク ) スコープ セキュリティスイートのスコープ 当サービスが提供するセキュリティ設定は、Google Cloud 組織・フォルダ上にセキュリティ・統制関係のリソースを作成します。その統制機能が、 継承 の仕組みにより各プロジェクトに影響を及ぼします。 またお客様でコードをカスタマイズし、各プロジェクトに個別の設定を入れ込むことも可能です。 ただし Google Cloud 上で稼働するアプリケーションのワークロードに関するセキュリティ (WAF やファイアウォールなど) は、当サービスのスコープ外です。 お申し込み方法 G-gen の請求代行サービスをまだご利用でないお客様は、お申し込みの際に「セキュリティスイート for Google Cloud の利用を希望する」にチェックを入れてお申し込みください ( お申込みページ )。 既に当社の Google Cloud 請求代行サービスをご利用頂いているお客様は、セールス担当までお問い合わせください。 三木宏昭 (記事一覧) クラウドソリューション部技術2課 HROne→ServerWorks→WealthNavi→G-gen。SREやCCoE、クラウドネイティブな組織文化などに興味があります。AWS 11資格、Google Cloud認定5資格。Twitter では クラウド関連のことや副業、その他雑多に呟いています。(頻度少なめ) Follow @cloudeep_miki
アバター