TECH PLAY

株式会社G-gen

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

744

G-genの小林です。当記事では、備品発注アプリを例にして、AppSheet で アプリ作成のメリット と アプリ開発時のポイント を解説します。 備品発注を Excel から AppSheet に置き換えたら? AppSheet でアプリ化するメリット 発注はスマートフォンからボタンポチポチ 自動で集計処理 フィルタリング簡単!アプリでの見せ方は自由自在 AppSheet アプリ開発の疑問4選 データソースはどうすればいいの? テーブルとは 型の設定 View (UX) はどうやって設定するの? メニュー画面はどうやって設定するの? 画像の利用方法は? 備品発注を Excel から AppSheet に置き換えたら? 小売業の会社様を想定して「店舗から備品発注申請を行う。それを受けて本社総務部が備品の一括発注を行う。店舗が備品を受け取る」という業務を AppSheet でアプリ化します。 店舗の備品発注アプリの申請に以下のような Excel を使っている方もいらっしゃるのではないでしょうか? 備品発注申請書サンプルExcel その場合のフローはこんな感じになります。 Excelの場合の業務フロー これを AppSheet でアプリ化してみると以下のようなアプリになります。 備品発注アプリメニュー画面 備品発注アプリ店舗別集計画面 AppSheet に置き換えた場合の業務フローはとてもシンプルになります。 AppSheetでアプリ化した場合の業務フロー AppSheet でアプリ化するメリット 上記のような備品発注アプリを AppSheet でアプリ化した場合、以下のようなメリットが考えられます。 発注はスマートフォンからボタンポチポチ Excel での発注申請の場合、PC を利用する必要があります。 しかし AppSheet ではスマートフォンのモバイルアプリとしての提供が可能なため、アプリをスマートフォンから利用できます。 自動で集計処理 Excel で備品申請を受け取り、備品の一括発注を行う際、店舗ごとの Excel を確認して集計を行っている本部スタッフの方もいらっしゃるのではないでしょうか? AppSheet の場合、事前にアプリで集計の設定を行うことが出来ます。 以下は備品別の集計画面ですが、ガムテープは7つ、ショップバッグは7つ、ビニールテープは2つ発注しなければならないことが分かりますね。 備品発注アプリ備品別集計画面 フィルタリング簡単!アプリでの見せ方は自由自在 備品発注アプリでは、備品別集計画面と店舗別集計画面を作成しました。 備品別集計画面 : 本社総務部担当が備品発注を行う場合に利用する画面 店舗別集計画面 : 店舗担当者がどの備品を申請したか確認する画面 / 備品が本社に到着した後本社が店舗に郵送するため分別するときに利用する画面 この他にも、備品カテゴリー別や注文金額別などの表示も可能です。業務に応じた画面の見せ方が出来ます。 ここまでの説明で AppSheet でアプリ化したいと思った方向けに、ここからはアプリ開発時のポイントを解説します。 AppSheet アプリ開発の疑問4選 データソースはどうすればいいの? テーブルとは AppSheet でアプリを作る場合、最初に考えるべきポイント。それは データの格納先 です。 初めてアプリを作成する場合は、 スプレッドシート をデータソースとすることがおすすめです。スプレッドシートの一つのシートがそのまま AppSheet のデータを格納する「 テーブル 」になります。 今回の備品発注アプリで作成したテーブル (スプレッドシートのシート) は以下の4つです。 備品マスタ 備品一覧を管理するテーブル 店舗マスタ 発注する店舗一覧を管理するテーブル 備品発注データ 備品注文データを格納するテーブル メニュー画面(UXを作りながら作成したテーブル) アプリ上でホーム画面とするためのテーブル。 まずは上記の3つのアプリを作成する上で必要な項目のためのシートを作成して、そのシートから AppSheet アプリを作成することで簡単にアプリの作成ができます。 アプリ作成時点のSpreadsheet 型の設定 AppSheet でアプリをする際、まずは 型 (Type) の設定を見ましょう。型とは、テーブルのデータ形式のこと。 例えば、日付情報にはテキスト型ではなく 日付型 を設定します。 テキスト型 だと10/3と入力する人もいれば、R5/10/03と入力する人もいて、データ管理が煩雑化する要因となってしまいます。日付型を設定していれば、2023/10/03 と入力することを強制することが出来ます。 型には複数の種類があるので、アプリ開発時に改めて確認してください。以下はたくさんある型の一例です。 Price : 金額 Image : 画像 Ref : 別テーブルのカラム情報を取得する テーブル設定 View (UX) はどうやって設定するの? テーブルが設定できたら、次は画面の作成です。データの入力画面、データの確認画面など、基本的な画面は作成済みの場合もあります。 作成済みの画面を使って変更を加える形で画面を作成しても大丈夫ですし、新規で画面を作り始めても OK です。 備品発注アプリでは、備品別集計画面と店舗別備品集計の画面を作りました。基本的な設定は同じですが、GROUP BY という特定の値に応じたグループ分け表示の設定で見せ方を変えています。 備品別集計では備品 ID 別にグループ分け、店舗別備品集計では店舗 ID 別にグループ分け、という設定です。 また、Group aggregate というグループごとの簡単な集計機能もあり、合計値や平均値、MAX 値 / MIN 値等を設定可能です。 Group by / Group aggregate メニュー画面はどうやって設定するの? メニュー画面を作りたい!そう思っても、ホーム画面をいい感じに作る設定項目が見当たらない。 そう、メニュー画面を作るためにもテーブルが必要なんですね。 スプレッドシートでテーブル作成 遷移用の画面の名前、アイコンの設定 Action でボタンを押すと、テーブルで設定された遷移用の画面に飛ぶように設定 Setting の View → General から Starting view をメニュー画面に設定 メニュー画面の作り方は複雑なので、別の記事でまとめたいと思います。 画像の利用方法は? アプリに備品画像を入れたい。そんな場合は Google Drive に画像を格納して、格納した画像への URL を値として登録する必要があります。 この場合、前述の「型」を Image にすることも忘れずに! 備品発注アプリでは、備品写真というフォルダを作り、画像を格納し、備品レコードに画像の在り処を設定しています。 備品画像ドライブ 備品マスタイメージ 小林 あゆみ (記事一覧) クラウドソリューション部 営業チーム AWSエンジニアからGoogle Cloud営業に転向
アバター
G-gen のタナです。Google Cloud (旧称 GCP) で、BigQuery へエクスポートした Google Analytics 4 (GA4) のデータを Looker Studio レポートのデータソースとして使用した際に、BigQuery の料金がスパイク (想定以上に膨らむこと) してしまいました。同じ問題に直面した方のために、私の経験と解決策を共有します。 やりたかったこと 事象 原因 1. BigQuery のキャッシュが効かなかった 症状 仕様 原因 2. Looker Studio のキャッシュが効かなかった 症状 仕様 原因 (推測) 解決方法 シャーディング分割テーブルをパーティション分割テーブルへ統合 データマートテーブルを利用 その他の工夫 クエリ課金の原因となっているレポートの調査 オンデマンド課金に上限を設ける その他のコスト削減手法 やりたかったこと Google Analytics 4 (GA4) のデータを BigQuery にエクスポートし、そのデータをカスタムクエリで Looker Studio に取り込んでからレポートで可視化しようとしました。 Google Analytics 4 (GA4) のデータをBigQueryにエクスポート Looker Studio データソースを作成。このときカスタムクエリを使用して BigQuery からデータを抽出・集計 Looker Studio レポートでデータソースを可視化 カスタムクエリ とは、Looker Studio で、単一のテーブルをデータソースとして利用する代わりに任意の SQL クエリを書いてその結果をデータソースとする機能です。 これにより、ユーザーはより柔軟にデータソースをレポート上で活用できます。例えば、特定のフィールドを選択したり、データをフィルタリングしたり、複数のテーブルを結合したりすることができます。 事象 通常では、Looker Studio が参照する BigQuery テーブルはデータマートと呼ばれる中間テーブル(特定の目的で集計済みのテーブル)にすることが推奨されます。しかしこの検証当時は開発段階だったため、データマートのスキーマが未確定でした。 そのため BigQuery 側でデータを事前に集計するのではなく、Looker Studio のカスタムクエリで集計することにしました。コストを最適化する作業は開発が落ち着いてからにしようと考えていました。 ...事件は、翌日おきました。数円のはずだった BigQuery の課金が、数十万円に膨れ上がっていたのです。 原因 1. BigQuery のキャッシュが効かなかった 症状 BigQuery のクエリ履歴を検証した結果、同一のクエリが複数回実行されたのにも関わらず、キャッシュが適用されずに料金が発生していることが確認されました。 仕様 通常、BigQuery は以前に実行したクエリと全く同じものが再度実行される際は、新たに計算処理を行わず、以前のクエリ結果をキャッシュから返します。これにより、追加の料金は発生しません。 しかしながら、以下のようなシナリオではキャッシュが機能しない仕様です。 テーブルのデータに変更が生じた場合(例えば、ストリーミング挿入が受信されたり、新しい行が追加された場合) キャッシュの有効期限である24時間が経過した場合(ただしベストエフォートでありこれより早い場合もある) これらの詳細については、以下のドキュメントをご覧ください。 参考 : クエリのキャッシュの例外 原因 参照した対象の GA4 の BigQuery テーブルは、日付によるシャーディング(分割)が行われています。つまり GA4 の仕様で、テーブルは events_${YYYYmmdd} という形式のテーブル名で、毎日テーブルが増えていきます。このため SQL の FROM 句は events_* のように記述していました。 前述の BigQuery 公式ドキュメントでキャッシュが無効になる条件を確認したところ、条件の一つに「 ワイルドカードを使用して複数のテーブルに対してクエリを実行する場合 」がありました。 今回は SQL の FROM 句で events_* を指定したため、キャッシュが効かなかったのです。 2. Looker Studio のキャッシュが効かなかった 症状 BigQuery のクエリ履歴を検証したところ Looker Studio から同じクエリが何度も送信されていることが確認されました。 仕様 Looker Studio のキャッシュは コンポーネントごとに保持 されます。 通常の動作では、レポートのコンポーネントがデータを要求する際、そのクエリが以前に受信したクエリと一致する場合、新たなデータ要求はキャッシュから提供されます。これにより、基本的には同じクエリが短期間で複数回、BigQuery に送信されることはありません。 参考 : キャッシュ保存の仕組み しかし今回は、同じクエリが何度も BigQuery に送信されるという状況が発生しています。 原因 (推測) BigQueryのクエリ履歴を確認したところ、短期間で何度も受信されたクエリの文字数が 数千文字 であることが判明しました。 カスタムクエリが長すぎる場合、レポートのコンポーネントがデータを要求する際に、そのクエリが以前に受信したクエリと一致するかどうか比較できず、BigQuery へクエリ結果のリクエストを送信してしまった可能性が考えられます。 ただしこの点については公式には明記されておらず、推測に過ぎません。正確な原因は判明していませんが、いずれにせよ Looker Studio レポート上のキャッシュが正しく効いているかを注意ポイントとして確認する必要があるとご理解ください。 解決方法 シャーディング分割テーブルをパーティション分割テーブルへ統合 シャーディングにより分割された複数のテーブル( events_${YYYYmmdd} )を1つのテーブルに統合することで、BigQuery のキャッシュ機能を活用することが可能となります。 また現在はシャーディングよりも パーティション分割テーブルを使うほうが推奨 されています。 参考 : パーティショニングとシャーディング 参考 : 日付別テーブルを取り込み時間パーティション分割テーブルへ変換する パーティション分割テーブルでは、パーティション列を事前に指定しておくことで、その列を WHERE 句で指定したときにスキャンデータの量を削減し、キャッシュが適用されない場合でも効率的にクエリを実行することができます。 詳細は以下の記事もご参照ください。 blog.g-gen.co.jp データマートテーブルを利用 開発段階でも元のデータを事前集計して一度中間テーブル(データマート)に保存し、カスタムクエリからはこのデータマートテーブルからデータを参照するよう変更しました。 レポートを参照するたびに集計が行われることがなくなり、また Looker Studio 側のキャッシュも効くようになりました。これにより BigQuery のコストは安価になりました。 「開発段階ではデータマートのスキーマはころころ変わってしまう。頻繁にデータマートのスキーマ変更があると、それに合わせてカスタムクエリも変更する必要になり、開発スピードに影響する」という心配があるかもしれませんが、コストやレポートの表示パフォーマンスなどにも影響しますので、開発段階から十分にデータマートの検討をすることが推奨されます。 その他の工夫 クエリ課金の原因となっているレポートの調査 どのレポートが BigQuery の課金の原因となっているかを探るには、以下の記事も役に立ちます。 blog.g-gen.co.jp オンデマンド課金に上限を設ける 以下の方法で、オンデマンドモードの BigQuery で過剰なデータスキャンを防ぐことが可能です。 blog.g-gen.co.jp その他のコスト削減手法 また以下の記事のような工夫を加えることで、コスト最適化を実現することが可能です。開発段階からコストの最適化に取り組むことを推奨します。 blog.g-gen.co.jp タナ (記事一覧) データアナリティクス準備室 データエンジニア バックエンド開発を含むデータ分析とデータエンジニアの経験を持つ。AIの活用にも関心がある。Professional Machine Learningを取得。出身地はタイのバンコクで、現在は広島在住。
アバター
G-gen の杉村です。Google Cloud (旧称 GCP) の BigQuery と BI ツールである Looker Studio のネイティブ統合機能である BigQuery native integration in Looker Studio が2023年10月2日に Private Preview (申込制) で公開されたため、その機能を活用して Looker Studio レポートごとの BigQuery 課金額を調査してみました。 はじめに やること 調査方法 留意事項 情報取得方法を理解する INFORMATION_SCHEMA.JOBS の構造 クエリ ID からのレポート特定 検索ボックス URL 集計クエリ その他の Tips はじめに やること 当記事では、Looker Studio レポートごとに、そのレポートからのクエリが BigQuery 課金のうちのどれくらいを占めているかを調査する方法をご紹介します。 BigQuery をデータソースとする Looker Studio レポートからは、BigQuery に対してクエリ (SQL) が発行されます。BigQuery の利用コスト削減を考えるとき、どのレポートからのどのようなクエリで課金がかさんでいるのかを調査することが必要です。 2023年10月2日に Private Preview (申込制) として公開された BigQuery native integration in Looker Studio 機能の登場でこの調査が容易になったため、これを利用した調査方法をご紹介します。 調査方法 BigQuery で実行されたクエリは INFORMATION_SCHEMA.JOBS ビューに記録されます。 INFORMATION_SCHEMA は BigQuery が自動的に記録する一連のシステムビューであり、 JOBS はその一つです。ここにはプロジェクト内で発行されたすべてのジョブ (QUERY、LOAD、COPY等) が記録され、クエリ内容や実行時間、スキャンデータ量、消費スロット数などを確認することができます。 参考 : BigQuery INFORMATION_SCHEMA の概要 参考 : JOBS ビュー 以前は INFORMATION_SCHEMA.JOBS のジョブ履歴を確認しても、発行元の Looker Studio レポートは分かりませんでした。しかし2023年10月2日に Private Preview になった BigQuery native integration in Looker Studio 機能により、ジョブ履歴に発行元のレポートやデータソース設定が記録されるようになりました。 参考 : BigQuery native integration in Looker Studio preview 当記事では、このジョブ履歴に記録されたラベルをもとに、レポートごとの課金額を調査します。 留意事項 当記事は2023年10月時点の Private Preview 時の仕様をもとに執筆しています。GA (Generally Available、一般公開) 後の仕様は違ったものになる可能性があることにご留意ください。 また当記事内で紹介するクエリは一部、前述の公式ドキュメントのクエリを流用しています。 Preview 中の機能の注意点については以下をご参照ください。 blog.g-gen.co.jp 情報取得方法を理解する INFORMATION_SCHEMA.JOBS の構造 INFORMATION_SCHEMA.JOBS にはジョブの様々な情報が履歴として記録されます。スキーマは以下の通りです。 参考 : JOBS ビュー - スキーマ BigQuery native integration in Looker Studio の登場により labels 列に looker_studio_report_id と looker_studio_datasource_id が記録されるようになります。 labels 列は「ネストされた繰り返し列」であり、配列形式かつ key-value で値が記録されます。その記録は以下のようになります (一部の列のみ抜粋)。 creation_time job_id labels.key labels.value 2023-10-06 02:47:02.480000 UTC job_xxxx1234 requestor looker_studio looker_studio_report_id ccb23aca-1234-1234-abcd-1234abcd1234 looker_studio_datasource_id e8820559-4321-4321-dcba-4321dcba4321 job_xxxx1234 というジョブのラベルとして、リクエスト元が looker_studio であることが記録されており、同時にレポート ID とデータソース ID が記録されています。レポート ID とデータソース ID は、Looker Studio のレポートとデータソースに一意に振られる ID です。これでレポートを特定することができます。 クエリ まずは探索的に以下の SQL を実行してみて、理解を深めます。 必要に応じて 対象リージョン ( region-us の部分。東京リージョンなら region-asia-northeast1 ) や WHERE 句の 対象期間 を変更してください。 CREATE TEMP FUNCTION GetLabel(labels ANY TYPE , label_key STRING) AS ( ( SELECT l.value FROM UNNEST(labels) l WHERE l.key = label_key) ); SELECT DATETIME (creation_time, " Asia/Tokyo " ) AS creation_time_jst, job_id, GetLabel(labels, " looker_studio_report_id " ) AS report_id, GetLabel(labels, " looker_studio_datasource_id " ) AS datasource_id, user_email, cache_hit, query, referenced_tables, total_bytes_billed, total_slot_ms FROM `region-us`.INFORMATION_SCHEMA.JOBS WHERE DATE (creation_time, " Asia/Tokyo " ) BETWEEN DATE_SUB( CURRENT_DATE ( " Asia/Tokyo " ), INTERVAL 1 DAY) AND DATE_SUB( CURRENT_DATE ( " Asia/Tokyo " ), INTERVAL 0 DAY) AND GetLabel(labels, ' requestor ' ) = ' looker_studio ' ORDER BY 1 DESC 上記クエリは INFORMATION_SCHEMA.JOBS から、 requestor が looker_studio であるジョブだけを抽出するクエリです。ネストされた繰り返し列を UNNEST() 関数で分解してクエリ・フィルタしています。 なお Private Preview で利用している場合、機能が有効化されて以降に Looker Studio から発行されたクエリだけにラベルが付与されますので、それ以前のクエリ結果は上記のクエリ結果には入りません。 このクエリの結果は以下のようなものになります (1行だけ抜粋)。 クエリ結果 列の意味を解説します。 列名 意味 creation_time クエリの発行時間。またこの列は JOBS ビューのパーティショニング列 job_id BigQuery ジョブに振られる一意の ID report_id クエリ発行元の Looker Studio レポートの ID datasource_id クエリ発行元の Looker Studio データソースの ID user_email クエリ発行ユーザ。データソース設定により「レポートのオーナー」「レポートの閲覧者」「サービスアカウント」のいずれかのメールアドレスとなる cache_hit キャッシュが適用されたか否か (TRUE/FALSE) query SQL 文 referenced_tables クエリ対象のテーブルに関する情報 (ネスト繰り返し列) total_bytes_billed 課金対象バイト数 (キャッシュ適用時は0になる) total_slot_ms ジョブの消費スロット時間 (ms) (キャッシュ適用時は null になる) report_id によりクエリ発行元の Looker Studio レポートが分かります。 BigQuery の課金モードがオンデマンド (デフォルト) の場合、 total_bytes_billed が課金対象バイト数です。このバイト数の合計にオンデマンド単価をかけたものが、BigQuery の利用料金になります。 参考 : BigQuery pricing もし BigQuery を Editions で利用している場合は、 total_slot_ms を見て、このジョブがどのくらいスロットを消費したかを確認できます。ただし Editions の Autoscaler ではその同時間帯に発行されたすべてのジョブのために必要なスロットを確保しますので、単純にこの数値を料金計算に使うことはできません。Editions をお使いの場合は、あくまでこのクエリでは「どのジョブがその時間帯のスロットのスケーリングの原因となったか」を調べられるにすぎない、ということにご注意ください。 ID からのレポート特定 検索ボックス 先ほどの方法でレポート ID やデータソース ID が取れることは分かりました。 これらの ID から、実際にレポートやデータソースを特定してみます。 Web UI であれば、Looker Studio トップ画面 ( https://lookerstudio.google.com/ ) の検索ボックスに ID を入れると、対象レポートが表示されます。 「最近」「共有アイテム」「自分がオーナー」「(Looker Studio Pro の場合のみ) チームワークスペース」など、検索対象の場所を選択してから検索してください。 検索方法 データソースの場合は、検索ボックスの下の「レポート / データソース / エクスプローラ」と3つ並んでいるボタンから「データソース」を選んでから検索します。 URL 上記の方法以外にも簡単な方法があります。 実はレポート ID やデータソース ID は、Web UI でアクセスするときの URL になっています。 report_id が abcd の場合、以下のように URL 末尾に付与すると、当該レポートの閲覧・編集画面に飛ぶことができます。 https://lookerstudio.google.com/reporting/abcd データソースの場合は以下です。 datasource_id が 1234 の場合、以下の URL になります。 https://lookerstudio.google.com/datasources/1234 ただし、最低でも閲覧権限を持っていないとエラー画面になります。 集計クエリ ここまでで、INFORMATION_SCHEMA.JOBS からレポート ID が特定できること、またレポート ID が分かればレポートを一意に特定できることが分かりました。 レポート ID ごとに処理バイト数を算出できる実用的なクエリもサンプルとして掲載しますので、ご活用ください (オンデマンド利用向け)。例のごとく、リージョン (11行目) や期間 (14〜15行目) は必要に応じて変更してください。 CREATE TEMP FUNCTION GetLabel(labels ANY TYPE , label_key STRING) AS ( ( SELECT l.value FROM UNNEST(labels) l WHERE l.key = label_key) ); WITH job_list AS ( SELECT GetLabel(labels, " looker_studio_report_id " ) AS report_id, total_bytes_billed, FROM `region-us`.INFORMATION_SCHEMA.JOBS WHERE DATE (creation_time, " Asia/Tokyo " ) BETWEEN DATE_SUB( CURRENT_DATE ( " Asia/Tokyo " ), INTERVAL 30 DAY) AND DATE_SUB( CURRENT_DATE ( " Asia/Tokyo " ), INTERVAL 0 DAY) AND GetLabel(labels, ' requestor ' ) = ' looker_studio ' ) SELECT report_id, SUM (total_bytes_billed)/ 1024 / 1024 / 1024 AS total_gb_billed FROM job_list GROUP BY report_id 過去30日間のレポート ID ごとの合計ギガバイト数を求めるクエリです。オンデマンド課金であれば、これにオンデマンド単価を掛け算することでレポートごとの BigQuery コストが計算できます。 以下のような結果が返ります。 report_id total_gb_billed abcd-1234 125.1953 efgh-5678 1026.224609375 BigQuery Editions をご利用の場合は total_bytes_billed を集計するのではなく、代わりに total_slot_ms を集計するなどして、スロットの Autoscale 要因のうちどれだけを占めているか、などの調査の参考にしてください。 繰り返しになりますが、Private Preview にお申し込み頂いた場合、機能が有効化されて以降のクエリにしか report_id ラベルがつきませんので、それ以前のクエリは report_id が null になる点にもご留意ください。 その他の Tips Google Workspace の「監査と調査」ツールによって、以前から Looker Studio レポートの閲覧履歴を取ることはできました。 参考 : Looker Studio のログイベント こちらの方法では BigQuery へのクエリに関する情報は取れませんが、Looker Studio レポートについて「いつ、誰が、どこから、どのレポートへアクセスしたか」等を確認することができます。当記事でご紹介した方法で合わせて、Looker Studio 利用状況の可視化にあたってご参考としてください。 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の西島です。Google Cloud (旧称 GCP) の Cloud Run jobs 上に構築したジョブから Compute Engine 上の MySQL サーバーへ、サーバーレス VPC アクセス経由でクエリを発行し、その結果を BigQuery にロードするジョブの検証を行ったので、その紹介です。 はじめに 構成 構成図 認証情報 ビルドとデプロイ ネットワーク ソースコード類の準備 Dockerfile requirements.txt credential.txt .gcloudignore main.py cloudbuild.yaml リソースの作成 プロジェクトと API ネットワーク Compute Engine VM Artifact Registry Secret Manager BigQuery サービスアカウント・IAM MySQL のセットアップ job のデプロイ 動作検証 Cloud Run jobsの実行 リソースの削除 はじめに Compute Engine 上のリレーショナルデータベース上のデータに対して、BigQuery を用いてデータ分析を行いたいケースがあると思います。 今回は Cloud Run jobs を用いて、Private IP 経由で Compute Engine 上の MySQL サーバーへクエリを発行し、その結果を BigQuery にロードする仕組みを作成していきます。 構成 構成図 システム構成は、以下の図のとおりです。 構成図 認証情報 Compute Engine 上の MySQL への接続情報は Secret Manager へ保存します。 BigQuery への認証は、Cloud Run jobs にアタッチしたサービスアカウントを用います。 ビルドとデプロイ コンテナイメージは Artifact Registry へ保存し、build・push・deploy の一連のフローは Cloud Build を用いて、開発者のローカルマシンからコマンドラインで実行します。 ネットワーク Cloud Run jobs から Private IP 経由で VPC ネットワーク内のリソースにアクセスする場合、次のいずれかの方法を利用できます。 サーバーレス VPC アクセス Direct VPC Egress 今回は Compute Engine 上に構築した MySQL データベースにサーバーレス VPC アクセス経由で接続しました。当検証実施した2023年9月現在は Direct VPC Egress が Preview 公開のため、本番環境に採用するのは GA 済みであるサーバーレス VPC アクセスが優先となるためです。 サーバーレス VPC アクセスでは、コネクタと呼ばれるリソースを作成する必要があり、コネクタの IP アドレス範囲は /28 の CIDR 範囲を指定しなければなりません。 Cloud Run jobs や Direct VPC Egress の詳細については、以下の記事もご参照ください。 blog.g-gen.co.jp blog.g-gen.co.jp ソースコード類の準備 Dockerfile Cloud Run jobs はサーバーレスなコンテナサービスであるため、プログラムと実行に必要な依存関係を含んだコンテナイメージを作成する必要があります。 FROM python:3.10 WORKDIR /src COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY src . CMD ["python", "main.py"] requirements.txt MySQL データベースへの接続用ライブラリとして、 PyMySQL を採用しました。 MIT License で配布されているため商用利用可能で、かつ Python におけるリレーショナルデータベースを操作するための仕様である PEP 249 に準拠しています。 google-cloud-bigquery==3.11.4 PyMySQL==1.1.0 pandas==2.1.1 pyarrow==13.0.0 credential.txt Secret Manager に登録する MySQL データベースへの接続情報は、以下のように JSON 形式のテキストファイルにしました。 { "DB_USER": "xxxxx", "DB_PASSWORD": "xxxxx", "DB_DATABASE": "xxxxx", "DB_HOST": "xxxxx", "DB_PORT": "xxxxx", "BQ_PROJECT_ID": "xxxxx", "BQ_DATASET_ID": "xxxxx" } .gcloudignore Cloud Build でのビルド時に、secret 情報をビルドパッケージに含まないようにするには、除外対象のファイル名を .gcloudignore ファイルで明示的に指定します。 credential.txt main.py メインとなるソースコードです。 プログラムの内容は、以下の流れになります。 環境変数経由で、Secret Managerに保存した接続情報を取得 MySQLサーバーに接続、cityテーブルに対してクエリを発行し、結果をDataFrameに変換 load_table_from_dataframe メソッドを利用し、BigQueryにロード import json import os import sys import logging import traceback import pymysql import pandas as pd from google.cloud import bigquery import time # ログの設定 logging.basicConfig(level=logging.INFO, format = "%(asctime)s - %(levelname)s:%(name)s - %(message)s" ) logger = logging.getLogger(__name__) def get_cred_config () -> dict [ str , str ]: secret = os.environ.get( "CREDENTIALS_SECRET" ) if secret: return json.loads(secret) def get_connection (cred: dict [ str , str ]) -> pymysql.Connection: connection = pymysql.connect( user=cred[ "DB_USER" ], password=cred[ "DB_PASSWORD" ], host=cred[ "DB_HOST" ], database=cred[ "DB_DATABASE" ], port= int (cred[ "DB_PORT" ]), charset= 'utf8' , cursorclass=pymysql.cursors.DictCursor ) return connection def fetch_data (cred: dict [ str , str ]) -> pd.DataFrame: with get_connection(cred) as connection: with connection.cursor() as cursor: sql = "SELECT * FROM city;" cursor.execute(sql) result = cursor.fetchall() return pd.DataFrame(result).rename(columns={ "ID" : "id" , "Name" : "name" , "CountryCode" : "country_code" , "District" : "district" , "Population" : "population" }) def main (): logger.info(f "Start" ) # 時間計測開始 start_time = time.perf_counter() cred = get_cred_config() df = fetch_data(cred) job_config = bigquery.LoadJobConfig( schema=[ bigquery.SchemaField( "id" , bigquery.enums.SqlTypeNames.INT64), bigquery.SchemaField( "name" , bigquery.enums.SqlTypeNames.STRING), bigquery.SchemaField( "country_code" , bigquery.enums.SqlTypeNames.STRING), bigquery.SchemaField( "district" , bigquery.enums.SqlTypeNames.STRING), bigquery.SchemaField( "population" , bigquery.enums.SqlTypeNames.INT64), ], write_disposition= "WRITE_TRUNCATE" , ) client = bigquery.Client() table_id = f "{cred['BQ_PROJECT_ID']}.{cred['BQ_DATASET_ID']}.city" job = client.load_table_from_dataframe( dataframe=df, destination=table_id, job_config=job_config ) job.result() # 時間計測終了 end_time = time.perf_counter() logger.info(f "Finish, 経過時間(秒) #{end_time - start_time}" ) if __name__ == "__main__" : try : main() except Exception as err: logger.error(f "TraceBack: {traceback.format_exc()}" ) sys.exit( 1 ) cloudbuild.yaml Cloud Build では構成ファイルの記述に沿って各ステップが実行されます。 下記の構成ファイルでは、ビルド・プッシュ・デプロイのステップがあり、name フィールドで各ステップを実行するコンテナイメージを指定しています。 steps : - id : "build" name : "gcr.io/cloud-builders/docker" args : [ "build" , "-t" , "asia-northeast1-docker.pkg.dev/${PROJECT_ID}/docker-repo/job:latest" , "." , ] dir : "." - id : "push" name : "gcr.io/cloud-builders/docker" args : [ "push" , "asia-northeast1-docker.pkg.dev/${PROJECT_ID}/docker-repo/job:latest" , ] - id : "deploy" name : "gcr.io/cloud-builders/gcloud" entrypoint : "gcloud" args : [ "run" , "jobs" , "deploy" , "job" , "--image" , "asia-northeast1-docker.pkg.dev/${PROJECT_ID}/docker-repo/job:latest" , "--region" , "asia-northeast1" , "--tasks" , "1" , "--max-retries" , "0" , "--service-account" , "job-sa@${PROJECT_ID}.iam.gserviceaccount.com" , "--set-secrets" , "CREDENTIALS_SECRET=secret:latest" , "--vpc-connector" , "vpc-access-connecctor" ] リソースの作成 プロジェクトと API # 環境変数の設定 export PROJECT_ID = { プロジェクト ID } export PROJECT_NAME = { プロジェクト名 } export BILLING_ACCOUNT_ID = { 請求先アカウント ID } # プロジェクト作成 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 \ vpcaccess.googleapis.com ネットワーク # VPC ネットワークの作成 gcloud compute networks create vpc1 \ --subnet-mode=custom \ --bgp-routing-mode=regional \ --mtu=1460 # サブネットの作成 ## VM用 gcloud compute networks subnets create subnet1 --network=vpc1 \ --range=10.0.0.0/28 --region=asia-northeast1 --enable-flow-logs \ --logging-flow-sampling=1.0 ## サーバーレス VPC アクセスコネクタ用サブネット gcloud compute networks subnets create subnet-for-serverless-vpc-access --network=vpc1 \ --range=10.8.0.0/28 --region=asia-northeast1 --enable-flow-logs \ --logging-flow-sampling=1.0 # Cloud Router の作成 gcloud compute routers create nat-router \ --network=vpc1 \ --region=asia-northeast1 # Cloud NAT の作成 gcloud compute routers nats create nat \ --router=nat-router \ --auto-allocate-nat-external-ips \ --nat-all-subnet-ip-ranges \ --region=asia-northeast1 # Firewallの作成 ## IAP 経由で VM に SSH する許可ルール gcloud compute firewall-rules create iap-rule --network=vpc1 --action=allow \ --source-ranges=35.235.240.0/20 \ --rules=tcp:22 --destination-ranges=10.0.0.4 # サーバーレス VPC アクセスコネクタの構築 gcloud compute networks vpc-access connectors create vpc-access-connector \ --region asia-northeast1 \ --subnet subnet-for-serverless-vpc-access \ --min-instances 2 \ --max-instances 3 \ --machine-type f1-micro Compute Engine VM # VM の作成 gcloud compute instances create mysql-server \ --zone=asia-northeast1-b \ --machine-type=e2-micro \ --network-interface=private-network-ip=10.0.0.4,stack-type=IPV4_ONLY,subnet=subnet1,no-address \ --create-disk=auto-delete=yes,boot=yes,device-name=mysql-server,image=projects/debian-cloud/global/images/debian-11-bullseye-v20230912,mode=rw,size=10 Artifact Registry # Artifact Registryにコンテナリポジトリを作成 gcloud artifacts repositories create docker-repo \ --repository-format=docker \ --location=asia-northeast1 \ --description= " Docker repository " Secret Manager # Secret Manager に認証情報を登録 gcloud secrets create secret && \ gcloud secrets versions add secret --data-file= " ./credential.txt " BigQuery # BigQueryにデータセットを作成 bq --location=asia-northeast1 mk \ --dataset \ ${PROJECT_ID} :world サービスアカウント・IAM # サービスアカウントの作成 gcloud iam service-accounts create job-sa \ --description= " Cloud Run jobsで使用するサービスアカウント " # 各リソースにおけるサービスアカウントへのロール付与 ## Secret Manager Secret Accessor (シークレットレベル) gcloud secrets add-iam-policy-binding secret \ --member= " serviceAccount:job-sa@ ${PROJECT_ID} .iam.gserviceaccount.com " \ --role= " roles/secretmanager.secretAccessor " ## BigQuery Job User (プロジェクトレベル) gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member= " serviceAccount:job-sa@ ${PROJECT_ID} .iam.gserviceaccount.com " \ --role= " roles/bigquery.jobUser " ## BigQuery Data Editor (プロジェクトレベル) gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member= " serviceAccount:job-sa@ ${PROJECT_ID} .iam.gserviceaccount.com " \ --role= " roles/bigquery.dataEditor " # Cloud Build から Cloud Run jobs へデプロイするため Cloud Build サービスアカウントに # Cloud Run 管理者ロールとサービスアカウントユーザーロールを付与 (プロジェクトレベル) export PROJECT_NUMBER = `gcloud projects describe $PROJECT_ID --format= " value(projectNumber) " ` gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member serviceAccount: ${PROJECT_NUMBER} @cloudbuild.gserviceaccount.com \ --role roles/run.admin gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member serviceAccount: ${PROJECT_NUMBER} @cloudbuild.gserviceaccount.com \ --role roles/iam.serviceAccountUser MySQL のセットアップ Compute Engine 上の VM に MySQL をインストールする方法は、Google Cloud の公式ドキュメントを参照ください。 参考 : Google Compute Engine で MySQL をセットアップする方法 次に、インストールした MySQL にサンプルデータをインポートし、Cloud Run jobs から接続するためのユーザを作成します。 サンプルデータは MySQL 公式の world database を使用します。 今回作成したユーザでは、接続元ホストをすべて許可していますが、本番運用の際は許可するホストに制限をかける事を推奨します。 # VM へ SSH 接続 gcloud compute ssh --tunnel-through-iap mysql-server --zone=asia-northeast1-b # サンプルデータのダウンロード sudo apt-get install -y unzip && \ wget https://downloads.mysql.com/docs/world-db.zip && \ unzip world-db.zip # データベースへ接続 mysql -u root -p # インポート SOURCE ./world-db/world.sql; # DB 接続用ユーザ作成 CREATE USER ユーザ名@ ' % ' IDENTIFIED BY ' 接続時のパスワード ' ; # 権限付与 (world データベースのすべてのテーブルに対して全権限を付与) GRANT ALL PRIVILEGES ON world.* TO ユーザ名@ ' % ' ; # 権限反映 flush privileges; job のデプロイ # Cloud Build で Docker イメージのビルド・プッシュ、Cloud Run jobs にデプロイ gcloud builds submit --region asia-northeast1 --config cloudbuild.yaml 動作検証 Cloud Run jobsの実行 以下のコマンドでデプロイ済みの job を実行します。 gcloud run jobs execute job --region=asia-northeast1 Cloud Run jobsのジョブ詳細画面から実行の成否が確認できます。 Cloud Run jobsのジョブ詳細画面 BigQueryのコンソール画面からは、MySQLにあったテーブルデータが転送されていることが確認できます。 BigQueryのコンソール画面 次に、MySQLサーバーへの接続が、サーバーレス VPC アクセス経由でされているか確認します。確認方法として、Cloud Logging に出力される VPC フローログを確認する方法があります。 gcloud コマンドで確認する場合は、次のコマンドを実行します。 gcloud logging read logName = " projects/ ${PROJECT_ID} /logs/compute.googleapis.com%2Fvpc_flows " 出力結果の jsonPayload.connection.dest_ip 、 jsonPayload.connection.src_ip は、接続先・接続元のIPアドレスです。 今回は、接続先 (Compute Engine 上の VM)のIPアドレスに 10.0.0.4 を割り当て、接続元 (サーバーレス VPC アクセスコネクタ)のサブネットの CIDR に 10.8.0.0/28 を割り当てたため、それぞれの値がフローログに出力されていることが確認出来ます。 insertId : r6y9jef2tr757 jsonPayload : bytes_sent : '9792' connection : dest_ip : 10.0.0.4 dest_port : 3306 protocol : 6 src_ip : 10.8.0.5 src_port : 42755 dest_instance : project_id : xxxxxx region : asia-northeast1 vm_name : mysql-server zone : asia-northeast1-b dest_vpc : project_id : xxxxxx subnetwork_name : subnet1 vpc_name : vpc1 end_time : '2023-09-30T11:50:20.864864005Z' packets_sent : '64' reporter : DEST rtt_msec : '1' src_instance : project_id : xxxxxx region : asia-northeast1 vm_name : aet-asianortheast1-vpc--access--connector-7zw0 zone : asia-northeast1-b src_vpc : project_id : xxxxxx subnetwork_name : subnet-for-serverless-vpc-access vpc_name : vpc1 start_time : '2023-09-30T11:50:20.796486189Z' logName : projects/xxxxxx/logs/compute.googleapis.com%2Fvpc_flows receiveTimestamp : '2023-09-30T11:50:33.956154690Z' resource : labels : location : asia-northeast1-b project_id : xxxxxx subnetwork_id : '6548845232760910546' subnetwork_name : subnet1 type : gce_subnetwork timestamp : '2023-09-30T11:50:33.956154690Z' リソースの削除 以下のコマンドを実行し、検証で作成したプロジェクトを削除します。 プロジェクトを削除すると、プロジェクト内のリソースは全て削除されるため、実際のご利用時はご注意ください。 gcloud projects delete ${PROJECT_ID} 西島 昌太 (記事一覧) データアナリティクス準備室 データエンジニア 2023年4月に新卒入社。 元はフロントエンド開発を主戦場に、現在はデータエンジニアリングを勉強中。何でも屋さんを目指して、日々邁進。 休日は大体プログラムを書いてる人
アバター
G-gen ビジネス推進部の北田です。本記事では2023年7月のアップデートに伴い、従来有償ライセンスだった AppSheet Core が Google Workspace のほぼ全てのエディションで利用できるようになった ことについて紹介します。 AppSheet とは? 従来まで AppSheet のエディション Google Workspace のエディションと AppSheet 今回のアップデートでの変更点 AppSheet のプランの違い AppSheet とは? AppSheetはノーコードでアプリケーション開発が可能なツールです。プログラミングができない方でも簡単にアプリケーションを開発することができます。 主な特徴は、以下のとおりです。 プログラミングの知識がなくても開発が可能 Google Sheetsをはじめ、様々なデータベースと連携が可能 簡単なUXで開発が可能なため、営業が作成している企業も多く存在 開発の流れ AppSheet を利用することで、「そこまでお金をかけたくないけど、こういうアプリがあったら便利かも!」というニーズを満たせるかも知れません。 当社の以下の関連記事も、是非ご覧ください。 blog.g-gen.co.jp news.mynavi.jp 従来まで AppSheet のエディション まず AppSheet には以下のエディションがあります。エディションによって使える機能が異なり、例として Apigee や BigQuery のコネクタを使うためには Enterprise Standard プランが必要です。また、アプリケーションのパフォーマンスのためのリソースの割り当てもプランによって異なるため、用途に合わせて選択するようになっています。 Starter Core Enterprise Plus 各プランの料金や詳細は、以下のドキュメントをご参照ください。 参考 : サブスクリプションの選び方 参考 : Pricing | Google AppSheet Google Workspace のエディションと AppSheet 従来では Google Workspace の Enterprise Plus と Education Plus の両エディションにのみ AppSheet Core のライセンスが含まれており、追加料金なしで Core プランの機能を使うことができていましたが、その他の Google Workspace エディションでは、AppSheet は無料の範囲内でのみの利用に制限されていました。 その制限として、公開(デプロイ)できない、10ユーザまで、Automation など主要な機能が利用できない等があり、多くの Google Workspace ユーザーがあくまで試験的に AppSheet に触れる程度に留まっていました。 今回のアップデートでの変更点 表題の通り2023年7月から、 主要な Google Workspace エディションに AppSheet Core ライセンスが含まれる ようになりました。 ただし「Google Workspace は利用しないが AppSheet は利用したいユーザー」は従来どおりAppSheet 単体でライセンスを購入する必要があります。 AppSheet ライセンスが含まれるようになる Google Workspace エディションは、以下の表のとおりです。 参考 : AppSheet Core licenses will be included by default for more Google Workspace editions, along with a new Admin security setting 参考 : AppSheet Core が含まれている Google Workspace のエディション AppSheet のプランの違い AppSheet には複数のプランがあり、今回のアップデートで Google Workspace に付帯されるのは Core プランです。 上位プランである Enterprise Plus では、Core の全機能に加えて、生成 AI 機能である Gemini for App Creation や Gemini for App Solution、バージョンの復元、Active Directory などの IdP との認証情報連携、BigQuery や PostgreSQL などのデータベースとの連携、組織向けのガバナンスなど、様々な機能が追加されます。 各プランの詳細は以下のドキュメントをご参照ください。 参考 : サブスクリプションの選び方 参考 : Pricing | Google AppSheet 北田 晋平 (記事一覧) ビジネス推進部 2022年5月にG-gen にジョイン。 2013年に大手オフィス機器販売会社へ入社、複合機の販売をメインとする営業としてキャリアをスタート。クラウド業界の未来に魅力を感じ、営業としてG-genへジョイン。2023年は【GWSを極める!】を目標に、日々精進していきます。
アバター
G-gen 又吉です。LangChain とは、大規模言語モデル (LLM) を効率よく実装するために使用するフレームワークです。 当記事では LangChain を用いて、Google Cloud (旧称 : GCP) の LLM である PaLM 2 を操作する基本的な方法をご紹介します。 はじめに Vertex AI PaLM API LLM 開発の課題 学習コスト 入力トークン制限 事実と異なる回答 最新情報に対応していない 準備 環境構築 ライブラリの準備 ユーティリティ関数を定義 各 AI モデルを初期化 LangChain とは 概要 Models 概要 LLMs Chat model Text Embedding Model Memory 概要 ConversationBufferMemory Prompts 概要 Prompt Template Output Parser Example Selectors Indexes 概要 Document loaders Text splitters Vector stores Retriever Chains 概要 Simple Sequential Chains Summarization Chain Agents 概要 Tools Agent Types 実装例 はじめに Vertex AI PaLM API PaLM 2 は Google の LLM であり、Bard の裏側でも使われています。Vertex AI では、PaLM 2 のエンドポイントを Vertex AI PaLM API として公開しています。 開発者は Vertex AI PaLM API を使用することで、自社のアプリケーションで LLM を組み込むことが可能となります。 詳細については、以下のブログをご覧ください。 blog.g-gen.co.jp LLM 開発の課題 学習コスト Google Cloud や OpenAI の他にも、様々な企業が LLM を提供しております。それぞれの LLM の長所を活かして使い分ける際、LLM の種類が増えるごとに学習コストがかかってしまいます。 入力トークン制限 LLM のプロンプトに入力できるトークン数には制限があります。そのため、長いテキストをそのままの形では処理できない場合は、入力テキストを複数に分割して処理する必要があります。 事実と異なる回答 LLM は確率的に高いテキストを並べて回答を生成しているため、時には事実と異なることを回答する、いわゆるハルシネーション (幻覚) を起こしてしまうことがあります。 そこで、事実に基づいた回答を生成させるため、正しい情報を先に取得して、その情報から回答を生成させるなどの工夫が必要です。 最新情報に対応していない LLM は過去のデータを学習させてモデルを構築しています。そのため、学習データより後のできごとには回答できません。ちなみに、Vertex AI 基盤モデルの text-bison は、2023 年 2 月までのデータを学習データとしてます。 参考: Foundation models 準備 環境構築 当記事では、LangChain の解説で Python コードを実行する際に Notebook を使用します。また、Notebook 環境は Colab Enterprise を用います。 Colab Enterprise を使用すると、インフラストラクチャを管理せずに Notebook で作業できます。 Colab Enterprise の Notebook 作成方法は公式ドキュメントのクイックスタートをご参考下さい。 cloud.google.com 尚、Colab Enterprise は 2023 年 9 月時点で Preview となります。 ライブラリの準備 Notebook が立ち上がり、ランタイムと接続できましたら以下のコードを実行してライブラリのインストールとインポートを行います。 # ライブラリのインストール ! pip install langchain== 0.0 . 301 openai== 0.28 . 0 faiss-cpu== 1.7 . 4 transformers== 4.33 . 2 # ライブラリのインポート import time from typing import List import langchain from pydantic import BaseModel from google.cloud import aiplatform from langchain.chat_models import ChatVertexAI from langchain.embeddings import VertexAIEmbeddings from langchain.llms import VertexAI, OpenAI from langchain.schema import HumanMessage, SystemMessage print (f "LangChain version: {langchain.__version__}" ) print (f "Vertex AI SDK version: {aiplatform.__version__}" ) # 出力 LangChain version: 0.0 . 301 Vertex AI SDK version: 1.32 . 0 ユーティリティ関数を定義 Vertex AI Embedding API for Text で使用するユーティリティ関数を定義します。 def rate_limit (max_per_minute): period = 60 / max_per_minute print ( "Waiting" ) while True : before = time.time() yield after = time.time() elapsed = after - before sleep_time = max ( 0 , period - elapsed) if sleep_time > 0 : print ( "." , end= "" ) time.sleep(sleep_time) class CustomVertexAIEmbeddings (VertexAIEmbeddings, BaseModel): requests_per_minute: int num_instances_per_batch: int # Overriding embed_documents method def embed_documents (self, texts: List[ str ]): limiter = rate_limit(self.requests_per_minute) results = [] docs = list (texts) while docs: # Working in batches because the API accepts maximum 5 # documents per request to get embeddings head, docs = ( docs[: self.num_instances_per_batch], docs[self.num_instances_per_batch :], ) chunk = self.client.get_embeddings(head) results.extend(chunk) next (limiter) return [r.values for r in results] 各 AI モデルを初期化 当記事では、以下の AI モデルを使用します。 No 提供元 モデル名 概要 1 OpneAI text-davinci-003 OpenAI が提供する汎用的な LLM であり、さまざまな言語タスクに適している。 2 Google Cloud text-bison@001 Google Cloud が提供する汎用的な LLM であり、さまざまな言語タスクに適している。 3 Google Cloud chat-bison@001 Google Cloud が提供する会話に最適化された LLM です。 4 Google Cloud textembedding-gecko@001 Google Cloud が提供するテキストエンべディングをサポートするモデルです。 API_KEY = ${OpenAI の API Key} ## OpenAI # LLM llm_openai = OpenAI( openai_api_key = API_KEY, model_name = "text-davinci-003" , max_tokens = 256 , temperature = 0.2 , top_p = 0.95 ) ## Vertex AI # LLM llm = VertexAI( model_name= "text-bison@001" , max_output_tokens= 256 , temperature= 0.1 , top_p= 0.8 , top_k= 40 , verbose= True , ) # Chat model chat = ChatVertexAI( model_name= "chat-bison@001" ) # Embedding model EMBEDDING_QPM = 100 EMBEDDING_NUM_BATCH = 5 embeddings = CustomVertexAIEmbeddings( requests_per_minute=EMBEDDING_QPM, num_instances_per_batch=EMBEDDING_NUM_BATCH, ) 参考: langchain.llms.openai.OpenAI 参考: langchain.llms.vertexai.VertexAI 参考: langchain.chat_models.vertexai.ChatVertexAI 参考: langchain.embeddings.vertexai.VertexAIEmbeddings LangChain とは 概要 LangChain とは、LLM を用いてアプリケーションを効率よく開発するためのフレームワークです。 LangChain を用いることで、先に述べた LLM 開発の課題に比較的容易に対処することが可能です。 LangChain にはさまざまなモジュールが提供されており、そのモジュールを組み合わせて複雑なアプリケーションを構築することができます。また、それらのモジュールは大きく 6 つのコンポーネントに分けることができます。 LangChain フレームワーク また、2023 年 9 月現在対応しているプログラム言語は、Python と JavaScript (TypeScript) のみとなっており、当記事では Python での実装を行います。 参考: LangChain - Python Doc 参考: LangChain - JS/TS Doc Models 概要 Models とは、Google の PaLM 2 や OpenAI の GPT-4 をはじめとした様々な言語モデルを切り替えたり、組み合わせたりできる機能です。 LangChain Models では、以下の AI モデルタイプをサポートしています。 LLMs Chat models Text embedding models 参考: LangChain - Language models 参考: LangChain - Text embedding models LLMs LLMs には、 Vertex AI PaLM API for Text や OpenAI GPT-3.5 などが統合されています。 ここでは、Vertex AI と OpenAI の LLM を LangChain を用いて同じ記述で実行しています。 PROMPT = "Tell me a joke" # Vertex AI LLM の呼び出し res_vertexai = llm(PROMPT) print (f "Joke from Vertex AI : {res_vertexai}" ) print ( "-----" ) # OpenAI LLM の呼び出し res_openai = llm_openai(PROMPT) print (f "Joke from OpenAI : {res_openai}" ) # 出力 Joke from Vertex AI : What's the difference between a snowman and a snowwoman? Snowballs. ----- Joke from OpenAI : Q: What did the fish say when it hit the wall? A: Dam! Chat model Chat model は、 Vertex AI PaLM API for Text chat と統合されています。 ここでは、SystemMessage に AI の役割を与え、HumanMessage に ユーザーが入力するプロンプトを設定しています。 messages = [ SystemMessage(content= "あなたは旅行計画を立てるのに役立つ AI ボットです。" ), HumanMessage(content= "ニューヨークに行きたいのですが、どうすればよいですか?" ) ] res = chat(messages).content print (res) # 出力 ニューヨークへの旅行を計画する際には、以下の点に注意してください。 * いつ行くか。ニューヨークは一年中楽しめる街ですが、最も人気のある時期は春(4月~6月)と秋(9月~11月)です。この時期は、天候が穏やかで、観光客も少なめです。 * どこに行くか。ニューヨークには、見どころがたくさんあります。自由の女神、エンパイアステートビル、タイムズスクエア、セントラルパークなど、誰もが知っている観光スポットから、地元の人しか知らない穴場まで、あらゆるものがあります。 Text Embedding Model Text Embedding Model は、 Vertex AI Embedding API for Text と統合されています。 text = "こんにちは" text_embedding = embeddings.embed_query(text) print (f "次元数 : {len(text_embedding)}" ) print (f "サンプル: {text_embedding[:5]}..." ) 次元数 : 768 サンプル: [ -0 . 0027207613456994295 , 0 . 026091288775205612 , -0 . 003294411115348339 , 0 . 0031894829589873552 , 0 . 01808055490255356 ] ... Memory 概要 Memory とは、ユーザーと LLM model との会話履歴を保存および取得する機能です。Memory を用いることで、過去の会話を記憶した上で LLMs が回答を生成することがようにできます。 もちろん、Chat Models で会話履歴に対応した回答を生成することもできますが、LLMs のみで会話履歴にも対応したいというユースケースでの利用が期待できます。 ここでは、ConversationBufferMemory をご紹介しますが、Memory には多くの機能があるため、ユースケースに合わせて使い分けましょう。 参考: Memory types ConversationBufferMemory ConversationBufferMemory を使用すると、ユーザーと LLM の過去の会話を保存し、また会話を抽出できます。 from langchain.chains import ConversationChain from langchain.memory import ConversationBufferMemory conversation = ConversationChain( llm=llm, verbose= False , memory=ConversationBufferMemory() ) conversation.predict( input = """ 私の名前はまたゆーです。私の特徴を以下に記載します。 * 出身地:沖縄県 * 好きな言語:Python * 好きな食べ物 : 寿司 """ ) # 出力1 はじめまして、またゆーさん。沖縄県出身なんですね。私は沖縄県に行ったことがありませんが、いつか行ってみたいです。 Pythonがお好きなんですね。私もPythonが好きです。寿司も大好きです。 conversation.predict( input = "私の名前はわかりますか?" ) # 出力2 はい、あなたの名前はまたゆーさんです。 conversation.predict( input = "私の出身地を教えてください。" ) # 出力3 沖縄県です。 Prompts 概要 Prompts とは、プロンプトの構築と操作を容易に管理できます。 また、チーム開発をする際、プロンプトの統一化が図れるのも利点です。 LangChain Prompts には、以下 3 つの機能があります。 Prompt Template Output Parser Example Selectors Prompt Template Prompt template を用いて、プロンプトのテンプレートを作成できます。 ここでは、テンプレートの中に location という変数を入れています。format メソッドを用いることで、変数に任意の文字列が代入してプロンプトを作成できます。 from langchain import PromptTemplate # テンプレートを作成し、location を変数化 template = """ 私は {location} に旅行したいと思っています。 そこで何をすればいいでしょうか? 短い文で返答する """ # PromptTemplate をインスタンス化 prompt = PromptTemplate( input_variables=[ "location" ], template=template, ) # formatのメソッドを使って、location に「ローマ」を代入してプロンプトを作成 final_prompt = prompt.format(location= "ローマ" ) print (f "Final Prompt: {final_prompt}" ) print ( "-----------" ) print (f "LLM Output: {llm(final_prompt)}" ) # 出力 Final Prompt: 私は ローマ に旅行したいと思っています。 そこで何をすればいいでしょうか? 短い文で返答する ----------- LLM Output: ローマはイタリアの首都であり、世界で最も人気のある観光地の1つです。 ローマには、コロッセオ、パンテオン、トレヴィの泉など、多くの歴史的な建造物があります。 ローマはまた、活気のあるナイトライフと素晴らしいレストランを備えています。 ローマを訪れる際には、これらの場所を訪れ、ローマの文化と歴史を体験することをお勧めします。 Output Parser Output parsers とは、言語モデルから得られる出力のフォーマットを指定することができる機能です。 例えば、CSV や JSON のような形式で出力できます。 また、指定のフォーマットにならなかった際、再試行または修正してくれるオプションもあります。 Example Selectors Example selectors は、多数のデータから、一部のみランダムで選択 (サンプリング) できる機能です。 例えば、リストに入った 100 個の語句データから、ランダムで 30 個だけ抽出したい時などに利用します。 Indexes 概要 Indexes とは、LLM がドキュメントを操作するのに役立つ機能がいくつか用意されています。 Indexes には、以下 4 つの機能があります。 Document loaders Text splitters Vector stores Retrievers Document loaders Document loaders を用いることで、PDF や Web ページなどのテキストコンテンツを容易にドキュメント化することができます。もちろん、それらドキュメントをテキストにして LLM の入力に与えることが可能です。 ここでは、 WebBaseLoader メソッドを使用して Google Cloud 公式ブログ からテキストを抽出しています。 from langchain.document_loaders import WebBaseLoader # Google Cloud 公式ブログ からテキストを抽出 loader = WebBaseLoader( "https://cloud.google.com/blog/products/ai-machine-learning/how-to-use-grounding-for-your-llms-with-text-embeddings" ) data = loader.load() print (f "# ドキュメント数 : {len(pg_work)}" ) print (f "# 文字数 : {len(data[0].page_content)}" ) print (f "# 最初の100文字 : {data[0].page_content[:100]}" ) # 出力 # ドキュメント数 : 1 # 文字数 : 13684 # 最初の100文字 : How to use Grounding for your LLMs with text embeddings | Google Cloud BlogJump to ContentCloudBlogC Text splitters Text splitters を用いることで、長いテキストを分割することができます。つまり、LLM への入力トークン数の制限への対策に有効です。 こちらは、 RecursiveCharacterTextSplitter メソッドを用いて、指定した文字数以下になるように分割します。尚、その際、すべての段落 (次に文、そして単語) をできるだけ長くまとめて保持しようとする効果もあります。 from langchain.text_splitter import RecursiveCharacterTextSplitter # 100 文字以下で文字を分割 (尚、オーバーラップ 50 文字まで許容) text_splitter = RecursiveCharacterTextSplitter( # Chunk の設定 chunk_size= 100 , chunk_overlap= 50 , ) # Google Cloud 公式ブログ から抽出したテキストを 100 文字以下に分割 texts = text_splitter.split_documents(pg_work) print (f "# ドキュメント数 : {len(texts)}" ) # 出力 # ドキュメント数 : 272 # 最初の 10 個のドキュメントからテキストを表示 for i in range ( 10 ): print (f "[{len(texts[i].page_content)}文字]{texts[i].page_content}" , " \n " ) # 出力 [82文字]How to use Grounding for your LLMs with text embeddings | Google Cloud BlogJump to [98文字]with text embeddings | Google Cloud BlogJump to ContentCloudBlogContact sales Get started for free [82文字]sales Get started for free CloudBlogSolutions & technologyAI & Machine LearningAPI [81文字]& technologyAI & Machine LearningAPI ManagementApplication DevelopmentApplication [94文字]ManagementApplication DevelopmentApplication ModernizationChrome EnterpriseComputeContainers & [99文字]ModernizationChrome EnterpriseComputeContainers & KubernetesData AnalyticsDatabasesDevOps & SREMaps [72文字]KubernetesData AnalyticsDatabasesDevOps & SREMaps & GeospatialSecurity & [69文字]& SREMaps & GeospatialSecurity & IdentityInfrastructureInfrastructure [96文字]& IdentityInfrastructureInfrastructure ModernizationNetworkingProductivity & CollaborationSAP on [82文字]& CollaborationSAP on Google CloudStorage & Data TransferSustainabilityEcosystemIT Vector stores Vector stores とは、エンべディングされたデータを保管し、ベクター検索することができる機能となります。ベクター検索では、クエリに対し「最も類似したテキスト」を取得することもできます。 尚、Vector stores ではベクトルデータベースとして一般的な Chroma や FAISS だけでなく、 Vertex AI Vector Search (旧称 : Vertex AI Matching Engine) もサポートしています。 ここでは、以下の流れで実装します。 1. Web 上のテキストをエンべディングして保管 Web サイトからテキスト抽出 テキストを LLM のトークン制限にかからないように分割 Embeddings model を用いて、分割したテキストをベクトル化 Vector Storesにベクトル化した情報を保管 2. ユーザーの質問をエンべディングして類似検索 ユーザーの質問をベクトル化 similarity_search メソッドで、Vector Stores に保管されたベクトルと類似のテキストを検索 3. LLM で回答生成 検索結果をもとに LLM で回答を生成 from langchain.document_loaders import TextLoader from langchain.embeddings import VertexAIEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.document_loaders import WebBaseLoader from langchain.vectorstores import FAISS # Google Cloud 公式ブログ からテキストを抽出 loader = WebBaseLoader( "https://cloud.google.com/blog/products/ai-machine-learning/how-to-use-grounding-for-your-llms-with-text-embeddings" ) documents = loader.load() # 100 文字以下で文字を分割 (尚、オーバーラップ 50 文字まで許容) text_splitter = RecursiveCharacterTextSplitter(chunk_size= 100 , chunk_overlap= 50 ) texts = text_splitter.split_documents(documents) # VertexAIEmbeddings を用いて分割したテキストをそれぞれエンべディングして VectorStores に保存 db = FAISS.from_documents(texts, VertexAIEmbeddings()) # query に対し類似したテキストをベクター検索して上位 3 つのテキストを取得 # query訳 : Vertex AI Embeddings for Text の入力トークン制限は何トークンですか? query = "What is the input token limit for Vertex AI Embeddings for Text?" docs = db.similarity_search(query, 3 ) for index, doc in enumerate (docs): print (f "No{index+1}. {doc.page_content}" ) # 出力 No1. URL /blog/products/ai-machine-learning/how-to-use-grounding-for-your-llms-with-text-embeddings was No2. ModernizationChrome EnterpriseComputeContainers & KubernetesData AnalyticsDatabasesDevOps & SREMaps No3. KubernetesData AnalyticsDatabasesDevOps & SREMaps & GeospatialSecurity & from langchain import PromptTemplate # 上位 3 つの回答を結合 facts = " \n " .join([doc.page_content for doc in docs]) # プロンプトテンプレートを作成 template = """ Prease generate answer based on facts for the following query. query: {query} facts: {facts} answer: """ prompt = PromptTemplate( input_variables=[ "query" , "facts" ], template=template, ) final_prompt = prompt.format(query=query, facts=facts) print (f "Final Prompt: {final_prompt}" ) print ( "-----------" ) print (f "LLM Output: {llm(final_prompt)}" ) # 出力 Final Prompt: Prease generate answer based on facts for the following query. query: What is the input token limit for Vertex AI Embeddings for Text? facts: on Vertex AI Model Garden. Embeddings for Text : The API takes text input up to 3,072 input tokens The API takes text input up to 3,072 input tokens and outputs 768 dimensional text embeddings, and "librarian-level" precision Vertex AI Embeddings for Text has an embedding space with 768 answer: ----------- LLM Output: The input token limit for Vertex AI Embeddings for Text is 3,072. LLM の回答を日本語訳すると「 Vertex AI Embeddings for Text の入力トークン制限は 3,072 です 」となり、この回答は Google Cloud 公式ブログから参照しているため正しい回答が生成されていることがわかります。 Retriever Retriever とは、クエリからドキュメントを返すインターフェイスです。ドキュメントを保管する必要がないため、ドキュメントの保管とベクター検索を分離できます。また、Vectorstore よりも汎用的です。 Chains 概要 Chains は、複数のプロンプトを実行するための機能であり、一度の処理で複雑な回答を生成したいときに有効です。 ユースケースとしては、LLM が生成した回答を、次の LLM のプロンプトとして渡すことが可能です。また、長いテキストを複数に分割し、それぞれを要約後、それらをまとめて一つの要約テキストにすることも可能です。 Simple Sequential Chains SimpleSequentialChain は、各ステップが LLM の出力を次のステップへの入力として使用するシンプルな Chains です。 この手法は、複雑な質問に対し、一度中間的な回答を生成し、最終的な回答を生成することで LLM の回答精度を高めることができます。このような手法を一般的に Chain of Thought (CoT) プロンプティングといいます。 参考: Language Models Perform Reasoning via Chain of Thought from langchain.prompts import PromptTemplate from langchain.chains import LLMChain from langchain.chains import SimpleSequentialChain # 1 つ目の Chain を定義 prompt_1 = PromptTemplate( input_variables=[ "occupation" ], template= "Question : {occupation}が使いそうなGoogle Cloudのプロダクトを1つ教えてください? \n Answer :" ) chain_1 = LLMChain(llm=llm, prompt=prompt_1) # 2 つ目の Chain を定義 prompt_2 = PromptTemplate( input_variables=[ "google_cloud_product" ], template= "{google_cloud_product}の勉強法を300字で教えてください" , ) chain_2 = LLMChain(llm=llm, prompt=prompt_2) # SimpleSequentialChain を作成 sequential_chain = SimpleSequentialChain(chains=[chain_1, chain_2], verbose= True ) # 実行 print (sequential_chain( "データエンジニア" )) # 出力 > Entering new SimpleSequentialChain chain... BigQuery BigQueryの勉強法は、以下の通りです。 1. BigQueryの基礎を学ぶ。 2. BigQueryのドキュメントを読む。 3. BigQueryのチュートリアルをこなす。 4. BigQueryのサンプルコードを読む。 5. BigQueryのコミュニティに参加する。 6. BigQueryの認定試験を受ける。 BigQueryの基礎を学ぶには、以下のリソースが役に立ちます。 * [BigQueryのドキュメント](https://cloud.google.com/bigquery/docs/) * [BigQueryのチュートリアル](https://cloud.google > Finished chain. {'input': 'データエンジニア', 'output': ' BigQueryの勉強法は、以下の通りです。\n\n1. BigQueryの基礎を学ぶ。\n2. BigQueryのドキュメントを読む。\n3. BigQueryのチュートリアルをこなす。\n4. BigQueryのサンプルコードを読む。\n5. BigQueryのコミュニティに参加する。\n6. BigQueryの認定試験を受ける。\n\nBigQueryの基礎を学ぶには、以下のリソースが役に立ちます。\n\n* [BigQueryのドキュメント](https://cloud.google.com/bigquery/docs/)\n* [BigQueryのチュートリアル](https://cloud.google'} Summarization Chain Summarization Chain は、長いテキストの要約を容易に実行できる Chain です。 参考: Summarization from langchain.chains.summarize import load_summarize_chain from langchain.document_loaders import WebBaseLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # Google Cloud 公式ブログ からテキストを抽出 loader = WebBaseLoader( "https://cloud.google.com/blog/products/ai-machine-learning/how-to-use-grounding-for-your-llms-with-text-embeddings" ) documents = loader.load() print (f "# 文字数 : {len(documents[0].page_content)}" ) # 1500 文字以下で文字を分割 (尚、オーバーラップ 50 文字まで許容) text_splitter = RecursiveCharacterTextSplitter(chunk_size= 1500 , chunk_overlap= 50 ) texts = text_splitter.split_documents(documents) # 長いテキストを要約する Chain を初期化 chain = load_summarize_chain(llm, chain_type= "map_reduce" ) # 要約を実行 summary = chain.run(texts) print (f "要約:{summary}" ) # 出力 # 文字数 : 13705 要約:This blog post introduces two new APIs from Google: Embeddings API for Text and Embeddings API for Image. The Embeddings API for Text is a public preview and is available to trusted testers. The API takes text input and outputs 1024 dimensional embeddings. The Embeddings API for Image is still in development and will be released in a future blog post. 要約後のテキストを Google 翻訳で日本語に変換すると、以下のとおりです。 このブログ投稿では、Google の 2 つの新しい API、Embeddings API for Text と Embeddings API for Image を紹介します。 Embeddings API for Text はパブリック プレビューであり、信頼できるテスターが利用できます。 API はテキスト入力を受け取り、1024 次元の埋め込みを出力します。 画像用の埋め込み API はまだ開発中であり、将来のブログ投稿でリリースされる予定です。 Agents 概要 Agents とは、ユーザーの要求に対し、どのアクションで、どの順番で解決するかを LLM を使って決定してくれる機能です。 Tools Tools とは、Agent が「外の世界と対話」することができる機能です。 Google 検索ができる Tool や、Wikipedia の情報を参照できる Tool など、さまざまな Tools が提供されています。 他にもさまざまな Tools が存在するため、ユースケースにあわせて選択してください。 参考: Tools Agent Types Agents には、いくつかの種類 (Agent Types) があります。 例えば、 Zero-shot ReAct Agent を用いると、任意の数の Tool を利用している場合、それぞれの Tool の説明 (description) に基づいて、どのツールを用いるかを決める Agent Type になります。この場合、各 Tool の説明がしっかりと書かれている必要があります。 他にもさまざまな Agent Types が存在するため、ユースケースにあわせて選択してください。 参考: Agent Types 実装例 Tool には Google 検索が行える SerpAPIWrapper を用いて、Agent には Zero-shot ReAct Agent を用い最新の情報を元に LLM に回答を生成させてみます。 参考: SerpAPI ここでは、以下の流れで実行したいと思います。 Agents を使わずに LLM から回答を生成 Agents を使い Google 検索を用いて最新情報を基に LLM から回答を生成 llm( """ 株式会社G-genの代表取締役は誰ですか? また、CTOは誰ですか? """ ) # 出力 株式会社G-genの代表取締役は、小林 大地氏です。CTOは、小林 大地氏です。 LLM (Vertex AI 基盤モデル text-bison) にそのまま回答させると、出力は間違っていました。 正しくは、株式会社G-genの代表取締役は 羽柴孝 、CTOは 杉村勇馬 です。 それでは次に、Agents を用いて実行してみます。 from langchain.utilities import GoogleSerperAPIWrapper from langchain.llms.vertexai import VertexAI from langchain.agents import initialize_agent, Tool from langchain.agents import AgentType SERPER_API_KEY = ${SERPER_API_KEY} # Tools の初期化 search = GoogleSerperAPIWrapper() tools = [ Tool( name= "Intermediate Answer" , func=search.run, description= "useful for when you need to search for latest information in web" ) ] # Agent の初期化 self_ask_with_search = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose= True ) # クエリを実行 self_ask_with_search.run( """ 株式会社G-genの代表取締役は誰ですか? また、CTOは誰ですか? """ ) # 出力 > Entering new AgentExecutor chain... 株式会社G-genの代表取締役は田中裕介氏です。 CTOは小池啓介氏です。 Action: Intermediate Answer Action Input: 株式会社G-gen 代表取締役 Observation: 株式会社G-gen(本社:東京都新宿区、代表取締役:羽柴孝)は、新ソリューション「Google Cloud ではじめる Generative AI 活用支援ソリューション」をリリースしたことをお知らせいたします。 Thought: 株式会社G-genの代表取締役は羽柴孝氏です。 Action: Intermediate Answer Action Input: 株式会社G-gen CTO Observation: 株式会社G-gen(本社:東京都新宿区、代表取締役:羽柴孝)はCOOに鈴木 達文 氏、CTOに杉村 勇馬 氏が就任したことをお知らせいたします。 杉村 勇馬. 執行役員CTO / クラウドソリューション部 部長. 株式会社G-gen上智大学 Sophia University. 日本 東京都 新宿区. 25人のフォロワー 24人のつながり. 執行役員CTO. 杉村 勇馬Yuma Sugimura. 杉村 勇馬. Twitter; Facebook. 会社概要. 商号, 株式会社G-gen(カブシキガイシャ ジージェン). 所在地, 〒162-0824 東京都新宿区 ... 株式会社G-gen(本社:東京都新宿区、代表取締役:羽柴孝)は、12月13日(火)にエンジニア向けのイベント「ベンチャー企業 CTO が語る! G-gen の CTO の杉村です。今回はエンジニアの成長の方法について、私なりの考え方を書いてみたいと思います。「できるエンジニア」と銘打って文章を ... クラウドネイティブな10の働き方とは? 杉村 勇馬. 株式会社G-gen / 執行役員CTO ... 株式会社G-gen / 執行役員CTO. Google Cloud 技術ブログで Google ... 会社の特長. CTOがいる B2B 1億円以上の資金を調達済み フルリモート可 転勤なし 副業してもOK 残業30H以内 服装自由 学歴不問 言語未経験可. 言語. Python SQL. イチオシ. 株式会社 G-gen 杉村 勇馬. 2012 年に上智大学を卒業後、埼玉県警察の警察官という異色の経歴を経て IT エンジニアに。インフラ中心のキャリアを積み ... 経営・CxO職. CEO・経営企画・経営管理. COO. CTO・CIO. CFO. CMO. 経理・管理・バックオフィス職. 人事・総務. 財務・会計・経理. 広報・IR. 法務(コンプライアンス)・ ... G-gen 杉村勇馬氏. 株式会社G-gen 執行役員CTO クラウドソリューション部部長杉村勇馬氏. 佐久間氏:当社は経営的な視点で、技術的要素も含めて提案 ... Thought: 株式会社G-genの代表取締役は羽柴孝氏、CTOは杉村勇馬氏です。 Final Answer: 株式会社G-genの代表取締役は羽柴孝氏、CTOは杉村勇馬氏です。 > Finished chain. 株式会社G-genの代表取締役は羽柴孝氏、CTOは杉村勇馬氏です。 インターネット上の最新情報を取得して、LLM が回答を生成していることがわかります。また、正しい回答が生成されていました。 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさい、沖縄出身のクラウドエンジニア! セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリストとして活動中。好きな分野は AI/ML。 Follow @matayuuuu
アバター
G-gen の佐々木です。当記事では、Google Cloud(旧称 GCP)のマネージド ETL サービスである Dataflow を解説します。 概要 Dataflow とは Apache Beam とは ユースケース Dataflow のユースケース 例1 : リアルタイムのデータ取り込み 例2 : データストア間のデータ移行 開発 SDK パイプライン構成 Python SDK で記述したパイプラインの例 Dataflow テンプレート 構成要素 Dataflow ワーカー リージョンエンドポイント Dataflow GPU カスタムコンテナ 周辺機能 Dataflow SQL Dataflow Prime Dataflow ML ノートブックでの開発 パイプライン最適化の仕組み Dataflow Runner v2 自動スケーリング 水平自動スケーリング 動的スレッドスケーリング 動的作業再調整 Dataflow Shuffle / Streaming Engine Flexible Resource Scheduling(FlexRS) モニタリング セキュリティ データの暗号化 プライベート IP アドレスの使用 ネットワークタグの使用 料金 Dataflow の料金 Dataflow Prime の料金 概要 Dataflow とは Dataflow は Google Cloud のインフラストラクチャ上でマネージドな Apache Beam 実行環境を提供するサービスです。Java や Python の SDK でパイプラインを記述することで、データをバッチ処理やストリーミング処理することができます。 Dataflow では Apache Beam SDK を使用してデータ処理のパイプライン(データの読み込みから変換、書き込みまでの一連の処理)をコードで記述します。それを Dataflow ジョブ として Google Cloud にデプロイするだけで、処理内容に応じて必要なぶんのコンピューティングリソースが自動でプロビジョニングされます。 したがって、ユーザーはパイプラインを実行するためのインフラストラクチャをほとんど意識することなく、パイプラインの設計のみに集中することができます。 参考 : Dataflow の概要 Apache Beam とは Apache Beam はバッチ処理、ストリーミング処理の両方を定義することができる オープンソースのプログラミングモデル で、大規模なデータ分散処理パイプラインを単純化して定義することができます。 参考 : Apache Beam Apache Beam では、パイプラインの実行時に ランナー (Runner) として実行環境を指定します。Dataflow は Apache Beam パイプラインの実行環境 「 Dataflow ランナー 」として動作するためのサービス であり、Apache Beam パイプライン自体は Google Cloud 以外の環境 (Dataflow ランナー以外のランナー) でも実行することができます。 Apache Beam は指定された Runner でパイプラインを実行する ユースケース Dataflow のユースケース Apache Beam ではバッチ処理とストリーミング処理の双方が利用できるため、Dataflow のユースケースは多岐にわたります。 単純なデータのバッチ/ストリーミング変換処理だけではなく、Vertex AI 上の機械学習モデルにデータをストリーミングすることで、リアルタイムの予測分析や異常検知を行うこともできます。 参考 : Dataflow - ユースケース 例1 : リアルタイムのデータ取り込み Dataflow では典型的なユースケースとして、メッセージングサービスである Pub/Sub と組み合わせたリアルタイムのデータ取り込み処理があります。 以下の図は、Pub/Sub と Dataflow を使用した クリックストリーム 分析の例です。 アプリケーション上でのエンドユーザーのアクションを Pub/Sub に送信し、Dataflow でデータのチェックや集計などを行ったあと、各種データストアに書き込みます。 Pub/Sub ではバックエンドに対する 最低 1回(at-least-once) の配信が保証されており、データの再配信が行われる場合があるため、データ重複が発生する可能性があります。Dataflow は Pub/Sub からデータを受け取るとき、 Pub/Sub のメッセージに含まれるメッセージ ID を使用してデータの重複削除を行います。 Dataflow を使用したリアルタイムのクリックストリーム分析 参考 : Pub/Sub を使用したストリーミング 例2 : データストア間のデータ移行 Dataflow では、各種データベースから BigQuery や Cloud Spanner などにデータを移行するためのテンプレートが提供されており、数クリックでデータ移行パイプラインを構築することができます。 Dataflow を使用したデータのバッチ移行 また、 Datastream を Dataflow のデータソースとして使用することで、データベースの変更を検知し、それをリアルタイムで BigQuery などのデータストアに同期することができます。 Datastream と Dataflow によるデータのリアルタイム同期処理 参考1 : Dataflow テンプレートによる簡単なデータ移動 参考2 : 分析のために Datastream と Dataflow を実装する 開発 SDK Apache Beam では、以下の言語の SDK が提供されています。 Java Python Go インストール手順等は以下の Google Cloud 公式ドキュメントをご参照ください。 参考 : Apache Beam SDK をインストールする パイプライン構成 Apache Beam のパイプラインは以下のような要素から構成されます。 要素名 説明 Pipeline Apache Beam によるデータ変換処理の開始から終了、つまり入力データの読み取りから変換、出力データの書き込みまでの一連の処理をカプセル化した概念です。 PCollection パイプラインで使用されるデータセットを抽象化するオブジェクトであり、パイプラインの各ステップにおける入力、出力となります。 PTransform 実際のデータ変換処理を表します。1 つ以上の PCollection を入力とし、その各要素に対して処理を実行した後、1 つ以上の PCollection を出力します。 I/O transforms Apache Beam の I/O コネクタ を使用して、外部ソースからパイプラインへのデータ読み込み、パイプラインから外部シンクへのデータの書き込みを行います。様々なデータ形式を読み書きすることができ、必要に応じて カスタムの I/O コネクタ を開発して使用することもできます。 Apache Beam パイプラインの構成 上記の基本的な構成要素の詳細、およびその他の構成要素については以下のドキュメントを参照してください。 参考 1: Apache Beam Programming Guide(Apache Beam ドキュメント) 参考 2: Apache Beam のプログラミング モデル(Google Cloud ドキュメント) Python SDK で記述したパイプラインの例 パイプラインの例として、Python 用の Apache Beam SDK で記述したシンプルな例を以下に示します。 この例では、3 人の人物の名前、年齢、身長を入力データとし、年齢と身長の平均をそれぞれ算出して出力するパイプラインが記述されています。 import logging import apache_beam as beam from apache_beam.options.pipeline_options import PipelineOptions # Dataflow ランナーで実行するためのパラメータ pipeline_args = [ #'--runner=DirectRunner' # ローカルで実行する場合 # Dataflow ランナーで実行(以下は Dataflow 用のパラメータ) '--runner=DataflowRunner' , '--project=myproject' , '--region=asia-northeast1' , '--subnetwork=regions/asia-northeast1/subnetworks/mysubnet' , '--temp_location=gs://mybucket/tmp/' ] def run (): # パイプラインの作成 with beam.Pipeline( options=PipelineOptions(pipeline_args) ) as pipeline: # PCollection を作成 inputdata = ( pipeline | 'Create' >> beam.Create([ { 'name' : 'Ichiro' , 'age' : 32 , 'height' : 172 }, { 'name' : 'Jiro' , 'age' : 27 , 'height' : 168 }, { 'name' : 'Saburo' , 'age' : 25 , 'height' : 179 } ]) ) # 平均年齢を算出する mean_age = ( inputdata | 'Extract age' >> beam.Map( lambda x: x[ 'age' ]) | 'Get mean age' >> beam.combiners.Mean.Globally() | 'Get KeyValue age' >> beam.Map( lambda x: ( 'mean age' , x)) ) # 平均身長を算出する mean_height = ( inputdata | 'Extract height' >> beam.Map( lambda x: x[ 'height' ]) | 'Get mean height' >> beam.combiners.Mean.Globally() | 'Get KeyValue height' >> beam.Map( lambda x: ( 'mean height' , x)) ) # 平均年齢と平均身長を出力する outputdata = ( (mean_age, mean_height) | 'Flatten data' >> beam.Flatten() | 'Output' >> beam.Map( lambda x: logging.info(f '{x[0]} = {x[1]}' )) ) if __name__ == '__main__' : run() このコードを実行すると Dataflow ジョブが作成され、ワーカーとなる Compute Engine VM が起動した後、パイプラインに記述された処理がそれぞれ ステップ として実行されます。一度限りのバッチジョブのため、処理が完了すると VM は削除されます。 このように、コンソールや CLI で直接 Dataflow ジョブを作成して実行するのではなく、 Dataflow ランナーを実行環境として指定したコードを実行 するとジョブが作成されます。 以下の図は Dataflow のコンソールから確認することができるジョブのグラフです。 Dataflow のコンソールでグラフ化されたパイプラインを確認できる コンソールからステップごとのログを確認できます。 「Output」ステップで平均年齢と平均身長をログ出力するように記述していたため、以下のログが確認できます。 ステップのログを確認する Dataflow テンプレート Dataflow では、ユーザーが Apache Beam SDK で記述したパイプラインを Dataflow ジョブとしてただ実行するだけでなく、 テンプレート としてパッケージ化することができます。 テンプレートは Google Cloud コンソールや CLI、REST API を使用して Dataflow ジョブとしてデプロイします。 参考 : Dataflow テンプレート テンプレートを使用することで、パイプライン開発とデプロイのプロセスが分離され、権限を持つユーザーであれば開発者が用意したパイプラインを必要なときにデプロイすることができるほか、テンプレート利用者がテンプレート側に設定されたパラメータを渡して実行することができます。 たとえば、テンプレート利用者が任意の入力ファイルやデータ出力先を指定してからパイプラインを実行できるようにテンプレートを作成できます。 Dataflow でテンプレートを使用してパイプラインをデプロイする テンプレート利用者がテンプレートに設定されたパラメータを渡すことができる Dataflow のテンプレートには以下の 3種類があります。 テンプレートの種類 説明 Google 提供のテンプレート ユーザーがパイプラインを開発する代わりに、一般的なシナリオ用に事前定義されたテンプレートを使用できます( Google 提供のテンプレート一覧 )。 クラシックテンプレート ユーザーが開発したパイプラインを Cloud Storage にステージングして Dataflow から利用します。 Flex テンプレート 開発したパイプラインを Docker イメージとしてパッケージ化し、Container Registry/Artifact Registry に格納します。 クラシックテンプレートと比較して処理内容を柔軟に変更することができ、入力パラメータに基づいて動的に I/O コネクタを選択したり、パイプライン作成中に入力パラメータの検証などの前処理を行ったりできます。 構成要素 Dataflow ワーカー Dataflow ジョブは、パイプラインのデプロイ時にプロビジョニングされる Compute Engine VM インスタンスで実行されます。この VM を Dataflow ワーカー と呼びます。ワーカー上ではコンテナ化された Apache Beam SDK プロセスが起動されます。 ワーカー VM に使用されるマシンタイプや、起動するワーカーの台数などはデプロイ時に パイプラインオプション で指定することができます。 参考 : Dataflow ワーカー リージョンエンドポイント ジョブごとに リージョンエンドポイント (Regional endpoints) を指定します。ジョブの作成時にリージョンを指定すると、そのリージョンのエンドポイント経由で Dataflow ジョブのメタデータが保存されたり、自動スケーリング等の Dataflow ワーカーの動作もこのリージョンから制御されます。 リージョンエンドポイントを作成するリージョンは Dataflow ワーカーを作成するリージョンと別にすることができますが、コンプライアンス上の理由から特定リージョン内にデータを閉じたい場合や、リージョン間のネットワークレイテンシによるパフォーマンス影響を抑えるには、ワーカーと同一リージョンを指定することが推奨されます。 Dataflow ワーカーとリージョンエンドポイント 参考 : リージョン エンドポイント Dataflow GPU Dataflow ワーカーは GPU を使用することができます。GPU を使うと、特定の計算や画像処理、機械学習タスクを CPU よりも高速に実行することができます。 GPU は、後述する Dataflow Runner v2 を使用するパイプラインでのみサポートされています。 参考 : Dataflow での GPU のサポート カスタムコンテナ デフォルトの設定では、Dataflow ワーカーは Apache Beam イメージ を使用するコンテナを起動して処理を実行します。 カスタムコンテナ を使用することで実行環境をカスタマイズし、起動時間を短縮したり、外部ライブラリなどの依存関係をプリインストールしたりできます。 GPU 同様、カスタムコンテナも Dataflow Runner v2 を使用するパイプラインでのみサポートされています。 参考 : Dataflow でカスタム コンテナを使用する 周辺機能 Dataflow SQL Dataflow SQL は、SQL を Apache Beam パイプラインに変換して Dataflow ジョブとして実行できる仕組みです。 Dataflow SQL のクエリは Dataflow コンソールの SQL ワークスペース や gcloud コマンドなどから作成し、そのままパイプラインへの変換、実行を行うことができます。 参考 : Dataflow SQL を使用する 例として、 公式ドキュメント のサンプルを実行してみます。 以下のコマンドでは、一般公開されている Pub/Sub トピック taxirides-realtime から 1分ごとにデータを pull し、1 分間のタクシーの乗客数を計算して BigQuery テーブルに書き込む Dataflow ジョブが作成されます。 $ gcloud dataflow sql query \ --job-name = dataflow-sql-quickstart \ --region = asia-northeast1 \ --subnetwork = regions/asia-northeast1/subnetworks/mysubnet \ --bigquery-dataset = taxirides \ --bigquery-table = passengers_per_minute \ ' SELECT TUMBLE_START("INTERVAL 60 SECOND") as period_start, SUM(passenger_count) AS pickup_count, FROM pubsub.topic.`pubsub-public-data`.`taxirides-realtime` WHERE ride_status = "pickup" GROUP BY TUMBLE(event_timestamp, "INTERVAL 60 SECOND") ' コマンドを実行すると Dataflow ジョブが作成され、処理が開始されます。 Dataflow SQL で作成されたジョブのグラフ BigQuery のテーブルを確認すると、1分ごとのタクシー乗客数のデータが記録されています。このサンプルのジョブはストリーミング処理のため、ジョブを停止するまで動作し続けます。実際に試す場合はジョブの止め忘れに気をつけましょう。 Dataflow ジョブの出力先となる BigQuery テーブル Dataflow Prime Dataflow Prime は「Dataflow のサーバーレス版」とも言うべき仕組みです。サーバーレスなプラットフォーム上で Apache Beam パイプラインを実行することができます。 Dataflow Prime ではワーカーのリソース最適化に役立つ機能が提供されており、たとえば先述の水平自動スケーリングに加え、 垂直方向の自動スケーリング を利用することができます。Dataflow ワーカーで使用可能なメモリは、パイプライン実行時に必要に応じて自動で調整され、 メモリ不足エラー(OOM) によるジョブの失敗を防止し、パイプラインのリソース効率を最適化することができます。 参考 : Dataflow Prime を使用する Dataflow ML Dataflow ML では Dataflow ジョブ内で機械学習 (AI/ML) モデルによる推論を実行することができます。 Apache Beam の RunInference API を使用して機械学習モデルをパイプラインに追加することでこれを実現します。以下は、Pub/Sub で受信したデータを Dataflow のパイプライン内で処理し、機械学習モデルに送信して推論を行わせたあと、その結果を BigQuery に書き込むストリーミング処理の例です。 機械学習モデルに推論を実行させる処理をパイプラインに組み込む 参考 : Dataflow ML について ノートブックでの開発 Dataflow ではパイプラインの開発、実行、解析を JupyterLab ノートブック上でインタラクティブに行うことができます。 Apache Beam ノートブックは Vertex AI Workbench ユーザー管理ノートブック として提供されており、JupyterLab ノートブックと Apache Beam インタラクティブランナー がプリインストールされています。 参考 : インタラクティブ ランナーで Apache Beam ノートブックを開発する パイプライン最適化の仕組み Dataflow Runner v2 Dataflow Runner v2 は Dataflow ランナー (Apache Beam 実行環境) の新しいバージョンで、今後 Dataflow に実装される新機能は Dataflow Runner v2 でのみ使用することができます。 従来のランナーを使用することもできますが、Dataflow Runner v2 は高パフォーマンス、低コストで利用できる傾向があるため、基本的にはこちらを使用すると良いでしょう。SDK によっては Dataflow Runner v2 で使用できない機能があるなど、いくつかの 制限事項 があります。 参考 : Dataflow Runner V2 を使用する 自動スケーリング 水平自動スケーリング Dataflow ジョブで 水平自動スケーリング を有効化することで、ワーカー VM をジョブの実行に必要な数だけ自動的にスケールアウト/スケールインするようにできます。 また、ストリーミング処理を実行するワーカーは、ジョブの実行中に手動でスケーリングすることも可能です。 参考 : 水平自動スケジューリング 動的スレッドスケーリング Dataflow では、パイプラインの実行時に複数のワーカーに処理を分散します。ワーカーは複数のスレッドを起動してタスクを並列実行します。 動的スレッドスケーリング (Dynamic Thread Scaling) を有効化すると、Dataflow は各ワーカーのリソース使用率に基づき、ワーカーで実行するスレッドの数を自動的に最適化します。 スレッド数の自動スケーリングはワーカーごとに行われ、ワーカーのメモリ使用率が 50% 未満かつ CPU 使用率が 65% 未満の場合はスレッド数を増やし、ワーカーのメモリ使用率が 70% を超えた場合にスレッド数を減らします。 参考 : Dynamic Thread Scaling 動的作業再調整 動的作業再調整 (Dynamic work rebalancing) 機能を使用すると、Dataflow はワーカーごとのタスク割り当ての不均衡や、終了に時間がかかるタスクが割り当たっているワーカー、タスクが早期に終了するワーカーを自動的に検出し、余裕のあるワーカーに対して動的にタスクを割り当てることでジョブ全体の処理時間を短縮します。 動的作業再調整によるタスクの動的割り当て 参考 : 動的作業再調整 Dataflow Shuffle / Streaming Engine Dataflow Shuffle と Streaming Engine は、それぞれ Dataflow ワーカー間のデータ移動(シャッフル)処理をワーカー外にオフロードする機能です。バッチ処理では Dataflow Shuffle、ストリーミング処理では Streaming Engine と呼ばれます。 参考 : バッチジョブに Dataflow Shuffle を使用する 参考 : ストリーミング ジョブに Streaming Engine を使用する パイプラインの中で GroupByKey のようなデータのグループ化処理を行う場合、特定のキーを持つすべてのデータを 1 つのワーカーに集める必要があります。この場合、それぞれのワーカー同士がメッシュ状にデータの移動が行うため、ワーカーの負荷や通信のオーバーヘッドが発生します。また、特定のワーカーで障害が発生した場合にデータが失われてしまい、ジョブ全体が失敗してしまう可能性があります。 Dataflow Shuffle / Streaming Engine を使用しない場合 Dataflow Shuffle および Streaming Engine では、処理の中間状態をワーカー外に保存し、次の処理ではここからデータを読み出します。 これにより、ワーカー間でメッシュ状の通信を行うことなくデータの移動を行うことができます。そして特定のワーカーで障害が発生した場合でも、正常なワーカーが中間に保存されているデータを再度読み出すことでジョブを続行できます。 Dataflow Shuffle / Streaming Engine を使用する場合 Flexible Resource Scheduling(FlexRS) Flexible Resource Scheduling (FlexRS) は「 遅延したスケジューリング 」と「 プリエンプティブル VM インスタンス 」の活用により バッチ処理のコストを削減する機能 です。FlexRS を使用するジョブは FlexRS ジョブ といいます。 Flex ワーカー(FlexRS で使用される Dataflow ワーカー)として、プリエンプティブル VM と通常の VM の組み合わせが使用され、通常の Dataflow ワーカーよりもリソース使用料が安く設定されています。 FlexRS ジョブではリソース使用量が安い代わりに「遅延したスケジューリング」により、ジョブの作成後すぐに実行されず、Google Cloud が所有するリソースに余裕があるタイミングで実行されます(作成から 6時間以内には実行される)。したがって、時間の制約が厳しくないジョブを実行する際に有効な機能となります。 FlexRS ジョブは開始される前に 早期検証 が行われ、Dataflow ジョブの実行パラメータ、IAM ロール、ネットワーク構成などの設定が事前に検証され、潜在的なエラーがあれば報告されます。 FlexRS を使用するには Dataflow Shuffle が有効化されている必要があります。 参考 : Cloud Dataflow で Flexible Resource Scheduling を使用する モニタリング Dataflow モニタリングインターフェース は、ウェブベースのパフォーマンスモニタリング用の画面です。Google Cloud コンソールから閲覧できます。 Dataflow モニタリングインターフェースでは以下のような項目が表示されます。 現在実行中のジョブを含む、過去 30日間に実行された Dataflow ジョブのリスト 各パイプラインの図( ジョブグラフ ) 自動スケーリング、スループット、リソース使用率などの指標( 参考 ) ジョブ実行中に発生したエラーや警告 ジョブのパフォーマンス向上、コスト削減などに関する推奨事項 詳細は以下のドキュメントもご参照ください。 参考 : Dataflow モニタリング インターフェースを使用する セキュリティ データの暗号化 Dataflow では、データパイプライン全体で処理中のデータ、保存されるデータの両方に対して、 デフォルトで暗号化が行われています 。デフォルトの暗号化は Google が管理する暗号鍵によって行われます。 また、Google 管理の暗号鍵の代わりに Cloud Key Management Service(KMS) による顧客管理の暗号鍵(CMEK)や Cloud HSM を使用することもできます。 参考 : 顧客管理の暗号鍵を使用する プライベート IP アドレスの使用 Dataflow では ワーカー VM のパブリック IP を無効にし、すべてのネットワーク通信にプライベート IP アドレスを使用するように指定することができます。パブリック IP を無効化する場合、ワーカーがリージョンエンドポイントにアクセスするために、ワーカー VM が使用するサブネットで 限定公開の Google アクセス を有効化する必要があります。 参考 : 外部 IP アドレスをオフにする ネットワークタグの使用 通常の Compute Engine VM 同様に、Dataflow ワーカー VM にはネットワークタグを付与し、ファイアウォールルールでトラフィックの制限を行うことができます。 Dataflow ジョブで Dataflow Shuffle または Streaming Engine を使用しない場合(シャッフル処理を VM 外にオフロードしない場合)、VM 間でデータのやり取りを行うために Dataflow 用のファイアウォールルール で TCP ポート 12345 と 12346 の通信を許可する必要があります。 参考 : インターネット アクセスとファイアウォール ルールの構成 料金 Dataflow の最新の料金については以下のドキュメントを参照してください。 参考: Dataflow の料金 Dataflow の料金 Dataflow の料金はジョブごとに秒単位で発生します。 Dataflow ワーカー VM のリソース料金を基本として、GPU、Dataflow Shuffle を使用する場合に追加で料金が発生します。 東京リージョン(asia-northeast1)で利用する場合の料金は以下のようになります。 ジョブの種類 vCPU(1 vCPU、1 時間あたり) メモリ(1 GB、1時間あたり)  シャッフル時に処理されたデータ(GB 単位) ※Dataflow Shuffle、Streaming Engine 使用時 バッチ $0.0728 $0.0046241 $0.0143 FlexRS $0.0437 $0.0027745 $0.0143 ストリーミング $0.0897 $0.0046241 $0.0234 バッチ処理で Dataflow Shuffle を使用する場合、上記の シャッフル時に処理されたデータ の料金はデータ量に応じて割引されます。シャッフル処理されたデータの最初の 250 GB までは 75%、次の 4,870 GB~5,120 GB は 50%、シャッフル時の処理料金が割引されます。5,120 GB 以降は上記の表の料金がそのまま適用されます。 例として、合計 10,240 GB(10 TB)のデータをシャッフル処理した場合、課金対象のデータ量は 7,617.5 GB(= 250 GB × 25% + 4,870 GB × 50% + 5,120 GB)となります。 ワーカー VM が使用するストレージ、GPU の料金は以下のようになっており、ジョブの種類ごとの違いはありません。 ストレージ - 標準永続ディスク(1 GB 1時間あたり) ストレージ - SSD 永続ディスク(1 GB 1時間あたり) NVIDIA® Tesla® K80 GPU(1 GPU 1 時間あたり) NVIDIA® Tesla® P100 GPU(1 GPU 1 時間あたり) NVIDIA® Tesla® V100 GPU(1 GPU 1 時間あたり) NVIDIA® Tesla® T4 GPU(1 GPU 1 時間あたり) NVIDIA® Tesla® P4 GPU(1 GPU 1 時間あたり) $0.0000702 $0.0003874 東京リージョンで使用不可 東京リージョンで使用不可 東京リージョンで使用不可 $0.4440 東京リージョンで使用不可 Dataflow Prime の料金 Dataflow Prime は課金単位が異なり、 Dataflow Processing Unit(DPU) という概念によって料金が決定されます。ジョブのリソース使用状況から DPU 使用量が測定され、リソース消費量に比例して DPU 使用量が多くなります。 1 DPU は 「 1 vCPU 、 メモリ 4 GB 、 250 GB の永続ディスク を使用するワーカー VM が 1時間処理を行った場合に使用されるリソース」に相当します。 Dataflow Prime を東京リージョン(asia-northeast1)で使用する場合の料金は以下のようになります。 ジョブの種類 1 DPU、1 時間あたりの料金 バッチ $0.091 ストリーミング $0.105 Dataflow Prime では使用する DPU 数を直接指定することができないため、コスト節約にはメモリ消費量の削減、シャッフルの際に処理されるデータ量の削減などをパイプラインの実装時に工夫する必要があります。先述した Dataflow モニタリングインターフェースはこれらの最適化に役立つ情報を提供してくれます。 佐々木 駿太 (記事一覧) G-gen最北端、北海道在住のクラウドソリューション部エンジニア 2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。 趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。 Follow @sasashun0805
アバター
G-gen の藤岡です。当記事では Google Cloud(旧称 GCP)で Cloud Run の タグ付きリビジョン (tagged revision)機能を使い、GitHub のプルリクエストをトリガとしたプレビュー環境の自動デプロイを実装する方法を紹介します。 概要 前提知識 Cloud Run のタグ付きリビジョン GitHub Actions と Workload Identity 連携 アーキテクチャ 事前準備 前提 ディレクトリ構成 GitHub Actions のワークフローファイル デプロイとテスト 初回デプロイ GitHub Google Cloud WEB 画面 プルリクエストの作成 GitHub Google Cloud WEB 画面 プルリクエストをマージ GitHub Google Cloud WEB 画面 考慮事項 Google Cloud GitHub Actions 概要 Netlify や Vercel などのホスティングサービスでは「Deploy Previews」と呼ばれる GitHub と連携してプルリクエストをトリガとして自動でプレビュー環境をデプロイする機能が提供されています。 Google Cloud のホスティングサービスである Firebase Hosting でも同様にプルリクエストごとにプレビュー環境を自動で作ることができます。 今回は、プルリクエストをトリガにして Cloud Run サービスの「タグ付きリビジョン」をデプロイすることで、本番環境へデプロイ前に試験や動作確認をできるようにします。 構成としては以下の通りです。 アーキテクチャ 参考: GitHub pull リクエストによるライブチャネルとプレビュー チャネルへのデプロイ 前提知識 Cloud Run のタグ付きリビジョン Cloud Run は、デプロイされるリビジョンごとにタグを付与できます。タグを付与すると通常のデプロイ時に生成される URL とは別に、タグが付与された URL が割り当てられます。 # Cloud Run の通常の URL 例 https:// < servicename > -xxxxxxxxxx-an.a.run.app # タグ付きリビジョンの URL 例(リビジョンに dev タグを付与した場合) https://dev--- < servicename > -xxxxxxxxxx-an.a.run.app そのため、タグ付きリビジョン URL へのトラフィックを 0% にしてデプロイすることで、本番環境へ影響することなく事前にフロントエンド等のテストをすることができます。 タグ付きリビジョンを使用した時のアクセスの流れ Cloud Run のタグ付きリビジョンに関する詳細は、以下の記事をご参照ください。 blog.g-gen.co.jp GitHub Actions と Workload Identity 連携 GitHub Actions では OIDC(OpenID Connect)トークンを使った認証がサポートされており、 Workload Identity 連携 と組み合わせることで従来のようにサービスアカウントキーを発行せずに Google Cloud へ認証が可能です。 GitHub Actions と Workload Identity 連携に関する詳細は、以下の記事をご参照ください。 blog.g-gen.co.jp アーキテクチャ これ以降では、以下の構成で検証をしていきます。 (再掲)アーキテクチャ 事前準備 前提 以下の環境および条件を前提とします。 Google Cloud のプロジェクト作成の作成および API の有効化済み Workload Identity 連携の設定がされていること 参考: GitHub Actions を使って Google Cloud 環境に Terraform を実行する方法 Cloud Run は第 2 世代を使用 Cloud Run のデプロイはソースコードから行う 参考: ソースコードからのデプロイ Cloud Run のサンプルアプリを使用 参考: Cloud Run に Go サービスをデプロイする ディレクトリ構成 ディレクトリ構成は以下の通りです。 . ├── README.md └── web # サンプルアプリ ├── go.mod └── main.go .github └── workflows # GitHub Actions を定義 ├── preview-workflow.yaml └── production-workflow.yaml GitHub Actions のワークフローファイル ワークフローファイルは以下の 2 つを用意しました。 # preview-workflow.yaml name : Preview Workflow on : pull_request : branches : - '**' types : - opened - synchronize env : PROJECT_ID : "PROJECT_ID" SERVICE : "sample-app" REGION : "asia-northeast1" SERVICE_ACCOUNT : "workload@PROJECT_ID.iam.gserviceaccount.com" PROJECT_NUMBER : "PROJECT_NUMBER" WORKLOAD_IDENTITY_POOL_ID : "cloud-run-test" WORKLOAD_IDENTITY_PROVIDER_ID : "github-actions" jobs : preview : runs-on : ubuntu-latest permissions : contents : read id-token : write pull-requests : write steps : - name : Checkout uses : actions/checkout@v4 - name : Authenticate to Google Cloud uses : google-github-actions/auth@v1 with : workload_identity_provider : projects/${{ env.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.WORKLOAD_IDENTITY_POOL_ID }}/providers/${{ env.WORKLOAD_IDENTITY_PROVIDER_ID }} service_account : ${{ env.SERVICE_ACCOUNT }} - name : Extract details for tag run : | echo "SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c 1-4)" >> $GITHUB_ENV echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV - name : Deploy to Cloud Run from Source id : deploy uses : google-github-actions/deploy-cloudrun@v1 with : service : ${{ env.SERVICE }} region : ${{ env.REGION }} source : ./web tag : pr-${{ env.PR_NUMBER }}-commit-${{ env.SHORT_SHA }} flags : "--allow-unauthenticated --platform=managed --execution-environment=gen2" no_traffic : true - name : Get datetime for now run : echo "CURRENT_DATETIME=$(date)" >> $GITHUB_ENV env : TZ : Asia/Tokyo - name : Create comment uses : peter-evans/create-or-update-comment@v3 with : comment-id : ${{ steps.find_comment.outputs.comment-id }} issue-number : ${{ github.event.pull_request.number }} body : | :tada : Successfully deployed preview revision for this PR (updated for commit ${{ github.event.pull_request.head.sha }}): <${{ steps.deploy.outputs.url }}> <sub>(:sparkles : updated at ${{ env.CURRENT_DATETIME }})</sub> edit-mode : replace # production-workflow.yaml name : Production Workflow on : workflow_dispatch : pull_request : types : [ closed ] env : PROJECT_ID : "PROJECT_ID" SERVICE : "sample-app" REGION : "asia-northeast1" SERVICE_ACCOUNT : "workload@PROJECT_ID.iam.gserviceaccount.com" PROJECT_NUMBER : "PROJECT_NUMBER" WORKLOAD_IDENTITY_POOL_ID : "cloud-run-test" WORKLOAD_IDENTITY_PROVIDER_ID : "github-actions" jobs : deploy : runs-on : ubuntu-latest permissions : contents : read id-token : write steps : - name : Checkout uses : actions/checkout@v4 - name : Authenticate to Google Cloud uses : google-github-actions/auth@v1 with : workload_identity_provider : projects/${{ env.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.WORKLOAD_IDENTITY_POOL_ID }}/providers/${{ env.WORKLOAD_IDENTITY_PROVIDER_ID }} service_account : ${{ env.SERVICE_ACCOUNT }} - name : Check if service exists id : check-service run : | EXISTS=$(gcloud run services list --platform managed --region ${{ env.REGION }} | grep "${{ env.SERVICE }}" || true ) if [[ -z "$EXISTS" ]] ; then echo "Service does not exist, setting deploy flag." echo "DEPLOY_FLAG=true" >> $GITHUB_ENV else echo "Service already exists, skipping deploy." echo "DEPLOY_FLAG=false" >> $GITHUB_ENV fi - name : Deploy to Cloud Run from Source if : env.DEPLOY_FLAG == 'true' id : deploy uses : google-github-actions/deploy-cloudrun@v1 with : service : ${{ env.SERVICE }} region : ${{ env.REGION }} source : ./web flags : "--allow-unauthenticated --platform=managed --execution-environment=gen2" - name : Extract details for tag if : env.DEPLOY_FLAG != 'true' run : | echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV echo "SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c 1-4)" >> $GITHUB_ENV - name : Remove revision with tag if : env.DEPLOY_FLAG != 'true' run : > gcloud run services update-traffic ${{ env.SERVICE }} --region ${{ env.REGION }} --remove-tags pr-${{ env.PR_NUMBER }}-commit-${{ env.SHORT_SHA }} - name : Add prod tag and Traffic routing if : env.DEPLOY_FLAG != 'true' run : > gcloud run services update-traffic ${{ env.SERVICE }} --region ${{ env.REGION }} --to-latest デプロイとテスト 初回デプロイ GitHub 初回は production-workflow.yaml のワークフローを手動実行し、Cloud Run へサンプルアプリをデプロイします。 トリガを workflow_dispatch とすることで、手動でワークフローの実行が可能になります。 # production-workflow.yaml on: workflow_dispatch: pull_request: types: [ closed ] GitHub のコンソールからは以下のように、ワークフローが手動で実行できる画面が表示されます。 初回デプロイ:GitHub Run workflow をクリックすると、Cloud Run へサンプルアプリがデプロイされます。 Google Cloud デプロイが完了すると、サンプルアプリの URL は https://sample-app-xxxx-an.a.run.app となっています。 初回デプロイ:Google Cloud WEB 画面 ブラウザで URL へアクセスすると以下の画面が表示されます。 初回デプロイ:WEB 画面 プルリクエストの作成 GitHub 新しいブランチで main.go ファイルの中身を一部変更します。 # main. go # 変更前 if name == "" { name = "World" } # 変更後 if name == "" { name = "World!!!!!!!!" } プルリクエストを作成すると、ワークフローがトリガされます。 ワークフローが完了すると、以下のようにプルリクエストにコメントと共にタグが付与された URL が表示されます。 プルリクエストの作成:GitHub Google Cloud ワークフローによってデプロイされた、タグ付きリビジョンのサービスがデプロイされました。 プルリクエストの作成:Google Cloud WEB 画面 ブラウザから確認すると、タグが付与された URL には main.go の変更後が反映され、通常の URL は変更前のままです。 プルリクエストの作成:WEB 画面 プルリクエストをマージ GitHub プルリクエストをマージすると、ワークフローがトリガされます。 プルリクエストをマージ:GitHub Google Cloud ワークフローによって、タグが削除され、トラフィックが最新のデプロイに 100% 流れるようになっています。 プルリクエストをマージ:Google Cloud WEB 画面 ブラウザから確認すると、タグが付与された URL は Error: Page not found となり、通常のサービス URL( https://sample-app-xxxx-an.a.run.app )へ変更が反映されています。 プルリクエストをマージ:WEB 画面 考慮事項 Google Cloud Cloud Run のサービスアカウント Cloud Run のサービスアカウント はデフォルトの Compute Engine のサービスアカウントを使っているため、 編集者(roles/editor) 権限が付与されています。 最小限の権限 の原則に従って適切な権限が付与されたサービスアカウントを使用してください。 ソースコードから Cloud Run をデプロイする時に作られるリソース ソースコードから Cloud Run をデプロイすると、Cloud Run 以外に以下のリソースが作られます。デプロイごとに容量が増えていくため、注意してください。 Cloud Storage: <PROJECT_ID>_cloudbuild> バケット(Cloud Build によってソースコードがアップロード) Artifact Registry: cloud-run-source-deploy リポジトリ(Cloud Build によってコンテナ化されたイメージがアップロード) Cloud Run のリビジョンタグ Cloud Run のタグが付与された URL にアクセスがある場合や最小インスタンスの設定によりコンテナが起動している場合、課金が発生します。 そのため、不要なリビジョンタグは削除すると意図しない課金が発生するリスクを軽減できます。 参考: リビジョンの管理 Cloud Run の認証 Cloud Run サービスの 公開(未認証)アクセスを許可する は、リビジョンではなくサービス自体に紐づいているため、本番環境のみ認証有り、プレビュー環境は認証無し、とすることは現状できません。 GitHub Actions 環境変数 今回は 2 つのワークフローで env として環境変数をファイル内で直接定義しています。共通する環境変数や機密情報を含むものは、GitHub Actions の 変数 や シークレット を使用してください。 set-output ワークフローコマンドが廃止 ワークフロー内のステップの出力値を設定し、後続のステップでその値を参照するために使われていた set-output ワークフローコマンドの廃止が発表 されています。 当記事では使っていませんが、ワークフロー内で使っている場合は注意が必要です。 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。ラーメンと甘いものが好きです :) Follow @fujioka57621469
アバター
G-gen の杉村です。BigQuery のオンデマンドクエリの利用量にフタをする、つまりスキャンデータ量に上限を設けて突発課金を防止する工夫について紹介します。 はじめに 割り当て(Quota)の設定 Query usage per day 設定手順 割り当て画面へ遷移 対象の割り当てをフィルタ 編集ボタンをクリック 割り当てを設定 新しい割り当ての確認 動作確認 クエリのサイズ上限設定 クエリ単位での上限設定 設定手順(コンソール) クエリ設定を開く 詳細オプションの設定 動作確認 設定手順(bq コマンドライン) 課金が想定を超えてしまった場合の検知 はじめに BigQuery の課金体系には オンデマンド と Editions の2つから選択できます。前者はスキャンしたデータ量に応じた従量課金です。後者は確保するコンピュートリソースの量に応じた課金で、オートスケールの幅(上限と下限)を設定できます。そのため、Editions を利用している場合は意図せぬ料金のスパイクは起こりにくいといえます。 Editions については以下の記事で解説しています。 blog.g-gen.co.jp 一方のオンデマンド課金では、うっかり大きなサイズのクエリを実行してしまうと、想定しない多額の課金が発生してしまう可能性もゼロではありません。 当記事では、そのような BigQuery の突発的な課金を防ぐために、クエリ利用量に制限をかける(上限を設ける)ための方法を2つご紹介します。 割り当て(Quota)の設定 (プロジェクト単位) クエリのサイズ上限設定 (クエリ単位) 割り当て(Quota)の設定 Query usage per day 割り当て (Quota)とは、Google Cloud の各種サービスに設定された、API 呼び出しの制限のことです。各 Google Cloud サービスには、組織単位・プロジェクト単位・ユーザ単位など様々な粒度で割り当てが設定されています。 参考 : 割り当て上限について 参考 : 割り当てを操作する 多くのクラウドユーザは「割り当て(Quota)は、利用が拡大してきたら上限緩和するもの」という認識を持っているかもしれません。しかし、今回はこの割り当てに任意の値を設定することによって、BigQuery の突発課金を防ぐ効果を期待してみます。 今回、設定対象となる割り当ては BigQuery API の Query usage per day (1 日あたりのクエリ使用量)です。この割り当ては一日あたりにプロジェクト内で利用できるオンデマンドクエリのスキャンデータ量を指定するもので、課金が有効になったプロジェクトではデフォルトで 無制限 となっています。 参考 : BigQuery - 割り当てと上限 - クエリジョブ この割り当てに任意の数字を設定することで、それ以上のサイズのクエリ実行を防ぐことができます。割り当てを超えるクエリが実行されようとした場合、実行前にエラーとなります。これにより、BigQuery のスキャン料金のスパイクを防ぐことができます。 設定手順 割り当て画面へ遷移 Google Cloud コンソールにて IAM と管理 > 割り当て へ遷移します。このとき、想定通りのプロジェクトが選択されていることを確かめてください。 IAM と管理 > 割り当て 対象の割り当てをフィルタ 割り当て一覧から、今回の設定対象である BigQuery API の Query usage per day を見つけ出します。 一覧表のフィルタ部分に サービス : BigQuery API を入力し、BigQuery の割り当てを絞り込みます。一覧の中から Query usage per day を見つけ出します。 割り当て一覧 編集ボタンをクリック Query usage per day の左端のチェックボックスを選択して、鉛筆マークのある「割り当てを編集」ボタンを押下します。 このとき Query usage per day per user というよく似た名前の割り当てもあることに注意してください。こちらを設定すると ユーザーあたりの 一日のクエリスキャン量に制限がかかります。すなわち、割り当ての適用範囲が異なります。 チェックボックスを選択して割り当てを編集 割り当てを設定 TiB 単位でクエリの上限スキャン量を設定します。 単位は TiB しか選べませんが、 0.5 TiB のように 1 TiB 未満の数字を指定することも可能です。 割り当ての設定 「リクエストを送信」を押下して、新しい割り当てを適用します。現在の割り当て設定との差が大きい場合は、以下のような警告文が表示されます。「確認」を押下します。 警告 新しい割り当ての確認 割り当て一覧に戻り、新しい数字が割り当てとして表示されていることを確認します。更新されていない場合、しばらくしてから画面をリロードします。 新しい割り当て 動作確認 1日のスキャン量割り当てを超えるクエリを実行しようとすると、以下のようなエラーメッセージが出て、クエリが実行できなくなります。 動作確認 Custom quota exceeded: Your usage exceeded the custom quota for QueryUsagePerDay, which is set by your administrator. For more information, see https://cloud.google.com/bigquery/cost-controls クエリのサイズ上限設定 クエリ単位での上限設定 BigQuery ではクエリの実行時に課金対象となるバイト数を指定することができます。クエリ実行時のオプションで 課金される最大バイト数 (maximum_bytes_billed)を指定することで、スキャン対象データの推定バイト数が事前に計算され、これが指定した最大バイト数を超えていればクエリを失敗します。 参考 : 課金されるバイト数を制限してクエリ費用を抑える 設定手順(コンソール) クエリ設定を開く ご紹介するコンソール画面の UI は2023年9月現在のものです。 BigQuery コンソールでクエリの編集中に、歯車マーク「展開」ボタンを押下し、表示されたプルダウンメニューから「クエリ設定」を選択します。 クエリ設定を開く 詳細オプションの設定 開いたクエリ設定画面で下の方へスクロールし「詳細オプション」を展開します。「課金される最大バイト数」のテキストボックスに、バイト単位で数字を入力します。その後「保存」ボタンを押下します。 詳細オプションの設定 動作確認 この状態でクエリを実行してみます。クエリエディタのテキストボックス下部に指定した「課金される最大バイト数」が表示されています。 クエリを実行しようとする際、推定バイト数が指定した最大バイト数を上回っていると、以下のようにエラーメッセージが出てクエリが中止されます。 動作確認 Query exceeded limit for bytes billed: 10485760. 168820736 or higher required. 設定手順(bq コマンドライン) bq コマンドラインでも同様に、最大バイト数を指定することができます。 bq query コマンド実行時に --maximum_bytes_billed オプションを指定するだけです(単位はバイト)。 bq query --use_legacy_sql =false \ --maximum_bytes_billed = 10485760 \ ' SELECT * FROM `my_project.my_dataset.my_table` ' 課金が想定を超えてしまった場合の検知 ここまで掲載した方法で、BigQuery の突発的な課金スパイクを防ぐことができます。 なお、万が一想定を超える課金が発生した場合には、 予算アラート や 異常検知 (Anomaly Detection)を設定することで早期に気がつくことができます。以下の記事もご参照ください。 blog.g-gen.co.jp blog.g-gen.co.jp 杉村 勇馬 (記事一覧) 執行役員 CTO 元警察官という経歴を持つ IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 認定資格および Google Cloud 認定資格はすべて取得。X(旧 Twitter)では Google Cloud や Google Workspace のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の鈴木です。約1年ぶりに記事を書きます。当記事では、AppSheet を利用する際に Google Drive の共有ドライブを利用する場合の注意点を紹介します。コミュニティ等でも同じような会話が行われており、解決してないケースも多いため、解説いたします。 環境説明および事前設定 環境説明 事前設定 アプリケーションの作成 スプレッドシートよりアプリケーションを作成 PDF 作成のための Automation 設定 データが追加されたときに PDF を作成するイベントの作成 データが追加されたときの実際のプロセスの設定 アプリケーションの実行確認とログの確認方法 アプリケーションから PDF ファイルを参照可能とするための Automation 設定 エラーの発生 解決方法 原因の確認 アプリケーションのデフォルトのパスを変更する 環境説明および事前設定 環境説明 当記事でご紹介するのは、AppSheet で共有ドライブを利用して、例えば請求書のような PDF ファイルを共有ドライブに作成、格納、参照する場合の注意点です。 今回は以下のような環境でアプリケーションの作成およびデータの格納を共有ドライブに実施することとします。 AppSheet作成時のフォルダ構成 事前設定 作成したアプリケーションで利用されるデータが共有ドライブに格納されるように、以下の設定を変更します。 アプリケーション名のアイコンをクリック > 上部「Admin」より「My Account」をクリック > Settings タブにて Default folder path を /[TeamDrive]AppSheetData/Data と設定します。必要に応じてパスは変更してください。 AppSheet のデフォルトのデータ格納先 アプリケーションの作成 スプレッドシートよりアプリケーションを作成 AppSheetData/Data/DataStore にあるスプレッドシートよりアプリケーションを作成します。今回は以下の通りの簡単なデータベースをもとにアプリを作成します。 AppSheet用データベース(スプレッドシート) 作成後のアプリケーションの設定は以下の通りです。 AppSheetのデータ設定 PDF 作成のための Automation 設定 以下の手順で Automation の設定を実施し、PDF を作成するように設定します。 データが追加されたときに PDF を作成するイベントの作成 以下の通り設定し、イベントを作成します。 Automationのイベント作成画面 データが追加されたときの実際のプロセスの設定 以下の通り設定し、レコードが追加されたら PDF を作成するためのプロセスを設定します。このときの設定は以下の通りです。 設定名 値 Settings Create a new file Template Docsファイル File Folder Path /orders/ File Name Prefix [Product_id] Disable Timestamp? ON ※ファイルパスの簡易化のために今回タイムスタンプは付与しません。本番運用の際にはご注意ください。 Automationのイベント作成画面 テンプレートとなる Docs ファイルはどのようなものでも構いませんが、今回は以下のような簡易的なファイルを利用します。 <<[]>> で囲ったデータヘッダーに実際に入ってきた値が挿入されますので、実際の見積書などを作成する場合には利用してみてください。 テンプレートで利用するDocsファイル アプリケーションの実行確認とログの確認方法 ここまで来たら一旦 Deploy してアプリケーションの動作を確認してみてください。 以下の通り、レコードを追加したら自動的に PDF が作成されているはずです。 作成したアプリケーションの実行画面 自動生成されたPDFファイル 自動置換されたファイルの内容 このとき、PDFファイルの格納はアプリケーションのデフォルトのパス /[TeamDrive]AppSheetData/Data/"アプリケーション名+アプリID" に格納されているはずです。 もし PDF が作成されない、などの事象が起きた場合には Manage > Monitor > Automation Monitor よりエラー等が出てないか確認してみてください。 アプリケーションから PDF ファイルを参照可能とするための Automation 設定 このままだとレコード追加時に自動生成された PDF ファイルをアプリケーションから参照することができません。そのため Automation ルールにて、URL を生成するための設定を以下の通り作成します。 このとき、以下のように設定してください。 設定名 値 Settings Set row values URL CONCATENATE("orders/",[Product_id],".pdf") URL作成のためのAutomation設定 再度実行してみます。 反映されない場合は、右上の更新ボタンを押して最新の状況にしてみてください。 PDF作成およびURL作成の画面 PDF が作成され、URL もデータベースのカラムとして生成されています。 作成されたPDFファイルとURL(1) 作成されたPDFファイルとURL(2) エラーの発生 しかし生成された URL をクリックしてみるとファイルは存在するもののアクセスできず、以下のようなエラー画面になります。 PDFファイルのオープンエラー コミュニティなど見ると様々なところでファイルが開けないとの悩みが見られます。 参考 : AppSheet Forum - How to call the PDF Link that i created 解決方法 原因の確認 前述のエラー解決にあたり、まずは手動でファイルをアップロードして実際どこのパスに格納されているかを確認します。PDF とは別で、画像ファイルをアップロードします。 手動でのファイルアップロード 格納後のパスを確認すると、以下の通りデータベースと同じパス /[TeamDrive]AppSheetData/Data/DataStore に画像用のフォルダが生成され、格納されています。 手動アップロード時のファイル格納パス つまり以下のような状況になっていることがわかります。 データの自動生成時はアプリケーションのフォルダパス /[TeamDrive]AppSheetData/Data/"アプリケーション名+アプリID" を、データの参照時には /[TeamDrive]AppSheetData/Data/DataStore を、というようにアプリケーションの動作ごとに違うパスを参照していることがわかりました。 アプリケーションのデフォルトのパスを変更する 前述の差異を修正するために、以下の設定を変更しアプリケーションのデフォルトのパスをデータベースのパスを同一に変更します。 左ペインのSettings > Informationより Default app folder を /[TeamDrive]AppSheetData/Data/DataStore と設定します。 ※データベース (スプレッドシートファイル) が格納されているパスと同様にしてください。 以下のようにファイルを正しく開くことが可能になります。 作成したPDFファイルが正しく開ける場合(1) 作成したPDFファイルが正しく開ける場合(2) ※作成直後は Signature エラーで開けない場合がありますが数秒待つことで開くことが可能になります。 このように、AppSheet のデータベースとしてスプレッドシートを利用する場合、データベースとしてのスプレッドシートが格納されているパスと、アプリケーションが利用、参照しているパスが、デフォルトだと別々となっているため、注意深く設定を変更する必要があります。 鈴木 達文 (記事一覧) 執行役員 COO ビジネス推進部 部長 基本、なんでも屋。主にビジネスの立ち上げや仕組みづくりが好き 日々、努力。日々、楽しむことを大事に   Professional Cloud Architect / Professional Workspace Administratorのみ保持していますがそろそろ失効してしまいそうな予感。
アバター
G-gen の藤岡です。当記事では、 Google Cloud(旧称 GCP)で定期的に取得した Compute Engine のマシンイメージを自動で古いイメージから削除する方法を紹介します。 はじめに 構成図 Pub/Subトリガーの Cloud Run functions を作成 Cloud Scheduler ジョブの設定 考慮事項 アーキテクチャ例 はじめに 以下の記事では Compute Engine API のクライアントライブラリ を使うことで、マシンイメージの取得を自動化しています。 blog.g-gen.co.jp 上記の記事にあるように、マシンイメージが増えていくとその分料金が発生します。そのため、定期的にマシンイメージを削除することで料金の節約につながります。 そこで当記事では、上記の記事とは逆に machineImages.delete メソッドを使いマシンイメージを自動で削除する方法を紹介します。 構成図 構成は マシンイメージの自動取得の記事 と同様です。設定方法も基本的には同様ですが、当記事では Cloud Run functions の第 2 世代を使用します。 構成図 Pub/Subトリガーの Cloud Run functions を作成 コンソールからリソースを作成していきます。 1. Cloud Run functions を作成します。以下のように 基本 項目を入力し、 トリガーを追加 をクリックします。 関数の作成-1 2. トピックを作成する をクリックします。 関数の作成-2 3. Pub/Sub トピックの作成画面が表示されるので、 トピック ID を入力して 作成 をクリックします。 関数の作成-3 4. Eventarc トリガーの画面が表示されるので、 トリガーを保存 をクリックします。 関数の作成-4 5. ファンクションの作成画面に戻るので、 次へ をクリックします。 関数の作成-5 6. コードの編集画面に進むので、以下の設定をし デプロイ をクリックします。 ランタイム:Python 3.11 エントリポイント:delete_machine_image # main.py from google.cloud.compute_v1.services.machine_images import MachineImagesClient from datetime import datetime, timezone, timedelta # マシンイメージ取得対象インスタンスの情報 project_id = 'プロジェクトID' # 対象インスタンスが存在するプロジェクトID instance_name = 'インスタンス名' # 対象インスタンスの名前 def delete_machine_image (event, context): client = MachineImagesClient() # 2日前の日付を取得 (UTC→JSTの変換もここで実施) two_days_ago = datetime.now(timezone(timedelta(hours= 9 ))) - timedelta(days= 2 ) deletion_date = two_days_ago.strftime( '%Y-%m-%d' ) # 削除対象のマシンイメージの名前を設定 image_name = f 'mimg-{instance_name}-{deletion_date}' # マシンイメージの削除 try : client.delete(project=project_id, machine_image=image_name) print (f "Deleted machine image: {image_name}" ) except Exception as e: print (f "Error deleting machine image {image_name}: {e}" ) # requirements.txt google-cloud-compute==1.14.1 Cloud Scheduler ジョブの設定 Pub/Sub トピックへ定期的にメッセージを送るジョブを作成します。 1. Cloud Scheduler のコンソール画面から ジョブを作成 をクリックします。 2. ジョブの作成画面で各項目を設定します。 今回は日次実行のため 0 0 * * * を設定しています。各項目の入力をし 作成 をクリックします。 ジョブの作成 考慮事項 当記事では簡易的な設定をしているため、実際に運用する際には以下の部分なども考慮して設定してください。 サービスアカウント Cloud Run functions では デフォルトのサービスアカウント を使っているため、 編集者(roles/editor) 権限が付与されています。 最小限の権限 の原則に従って適切な権限が付与されたサービスアカウントを使用してください。 Cloud Run functions のメモリと CPU 関数の実行に必要な メモリや CPU は環境に応じて適宜設定してください。 Cloud Run functions のコード 当記事では、コード内でプロジェクト ID やインスタンス名を記載していますが、 環境変数 にすることで、設定とコードを分離でき管理が容易になります。 失敗時の通知 何らかの理由で関数の実行が失敗した場合に E メールや Slack などに通知を発報するよう、 ログベースのアラート を設定することなども検討してください。 アーキテクチャ例 当記事のマシンイメージの自動削除と マシンイメージの自動取得 を組み合わせたアーキテクチャ例は以下です。 Cloud Pub/Sub を挟むことで疎結合アーキテクチャとなり、拡張性や保守性、可用性の向上が見込めます。 アーキテクチャ例 疎結合アーキテクチャに関する解説は以下の記事をご参照ください。 blog.g-gen.co.jp 藤岡 里美 (記事一覧) クラウドソリューション部 数年前までチキン売ったりドレスショップで働いてました!2022年9月 G-gen にジョイン。ハイキューの映画を4回は見に行きたい。 Google Cloud All Certifications Engineer / Google Cloud Partner Top Engineer 2024 Follow @fujioka57621469
アバター
G-gen の藤岡です。当記事では、Google Cloud(旧称 GCP)の Cloud Functions(第 2 世代)で関数をデプロイする時の裏側の動きを Cloud Logging から深掘りしていきます。Cloud Functions は裏側を意識せずに使えるサービスではありますが、各サービスとの関係を理解しておくとエラー時に役立ちます。 前提知識 Cloud Functions サービスアカウントとサービスエージェント ユーザー管理サービスアカウント(サービスアカウント) ユーザー管理サービスアカウント デフォルトのサービスアカウント Google 管理サービスアカウント(サービスエージェント) サービス固有のサービスエージェント Google API サービスエージェント Google 管理のサービスアカウントのロールマネージャー Cloud Functions(第 2 世代) サービスアカウントとサービスエージェント Cloud Logging から確認した結果 前提 リソースの相関図 Cloud Logging のログ内容 0. API の有効化 1. Cloud Storage へソースコードのアップロード 2. Artifact Registry へリポジトリの作成 3. Cloud Build でビルド 4. Artifact Registry へコンテナイメージのプッシュ Tips Artifact Registry のリポジトリ 前提知識 Cloud Functions Cloud Functions は、FaaS(Function as a Service)と呼ばれるサーバーレスのコンピューティングサービスです。 詳細は以下の記事をご参照ください。 blog.g-gen.co.jp Cloud Functions には従来の第 1 世代と、 Cloud Run と Eventarc がベースの機能強化された第 2 世代の 2 種類が提供されています。 両者の詳細な違いは ドキュメント をご参照ください。当記事ではこれ以降 Cloud Functions の第 2 世代を前提に記載します。 サービスアカウントとサービスエージェント サービスアカウント は「ユーザー管理サービスアカウント」と「Google 管理サービスアカウント」の 2 つのカテゴリに分けられます。 サイトによって表記が異なる場合がありますが、当記事では こちらのドキュメント の表記に従って記載します。 一般的に、「ユーザー管理サービスアカウント」が サービスアカウント と呼ばれ、「Google 管理サービスアカウント」が サービスエージェント と呼ばれます。 blog.g-gen.co.jp 更に詳細に分類すると以下のようになります。 カテゴリ タイプ プリンシパル ユーザー管理サービスアカウント ユーザー管理サービスアカウント SERVICE_ACCOUNT_NAME @PROJECT_ID.iam.gserviceaccount.com ユーザー管理サービスアカウント デフォルトのサービスアカウント サービスによる (例:Compute Engine の場合 PROJECT_NUMBER-compute @developer.gserviceaccount.com Google 管理サービスアカウント サービス固有のサービス エージェント サービスによる (例:Compute Engine の場合 service-PROJECT_NUMBER @compute-system.iam.gserviceaccount.com Google 管理サービスアカウント Google API サービス エージェント PROJECT_NUMBER @cloudservices.gserviceaccount.com Google 管理サービスアカウント Google 管理のサービス アカウントのロール マネージャー service-agent-manager @system.gserviceaccount.com ユーザー管理サービスアカウント(サービスアカウント) ユーザー管理サービスアカウント ユーザー管理サービスアカウント は、プロジェクトリソースで、ユーザー自身が作り Compute Engine や Cloud Functions 等のリソースにアタッチして使います。 プリンシパルは SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com です。 デフォルトのサービスアカウント デフォルトのサービス アカウント は、API の有効化やサービスの利用時に自動で作成されるアカウントです。 デフォルトのサービスアカウントには 編集者(roles/editor) が付与されます。 Google 管理サービスアカウント(サービスエージェント) サービス固有のサービスエージェント サービス固有のサービス エージェント は、Google Cloud サービスが別のサービスを呼び出すときに用いる特殊なサービスアカウントです。 例として、Cloud Logging で Cloud Storage バケットにログシンクする時は、サービスエージェントが裏側では使われています。 付与されているロールを削除すると、普段は意識しない裏側で動いている Google Cloud サービスが機能しなくなる場合があるので注意が必要です。 Google API サービスエージェント Google API サービスエージェント は、サービス固有のサービスエージェントと同様に裏側で使われます。 PROJECT_NUMBER@cloudservices.gserviceaccount.com というプリンシパルでデフォルトで 編集者(roles/editor) が付与されます。 Google 管理のサービスアカウントのロールマネージャー Google 管理のサービスアカウントのロールマネージャー は、上記の Google 管理サービスアカウントを管理します。 サービスエージェントが自動で作成された際、サービスエージェントへのロールの付与をこのロールマネージャーが裏で行っています。 プリンシパルは service-agent-manager@system.gserviceaccount.com で、これは IAM のページには表示されず、 監査ログ にのみ表示されます。 Cloud Functions(第 2 世代) サービスアカウントとサービスエージェント Cloud Functions では、デフォルトで以下のサービスアカウントとサービスエージェントを使います。 第 2 世代では、デフォルトでは Cloud Functions のサービスアカウントに Compute Engine のデフォルトのサービスアカウント( PROJECT_NUMBER-compute@developer.gserviceaccount.com )を使いますが、Compute Engine API が有効化されていない場合はこのサービスアカウントが存在しないため、App Engine のデフォルトのサービスアカウント( PROJECT_ID@appspot.gserviceaccount.com )が使われます。 サービス カテゴリ プリンシパル デフォルトロール Cloud Functions サービスアカウント PROJECT_NUMBER-compute @developer.gserviceaccount.com / PROJECT_ID @appspot.gserviceaccount.com 編集者 Cloud Functions サービスエージェント service-PROJECT_NUMBER @gcf-admin-robot.iam.gserviceaccount.com Cloud Functions サービス エージェント Cloud Build サービスアカウント PROJECT_NUMBER @cloudbuild.gserviceaccount.com Cloud Build サービス アカウント Cloud Build サービスエージェント service-PROJECT_NUMBER @gcp-sa-cloudbuild.iam.gserviceaccount.com Cloud Build サービス エージェント Artifact Registry サービスエージェント service-PROJECT_NUMBER @gcp-sa-artifactregistry.iam.gserviceaccount.com Artifact Registry サービス エージェント 参考: IAM によるアクセス制御 Cloud Logging から確認した結果 前提 コンソールから Cloud Functions(第 2 世代)をデプロイした時の一連の流れを示しています。 Cloud Logging のログから挙動を確認しています。 ログは一部を抜粋しています。 リソースの相関図 Cloud Functions をデプロイする時の各リソースの相関は以下の通りです。 リソースの相関図 Cloud Logging のログ内容 0. API の有効化 Cloud Functions(第 2 世代)で関数をデプロイするには以下の API を有効化します。 Cloud Build API Cloud Functions API Cloud Logging API Cloud Pub/Sub API Artifact Registry API Cloud Run Admin API API を有効化すると、サービスアカウントやサービスエージェントが自動で作られ、前述の Google 管理のサービスアカウントのロールマネージャー( service-agent-manager@system.gserviceaccount.com )がそれぞれにロールを付与していきます。 # 一部抜粋 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Google 管理のサービス アカウントのロールマネージャーが実行 " principalEmail ": " service-agent-manager@system.gserviceaccount.com " } , ... " serviceName ": " cloudresourcemanager.googleapis.com ", " methodName ": " SetIamPolicy ", ... " resourceName ": " projects/PROJECT_ID ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { # Cloud Build サービスアカウントへ権限付与 " action ": " ADD ", " role ": " roles/cloudbuild.builds.builder ", " member ": " serviceAccount:PROJECT_NUMBER@cloudbuild.gserviceaccount.com " } ] } } , ... 1. Cloud Storage へソースコードのアップロード Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Cloud Storage に 2 つのバケットをの作成と、zip 化されたソースコードをアップロードします。 gcf-v2-uploads-PROJECT_NUMBER-REGION gcf-v2-sources-PROJECT_NUMBER-REGION # gcf - v2 - uploads -PROJECT_NUMBER- asia - northeast1 バケットの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.buckets.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-uploads-PROJECT_NUMBER-asia-northeast1 ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketReader ", " member ": " projectViewer:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectReader ", " member ": " projectViewer:fast-chess-399414 " } ] } } , ... # gcf - v2 - uploads -PROJECT_NUMBER- asia - northeast1 バケットへ zip 化したソースコードをアップロード { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-uploads-PROJECT_NUMBER-asia-northeast1/objects/5065d76a-5591-4d40-980f-5b9c425f92e4.zip ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": {} } , ... # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケットの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.buckets.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1 ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": { " bindingDeltas ": [ { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyBucketReader ", " member ": " projectViewer:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectEditor:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectOwner ", " member ": " projectOwner:fast-chess-399414 " } , { " action ": " ADD ", " role ": " roles/storage.legacyObjectReader ", " member ": " projectViewer:fast-chess-399414 " } ] } } , ... # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケットへ zip 化したソースコードをアップロード { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.create ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1/objects/function-1/function-source.zip ", " serviceData ": { " @type ": " type.googleapis.com/google.iam.v1.logging.AuditData ", " policyDelta ": {} } , ... 2. Artifact Registry へリポジトリの作成 Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Artifact Registry に REGION-docker.pkg.dev/PROJECT_ID/gcf-artifacts リポジトリを作成します。 # gcf - artifacts リポジトリの作成 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " google.devtools.artifactregistry.v1.ArtifactRegistry.CreateRepository ", ... " resourceName ": " projects/PROJECT_ID/locations/asia-northeast1 ", " request ": { " repository ": { " labels ": { " goog-managed-by ": " cloudfunctions " } , " name ": " projects/PROJECT_ID/locations/asia-northeast1/repositories/gcf-artifacts ", " format ": " DOCKER " } , " @type ": " type.googleapis.com/google.devtools.artifactregistry.v1.CreateRepositoryRequest ", " repositoryId ": " gcf-artifacts ", " parent ": " projects/PROJECT_ID/locations/asia-northeast1 " } , ... 3. Cloud Build でビルド Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )が Cloud Build でビルドを開始します。 # Cloud Build が開始 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " cloudbuild.googleapis.com ", " methodName ": " google.devtools.cloudbuild.v1.CloudBuild.CreateBuild ", ... " resourceName ": " projects/PROJECT_ID/builds ", ... Cloud Storage の gcf-v2-sources-PROJECT_NUMBER-asia-northeast1 バケット内の zip 化されたソースコードをビルドで使います。 # gcf - v2 - sources -PROJECT_NUMBER- asia - northeast1 バケット内のソースコードを取得 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Functions のサービスエージェントが実行 " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " storage.googleapis.com ", " methodName ": " storage.objects.get ", ... " resourceName ": " projects/_/buckets/gcf-v2-sources-PROJECT_NUMBER-asia-northeast1/objects/function-1/function-source.zip ", ... 4. Artifact Registry へコンテナイメージのプッシュ Cloud Build のサービスアカウント( PROJECT_NUMBER@cloudbuild.gserviceaccount.com )がビルドしたコンテナイメージを Artifact Registry へプッシュします。 # ビルド済みコンテナイメージのプッシュ { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": {} , " authenticationInfo ": { # Cloud Build のサービスアカウントが実行 " principalEmail ": " PROJECT_NUMBER@cloudbuild.gserviceaccount.com ", " serviceAccountDelegationInfo ": [ { " firstPartyPrincipal ": { " principalEmail ": " cloud-build-argo-foreman@prod.google.com " } } ] , " principalSubject ": " serviceAccount:PROJECT_NUMBER@cloudbuild.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " Docker-StartUpload ", ... " resourceName ": " projects/PROJECT_ID/locations/asia-northeast1/repositories/gcf-artifacts ", ... Tips Artifact Registry のリポジトリ Cloud Functions のサービスエージェント( service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com )は Artifact Registry のリポジトリを作る前に REGION-docker.pkg.dev/PROJECT_ID/gcf-artifacts リポジトリを探しに行きます。 初回デプロイでは、対象のリポジトリは存在しないため、以下のエラーとなります。 { " protoPayload ": { " @type ": " type.googleapis.com/google.cloud.audit.AuditLog ", " status ": { " code ": 5 , " message ": " repository not found: namespaces/PROJECT_ID/repositories/gcf-artifacts " } , " authenticationInfo ": { " principalEmail ": " service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com " } , ... " serviceName ": " artifactregistry.googleapis.com ", " methodName ": " google.devtools.artifactregistry.v1.ArtifactRegistry.GetRepository ", ... " severity ": " ERROR ", ... 上記のエラーの後に、CreateRepository メソッドでリポジトリが作成されます。このことから、以下の記事の「(b)Cloud Functions(第二世代)が自動生成するCloud Storage バケット、Artifact Registry リポジトリはCMEKでの暗号化に非対応」の項目にあるように先にリポジトリを作成し CMEK の設定をすることで、Cloud Functions で使うリポジトリの CMEK の利用が可能となります。 blog.g-gen.co.jp 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。2023 夏アニメのオススメは、ダークギャザリング。箏を習っています :) Follow @fujioka57621469
アバター
G-gen の杉村です。Google Cloud (旧称 GCP) の Compute Engine で動作する Windows Server 2022 で Active Directory ドメインコントローラを構成する方法と、そのドメインコントローラに Windows Server を参加させる方法について整理したので、注意点を解説します。 概要 オンプレミスとの差異 デフォルトではローカル Administrator が無効 IP アドレスが原則的に自動取得 ドメインコントローラ構築手順 概要 1. Windows Server の日本語化 2. ローカル Administrator のパスワード設定・アカウント有効化 3. Active Directory ドメインサービスをインストール 4. ドメインコントローラへの昇格 ドメイン参加手順 手順の概要 ファイアウォール DNS 設定 概要 Google Cloud (旧称 GCP) の仮想サーバ提供サービスである Compute Engine で動作する Windows Server 2022 で、以下の手順を確認しました。 Active Directory ドメインコントローラを構築 (新規フォレスト・ドメイン) その新ドメインに別の Windows Server を参加させる これらの手順は、原則的にはオンプレミスの Windows Server と異なりません。しかし数点の留意事項があるので、それらについてご紹介します。 オンプレミスとの差異 デフォルトではローカル Administrator が無効 Compute Engine で動作する Windows Server のオフィシャルイメージでは、ローカル Administrator が 無効化 されています。またパスワードが設定されていません。 参考 : デフォルトで無効になっているアカウント Windows Server がドメインコントローラに昇格するためにはローカル Administrator が必須ですので、パスワードを設定したうえでアカウントを有効化する必要があります。 ローカルの Administrator が無効のままドメインコントローラの昇格を試みると、以下のようなエラーが表示されます。 有効化の方法は後述します。 エラー画面のキャプチャ ドメインコントローラーの昇格に関する前提条件の検証に失敗しました。 新しいドメインを作成する場合、ローカルの Administrator アカウントがドメインの Administrator アカウントになります。ローカル の Administrator アカウントのパスワードが要件を満たしていないため、新しいドメインを作成できません。 現在、ローカルの Administrator のパスワードは空白です。このままではセキュリティ上の問題が生じる 可能性があります。新しいドメインを作成する前に、Ctrl+Alt+Del キーを押すか、net user コマンドラインツールを使用するか、またはローカルユーザーとグループを使用して、ローカルの Administrator アカウントに強力なパスワードを設定することをお勧めします。 IP アドレスが原則的に自動取得 Compute Engine の Windows Server では、ネットワークアダプタの設定にて「IP アドレスを自動的に取得する」に設定されています。 Compute Engine のプライベート IP アドレスは Compute Engine VM リソースの属性としてのネットワークインターフェイスに割り当てられます。 OS 上からはこれを「IP アドレスを自動的に取得する」設定により DHCP から取得してます。 IP アドレスは「自動取得」になっている この状態では、ドメインコントローラとして昇格する際に「ネットワークアダプタに静的 IP アドレスが割り当てられていないため、DNS の動作が信頼できなくなる可能性がある」旨が警告表示されます。 しかしながら、前述の通り Compute Engine のプライベート IP アドレスは VPC 内のネットワークインターフェイスに一意に割り当てられ、基本的に変更されることがありませんので「IP アドレスを自動的に取得する」のままで問題はありません。 この観点から、VM のネットワークインターフェイスに割り当てる内部 IP アドレスは、エフェメラルではなく 静的 IP アドレス がベストです。 固定 IP アドレスに関する警告表示 このコンピューターには、IP プロパティに静的 IP アドレスが割り当てられていない物理ネットワーク アダプターが、少なくとも1つあります。ネットワークアダプターで IPv4 と IPv6 の両方が有効にされている場合、そのネットワーク アダプターの IPv4 および IPv6 プロパティの両方に、IPv4 と IPv6 の両方の静的 IP アドレスを割り当てる必要があります。ドメインネームシステム (DNS) 動作を信頼できるものにする ために、このような静的 IP アドレスの割り当てを、すべての物理ネットワークアダプターに対して行う必 要があります。 ただし、DNS 設定は静的に指定することになります。ドメインコントローラの VM では、使用する DNS としてループバックアドレス (127.0.0.1) が、ドメインコントローラへの昇格時に自動的に指定されます。またドメインに参加する側の VM では、ドメインコントローラの IP アドレス (ないし AD ドメインのドメイン名を解決できる DNS の IP アドレス) を直接指定します。 ドメイン参加側の VM ではこのように静的に DNS を指定する方法のほか、Cloud DNS に DNS フォワーディングゾーンを作成して特定ドメインだけをドメインコントローラの DNS にフォワードするよう設定する方法も紹介されています。 参考 : Create a private DNS forwarding zone ドメインコントローラ構築手順 概要 前述の通り、Compute Engine において Active Directory ドメインコントローラを構築する際の手順は、オンプレミスと大きく変わりません。 ただし前述の「ローカル Administrator がデフォルトでは無効化されている」点や、Windows Server は日本語イメージが配布されていない点を考慮すると、以下のようになります。 必要に応じて Windows Server を日本語化 ローカル Administrator のパスワード設定・アカウント有効化 「役割と機能の追加」から Active Directory ドメインサービスをインストール サーバーマネージャで、ドメインコントローラへの昇格を実施 1. Windows Server の日本語化 必要に応じて、以下の記事を参考にして日本語化を実施します。 blog.g-gen.co.jp 2. ローカル Administrator のパスワード設定・アカウント有効化 ローカル Administrator はデフォルトではパスワードが設定されておらず、アカウント自体も無効化されています。 以下のように有効化します。 コンピューターの管理から「ローカルユーザーとグループ」>「ユーザー」を開く Administrator を右クリックして、新しいパスワードを設定 Administrator を右クリックしてプロパティを開き、「アカウントを無効にする」をオフにして保存する パスワードの設定とアカウントの有効化を両方行う 3. Active Directory ドメインサービスをインストール 「役割と機能の追加」から Active Directory ドメインサービスをインストールします。 オンプレミス等の通常の手順と変わらないため、当記事では割愛します。 4. ドメインコントローラへの昇格 サーバーマネージャを開き、ドメインコントローラへの昇格を実施します。 こちらも通常の手順と変わらないため、当記事では割愛します。 前述の通り、固定 IP アドレスに関する警告文が表示されますが、問題ありません。 ドメイン参加手順 手順の概要 Compute Engine で動作する Windows Server を、同じく Compute Engine 上のドメインコントローラでホストされるドメインに参加させる方法については、こちらも通常の手順と変わりありません。 当記事では詳細な解説は割愛しますが、概ね以下のとおりです。 DNS の設定 (AD ドメインのドメイン名を名前解決可能にする) システム設定の「このPCの名前を変更 (詳細設定)」からドメインへ参加 適切な権限を持つ AD アカウントで認証 ファイアウォール 注意点としては、VPC ファイアウォールにてドメインコントローラと参加サーバの間で必要なポートがオープンである必要あります。 以下の Microsoft の公式ドキュメントに沿って必要なポートを最小限で開けるのが理想的ですが、通信要件が多岐にわたるので、リスクを理解した上でドメインコントローラと参加サーバの間で全 TCP/UDP ポートを許可するのも選択肢と成り得ます。 参考 : Windows のサービス概要およびネットワーク ポート要件 DNS 設定 また前述の通り、ドメイン参加側の VM の「アダプタ設定」にて静的に DNS を指定する方法のほか、Cloud DNS に DNS フォワーディングゾーンを作成して特定ドメインだけをドメインコントローラの DNS にフォワードするよう設定する方法もあります。 参考 : Create a private DNS forwarding zone この方法では VPC ネットワーク内の全 VM の名前解決に影響が及ぶので、影響を理解してご利用ください。 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。Twitter では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の藤岡です。当記事では、Google Workspace で削除済みのユーザーが主催者の予定を Google カレンダーから削除する方法を紹介します。 はじめに ユーザーの削除 Google カレンダーの予定 削除済みユーザーが主催者の予定を削除する手順 前提 ID の取得 予定の削除 トラブルシューティング はじめに ユーザーの削除 組織で Google Workspace を利用している場合、退職者が出るとユーザー (Google アカウント) の削除が発生します。削除時にデータを移行することで、ユーザーの削除をしてもデータを引き継ぐことができます。 削除時に以下の画面で データのオーナー権限を移行しない にチェックを入れ削除をすると、各データが引き継がれません。 ユーザーの削除画面 参考: 組織からユーザーを削除する Google カレンダーの予定 予定のキャンセルや譲渡をせずにユーザーを削除してしまい、削除済みのユーザーが主催者の予定がある場合、招待されたユーザーの Google カレンダーに予定が残ります。 例として、以下のメンバーで構成された予定があるとします。 予定の画面 予定の主催者である aaa@example.com を データのオーナー権限を移行しない で削除します。 ユーザーの削除画面 予定の主催者ユーザーが削除されても、以下のように予定に招待されている bbb@example.com と ccc@example.com のカレンダーには予定が残ります。 主催者削除後の予定画面 ここで bbb@example.com が手動で自身の Google カレンダーから このカレンダーから削除 で予定を削除したとします。 手動で予定の削除 その場合、 bbb@example.com の Google カレンダー上では予定が無くなりますが、 ccc@example.com の Google カレンダー上では予定が残ります。 予定自体は残る この例では参加者が 3 人予定ですが、100 人を超える招待者がいた場合に、招待者された側で手動で予定を削除することはかなりの手間となります。 そこで、削除済みユーザーが主催者となっている予定を、管理者側が一度に削除するには以下の手順を実施します。 削除済みユーザーが主催者の予定を削除する手順 前提 削除済みユーザーが主催者の予定を管理コンソールから一覧で表示させて削除する等といったことは現在の仕様ではできません。 そのため、削除したい予定があるが、主催者が既に削除されているユーザーの場合は Calendar API を使い、予定を削除します。 参考: 削除したユーザーの予定をカレンダーから削除する ID の取得 以下の手順で対象の予定 ID を取得します。 https://www.google.com/calendar/render?gsessionid=OK&eventdeb=1 から Google カレンダーを開く (通常の Google カレンダー URL は https://calendar.google.com/calendar/ ですが、この URL では [ トラブルシューティング情報 ] が表示されません) 予定 > その他アイコン [ ︙ ] > [ トラブルシューティング情報 ] を押下 トラブルシューティング情報の画面 以下のような ID が表示されます。(見やすいよう適宜改行しています) Event{ eid=2c37797xxxxxxx, organizer=aaa@example.com, participant=bbb@example.com, actor=bbb@example.com, summary=ブログ執筆, status=CONFIRMED, seq=1, startTime=2023-09-18T22:00:00Z, endTime=2023-09-18T23:00:00Z } このうち、 eid と organizer の 2 つの ID を使い削除済みのユーザーが主催者の予定を Google カレンダーから削除します。 参考: Google カレンダー > 一般的な問題 予定の削除 予定を削除するには 特権管理者 権限が必要です。 特権管理者権限のあるアカウントでログインし、以下の手順で予定を削除します。 Calendar API の Events: delete にアクセス 画面右部の Try this method に以下の情報を入力(ID の取得で表示された内容) calendarId: organizer の値 eventId: eid の値 Execute を押下 Google にログイン画面 アカウントを選択 :ログインしているユーザーを選択 Google APIs Explorer が Google アカウントへのアクセスをリクエストしています:許可 200 番台が返ってくること API で予定の削除 これで削除済みのユーザーが主催者の予定を Google カレンダーから削除できました。 今回の例では Google API Explorer という、ブラウザ上の API リファレンスドキュメントから試験的に直接 API コールを実行できるツールを用いて実行しましたが、通常通り Python 等のプログラムから実行することもできます。今回の記事では、そちらの方法は割愛します。 トラブルシューティング Calendar API の Events: delete を特権管理者ではないユーザーで実行すると 404 が返ってきます。 { "error": { "errors": [ { "domain": "global", "reason": "notFound", "message": "Not Found" } ], "code": 404, "message": "Not Found" } } 藤岡 里美 (記事一覧) クラウドソリューション部 接客業からエンジニアへ。2022年9月 G-gen にジョイン。Google Cloud 認定資格は全冠。2023 夏アニメのオススメは、ダークギャザリング。箏を習っています :) Follow @fujioka57621469
アバター
G-gen の藤岡です。当記事では、Google Cloud(旧称 GCP)の 2023 年 8 月にリリース された Cloud Load Balancing のクロスリージョン内部アプリケーションロードバランサを紹介します。 はじめに Cloud Load Balancing とは 内部アプリケーションロードバランサとは アーキテクチャ 実施内容 構成図 前提 構築 事前準備 プロジェクトの作成と請求先アカウントの紐づけ デフォルトプロジェクトのセット API の有効化 リソースの作成 VPC とサブネットの作成 プロキシ専用サブネットの作成 ファイアウォールルールの作成 マネージドインスタンスグループの作成 クロスリージョン内部アプリケーションロードバランサの作成 検証クライアントの作成 フェイルオーバーとフェイルバックの検証 正常時のトラフィック フェイルオーバー フェイルバック はじめに Cloud Load Balancing とは Cloud Load Balancing とは Google Cloud が提供する仮想的なロードバランサです。 以下の記事では、External Application Load Balancer だけでなく Cloud Load Balancing の内部的な仕組みについて触れていますので、当記事と併せてご参照ください。 blog.g-gen.co.jp Cloud Load Balancing は以下の 9 種類 が提供されています。当記事では図内の Cross-region internal Application Load Balancer であるクロスリージョン内部アプリケーションロードバランサに焦点を当てています。 公式ドキュメント から引用 内部アプリケーションロードバランサとは 内部アプリケーションロードバランサ は、Envoy プロキシベースのレイヤ 7 ロードバランサで、接続元が Google Cloud 内や Cloud VPN、Cloud Interconnect で接続されたオンプレミスの他、Amazon Web Services(AWS)等からのトラフィックをバックエンドサービスへ分散します。 内部アプリケーションロードバランサは、バックエンドのタイプによってリージョンモードとクロスリージョンモードの 2 種類が提供されています。 リージョン内部アプリケーションロードバランサ クロスリージョン内部アプリケーションロードバランサ(Preview 版) リージョン内部アプリケーションロードバランサ では、バックエンドはロードバランサと同一リージョンに配置します。 バックエンドがリージョンを跨ぐ場合には、 クロスリージョン内部アプリケーションロードバランサ を選択します。 これによって、オフィスやデータセンター拠点から Google Cloud へ国内であれば東京(asia-northeast1)と大阪(asia-northeast2)への負荷分散やフェイルオーバーができ、単一リージョンにおける障害回避が可能になります。 クロスリージョン内部アプリケーションロードバランサがリリースされる前は、バックエンドがリージョンを跨ぐ場合には以下の記事あるように Cloud DNS による分散が候補に挙げられましたが、バックエンドに異常が発生した場合の自動フェイルオーバー機能は用意されていないため、独自に実装する必要がある等の制限がありました。 blog.g-gen.co.jp アーキテクチャ クロスリージョン内部アプリケーションロードバランサのアーキテクチャは以下の通りです。 クロスリージョン内部アプリケーションロードバランサからバックエンドサービス (インスタンスグループやサーバレス NEG 等) へのパケットは「プロキシ専用サブネット」から送信されます。また、バックエンドサービスは ヘルスチェック によってバックエンド (VM 等) の正常性を確認します。 そのため VPC ファイアウォールではそれらの通信を許可する必要があります。 アーキテクチャ 実施内容 構成図 当記事で作成するリソースは以下の通りです。 構成図 前提 gcloud コマンドでリソースを作成します。実行環境は Cloud Shell で、Google Cloud SDK バージョンは以下の通りです。 fujioka@cloudshell:~ ( xxxx ) $ gcloud version | grep " Google Cloud SDK " Google Cloud SDK 445 . 0 . 0 fujioka@cloudshell:~ ( xxxx ) $ 構築 事前準備 プロジェクトの作成と請求先アカウントの紐づけ プロジェクトを作成し、作成したプロジェクトに請求先アカウントを紐づけます。 $ gcloud projects create ${PROJECT_ID} --name= ${PROJECT_NAME} --organization= ${ORGANIZATION_ID} && \ gcloud beta billing projects link ${PROJECT_ID} --billing-account= ${BILLING_ACCOUNT_ID} デフォルトプロジェクトのセット 作成したプロジェクトをデフォルトプロジェクトとしてセットし、結果を確認します。 $ gcloud config set project ${PROJECT_ID} && \ gcloud config list project API の有効化 今回の検証で必要な API を有効化します。 $ gcloud services enable compute.googleapis.com dns.googleapis.com リソースの作成 VPC とサブネットの作成 VPC とサブネットを作成します。 # VPC の作成 $ gcloud compute networks create lb-network --subnet-mode=custom # asia-northeast1(Tokyo)にサブネットの作成 $ gcloud compute networks subnets create lb-subnet-asia-northeast1 \ --network=lb-network \ --range=10.1.2.0/24 \ --region=asia-northeast1 # asia-northeast2(Osaka)にサブネットの作成 $ gcloud compute networks subnets create lb-subnet-asia-northeast2 \ --network=lb-network \ --range=10.1.3.0/24 \ --region=asia-northeast2 プロキシ専用サブネットの作成 プロキシ専用サブネットを作成します。バックエンドサービスへのアクセスは、プロキシ専用サブネットからになるため、後述のファイアウォールの設定でプロキシ専用サブネットからのアクセスを許可する必要があります。 プロキシ専用サブネットは 64 個以上の IP アドレスが必要であり、推奨されるサブネットサイズは /23 です。 # asia-northeast1(Tokyo)にプロキシ専用サブネットの作成 $ gcloud beta compute networks subnets create proxy-only-subnet-asia-northeast1 \ --purpose=GLOBAL_MANAGED_PROXY \ --role=ACTIVE \ --region=asia-northeast1 \ --network=lb-network \ --range=10.129.0.0/23 # asia-northeast2(Osaka)にプロキシ専用サブネットの作成 $ gcloud beta compute networks subnets create proxy-only-subnet-asia-northeast2 \ --purpose=GLOBAL_MANAGED_PROXY \ --role=ACTIVE \ --region=asia-northeast2 \ --network=lb-network \ --range=10.130.0.0/23 参考: Envoy ベースのロードバランサ向けプロキシ専用サブネット プロキシ専用サブネットを作成すると、コンソールでは以下のように表示されます。 プロキシ専用サブネットの表記 ファイアウォールルールの作成 3 つのファイアウォールルールを作成します。 fw-allow-ssh :Compute Engine へ SSH 用 fw-allow-health-check : ヘルスチェック用 fw-allow-proxies :プロキシ専用サブネットからバックエンドサービスへのアクセス用(今回は 80/tcp) フェイルオーバーの検証をタグの削除によるヘルスチェックの失敗で確認をするため、ファイアウォールのターゲットはタグにします。 # fw-allow-ssh $ gcloud compute firewall-rules create fw-allow-ssh \ --network=lb-network \ --action=allow \ --direction=ingress \ --source-ranges=0.0.0.0/0 \ --target-tags=allow-ssh \ --rules=tcp:22 # fw-allow-health-check $ gcloud compute firewall-rules create fw-allow-health-check \ --network=lb-network \ --action=allow \ --direction=ingress \ --source-ranges=130.211.0.0/22,35.191.0.0/16 \ --target-tags=load-balanced-backend \ --rules=tcp # fw-allow-proxies $ gcloud compute firewall-rules create fw-allow-proxies \ --network=lb-network \ --action=allow \ --direction=ingress \ --source-ranges=10.129.0.0/23,10.130.0.0/23 \ --target-tags=load-balanced-backend \ --rules=tcp:80 マネージドインスタンスグループの作成 インスタンステンプレートを作成します。 # asia-northeast1(Tokyo)にインスタンステンプレートの作成 $ gcloud compute instance-templates create gil7-backend-asia-northeast1-template \ --region=asia-northeast1 \ --network=lb-network \ --subnet=lb-subnet-asia-northeast1 \ --tags=allow-ssh,load-balanced-backend \ --image-family=debian-10 \ --image-project=debian-cloud \ --metadata=startup-script= ' #! /bin/bash apt-get update apt-get install apache2 -y a2ensite default-ssl a2enmod ssl vm_hostname="$(curl -H "Metadata-Flavor:Google" \ http://169.254.169.254/computeMetadata/v1/instance/name)" echo "Page served from: $vm_hostname" | \ tee /var/www/html/index.html systemctl restart apache2 ' # asia-northeast2(Osaka)にインスタンステンプレートの作成 $ gcloud compute instance-templates create gil7-backend-asia-northeast2-template \ --region=asia-northeast2 \ --network=lb-network \ --subnet=lb-subnet-asia-northeast2 \ --tags=allow-ssh,load-balanced-backend \ --image-family=debian-10 \ --image-project=debian-cloud \ --metadata=startup-script= ' #! /bin/bash apt-get update apt-get install apache2 -y a2ensite default-ssl a2enmod ssl vm_hostname="$(curl -H "Metadata-Flavor:Google" \ http://169.254.169.254/computeMetadata/v1/instance/name)" echo "Page served from: $vm_hostname" | \ tee /var/www/html/index.html systemctl restart apache2 ' インスタンステンプレートを使って、マネージドインスタンスグループを作成します。 # asia-northeast1(Tokyo)にインスタンスグループの作成 $ gcloud compute instance-groups managed create l7-ilb-backend-asia-northeast1 \ --zone=asia-northeast1-a \ --size=2 \ --template=gil7-backend-asia-northeast1-template # asia-northeast2(Osaka)にインスタンスグループの作成 $ gcloud compute instance-groups managed create l7-ilb-backend-asia-northeast2 \ --zone=asia-northeast2-a \ --size=2 \ --template=gil7-backend-asia-northeast2-template クロスリージョン内部アプリケーションロードバランサの作成 ヘルスチェックを作成します。 ここでは HTTP ヘルスチェックを作成しますが、クロスリージョン内部アプリケーションロードバランサでは他にも TCP、SSL、HTTPS 等もサポートしています。 # HTTP ヘルスチェックの作成 $ gcloud compute health-checks create http gil7-basic-check \ --use-serving-port \ --global 参考: ヘルスチェックの概要 バックエンドサービスを定義します。 ロギング はバックエンドサービス単位で有効にできます。ロギングはトラブルシューティング時に有用です。 $ gcloud compute backend-services create gil7-backend-service \ --load-balancing-scheme=INTERNAL_MANAGED \ --protocol=HTTP \ --enable-logging \ --logging-sample-rate=1.0 \ --health-checks=gil7-basic-check \ --global-health-checks \ --global バックエンドサービスにインスタンスグループを追加します。 # asia-northeast1(Tokyo)にインスタンスグループをバックエンドに追加 $ gcloud compute backend-services add-backend gil7-backend-service \ --balancing-mode=UTILIZATION \ --instance-group=l7-ilb-backend-asia-northeast1 \ --instance-group-zone=asia-northeast1-a \ --global # asia-northeast2(Osaka)にインスタンスグループをバックエンドに追加 $ gcloud compute backend-services add-backend gil7-backend-service \ --balancing-mode=UTILIZATION \ --instance-group=l7-ilb-backend-asia-northeast2 \ --instance-group-zone=asia-northeast2-a \ --global URL マップを作成します。 $ gcloud compute url-maps create gil7-map \ --default-service=gil7-backend-service \ --global ターゲットプロキシを作成します。 $ gcloud compute target-http-proxies create gil7-http-proxy \ --url-map=gil7-map \ --global 今回はターゲットプロキシを HTTP としていますが、HTTPS で DNS 認証を使う場合、以下の記事を参考にしてください。 blog.g-gen.co.jp フォワーディングルールを作成します。 # asia-northeast1(Tokyo) $ gcloud compute forwarding-rules create gil7-forwarding-rule-asia-northeast1 \ --load-balancing-scheme=INTERNAL_MANAGED \ --network=lb-network \ --subnet=lb-subnet-asia-northeast1 \ --subnet-region=asia-northeast1 \ --address=10.1.2.99 \ --ports=80 \ --target-http-proxy=gil7-http-proxy \ --global # asia-northeast2(Osaka) $ gcloud compute forwarding-rules create gil7-forwarding-rule-asia-northeast2 \ --load-balancing-scheme=INTERNAL_MANAGED \ --network=lb-network \ --subnet=lb-subnet-asia-northeast2 \ --subnet-region=asia-northeast2 \ --address=10.1.3.99 \ --ports=80 \ --target-http-proxy=gil7-http-proxy \ --global クロスリージョン内部アプリケーションロードバランサはコンソールで見た時に、「リージョン」が空欄になるのに対し、リージョン内部アプリケーション ロードバランサは属するリージョンが表示されます。 コンソール表示 参考: モードの識別 検証クライアントの作成 検証クライアントとして、Compute Engine インスタンスを作成します。 # asia-northeast1(Tokyo) $ gcloud compute instances create l7-ilb-client-asia-northeast1-a \ --image-family=debian-10 \ --image-project=debian-cloud \ --network=lb-network \ --subnet=lb-subnet-asia-northeast1 \ --zone=asia-northeast1-a \ --tags=allow-ssh \ --metadata=startup-script= ' #!/bin/bash apt-get update apt-get install -y dnsutils ' # asia-northeast2(Osaka) gcloud compute instances create l7-ilb-client-asia-northeast2-a \ --image-family=debian-10 \ --image-project=debian-cloud \ --network=lb-network \ --subnet=lb-subnet-asia-northeast2 \ --zone=asia-northeast2-a \ --tags=allow-ssh \ --metadata=startup-script= ' #!/bin/bash apt-get update apt-get install -y dnsutils ' フェイルオーバーとフェイルバックの検証 正常時のトラフィック 検証クライアントからクロスリージョン内部アプリケーションロードバランサ( 10.1.2.99 / 10.1.3.99 )へ 100 個のリクエストをし、負荷分散されていることを確認します。 正常時のアクセス ヘルスチェックが正常時のバックエンドサービス インスタンス名は l7-ilb-backend-<リージョン名>-<乱数> です。 インスタンスグループのインスタンス 以下のように、各リージョンのバックエンドの 2 インスタンスへトラフィックが分散されていることがわかります。 # asia-northeast1 のインスタンスグループへ分散 fujioka@l7-ilb-client-asia-northeast1-a:~$ { > RESULTS= > for i in {1..100} > do > RESULTS="$RESULTS:$(curl --silent 10.1.2.99)" > done > echo "" > echo " Results of load-balancing to 10.1.2.99: " > echo "***" > echo "$RESULTS" | tr ':' '\n' | grep -Ev "^$" | sort | uniq -c > echo > } Results of load-balancing to 10.1.2.99: *** 49 l7-ilb-backend-asia-northeast1-cn22 51 l7-ilb-backend-asia-northeast1-pdtr 100 Page served from fujioka@l7-ilb-client-asia-northeast1-a:~$ # asia-northeast2 のインスタンスグループへ分散 fujioka@l7-ilb-client-asia-northeast2-a:~$ { > RESULTS= > for i in {1..100} > do > RESULTS="$RESULTS:$(curl --silent 10.1.3.99)" > done > echo "" > echo " Results of load-balancing to 10.1.3.99: " > echo "***" > echo "$RESULTS" | tr ':' '\n' | grep -Ev "^$" | sort | uniq -c > echo > } Results of load-balancing to 10.1.3.99: *** 49 l7-ilb-backend-asia-northeast2-088t 51 l7-ilb-backend-asia-northeast2-zqtx 100 Page served from fujioka@l7-ilb-client-asia-northeast2-a:~$ フェイルオーバー asia-northeast1 のバックエンドのインスタンスグループに付与された load-balanced-backend タグを削除します。これによってファイアウォールのタグで許可されていたヘルスチェックのアクセスが拒否されるため、ヘルスチェックが失敗し asia-northeast1 のインスタンスグループへトラフィックが流れなくなります。 異常時のアクセス # l7-ilb-backend-asia-northeast1-cn22 からタグを削除 $ gcloud compute instances remove-tags l7-ilb-backend-asia-northeast1-cn22 \ --zone = asia-northeast1-a \ --tags = load-balanced-backend # l7-ilb-backend-asia-northeast1-pdtr からタグを削除 $ gcloud compute instances remove-tags l7-ilb-backend-asia-northeast1-pdtr \ --zone = asia-northeast1-a \ --tags = load-balanced-backend タグの削除により、ヘルスチェックが失敗しています。 ヘルスチェックが異常時のバックエンドサービス この状態で再度クロスリージョン内部アプリケーションロードバランサ( 10.1.2.99 / 10.1.3.99 )へ 100 個のリクエストをすると、 10.1.2.99 へのリクエストは asia-northeast1 のバックエンドではなく、asia-northeast2 のバックエンドへトラフィックが流れるようになります。 # asia-northeast2 のインスタンスグループへトラフィックが流れるようになる fujioka@l7-ilb-client-asia-northeast1-a:~$ { > RESULTS= > for i in {1..100} > do > RESULTS="$RESULTS:$(curl --silent 10.1.2.99)" > done > echo "" > echo " Results of load-balancing to 10.1.2.99: " > echo "***" > echo "$RESULTS" | tr ':' '\n' | grep -Ev "^$" | sort | uniq -c > echo > } Results of load-balancing to 10.1.2.99: *** 51 l7-ilb-backend-asia-northeast2-088t 49 l7-ilb-backend-asia-northeast2-zqtx 100 Page served from fujioka@l7-ilb-client-asia-northeast1-a:~$ # asia-northeast2 のインスタンスグループへ分散 fujioka@l7-ilb-client-asia-northeast2-a:~$ { > RESULTS= > for i in {1..100} > do > RESULTS="$RESULTS:$(curl --silent 10.1.3.99)" > done > echo "" > echo " Results of load-balancing to 10.1.3.99: " > echo "***" > echo "$RESULTS" | tr ':' '\n' | grep -Ev "^$" | sort | uniq -c > echo > } Results of load-balancing to 10.1.3.99: *** 51 l7-ilb-backend-asia-northeast2-088t 49 l7-ilb-backend-asia-northeast2-zqtx 100 Page served from fujioka@l7-ilb-client-asia-northeast2-a:~$ フェイルバック タグを再度付与し、ヘルスチェックが成功後のトラフィックを確認します。 # タグを付与 $ gcloud compute instances add-tags l7-ilb-backend-asia-northeast1-cn22 --tags=load-balanced-backend --zone=asia-northeast1-a && gcloud compute instances add-tags l7-ilb-backend-asia-northeast1-pdtr --tags=load-balanced-backend --zone=asia-northeast1-a タグ再付与後のヘルスチェックが正常なバックエンドサービス 10.1.2.99 へのトラフィックが元通り asia-northeast2 ではなく、asia-northeast1 のバックエンドに流れていることがわかります。 fujioka@l7-ilb-client-asia-northeast1-a:~$ { > RESULTS= > for i in {1..100} > do > RESULTS="$RESULTS:$(curl --silent 10.1.2.99)" > done > echo "" > echo " Results of load-balancing to 10.1.2.99: " > echo "***" > echo "$RESULTS" | tr ':' '\n' | grep -Ev "^$" | sort | uniq -c > echo > } Results of load-balancing to 10.1.2.99: *** 51 l7-ilb-backend-asia-northeast1-cn22 49 l7-ilb-backend-asia-northeast1-pdtr 100 Page served from fujioka@l7-ilb-client-asia-northeast1-a:~$ 実際の運用では Cloud DNS の ルーティングポリシー も併せて使うことで、バックエンドのサービス停止だけでなく、リージョン停止やレイテンシ、ネットワーク転送費用を抑えることもできます。 参考: DNS ルーティング ポリシーを構成する 藤岡 里美 (記事一覧) クラウドソリューション部 数年前までチキン売ったりドレスショップで働いてました!2022年9月 G-gen にジョイン。ハイキューの映画を4回は見に行きたい。 Google Cloud All Certifications Engineer / Google Cloud Partner Top Engineer 2024 Follow @fujioka57621469
アバター
G-gen 又吉です。Google Cloud (旧称 GCP) の生成 AI (Generative AI) である PaLM 2 を用いて、Cloud Run 上に社内 LLM Web アプリを構築してみました。 はじめに 前提知識 Vertex AI PaLM API Gradio Cloud Runサービスへのアクセス制御 準備 ディレクトリ構成 app.py requirements.txt Dockerfile デプロイ 動作検証 はじめに 今回は、Google Cloud の生成 AI である Vertex AI PaLM API を用いて、社内向け LLM Web アプリを Cloud Run 上にデプロイします。 また、Cloud Run サービスの認証には Identity-Aware Proxy (IAP) を用いることで、社内ユーザーのみがアクセスできる状態を構成できます。 尚、コンシューマ向け LLM サービスの Bard と比べ、Vertex AI PaLM API を用いることで LLM モデルへの入出力データを自社内に閉じることができ、データガバナンスを維持することが出来ます。 構成図 先日、当社ブログで Vertex AI PaLM API を Slack と連携した記事も公開されてますので、ご興味ある方はこちらもご覧下さい。 blog.g-gen.co.jp 前提知識 Vertex AI PaLM API PaLM 2 は Google の生成 AI モデルであり、Bard の裏側でも使われています。Vertex AI では、PaLM 2 のエンドポイントを Vertex AI PaLM API として公開しています。 開発者は Vertex AI PaLM API を使用することで、自社のアプリケーションで生成 AI を組み込むことが可能となります。 今回は、 Vertex AI PaLM API の中でも会話に特化した chat-bison を利用します。 その他、Vertex AI PaLM API の詳細については、以下のブログをご覧ください。 blog.g-gen.co.jp Gradio Gradio とは、Python で機械学習 Web アプリを容易に構築できるフレームワークです。 今回は、簡易的な LLM Webアプリを構築するために以下のコンポーネントを使用しました。 Web アプリの画面構成 No コンポーネント 説明 1 Chatbot LLM との会話履歴を表示する部分 2 Textbox ユーザーがプロンプトを入力するテキストボックス 3 ClearButton ユーザーが入力したプロンプトを削除するボタン 参考: How to Create a Chatbot with Gradio Cloud Runサービスへのアクセス制御 Cloud Run サービスへのアクセス制御は、 Identity-Aware Proxy (IAP) を用いることで IAP を利用できる IAM ロール がアタッチされた組織内 Google アカウント / グループ に制限することができます。 参考: Enabling IAP for Cloud Run 準備 ディレクトリ構成 開発環境は Cloud Shell を用いて行います。ディレクトリ構成は以下のとおりです。 llm_app ディレクトリ配下は、以下のとおりです。 llm_app |-- app | |-- app.py | `-- requirements.txt `-- Dockerfile app.py app.py には、以下の Python コードを記述します。 import os import logging import gradio as gr import google.cloud.logging import vertexai from vertexai.language_models import ChatModel, ChatMessage, InputOutputTextPair # Cloud Logging ハンドラを logger に接続 logger = logging.getLogger() logging_client = google.cloud.logging.Client() logging_client.setup_logging() # 定数の定義 PROJECT_ID = os.environ.get( "PROJECT_ID" ) LOCATION = os.environ.get( "LOCATION" ) # vertexai インスタンスの初期化 vertexai.init(project=PROJECT_ID, location= "us-central1" ) def llm_chat (message, chat_history): # 基盤モデルを設定 chat_model = ChatModel.from_pretrained( "chat-bison@001" ) # パラメータを指定 parameters = { "max_output_tokens" : 1024 , "temperature" : 0.2 , "top_p" : 0.8 , "top_k" : 40 } # 会話履歴のリストを初期化 message_history = [] # 会話履歴のフォーマットを整形 for row in chat_history: input_from_user = row[ 0 ] output_from_llm = row[ 1 ] q_message = ChatMessage(author= "user" , content=input_from_user) message_history.append(q_message) a_message = ChatMessage(author= "llm" , content=output_from_llm) message_history.append(a_message) # 基盤モデルに会話履歴をインプット chat = chat_model.start_chat(message_history=message_history) # 基盤モデルにプロンプトリクエストを送信 response = chat.send_message(message, **parameters) return response.text def respond (message, chat_history): # llm で回答を生成 bot_message = llm_chat(message, chat_history) # Cloud Logging 書き込み用 logger.info(f "message: {message}" ) logger.info(f "chat_history: {chat_history}" ) logger.info(f "bot_message: {bot_message}" ) chat_history.append((message, bot_message)) return "" , chat_history # gradio の設定 with gr.Blocks() as llm_web_app: chatbot = gr.Chatbot() msg = gr.Textbox() clear = gr.ClearButton([msg, chatbot]) msg.submit(respond, [msg, chatbot], [msg, chatbot]) clear.click( lambda : None , None , chatbot, queue= False ) # Gradio の立ち上げ llm_web_app.launch(server_name= "0.0.0.0" , server_port= 7860 ) 参考: Gradio - Chatbot - Domos requirements.txt gradio==3.41.2 google-cloud-aiplatform==1.31.1 google-cloud-logging==3.6.0 Dockerfile FROM python:3.9-slim COPY ./app /app WORKDIR /app RUN pip install --no-cache-dir -r requirements.txt EXPOSE 7860 CMD [ "python" , "app.py" ] デプロイ Cloud Shell にて、現在のディレクトリが llm_app ディレクトリであることを確認し、以下 gcloud コマンドを順次実行します。 # 環境変数の設定 PROJECT_ID = { プロジェクト ID } BILLING_ACCOUNT_ID = { 請求先アカウント ID } REGION =asia-northeast1 AR_REPO = { Artifact Repogitory のレポジトリ名 } SERVICE_NAME = { Cloud Run サービス名 } SA_NAME = { サービスアカウント名 } # プロジェクト作成 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 --project= $PROJECT_ID run.googleapis.com \ artifactregistry.googleapis.com \ cloudbuild.googleapis.com \ compute.googleapis.com \ aiplatform.googleapis.com \ iap.googleapis.com # サービスアカウント作成 gcloud iam service-accounts create $SA_NAME # サービスアカウントへ権限付与 gcloud projects add-iam-policy-binding $PROJECT_ID \ --member= " serviceAccount: $SA_NAME @ $PROJECT_ID .iam.gserviceaccount.com " \ --role= " roles/aiplatform.user " # Artifacts repositories 作成 gcloud artifacts repositories create $AR_REPO \ --location= $REGION \ --repository-format=Docker \ --project= $PROJECT_ID # イメージの作成&更新 gcloud builds submit --tag $REGION -docker.pkg.dev/ $PROJECT_ID / $AR_REPO / $SERVICE_NAME \ --project= $PROJECT_ID # Cloud Run デプロイ gcloud run deploy $SERVICE_NAME --port 7860 \ --image $REGION -docker.pkg.dev/ $PROJECT_ID / $AR_REPO / $SERVICE_NAME \ --no-allow-unauthenticated \ --service-account= $SA_NAME @ $PROJECT_ID .iam.gserviceaccount.com \ --ingress=internal-and-cloud-load-balancing \ --region= $REGION \ --set-env-vars=PROJECT_ID= $PROJECT_ID , LOCATION = $REGION \ --project= $PROJECT_ID Cloud Run サービスと IAP の連携は、以下ブログの手順をご参照下さい。 blog.g-gen.co.jp 動作検証 LLM Web アプリの URL にアクセスすると、まず最初に IAP による認証画面が現れます。 IAP による認証画面 許可された Google アカウントでログインすると LLM Web アプリの画面に遷移します。 試しに、「BigQuery の特徴」を聞いてみてます。また、会話形式もサポートできているか併せて確認してみます。 LLM Web アプリ画面 LLM が前の会話を記憶して、回答を生成してくれました。 このように、Vertex AI PaLM API を利用することで、入出力データは学習に使われることなく、企業のデータガバナンスを維持しながら社内 LLM Web アプリを構築することができます。 又吉 佑樹 (記事一覧) クラウドソリューション部 はいさい、沖縄出身のクラウドエンジニア! セールスからエンジニアへ転身。Google Cloud 全 11 資格保有。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリストとして活動中。好きな分野は AI/ML。 Follow @matayuuuu
アバター
G-gen の杉村です。当記事では、生成 AI 技術を利用した検索エンジンやエージェント開発のための Google Cloud プロダクトである Vertex AI Search (旧名称 Generative AI App Builder、Vertex AI Agent Builder など)を解説します。 概要 Vertex AI Search とは RAG の実現 名称の変遷 ユースケース 社内検索エンジン 顧客対応補助 AI エージェントのバックエンド 料金 クエリ料金とエディション インデックスストレージ料金 無料枠 構成可能な料金(Configurable pricing) 検索・要約機能の概要 検索と要約 要約 対応言語 検索・要約機能の詳細 オートコンプリート スニペットと抽出コンテンツ 検索結果の要約 フォローアップ検索 アプリとの統合 API による独自アプリとの統合 ウェブサイトへのウィジェット追加 分析 オプション Search Enterprise Edition Advanced Generative Answers(高度な LLM 機能) 機能と対応するオプション アプリとデータストア アプリ データストア Web データストア 構造化データストア 非構造化データストア 非構造化データのパース(解析) 概要 Vertex AI Search とは Vertex AI Search は、PDF や Word ドキュメントなどの社内情報や、外部の Web サイトに対する検索機能を提供するフルマネージドなプラットフォームです。 セマンティック検索 (意味論検索)を容易に実装でき、Google ドライブや BigQuery、Cloud Storage に格納された構造化データや非構造化データに対して検索できるほか、ウェブサイトに対する検索を実施できます。 また Vertex AI Search は生成 AI と統合されており、検索結果の要約や、ユーザーの自然言語による質問に答えることができます。 Vertex AI Search はフルマネージドで提供されており、インフラの構築や運用を意識する必要がありません。また、API 経由で自社の Web サイトやアプリケーション、チャット等から呼び出して利用できます。開発用の Web UI(Google Cloud コンソール)が用意されており、設定や調整、動作確認はこの画面から行うことが可能です。 なお、Google Cloud コンソールの管理画面上では「 AI Applications 」と表示される画面で Vertex AI Search アプリを管理します。 参考 : Vertex AI Search の概要 RAG の実現 RAG とは Retrieval Augmented Generation の略称であり、生成 AI によるテキスト生成などに、外部データソースを用いて情報の根拠付けを行うアーキテクチャのことです。 生成 AI が誤った情報を生成してしまうことを「ハルシネーション」と言います。RAG アーキテクチャでは、インターネットの Web サイトや事前に準備したドキュメント集などをデータセットとして、生成する情報に組み込んだり脚注のような形で参考情報として添付することでハルシネーションを軽減します。 Vertex AI Search では、Web サイトや PDF、Word ファイル、パワーポイントのスライドなどをデータストアとする RAG 構成を容易に構築 できます。 なお Web サイトをデータストアとする場合、Vertex AI Search で要約文を生成するには当該 Web サイトのドメイン認証(DNS レコードを追加する等)が必要ですが、以下の記事のように、LangChain 等を用いて自前で実装することでドメイン認証なしで実現することもできます。 blog.g-gen.co.jp 名称の変遷 Vertex AI Search の名称は、繰り返し変更されてきました。名称の変遷は以下のとおりです。 名称 時期 備考 Generative AI App Builder 2023-03-29 Preview 公開。略称 Gen App Builder Vertex AI Search and Conversation 2023-08-29 一般公開 (GA)。Vertex AI Search と Vertex AI Agents の2機能を含む Vertex AI Agent Builder 2024-04-24 改名のみ AI Applications 2025-04-02 改名のみ Vertex AI Search 2025-10-02 Vertex AI Agents が分離され Vertex AI Search が単独プロダクト化 2025年10月2日以前は、Vertex AI Search と Vertex AI Agents(現名称 Conversational Agents)の2機能が単一プロダクトとして提供されていました。2025年10月2日に Vertex AI Search が単一プロダクトとして独立し、プロダクト名称も Vertex AI Search になりました。 これらの名称変更により、ドキュメントやコンソール上の表記が変更されましたが、API エンドポイント名( discoveryengine.googleapis.com )等は変更されません。また IAM ロール名や API 名などは、Discovery Engine(ディスカバリーエンジン)という名称が使われています。これらはすべて、同じものを指していると考えて差し支えありません。 参考 : Vertex AI Search release notes - August 29, 2023 参考 : Vertex AI Search release notes - April 24, 2024 参考 : Vertex AI Search release notes - April 02, 2025 参考 : Vertex AI Search release notes - April 02, 2025 公式ドキュメントや解説動画、パートナー各社の資料やブログ記事などには、旧来の名称で記載されていることがあります。 ユースケース 社内検索エンジン この例は、Vertex AI Search を使った社内検索エンジンです。テキストや Microsoft Word で作成された社内ドキュメントをデータソースとして、社内従業員向けのポータルサイトから利用します。 このツールでは、人間が普段使うような自然言語を使った検索ができます。また結果の返答には、AI によって生成された質問に対する回答の要約や、対象ドキュメントへのリンクを含ませることができます。一度質問したことに対して、追加質問を重ねて行うようなフォローアップ検索を行うこともできます。 社内検索エンジン 顧客対応補助 以下の例は、Vertex AI Search と、Vertex AI による Gemini API の呼び出しと組み合わせることで、顧客からの問い合わせ業務を補助する仕組みを示しています。 顧客対応補助 AI エージェントのバックエンド AI エージェント とは、生成 AI が複数ステップのタスクをこなし、人間の代わりに仕事をする仕組みのことです。Vertex AI Search は、AI エージェントを構成する部品の1つとして使用することができます。 以下の記事は、G-gen が提供する AI エージェントサービスのアーキテクチャについての解説です。バックエンドに Vertex AI Search を使用しています。 blog.g-gen.co.jp 料金 クエリ料金とエディション Vertex AI Search では、 クエリ回数 に対する従量課金が発生します。また、利用するオプション機能ごとに単価が異なります。これに加えて、いずれの機能でも データインデックス料金 が発生します。これは回答に用いる根拠データとして取り込んだデータの容量に応じて課金されます。 クエリ回数の料金単価は、作成した検索アプリで Search Standard Edition を選ぶか、 Search Enterprise Edition を選ぶかによって異なります。 エディション 検索機能 AI 機能 単価 Search Standard Edition ・構造化データストアに対する検索 ・非構造化データストアに対する検索 なし $1.50 / 1,000 クエリ Search Enterprise Edition ・構造化データストアに対する検索 ・非構造化データストアに対する検索 ・Web データストアに対する検索 ・回答や要約の生成 ・フォローアップ検索 $4.00 / 1,000 クエリ さらに上記に加えて、アプリで Advanced Generative Answers を有効化すると、 上記に加えて $4.00 / 1,000 クエリの料金が加算されます。Advanced Generative Answers では、フォローアップ検索のサジェスト、クエリの自動書き換えなどの複雑なハンドリング、マルチモーダル機能などが含まれます。例えば、Search Enterprise Edition のアプリで Advanced Generative Answers を有効化すると、クエリ単価は $8.00 / 1,000 クエリとなります。 上記の表は、2025年6月現在のものです。最新の料金単価は、以下の公式ページを参照してください。Vertex AI Search の料金はたびたび改定されており、最新の情報を得るには英語版のページを見ることが推奨されます。日本語版ページは、遅れて翻訳される可能性があります。 参考 : Vertex AI Search pricing インデックスストレージ料金 加えて Vertex AI Search では、 インデックスストレージ の保管料金が発生します。 Vertex AI Search でデータストアを作成すると、内部的なインデックスデータベースにデータが登録されます。このインデックスストレージの保管料金が発生します。2025年6月現在の単価は、$5.00 / GiB / 月 です。インデックスのリフレッシュはコストに影響しません。 また Web サイトデータストアの場合、この容量は「500 KiB × ページ数」で計算されます。 参考 : Vertex AI Search pricing - Index storage pricing 無料枠 請求先アカウントごとに、無料で毎月10,000クエリを実行できます。 さらにインデックスストレージ料金には、毎月 10 GiB の無料枠が用意されています。 構成可能な料金(Configurable pricing) 月間1500万クエリを超える、特に利用ボリュームの多いワークロード向けに、Vertex AI Search では 構成可能な料金 (Configurable pricing)という料金体系も用意されています。 検索クエリ料金とインデックスストレージ料金のそれぞれに対して、月額サブスクリプション形式での支払を設定することで、コスト最適化を図ることができます。 注意点として、Google Cloud プロジェクトで一度「構成可能な料金」を有効化すると、 無効にすることはできません 。有効化すると、指定したリソース確保量に応じて料金が1時間単位で発生するようになりますので、有効化の可否やリソースの確保量は 慎重に検討 してください。 参考 : カスタム検索の構成可能な料金を設定する 検索・要約機能の概要 検索と要約 Vertex AI Search では、BigQuery テーブルに格納された構造化データや、Cloud Storage や Google ドライブに格納された TXT、HTML、PDF、docs、pptx、xlsx などの非構造化データに対して検索を実行できます。 検索は セマンティック検索 で行われます。セマンティック検索は意味論検索とも呼ばれ、文字列一致ではなく、言葉の意味を捉えた検索のことです。Vertex AI Search では データストア と呼ばれるデータベースに、ベクトル化されたデータソースのデータをインデックス情報として格納して、これに対して検索を実行します。 また Google 検索のように、キーワード検索、画像検索、検索キーワードのオートコンプリートやスニペット表示ができるほか、利用者の検索傾向を分析する機能なども備えています。 Vertex AI Search の検索機能は Web API 経由で呼び出すことができるため、上記のような高度な検索機能を、簡単に自社サイトやアプリケーションに組み込むことができます。 参考 : Introduction to Vertex AI Search 開発 UI における検索プレビュー画面 要約 Vertex AI Search は生成 AI モデル Gemini と統合されており、検索結果から要約や質問に対する回答を生成したり、フォローアップ検索(深堀り質問)を行うことができます。 社内ドキュメントなどを Cloud Storage に格納しておき、これを Vertex AI Search のデータストアとして登録しておくことで、「当社の有給休暇の付与日数は?」といった質問に答えさせることができます。 また、情報の引用元を明示させることができます。 要約結果の表示 対応言語 日本語、英語、フランス語、スペイン語、ポルトガル語、ヒンディー語などの言語に対応しています。 日本語は2025年4月現在、Healthcare search を除いたすべての機能でサポートされています。 参考 : Languages 検索・要約機能の詳細 オートコンプリート Vertex AI Search では、Google 検索のような検索キーワードのオートコンプリート(予測入力)を有効化できます。 データストアが構造化データや非構造化データの場合は、データストア内のドキュメントに基づいてオートコンプリートが生成されます。データストアが Web サイトの場合は、過去の検索履歴から生成されます。過去履歴の反映は利用開始から数日かかります。 参考 : 予測入力を設定する スニペットと抽出コンテンツ Vertex AI Search ではスニペットや抽出回答(Extractive answers)、抽出セグメント(Extractive segments)を返すこともできます。 スニペット は、各検索結果の下に表示される、ドキュメントの抜粋です。 抽出回答 (Extractive answers)は、検索結果から関連性が高い一部分を抜粋したものです。Google 検索でいうところの「強調スニペット」がこれにあたり、検索結果の中でも最も関連性が高いと思われるドキュメントの一部抜粋を最上部に表示しています。 抽出セグメント (Extractive segments)は、抽出回答よりさらに長く抜粋したもので、LLM にインプットして新たな回答文を生成する時などに用います。 以下のスクリーンショットは Vertex AI Search ではありませんが、参考として Google 検索でスニペットや抽出回答がどの部分に当たるかを示したものです。 参考 : スニペットと抽出コンテンツを取得する Google 検索の場合 (Vertex AI Search の画面ではありません) 検索結果の要約 検索結果の要約 (search summary)を有効化すると、上位の検索結果をサマリして検索結果として表示する機能です。要約は前述の抽出回答(Extractive answers)から生成されます。 要約には引用元(Citations)ドキュメントを明示させたり、情報サイトを求めるだけでサマリを作るべきではないクエリにはサマリを返さない、脚注に情報ソースを表示など、豊富なオプションがあります。プロンプトを挿入して、要約結果をカスタマイズすることも可能です(Customized summaries)。 当機能の利用には、アプリで Search Enterprise Edition を有効化する必要があります。また Web サイトへの検索の場合は、データストアで Advanced website indexing の有効化が必要であり、対象 Web サイトのドメイン認証が必要になります(後述)。 参考 : 検索のサマリーを取得する フォローアップ検索 フォローアップ検索 (Search with follow-ups)は生成 AI の機能を活かしてより先進的な検索を行う機能です。以下が含まれます。 Natural language query processing : 自然言語による質問文の意図を汲んで検索結果を返す Context awareness : 先の質問を背景として考慮に入れて回答する Multi-turn(マルチターン): ある質問に続けて追加の質問が可能 すなわち以下のようなやりとりができることであると、公式ドキュメントでは示されています。 あなた : メキシコで休暇を過ごすのにベストな時期はいつ? フォローアップ検索 : メキシコでの休暇に最適な時期は、11月から4月の乾季です。 あなた : 為替レートはいくら? フォローアップ検索 : 1USD は約17.65メキシコペソです。 あなた : 12 月の平均気温は何度? フォローアップ検索 : 平均気温は70~78°F の範囲です。カンクンの平均気温は約77°F です。 フォローアップ検索を利用するには、アプリで Search Enterprise Edition の有効化が必要です。 参考 : 回答とフォローアップを取得する - フォローアップの質問のコマンド また、フォローアップ検索のサジェスト(提案)を表示させることもできます。 フォローアップ検索のサジェスト(提案) アプリとの統合 API による独自アプリとの統合 Vertex AI Search には API が用意されており、独自開発したアプリケーションから API を呼び出すことで、検索結果を得たり、AI による回答を得ることができます。 ドキュメントの検索結果を得る search メソッドや、検索結果と AI による回答の両方を得る answer メソッドなどが用意されています。 2025年5月現在、answer メソッドは統合検索アプリ(複数のデータストアに接続されているアプリ。Blended Search とも呼ぶ)では Allowlisted Preview 段階であり、Google に申請が必要です。 参考 : 検索結果を取得する 参考 : 回答とフォローアップを取得する ウェブサイトへのウィジェット追加 Vertex AI Search を使った検索結果を得るには、前述の API へリクエストする方法のほか、JavaScript で ウィジェット として埋め込む方法もあります。 Vertex AI Search の開発 UI から、自社ウェブサイト等に簡単にウィジェットを追加するための JavaScript コードを得ることができます。 ウィジェット方式では JWT または OAuth による認証をかけるか、公開検索エンジンとするかを、アプリごとに選択可能です。 さらに許可対象とする呼び出し元ドメイン名もホワイトリスト登録できます。 参考 : 検索ウィジェットをウェブページに追加する 標準のウィジェットの UI 分析 Google Cloud コンソールの分析画面では、検索の回数、クリックスルーレート(CTR)、デバイス情報などの分析情報を確認することができます。 参考 : 分析情報を表示 オプション Search Enterprise Edition Vertex AI Search アプリを作成する際に、 Search Enterprise Edition (Enterprise エディションの機能とも)を有効化することができます。アプリ作成時に有効化を選択できるほか、アプリ作成後の変更も可能です。なお、ウェブサイト検索を行うには Search Enterprise Edition の有効化が必須です。 Search Enterprise Edition を有効化すると、以下の機能を利用できます。 検索結果から要約の生成 フォローアップ検索 抽出回答(Extractive answers、Extractive segments) ウェブサイト検索 Search Enterprise Edition を有効化すると、クエリあたりの料金単価に追加料金が加算されます。 参考 : AI Applications pricing - Vertex AI Search pricing Advanced Generative Answers(高度な LLM 機能) Vertex AI Search アプリを作成する際に Advanced Generative Answers (高度な生成回答、もしくは高度な LLM 機能とも)を有効化することができます。作成後に変更することも可能です。 Advanced Generative Answers を有効化すると、フォローアップ検索のサジェストや、複雑なクエリのハンドリングが利用可能になります。ただし、クエリあたりの料金単価に追加料金が加算されます。 参考 : AI Applications pricing - Vertex AI Search pricing Enterprise エディションの機能と高度な LLM 機能の有効化 なお2025年5月の料金改定前は、このオプションは LLM アドオンの Basic(基本)および Advanced(上級者向け)という2つのオプションとして用意されていました。料金改定に伴い、Advanced Generative Answers として一本化され、またクエリ単価も値下げされました。 機能と対応するオプション ある機能を使うためにどのオプションを有効化する必要があるかは、以下のドキュメントに表でまとめられていますので、ご参照ください。 参考 : 高度な機能について アプリとデータストア アプリ Vertex AI Search の設定は アプリ (Apps)というリソースで管理され、それぞれのアプリが使うデータは データストア というリソースとして管理されます。 アプリ (Apps)は「カスタム検索アプリ」「ウェブサイト検索アプリ」など、機能を選んで作成する、ひとまとまりの設定単位です。 例として、カスタム検索アプリを作成するときは、アプリ名やエディション、有効化する検索機能(オートコンプリートや LLM による要約など)、検索対象のデータストア、などを選択します。 アプリの一覧 データストア Vertex AI Search は、 データストア として取り込んだ情報を基に、利用者からの質問に答えたり、検索を行ったり、エージェントとしてタスクを行ったりします。 データストアの一覧 データストアにはウェブサイト、テキストドキュメントなどを取り込むことが可能です。より正確には、以下のようなフォーマットのデータを取り込み可能です。 データストアタイプ フォーマット 取り込み方式 Web HTML / PDF ドメイン名や URL で指定。Google Search Index で収集 非構造化データ HTML / TXT / JSON / XHTML / XML / PPTX / DOCX / XLSX / PDF Cloud Storage または API 経由でアップロード 構造化データ CSV / JSONL / BigQuery table Cloud Storage、BigQuery、API 経由でアップロード 参考 : アプリとデータストアについて 参考 : 取り込み用にデータを準備する Web データストア Web タイプのデータストアは、その名の通り Web サイトを取り込むためのデータストアです。Web サイト内の HTML のほか、PDF ファイルもインデックスされます。サイト全体を取り込むことも、一部を指定して取り込んだり、逆に一部を除外することもできます。 Google が検索エンジンに Web サイトを取り込むときに用いる「Google Search Index」と同じ仕組みで情報が収集されるため、Web サイトは インターネットからアクセスできる 必要があります。 また Advanced website indexing (ウェブサイトの高度なインデックス登録)を有効化することで、検索結果の要約やフォローアップ検索等を実現できます。これを有効化するにはサイトの正当な所有者であることを示すため ドメインの認証 が必要です。ドメインの認証には Google 検索の最適化にも使われる Google Search Console と呼ばれる Google の Web ツールを使います。「ドメインの DNS ゾーンに指定の TXT レコードを追加する」「ページに Google Analytics のトラッキングコードを挿入する」「HTML ファイルを特定 URL にアップロードする」などの方法でドメイン認証を行うことができます。ドメイン認証は、Advanced でない通常のインデックス作成であれば不要です。 参考 : 高度な機能について - ウェブサイトの高度なインデックス登録 参考 : アプリとデータストアについて - ウェブサイトのデータ 構造化データストア 構造化 (Structured)タイプのデータストアは、FAQ(よくある質問)や商品カタログ等を取り込むために用います。 構造化データストアの作成時は、BigQuery テーブルか、JSON(NDJSON/JSONL)形式で Cloud Storage バケット上にデータを用意します。また、API 経由でデータを投入することも可能です。スキーマは自動検知させることも、明示的にスキーマ情報を与えることもできます。 参考 : アプリとデータストアについて - データストアとアプリ - 構造化データ ただし BigQuery の外部テーブルは、Vertex AI Search のデータストアとして取り込むことはできませんのでご注意ください。 参考 : Prepare data for ingesting - Structured data - BigQuery 非構造化データストア 非構造化 (Unstructured)タイプのデータストアは、PDF や HTML、TXT ファイル等をファイルとして直接取り込むためのデータストアです。また PPTX(PowerPoint)や DOCX(Word)、XLSX(Excel)などの形式にも対応しています。 ファイルは、原則として Cloud Storage バケット経由でアップロードします。アップロードするファイルには「タイトル」と「URL」をメタデータとして付与することができ、付与した場合はチャットボットが利用者に返答する際に、その URL を添付して回答することができます。これにより利用者は、インターネットに接していない内部 Web サイトを情報源として参照することができます。 データストアへの取り込み指示は、メタデータ無しの場合は単純に Cloud Storage バケットやそのパスを指定します。メタデータ有りの場合は、取り込み対象ファイルの Cloud Storage URL とメタデータ情報を JSONL 形式で記述したり、BigQuery テーブルに格納して、これに対して取り込みを指示します。 参考 : アプリとデータストアについて - 非構造化データ 非構造化データのパース(解析) Vertex AI Search では非構造化データとして Cloud Storage に PDF ファイル、HTML ファイル、docx ファイル、pptx ファイル、xlsx ファイル、txt ファイル等を検索対象とすることができます。これらのファイルを配置した非構造化データストアを作成する際に、 パーサー と呼ばれるファイルをパース(解析)するための処理の種類を選択できます。パーサーには以下の種類があります。 パーサー名 対応フォーマット デジタルパーサー (Digital parser) HTML、PDF、docx、pptx、txt、xlsx PDF の OCR 解析 (OCR parsing for PDFs) PDF レイアウトパーサー (Layout parser) HTML、PDF、docx、pptx、xlsx デフォルトでは デジタルパーサー が選択されます。Vertex AI Search は、ファイルにデジタル情報として格納されたテキストを検出します。デジタルパーサーは、段落情報を検知することができます。 もし PDF が手書きの文字をスキャンしたものであれば、 OCR パーサー を選択して文字を読み取ることができます。 レイアウトパーサー を選択した場合は、ファイルから段落、表、画像、タイトル、見出し要素等が検出されます。レイアウトパーサーを選択するには、 ドキュメントのチャンク化 (document chunking for RAG)を有効化する必要があります。ドキュメントのチャンク化を有効化すると、ファイルは非構造化データストア内でチャンク化(ファイルを分割して細かい情報にすること)されます。これにより、データストアは RAG に最適化されます。また Gemini レイアウト解析 を有効化すると、PDF ファイルの解析に生成 AI モデル Gemini が使用され、品質向上が期待できます。 参考 : アプリとデータストアについて - データストアとアプリ - 非構造化データ 参考 : ドキュメントを解析してチャンク処理する 杉村 勇馬 (記事一覧) 執行役員 CTO 元警察官という経歴を持つ IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 認定資格および Google Cloud 認定資格はすべて取得。X(旧 Twitter)では Google Cloud や Google Workspace のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の武井です。 Config Controller (Config Sync) で Google Cloud (旧称 GCP) のブループリントを利用して GitOps なリソース管理を実現するための検証を行いました。当記事ではその方法をご紹介します。 Config Controller (Config Sync) はじめに 当記事の概要 アーキテクチャ 構成 構成要素 一連の処理 前提知識 (用語解説) Google Cloud ブループリント ランディングゾーン Anthos Config Management kpt レンダリング setters 関連記事 事前準備 参考ドキュメント 実行環境 Project / VPC / Subnet の準備 環境変数と gcloud の設定 Config Controller の設定 API 有効化 Config Controller クラスタの作成 Config Controller クラスタの認証 Config Controller クラスタへの権限付与 GitOps パイプラインの設定 成果物の確認 API 有効化 GitOps ブループリントのダウンロード setters の定義 レンダリング GitOps パイプラインのデプロイ source-repo リポジトリへの Push ランディングゾーンのデプロイ 概要 ランディングゾーンブループリントのダウンロード setters の定義 source-repo リポジトリへの Push トラブルシューティング 想定エラー エラー原因 ファイル確認 対処方法 nomos status の再確認 動作確認 リソース GitOps パイプライン リポジトリ Cloud Build トリガー Config Sync さいごに はじめに 当記事の概要 Config Controller で Google Cloud のブループリントを利用すると以下を実現することができます。 GitOps パイプライン のデプロイ GitOps パイプライン を用い、 ランディングゾーン を始めとする各種 Google Cloud リソース構成のデプロイ・管理 当記事では実際の検証結果をふまえ分かりやすく解説します。 アーキテクチャ 構成 前述の概要を図示すると以下のとおりです。 構成図 構成要素 各構成要素とそれらが担う役割 (処理) の概要を説明します。なお、 GitOps パイプライン とは以下表の #3 ~ #5 から構成される CI/CD パイプラインのことを指します。 # 構成要素 役割 1 GitHub Google Cloud ブループリント (テンプレート) を管理する Git リポジトリ 2 setters ブループリント上の設定値を一元管理する機能 3 source-repo setters を定義したブループリントが格納される Git リポジトリ 4 Cloud Build setters から環境固有のマニフェストをレンダリングする kpt 関数 5 deployment-repo レンダリングされたマニフェストが格納される Git リポジトリ 6 Config Sync Config Controller に GitOps サービスを提供するコンポーネント 7 Config Controller マニフェストにもとづきリソースをデプロイ管理するコンポーネント 一連の処理 setters を定義したブループリントを GitOps パイプラインに push すると、Cloud Build による kpt 関数 (レンダリング) が実行され、各環境固有のマニフェストが自動的に生成されます。 Config Sync はレンダリングされたマニフェストが格納されたリポジトリを定期的に参照します。参照結果は Config Controller に連携され、結果に基づくリソースの管理やデプロイを自動実行します。 前提知識 (用語解説) Google Cloud ブループリント Google Cloud には ブループリント と呼ばれる、Google Cloud 上のリソースやサービスの構成を宣言的に管理するためのフレームワークが存在します。 ブループリントには ランディングゾーン (後述) を始め、様々な構成に関するベストプラクティスが提供されており、それらを利用することで、企業や組織は Google Cloud の構成を一貫性のある方法でデプロイ・管理することができます。 ランディングゾーン ランディングゾーン とは、「クラウド上に新しい環境やワークロードを迅速かつ安全にデプロイするための事前定義された設計パターン」を表す概念 (用語) です。 セキュリティ、運用、ネットワーク設計、アクセス管理など、クラウド基盤を構築する上でのベストプラクティスが考慮された形で設計されており、上記ブループリントの landing-zone ディレクトリ配下にはマニフェストファイル一式が用意されています。 Anthos Config Management Anthos Config Management は、Google Cloud リソースのプロビジョニングとオーケストレーションを行うサービスです。 Config Controller 、 Config Sync 、 Policy Controller という 3つ のコンポーネントから構成されます。 詳細についてはこちらの記事をご確認ください。 blog.g-gen.co.jp kpt kpt とは マニフェストファイルを操作(パッケージ化、pull、更新、変更)するための Google が開発したオープンソースツールです。 今回のケースでは、当ツールを使って Google Cloud のブループリントを取得したり、取得したブループリント (テンプレート) から、適用先環境に合わせたマニフェストファイルを レンダリング (後述) したりします。 レンダリング 前述の通り、Config Controller や kpt の文脈におけるレンダリングとは 「雛形となるブループリントから各環境固有のマニフェストファイルを生成する処理のこと」 を指しており、その処理の中で kpt の setters (後述) という機能が用いられています。 setters setters とは、 「マニフェストファイル内の特定の値を一元的に管理・更新するための機能」 のことで、Linux でいう環境変数の概念と同じです。 Google Cloud のブループリントは様々なプロジェクトや環境で再利用することが想定されます。当機能を利用すれば、ブループリント上の初期値をそれぞれの環境やプロジェクト固有の値 (例: プロジェクト ID やリージョン名) に簡単に更新でき、ブループリントを効率的に利用できます。 関連記事 以下の記事でも Config Controller について解説しています。こちらもあわせてご参照ください。 blog.g-gen.co.jp blog.g-gen.co.jp 事前準備 参考ドキュメント 今回の検証は以下の公式ガイドを参考にして実施しています。 参考: ランディング ゾーンのブループリントをデプロイする 実行環境 本設定では gcloud コマンドの他に、 kubectl 、 nomos 、 kpt といったコマンドを利用するため、これらがプリインストールされた Cloud Shell を利用します。 # コマンド which kubectl nomos kpt # 戻り値 /usr/bin/kubectl /usr/bin/nomos /usr/bin/kpt Project / VPC / Subnet の準備 Config Controller (実体は GKE クラスタ) を作成する Project / VPC / Subent を準備します。 ※ 作成方法の解説は割愛します。 # リソース リソース名 1 Project (Project ID) sandbox-cc-test-prj 2 VPC sandbox-cc-test-vpc 3 Subnet sandbox-cc-test-subnet 環境変数と gcloud の設定 Cloud Shell で環境変数と gcloud コマンドのプロジェクト設定を行います。 ※ 設定途中で Cloud Shell の接続が切れた場合は再度環境変数を設定してください。 # CONFIG_CONTROLLER_NAME は 25 文字以内 export PROJECT_ID =sandbox-cc-test-prj export CONFIG_CONTROLLER_NAME =config-controller-1 export BILLING_ACCOUNT = $( gcloud alpha billing projects describe $PROJECT_ID \ ' --format=value(billingAccountName) ' | sed ' s/.*\/// ' ) export ORG_ID = $( gcloud projects get-ancestors ${PROJECT_ID} --format= ' get(id) ' | tail -1 ) # gcloud の設定 (向き先となるプロジェクトの指定) gcloud config set project ${PROJECT_ID} # 戻り値 Updated property [ core/project ] . Config Controller の設定 API 有効化 Config Controller API と GKE API を有効にします。 # コマンド gcloud services enable krmapihosting.googleapis.com container.googleapis.com # 戻り値 Operation " operations/acf.p2-566560710327-a294ce1f-f676-4214-9e8e-1340fe0bee36 " finished successfully. Config Controller クラスタの作成 クラスタを以下の条件で作成します。(この操作には時間がかかる場合があります) Autopilot asia-northeast1 (東京リージョン) 事前準備で作成した VPC / Subnet # コマンド gcloud anthos config controller create ${CONFIG_CONTROLLER_NAME} \ --location=asia-northeast1 \ --network=sandbox-cc-test-vpc \ --subnet=sandbox-cc-test-subnet \ --full-management # 戻り値 Create request issued for: [ config-controller -1] Waiting for operation [ projects/sandbox-cc-test-prj/locations/asia-northeast1/operations/operation-1692153896333-6030147e571e0-03bfd650-4d82d756 ] to comple te...done. Created instance [ config-controller -1] . Fetching cluster endpoint and auth data. kubeconfig entry generated for krmapihost-config-controller -1 . クラスタの作成が正常に完了したことを確認します。 # コマンド gcloud anthos config controller list --location=asia-northeast1 # 戻り値 NAME: config-controller-1 LOCATION: asia-northeast1 STATE: RUNNING Config Controller クラスタの認証 クラスタの認証情報を取得します。 # コマンド gcloud anthos config controller get-credentials ${CONFIG_CONTROLLER_NAME} \ --location=asia-northeast1 # 戻り値 Fetching cluster endpoint and auth data. kubeconfig entry generated for krmapihost-config-controller -1 . Config Controller クラスタへの権限付与 クラスタのサービスアカウント ( service-PROJECT_NUMBER@gcp-sa-yakima.iam.gserviceaccount.com ) に、プロジェクトおよび組織レベルで管理権限を付与します。 # 環境変数設定 export SA_EMAIL = " $( kubectl get ConfigConnectorContext -n config-control \ -o jsonpath = ' {.items[0].spec.googleServiceAccount} ' 2 > /dev/null ) " # IAM Policy 設定 (プロジェクトレベル) gcloud projects add-iam-policy-binding " ${PROJECT_ID} " \ --member " serviceAccount: ${SA_EMAIL} " \ --role " roles/owner " \ --project " ${PROJECT_ID} " # 戻り値 (一部のみ抜粋) Updated IAM policy for project [ sandbox-cc-test-prj ] . bindings: - members: - serviceAccount:service-566560710327@gcp-sa-yakima.iam.gserviceaccount.com - user:user@example.co.jp role: roles/owner etag: BwYDAjdY4F0 = version: 1 # IAM Policy 設定 (組織レベル) gcloud organizations add-iam-policy-binding $ORG_ID \ --role=roles/resourcemanager.organizationAdmin \ --condition=None \ --member= " serviceAccount: ${SA_EMAIL} " # 戻り値 (一部のみ抜粋) Updated IAM policy for organization [ 999999999999 ] . bindings: - members: - serviceAccount:service-566560710327@gcp-sa-yakima.iam.gserviceaccount.com role: roles/resourcemanager.organizationAdmin etag: BwYDAoGjp8Q = version: 1 GitOps パイプラインの設定 成果物の確認 このプロセスでは以下のリソースで構成される GitOps パイプラインを作成します。 # リソース 役割 1 source-repo ローカルから push されるファイル一式を格納するリポジトリ 2 Build トリガー 上記からレンダリングされたファイルを deployment-repo に push 3 deployment-repo レンダリングされたファイル一式を格納するリポジトリ 4 Config Sync deployment-repo に接続する GitOps サービス API 有効化 Cloud Source Repositories API と Resource Manager API を有効にします。 # コマンド gcloud services enable sourcerepo.googleapis.com cloudresourcemanager.googleapis.com # 戻り値 Operation " operations/acat.p2-566560710327-99c27c46-29ce-4747-a867-eea8ff811998 " finished successfully. GitOps ブループリントのダウンロード kpt を使用して GitHub から Cloud Shell に GitOps ブループリント をダウンロードして GitOps パイプラインをデプロイします。 バージョンは GitOps ブループリントの GHANGELOG.md から確認できます。 2023年8月時点の最新バージョンは v0.6.1 # 最新の GitOps ブループリントをダウンロード kpt pkg get https://github.com/GoogleCloudPlatform/blueprints.git/catalog/gitops@gitops-blueprint-v0. 6 . 1 gitops # 戻り値 Package " gitops " : Fetching https://github.com/GoogleCloudPlatform/blueprints@gitops-blueprint-v0. 6 . 1 From https://github.com/GoogleCloudPlatform/blueprints * tag gitops-blueprint-v0. 6 . 1 - > FETCH_HEAD Adding package " catalog/gitops " . Fetched 1 package ( s ) . Cloud Shell にダウンロードできたことを確認します。 # コマンド tree gitops/ # 戻り値 gitops/ ├── CHANGELOG.md ├── cloudbuild-iam.yaml ├── configsync │ ├── config-management.yaml │ ├── configsync-iam.yaml │ ├── Kptfile │ ├── README.md │ ├── rootsync.yaml │ ├── setters.yaml │ └── validation.yaml ├── hydration-trigger.yaml ├── Kptfile ├── README.md ├── services.yaml ├── setters.yaml └── source-repositories.yaml 1 directory, 15 files 主要なマニフェストファイルの役割は以下の通りです。 # gitops/ 以下 役割 1 Kptfile kpt の設定ファイル 2 setters.yaml gitops/ 以下の各マニフェストファイルの設定値を一元管理 3 services.yaml パイプラインが使用するサービス API を管理 4 source-repositories.yaml リポジトリ (Cloud Source Repositories, CSR) を管理 5 cloudbuild-iam.yaml Cloud Build サービスアカウントの IAM Policy を管理 6 hydration-trigger.yaml リポジトリ間で行われる処理 (Cloud Build トリガー) を管理 # gitops/configsync 以下 役割 1 configsync-iam.yaml Config Sync と CSR 間の認証 (Workload Identity 方式) を管理 2 rootsync.yaml Config Sync (CSR に対する GitOps サービス) を管理 setters の定義 ダウンロードしたブループリントを setters (セッター) を使って自身の環境向けにカスタマイズします。 setters とは kpt の機能の一つで、 マニフェスト内の特定の値を一元的に変更・設定するための役割 を提供します。簡単に表すとマニフェスト内の変数を管理するイメージです。 これにより、マニフェスト内の初期値が setters を介して一意の値に置き換えられます。 setters は gitops/setters.yaml ファイル内で管理されていますので、そちらを編集します。 # 変更前 apiVersion : v1 kind : ConfigMap metadata : # kpt-merge: /setters name : setters annotations : config.kubernetes.io/local-config : "true" internal.kpt.dev/upstream-identifier : '|ConfigMap|default|setters' data : # This should be the project where you deployed Config Controller project-id : project-id project-number : "1234567890123" # This should be the name of your Config Controller instance cluster-name : cluster-name # You can leave these defaults namespace : config-control deployment-repo : deployment-repo source-repo : source-repo # 変更後、cluster-name / project-id / project-number を環境固有の値に置換 apiVersion : v1 kind : ConfigMap metadata : # kpt-merge: /setters name : setters annotations : config.kubernetes.io/local-config : "true" internal.kpt.dev/upstream-identifier : '|ConfigMap|default|setters' data : # This should be the project where you deployed Config Controller project-id : sandbox-cc-test-prj project-number : "566560710327" # This should be the name of your Config Controller instance cluster-name : config-controller-1 # You can leave these defaults namespace : config-control deployment-repo : deployment-repo source-repo : source-repo レンダリング 定義した setters を使ってブループリントをレンダリングします。 レンダリングとは、 setters.yaml で定義した内容にもどづきマニフェストを完成 (カスタマイズ) させる という意味に近しいです。 # コマンド kpt fn render gitops/ # 戻り値 Package " gitops/configsync " : [ RUNNING ] " gcr.io/kpt-fn/apply-setters:v0.1 " [ PASS ] " gcr.io/kpt-fn/apply-setters:v0.1 " in 4 .1s Results: [ info ] spec.clusterName: set field value to " cluster-name " [ info ] metadata.name: set field value to " sync-cluster-name " [ info ] metadata.namespace: set field value to " config-control " [ info ] metadata.annotations.cnrm.cloud.google.com/project-id: set field value to " project-id " ... ( 14 line ( s ) truncated, use ' --truncate-output=false ' to disable ) [ RUNNING ] " gcr.io/kpt-fn/starlark:v0.4 " [ PASS ] " gcr.io/kpt-fn/starlark:v0.4 " in 4 .4s Package " gitops " : [ RUNNING ] " gcr.io/kpt-fn/apply-setters:v0.1 " [ PASS ] " gcr.io/kpt-fn/apply-setters:v0.1 " in 300ms Results: [ info ] spec.clusterName: set field value to " config-controller-1 " [ info ] metadata.name: set field value to " sync-config-controller-1 " [ info ] metadata.namespace: set field value to " config-control " [ info ] metadata.annotations.cnrm.cloud.google.com/project-id: set field value to " sandbox-cc-test-prj " ... ( 38 line ( s ) truncated, use ' --truncate-output=false ' to disable ) Successfully executed 3 function ( s ) in 2 package ( s ) . レンダリング実行後、例えば gitops/source-repositories.yaml を確認すると project-id が定義した値に置換されたことが分かります。 # 変更前 apiVersion : sourcerepo.cnrm.cloud.google.com/v1beta1 kind : SourceRepoRepository metadata : # kpt-merge: config-control/source-repo name : source-repo # kpt-set: ${source-repo} namespace : config-control # kpt-set: ${namespace} annotations : cnrm.cloud.google.com/blueprint : cnrm/gitops/v0.6.1,kpt-pkg cnrm.cloud.google.com/project-id : project-id # kpt-set: ${project-id} internal.kpt.dev/upstream-identifier : 'sourcerepo.cnrm.cloud.google.com|SourceRepoRepository|config-control|source-repo' --- apiVersion : sourcerepo.cnrm.cloud.google.com/v1beta1 kind : SourceRepoRepository metadata : # kpt-merge: config-control/deployment-repo name : deployment-repo # kpt-set: ${deployment-repo} namespace : config-control # kpt-set: ${namespace} annotations : cnrm.cloud.google.com/blueprint : cnrm/gitops/v0.6.1,kpt-pkg cnrm.cloud.google.com/project-id : project-id # kpt-set: ${project-id} internal.kpt.dev/upstream-identifier : 'sourcerepo.cnrm.cloud.google.com|SourceRepoRepository|config-control|deployment-repo' # 変更後 apiVersion : sourcerepo.cnrm.cloud.google.com/v1beta1 kind : SourceRepoRepository metadata : # kpt-merge: config-control/source-repo name : source-repo # kpt-set: ${source-repo} namespace : config-control # kpt-set: ${namespace} annotations : cnrm.cloud.google.com/blueprint : cnrm/gitops/v0.6.1,kpt-pkg-fn cnrm.cloud.google.com/project-id : sandbox-cc-test-prj # kpt-set: ${project-id} internal.kpt.dev/upstream-identifier : 'sourcerepo.cnrm.cloud.google.com|SourceRepoRepository|config-control|source-repo' --- apiVersion : sourcerepo.cnrm.cloud.google.com/v1beta1 kind : SourceRepoRepository metadata : # kpt-merge: config-control/deployment-repo name : deployment-repo # kpt-set: ${deployment-repo} namespace : config-control # kpt-set: ${namespace} annotations : cnrm.cloud.google.com/blueprint : cnrm/gitops/v0.6.1,kpt-pkg-fn cnrm.cloud.google.com/project-id : sandbox-cc-test-prj # kpt-set: ${project-id} internal.kpt.dev/upstream-identifier : 'sourcerepo.cnrm.cloud.google.com|SourceRepoRepository|config-control|deployment-repo' また、 gitops/source-repositories.yaml ファイル以外も setters によってレンダリングされており、これらレンダリングされたファイル一式を使って GitOps パイプラインをデプロイします。 GitOps パイプラインのデプロイ kubectl apply でレンダリングしたファイル一式を適用し、GitOps パイプラインをデプロイします。 # コマンド kubectl apply --wait -f gitops/ --recursive # 戻り値 iampartialpolicy.iam.cnrm.cloud.google.com/deployment-repo-cloudbuild-write created iampartialpolicy.iam.cnrm.cloud.google.com/source-repo-cloudbuild-read created configmanagement.configmanagement.gke.io/config-management configured iamserviceaccount.iam.cnrm.cloud.google.com/sync-config-controller-1 created iampartialpolicy.iam.cnrm.cloud.google.com/sync-config-controller-1 created iampartialpolicy.iam.cnrm.cloud.google.com/source-reader-sync-config-controller-1-sandbox-cc-test-prj created rootsync.configsync.gke.io/root-sync created configmap/setters created cloudbuildtrigger.cloudbuild.cnrm.cloud.google.com/source-repo-cicd-trigger created service.serviceusage.cnrm.cloud.google.com/sourcerepo.googleapis.com created service.serviceusage.cnrm.cloud.google.com/cloudbuild.googleapis.com created configmap/setters configured sourcereporepository.sourcerepo.cnrm.cloud.google.com/source-repo created sourcereporepository.sourcerepo.cnrm.cloud.google.com/deployment-repo created error: resource mapping not found for name: " validate-cluster-name-length " namespace: "" from " gitops/configsync/validation.yaml " : no matches for kind " StarlarkRun " in version " fn.kpt.dev/v1alpha1 " ensure CRDs are installed first リポジトリを作成します。 # コマンド kubectl wait --for=condition=READY -f gitops/source-repositories.yaml # 戻り値 sourcereporepository.sourcerepo.cnrm.cloud.google.com/source-repo condition met sourcereporepository.sourcerepo.cnrm.cloud.google.com/deployment-repo condition met 上記実行後、 source-repo と deployment-repo の 2つのリポジトリが作成されました。 # コマンド gcloud source repos list # 戻り値 REPO_NAME: deployment-repo PROJECT_ID: sandbox-cc-test-prj URL: https:// source .developers.google.com/p/sandbox-cc-test-prj/ r /deployment-repo REPO_NAME: source-repo PROJECT_ID: sandbox-cc-test-prj URL: https:// source .developers.google.com/p/sandbox-cc-test-prj/ r /source-repo source-repo と deployment-repo の 2つのリポジトリが作成 また、以下のコマンドで Build トリガーと Config Sync が作成されたことを確認できます。 # コマンド kubectl get gcp -n config-control -o yaml \ | grep " ^ name: \\ |message " # 戻り値 name: source-repo-cicd-trigger message: The resource is up to date name: deployment-repo-cloudbuild-write message: The resource is up to date name: source-reader-sync-config-controller-1-sandbox-cc-test-prj message: The resource is up to date name: source-repo-cloudbuild-read message: The resource is up to date name: sync-config-controller-1 message: The resource is up to date name: sync-config-controller-1 message: The resource is up to date name: projects/sandbox-cc-test-prj/serviceAccounts/sync-config-controller-1@sandbox-cc-test-prj.iam.gserviceaccount.com name: cloudbuild.googleapis.com message: The resource is up to date name: sourcerepo.googleapis.com message: The resource is up to date name: deployment-repo message: The resource is up to date name: source-repo message: The resource is up to date source-repo リポジトリへの Push source-repo リポジトリに GitOps パイプラインのデプロイに使用したファイル一式を push します。 まず、リポジトリのデフォルトブランチを main に設定します。 # コマンド (戻り値なし) git config --global init.defaultBranch main 次に、 gcloud source repos clone コマンドで source-repo リポジトリを Cloud Shell にクローンします。 # コマンド gcloud source repos clone source-repo # 戻り値 Cloning into ' /home/user/gitops_test_20230816/source-repo ' ... warning: You appear to have cloned an empty repository. Project [ sandbox-cc-test-prj ] repository [ source-repo ] was cloned to [ /home/user/gitops_test_20230816/source-repo ] . クローンしたリポジトリに gitops/ ディレクトリを移動します。 # 移動前の状態確認 ls -l total 8 drwx------ 3 user user 4096 Aug 16 12:52 gitops drwxr-xr-x 3 user user 4096 Aug 16 14:46 source-repo # 移動とその後の状態確認 mv gitops/ source-repo/ && ls -l total 4 drwxr-xr-x 4 user user 4096 Aug 16 14:51 source-repo 最後にファイル一式を commit / push します。 # コマンド (git commit まで) cd source-repo/ git add gitops/ git commit -m " Add GitOps blueprint " [ main (root-commit) f989115 ] Add GitOps blueprint 15 files changed, 726 insertions ( + ) create mode 100644 gitops/CHANGELOG.md create mode 100644 gitops/Kptfile create mode 100644 gitops/README.md create mode 100644 gitops/cloudbuild-iam.yaml create mode 100644 gitops/configsync/Kptfile create mode 100644 gitops/configsync/README.md create mode 100644 gitops/configsync/config-management.yaml create mode 100644 gitops/configsync/configsync-iam.yaml create mode 100644 gitops/configsync/rootsync.yaml create mode 100644 gitops/configsync/setters.yaml create mode 100644 gitops/configsync/validation.yaml create mode 100644 gitops/hydration-trigger.yaml create mode 100644 gitops/services.yaml create mode 100644 gitops/setters.yaml create mode 100644 gitops/source-repositories.yaml # コマンド (git push) git push [ main (root-commit) f989115 ] Add GitOps blueprint 15 files changed, 726 insertions ( + ) create mode 100644 gitops/CHANGELOG.md create mode 100644 gitops/Kptfile create mode 100644 gitops/README.md create mode 100644 gitops/cloudbuild-iam.yaml create mode 100644 gitops/configsync/Kptfile create mode 100644 gitops/configsync/README.md create mode 100644 gitops/configsync/config-management.yaml create mode 100644 gitops/configsync/configsync-iam.yaml create mode 100644 gitops/configsync/rootsync.yaml create mode 100644 gitops/configsync/setters.yaml create mode 100644 gitops/configsync/validation.yaml create mode 100644 gitops/hydration-trigger.yaml create mode 100644 gitops/services.yaml create mode 100644 gitops/setters.yaml create mode 100644 gitops/source-repositories.yaml source-repo リポジトリの main ブランチにファイル一式が格納されました。 push されたブループリント ランディングゾーンのデプロイ 概要 以下の手順でランディングゾーンをデプロイします。 ランディングゾーンのブループリントをダウンロードする setters を定義して source-repo に push する kpt によるレンダリング、 deployment-repo への push はパイプラインが行いますので、上記手順を実行するだけでランディングゾーンがデプロイされます。 ランディングゾーンブループリントのダウンロード kpt を使って ランディングゾーンのブループリント を Cloud Shell にダウンロードします。 # コマンド、@main でリポジトリの main ブランチから取得する kpt pkg get https://github.com/GoogleCloudPlatform/blueprints.git/catalog/landing-zone@main ./landing-zone # kpt pkg の戻り値は先程と同様のため割愛 Cloud Shell にダウンロードできたことを確認します。 # コマンド tree -a landing-zone/ # 戻り値 landing-zone/ ├── CHANGELOG.md ├── iam.yaml ├── Kptfile ├── logging │ └── .gitkeep # 空ディレクトリを維持するためのファイル ├── namespaces │ ├── hierarchy.yaml │ ├── logging.yaml │ ├── networking.yaml │ ├── policies.yaml │ └── projects.yaml ├── network │ └── .gitkeep # 空ディレクトリを維持するためのファイル ├── policies │ ├── deletion-policy-required-template.yaml │ ├── disable-guest-attributes.yaml │ ├── disable-iam-grants-default-sa.yaml │ ├── disable-nested-virtualization.yaml │ ├── disable-sa-key-creation.yaml │ ├── disable-serial-port.yaml │ ├── disable-vm-external-ip.yaml │ ├── enforce-uniform-bucket-lvl-access.yaml │ ├── folder-naming-constraint-template.yaml │ ├── restrict-cloud-sql-public-ip.yaml │ ├── restrict-lien-removal.yaml │ └── skip-default-network.yaml ├── projects │ └── .gitkeep # 空ディレクトリを維持するためのファイル ├── README.md ├── services.yaml └── setters.yaml 5 directories, 26 files 主要なマニフェストファイルの役割は以下の通りです。 # landing-zone/ 以下 役割 1 Kptfile kpt の設定ファイル 2 setters.yaml landing-zone/ 以下の各マニフェストファイルの設定値を一元管理 3 services.yaml ランディングゾーンが使用するサービス API を管理 4 iam.yaml グループアカウントの IAM Policy (組織レベル) を管理 namespaces ディレクトリには Config Controller が使用するネームスペース、 policies ディレクトリは組織ポリシーに関するマニフェストファイルが格納されています。 logging 、 network 、 projects ディレクトリは現時点で空ディレクトリを維持するためのファイルしかないためデプロイされるリソースはありませんが、別途拡張・カスムすることができます。 setters の定義 landing-zone/setters.yaml ファイルを編集して setters を定義します。 その前に上記ファイルの編集差分を後ほど確認できるよう、 git commit まで実行します。 # コマンド ls -l # 戻り値 total 8 drwx------ 3 user user 4096 Aug 16 12:52 gitops drwx------ 7 user user 4096 Aug 17 05:56 landing-zone # git commit まで実行 (レンダリング後の差分を diff するため push はしない) git add landing-zone/ git commit -m " Add landing zone " # git の戻り値は先程と同様のため割愛 あらためて landing-zone/setters.yaml ファイルを編集します。 # 変更前 apiVersion : v1 kind : ConfigMap metadata : # kpt-merge: /setters name : setters annotations : config.kubernetes.io/local-config : "true" internal.kpt.dev/upstream-identifier : '|ConfigMap|default|setters' data : # Organization ID and billing account org-id : "123456789012" billing-account-id : AAAAAA-BBBBBB-CCCCCC # Groups to use for org-level roles group-org-admins : gcp-organization-admins@example.com group-billing-admins : gcp-billing-admins@example.com # The project where Config Controller is deployed management-project-id : management-project-id # This default is safe to keep management-namespace : config-control # 変更後、org-id / billing-account-id / management-project-id / group-org-admins / group-billing-admins を変更 apiVersion : v1 kind : ConfigMap metadata : # kpt-merge: /setters name : setters annotations : config.kubernetes.io/local-config : "true" internal.kpt.dev/upstream-identifier : '|ConfigMap|default|setters' data : # Organization ID and billing account org-id : "999999999999" # billing-account-id : 00000B-66666E-DDDD77 # Groups to use for org-level roles group-org-admins : ggen@sandbox.co.jp group-billing-admins : ggen@sandbox.co.jp # The project where Config Controller is deployed management-project-id : sandbox-cc-test-prj # This default is safe to keep management-namespace : config-control landing-zone/setters.yaml ファイル編集後の差分は git diff コマンドでも確認できます。 # コマンド git diff # 戻り値 diff --git a/landing-zone/setters.yaml b/landing-zone/setters.yaml index 398cbcb..0905af7 100644 --- a/landing-zone/setters.yaml +++ b/landing-zone/setters.yaml @@ -20 , 12 + 20 , 12 @@ metadata: # kpt-merge: /setters internal.kpt.dev/upstream-identifier: ' |ConfigMap|default|setters ' data: # Organization ID and billing account - org-id: " 123456789012 " - billing-account-id: AAAAAA-BBBBBB-CCCCCC + org-id: " 999999999999 " + billing-account-id: 00000B-66666E-DDDD77 # Groups to use for org-level roles - group-org-admins: gcp-organization-admins@example.com - group-billing-admins: gcp-billing-admins@example.com + group-org-admins: ggen@sandbox.co.jp + group-billing-admins: ggen@sandbox.co.jp # The project where Config Controller is deployed - management-project-id: management-project-id + management-project-id: sandbox-cc-test-prj # This default is safe to keep management-namespace: config-control source-repo リポジトリへの Push 変更内容を確認したら source-repo リポジトリに push します。 # コマンド git commit -a -m " Customize landing zone blueprint " git push # git の戻り値は先程と同様のため割愛 その後、上記をトリガーとして GitOps パイプラインが以下のように動作し、最終的にランディングゾーンがデプロイされます。 landing-zone/setters.yaml ファイルにもとづきレンダリングが行われる レンダリングされたファイル一式が deployment-repo に push される Config Sync が deployment-repo を参照する Config Controller が 参照結果にもとづきランディングゾーンをデプロイする トラブルシューティング 想定エラー 参考ドキュメントをベースに進めてきましたが、 nomos status (Config Sync が管理するリソースの状態確認) コマンドを実行すると KNV1069: SelfManageError が発生するものと思われます。 # コマンド nomos status # 戻り値 (エラーのみ抜粋) KNV1069: RootSync config-management-system/root-sync must not manage itself in its repo エラー原因 原因は Config Sync が、 自身のマニフェストファイル ( rootsync.yaml ) を含め、リポジトリ全体を参照するように設定されている からです。 Config Sync は 管理対象リソースのマニフェストファイル だけを参照すべきで、自身の状態を定義したマニフェストファイルを参照させてはいけません。 ファイル確認 パイプラインをデプロイした際のファイル ( gitops/configsync/rootsync.yaml ) を確認します。 dir が config になっています。 config とは source-repo リポジトリ上のルートディレクトリ名を指しており、すなわち Config Sync の設定を含むリポジトリ全体 を参照するようになっています。 apiVersion : configsync.gke.io/v1beta1 kind : RootSync metadata : # kpt-merge: config-management-system/root-sync name : root-sync namespace : config-management-system annotations : internal.kpt.dev/upstream-identifier : 'configsync.gke.io|RootSync|config-management-system|root-sync' spec : sourceFormat : unstructured git : repo : https://source.developers.google.com/p/sandbox-cc-test-prj/r/deployment-repo # kpt-set: https://source.developers.google.com/p/${project-id}/r/${deployment-repo} revision : HEAD branch : main dir : config # kpt-set: ${configsync-dir} auth : gcpserviceaccount gcpServiceAccountEmail : sync-config-controller-1@sandbox-cc-test-prj.iam.gserviceaccount.com # kpt-set: sync-${cluster-name}@${project-id}.iam.gserviceaccount.com 対処方法 ランディングゾーンに関するマニフェストだけを参照するよう設定を修正します。 Cloud Shell 上にある gitops/configsync/setters.yaml を開き、 configsync-dir の値を config/landing-zone に変更します。 # 変更後 apiVersion : v1 kind : ConfigMap metadata : # kpt-merge: /setters name : setters annotations : config.kubernetes.io/local-config : "true" internal.kpt.dev/upstream-identifier : '|ConfigMap|default|setters' data : namespace : config-control # cluster-name must not exceed 25 characters in length cluster-name : cluster-name configsync-dir : config/landing-zone deployment-repo : deployment-repo project-id : project-id レンダリングを実行して gitops/configsync/rootsync.yaml の dir に反映させます。 # レンダリング実行場所の確認 (gitops ディレクトリが表示されること) ls -l # レンダリング (戻り値は割愛) kpt fn render gitops/ # レンダリング後 apiVersion : configsync.gke.io/v1beta1 kind : RootSync metadata : # kpt-merge: config-management-system/root-sync name : root-sync namespace : config-management-system annotations : internal.kpt.dev/upstream-identifier : 'configsync.gke.io|RootSync|config-management-system|root-sync' spec : sourceFormat : unstructured git : repo : https://source.developers.google.com/p/sandbox-cc-test-prj/r/deployment-repo # kpt-set: https://source.developers.google.com/p/${project-id}/r/${deployment-repo} revision : HEAD branch : main dir : config/landing-zone # kpt-set: ${configsync-dir} auth : gcpserviceaccount gcpServiceAccountEmail : sync-config-controller-1@sandbox-cc-test-prj.iam.gserviceaccount.com # kpt-set: sync-${cluster-name}@${project-id}.iam.gserviceaccount.com kubectl apply で Config Sync の設定を適用 (変更) します。 # 適用 (戻り値は割愛) kubectl apply --wait -f gitops/ --recursive レンダリングしたファイルを source-repo リポジトリに push します。 # コマンド (戻り値は割愛) git add . git commit -m " update rootsync " git push nomos status の再確認 再度 nomos status を実行しエラーが解消されたことを確認します。 各リソースのステータスが Current になっていればデプロイが完了しています。 # コマンド nomos status # 戻り値 *gke_sandbox-cc-test-prj_asia-northeast1_krmapihost-config-controller-1 -------------------- < root > :root-sync https:// source .developers.google.com/p/sandbox-cc-test-prj/ r /deployment-repo/config/landing-zone@main SYNCED @ 2023-08-18 10:47:07 + 0000 UTC 2f1935299502189667f71e2c4d03de46728447e2 Managed resources: NAMESPACE NAME STATUS SOURCEHASH constrainttemplate.templates.gatekeeper.sh/gcpenforcenamingv2 Current 2f19352 constrainttemplate.templates.gatekeeper.sh/gcprequiredeletionpolicy Current 2f19352 namespace/hierarchy Current 2f19352 namespace/logging Current 2f19352 namespace/networking Current 2f19352 namespace/policies Current 2f19352 namespace/projects Current 2f19352 config-control iampartialpolicy.iam.cnrm.cloud.google.com/hierarchy-sa-workload-identity-binding Current 2f19352 config-control iampartialpolicy.iam.cnrm.cloud.google.com/logging-sa-workload-identity-binding Current 2f19352 config-control iampartialpolicy.iam.cnrm.cloud.google.com/networking-sa-workload-identity-binding Current 2f19352 config-control iampartialpolicy.iam.cnrm.cloud.google.com/policies-sa-workload-identity-binding Current 2f19352 config-control iampartialpolicy.iam.cnrm.cloud.google.com/projects-sa-workload-identity-binding Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/billing-admins-iam Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/hierarchy-sa-folderadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/logging-sa-bigqueryadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/logging-sa-logadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/networking-sa-dns-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/networking-sa-networkadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/networking-sa-security-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/networking-sa-service-control-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/networking-sa-xpnadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/org-admins-iam Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/policies-sa-orgpolicyadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-billinguser-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-projectcreator-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-projectdeleter-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-projectiamadmin-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-projectmover-permissions Current 2f19352 config-control iampolicymember.iam.cnrm.cloud.google.com/projects-sa-serviceusageadmin-permissions Current 2f19352 config-control iamserviceaccount.iam.cnrm.cloud.google.com/hierarchy-sa Current 2f19352 config-control iamserviceaccount.iam.cnrm.cloud.google.com/logging-sa Current 2f19352 config-control iamserviceaccount.iam.cnrm.cloud.google.com/networking-sa Current 2f19352 config-control iamserviceaccount.iam.cnrm.cloud.google.com/policies-sa Current 2f19352 config-control iamserviceaccount.iam.cnrm.cloud.google.com/projects-sa Current 2f19352 config-control service.serviceusage.cnrm.cloud.google.com/sandbox-cc-test-prj-cloudbilling Current 2f19352 config-control service.serviceusage.cnrm.cloud.google.com/sandbox-cc-test-prj-cloudresourcemanager Current 2f19352 config-control service.serviceusage.cnrm.cloud.google.com/sandbox-cc-test-prj-serviceusage Current 2f19352 hierarchy configconnectorcontext.core.cnrm.cloud.google.com/configconnectorcontext.core.cnrm.cloud.google.com Current 2f19352 hierarchy rolebinding.rbac.authorization.k8s.io/allow-resource-reference-from-hierarchy Current 2f19352 logging configconnectorcontext.core.cnrm.cloud.google.com/configconnectorcontext.core.cnrm.cloud.google.com Current 2f19352 networking configconnectorcontext.core.cnrm.cloud.google.com/configconnectorcontext.core.cnrm.cloud.google.com Current 2f19352 policies configconnectorcontext.core.cnrm.cloud.google.com/configconnectorcontext.core.cnrm.cloud.google.com Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-guest-attributes Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-iam-grants-default-sa Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-nested-virtualization Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-sa-key-creation Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-serial-port Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/disable-vm-external-ip Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/enforce-uniform-bucket-lvl-access Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/restrict-cloud-sql-public-ip Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/restrict-lien-removal Current 2f19352 policies resourcemanagerpolicy.resourcemanager.cnrm.cloud.google.com/skip-default-network Current 2f19352 projects configconnectorcontext.core.cnrm.cloud.google.com/configconnectorcontext.core.cnrm.cloud.google.com Current 2f19352 projects rolebinding.rbac.authorization.k8s.io/allow-resource-reference-from-logging Current 2f19352 projects rolebinding.rbac.authorization.k8s.io/allow-resource-reference-from-networking Current 2f19352 動作確認 リソース nomos status でリソース状況を確認しましたが、実際に組織ポリシーがデプロイされたことを確認してみます。 Cloud コンソール > IAM と管理 > 組織のポリシー と遷移したら、スコープを 組織 に切り替え、フィルタに constraints/sql.restrictPublicIp と入力します。 組織ポリシーを確認 ポリシー名をクリックすると、Cloud SQL にパブリック IP アドレスの付与を禁止するポリシーが有効化されていました。 組織ポリシーが適用されている Reconciliation Loop が働くことを確認するため、このポリシーを手動で削除します。 ポリシーを管理 > 親のポリシーを継承する を選択し、 ポリシーを設定 をクリックします。これにより、組織ポリシーの設定が初期化されます。 組織ポリシーを初期化する ステータスが [未適用] に変わった (初期化された) しばらく時間をおいて再確認すると、組織ポリシーが再適用されています。Reconciliation Loop が働きリソースがあるべき状態に戻りました。 Reconciliation Loop によりリソースがあるべき状態に自動的に戻る GitOps パイプライン リポジトリ リポジトリは Cloud コンソール > Source Repositories から確認できます。 source-repo にはローカルリポジトリから push したブループリント一式が格納されています。 source-repo リポジトリはローカルから push したブループリント一式が格納 対する deployment-repo は、Cloud Build トリガーによるレンダリングで生成されたマニフェストファイルのみが格納されます。 Config Sync はこれら deployment-repo に格納されたマニフェストファイルを参照しリソースをデプロイ・管理します。 deployment-repo はレンダリングされたマニフェストファイルのみ格納 Cloud Build トリガー Cloud Build トリガーはグローバルリージョンにデプロイされています。 内容としては、 source-repo の push をトリガーに、レンダリングされたファイルを deployment-repo に push する処理を実行します。 source-repo への push をトリガーに、レンダリングされたファイルを deployment-repo に push 詳細は Build 履歴 から確認できます。 Cloud Build トリガーによって実行された処理の詳細 Config Sync nomos status だけでなく、Cloud コンソールからも Config Sync のステータスを確認できます。 Cloud コンソール > Anthos > Config と遷移すると、Config Sync のダッシュボードが確認可能です。 Config Sync ダッシュボード パッケージ タブに切り替えると、 nomos status 同様 Config Sync によってデプロイされたリソースの状態が確認できます。 パッケージタブからリソースの状態が確認可能 さいごに 公式ガイドではこの後フォルダやプロジェクトといったランディングゾーンリソースのデプロイへと続いていきますが、当記事では一旦ここで終了とします。 所感として、GitOps パイプラインでは Cloud Source Repositories を使用していますが、2023年8月時点ではプルリクエスト機能が搭載されていないため、GitHub をミラーしたリポジトリにカスタムすることでより利便性が向上すると思います。 今回の続きやリポジトリのカスタマイズについては今後記事にできればと思っています。 武井 祐介 (記事一覧) 2022年4月入社 / クラウドソリューション部 / 技術2課所属 趣味はゴルフにロードバイク。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。 Google Cloud 認定全冠達成!(2023年6月)
アバター
G-gen の杉村です。Pub/Sub の Cloud Storage サブスクリプション 機能を実際に使ってみて、その細かい仕様を確認してみました。 はじめに 試したこと 前提知識 Cloud Storage バケットの作成 Pub/Sub サービスエージェントに権限付与 サービスエージェントとは 権限の付与 トピックの作成 Cloud Storage サブスクリプションの作成 概要 パラメータ 検証 メッセージの発行 ファイルの確認 オブジェクトのメタデータ 改行の扱い 複数サブスクリプション トラブルシューティング はじめに 試したこと 当記事では Cloud Pub/Sub の Cloud Storage サブスクリプション機能を実際に使用してみた際の手順と挙動をご紹介します。 試した使い方はシンプルであり、以下の通りです。 検証用 Cloud Storage バケットを作成 Pub/Sub サービスエージェントに検証用バケットに対する書き込み権限を付与 検証用 Pub/Sub トピックを作成 トピックに所属する Cloud Storage サブスクリプションを作成 トピックにメッセージをパブリッシュ Cloud Storage への出力ファイルを確認 前提知識 Cloud Pub/Sub (以下、Pub/Sub) は Google Cloud (旧称 GCP) のフルマネージドなメッセージキューイングサービスです。 Cloud Storage サブスクリプション とは、Pub/Sub のサブスクリプション (メッセージ出力先) の一種で、トピックが受け取ったメッセージを直接 Cloud Storage バケットに書き込む仕組みです。Amazon Web Services (AWS) の Amazon Kinesis Data Firehose に類似する機能であるとも言えます。扱ったデータサイズに応じて利用料金が発生します。 参考 : Pub/Sub とは 参考 : Cloud Storage サブスクリプション 参考 : Cloud Storage サブスクリプションの作成 参考 : Cloud Storage サブスクリプションのスループット コスト Cloud Storage バケットの作成 検証用のバケットとして my-test-bucket-for-pub-sub を作成します。 リージョンは us-central1 としましたが、今回の機能に特段のリージョン制限はありません。 作成手順の詳細は解説しません。以下のドキュメントをご参照ください。 参考 : バケットの作成 バケットの作成 Pub/Sub サービスエージェントに権限付与 サービスエージェントとは Pub/Sub サブスクリプションが Cloud Storage バケットにファイルを書き込むためには、適切な IAM 権限の付与が必要です。 Pub/Sub は、サービスエージェントと呼ばれる特殊なサービスアカウントの権限でファイルの書き込みを行います。サービスエージェントとは、Google Cloud のシステムが内部的に使用するサービスアカウントのことです。詳細は以下の記事をご参照ください。 blog.g-gen.co.jp 権限の付与 Pub/Sub のサービスエージェントは service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com という名称です ( ${PROJECT_NUMBER} は自身のプロジェクト番号に置き換え)。 このサービスエージェントには、書き込み先バケットに対する Storage オブジェクト作成者 (roles/storage.objectCreator) ロールと Storage レガシー バケット読み取り (roles/storage.legacyBucketReader ロールの2つが必要です。 対象バケット単体に対してのみ IAM ロールを付与することもできますし、プロジェクト全体に IAM ロールを付与することもできます。後者の場合はプロジェクト内の全てのバケットに対して、Pub/Sub からファイルを書き込めるようになります。 手順は以下をご参照ください。 参考 : Pub/Sub サービス アカウントに Cloud Storage のロールを割り当てる バケットの IAM 権限付与画面 トピックの作成 まず検証用のトピックを作成します。名称は my-test-topic としました。 こちらも作成手順の詳細は解説しませんので、以下のドキュメントをご参照ください。 参考 : トピックの作成と管理 検証用トピックの作成 Cloud Storage サブスクリプションの作成 概要 先程のトピックにぶらさげる形でサブスクリプションを作成します。 my-test-subscription という名称にします。 サブスクリプションは pull push BigQuery Cloud Storage の4種類から選択できます。今回は Cloud Storage を選びます。 Cloud Storage サブスクリプションに固有なパラメータは「書き込み先バケット」「書き込み時のファイル形式」「接頭辞・接尾辞」「メタデータ書き込み有無」「ストレージバッチの最長時間」「ストレージバッチの最大バイト数」などです。 ファイル形式はシンプルに内容が確認できる text としました。その他には Avro (バイナリエンコードされスキーマ定義されたファイル形式。スキーマハンドリングにメリット) が選択可能です。「メタデータ書き込み有無」は、Avro でのみ利用可能です。text 形式の場合は、message_id などのメタデータやパブリッシュ時に付与したプロパティは失われることに注意が必要です。 「ストレージバッチの最長時間」「ストレージバッチの最大バイト数」は、受け取ったメッセージをどのくらいまで保持してからまとめてバケットに書き込むかを定義する値です。例えばそれぞれ「5分」「1024 KB」とすると、5分経過するか受け取り済みメッセージの合計サイズが 1024 KBを超えるかの いずれか を満たした場合に、まとめて書き込みが実行されます。なお「ストレージバッチの最大バイト数」はオプションであり、未設定のままにすることもできます。 参考 : Cloud Storage サブスクリプションのプロパティ トピックの作成 パラメータ 今回はパラメータを以下のように設定しました。メッセージ有効期限など他のサブスクリプションタイプにも共通な値は省略しています。 パラメータ名 値 配信タイプ Cloud Storage への書き込み Cloud Storage バケット my-test-bucket-for-pub-sub ストレージ ファイル形式 text ストレージ ファイル接頭辞 test-file- ストレージ ファイル接尾辞 .txt ストレージバッチの最長時間 5 分 ストレージバッチの最大バイト数 未設定 スクリーンショット パラメータ 検証 メッセージの発行 実際にメッセージを発行してみます。 以下のコマンドで Hello world. It’s ${現在の日付と日時}" という文字列をトピックにパブリッシュ (発行) してみます。これを数秒間隔で繰り返して実行します。 gcloud pubsub topics publish my-test-topic --message =" Hello world. It’s `date ' +%Y-%m-%d %H:%M:%S ' ` " ファイルの確認 メッセージのパブリッシュから5分程度待つと、Cloud Storage にファイルが吐き出されました。ファイル名は <ファイル接頭辞><日時 (UTC)>_<UUID><ファイル接尾辞> の形式になります。 参考 : ファイル名の接頭辞と接尾辞 バケットに出力されたオブジェクト名、作成日時、ファイルの内容 (パブリッシュされた日時) は以下の通りです。 出力順 オブジェクト名 オブジェクト 作成時刻 ファイルの内容 1 test-file-2023-08-15T 02_35_08+00_00_d1c49b.txt 11:40:08 Hello world. It’s 2023-08-15 11:35:01 2 test-file-2023-08-15T 02_35_10+00_00_f504c5.txt 11:40:10 Hello world. It’s 2023-08-15 11:35:09 3 test-file-2023-08-15T 02_35_11+00_00_3b783f.txt 11:40:11 Hello world. It’s 2023-08-15 11:35:07 4 test-file-2023-08-15T 02_35_14+00_00_ba1f59.txt 11:40:14 Hello world. It’s 2023-08-15 11:35:10 5 test-file-2023-08-15T 02_48_05+00_00_13cd63.txt 11:53:05 Hello world. It’s 2023-08-15 11:48:02 6 test-file-2023-08-15T 02_51_05+00_00_23d695.txt 11:56:05 Hello world. It’s 2023-08-15 11:51:04 7 test-file-2023-08-15T 03_01_11+00_00_53375c.txt 12:06:11 Hello world. It’s 2023-08-15 12:01:03 Hello world. It’s 2023-08-15 12:01:06 8 test-file-2023-08-15T 03_01_11+00_00_94e77b.txt 12:06:11 Hello world. It’s 2023-08-15 12:01:08 分かったことは、以下のとおりです。 必ずしもメッセージをパブリッシュした順にファイルに出力されるわけではない (No 2と3が逆転している) メッセージのパブリッシュから概ね「ストレージバッチの最長時間」で設定した時間が経過したあとにオブジェクトが生成される 1メッセージにつき1ファイルが生成されるわけではなく、同時間帯に複数メッセージを受け取った場合は、改行区切りで1ファイル内に複数メッセージが書き込まれる オブジェクト名に含まれる UTC 日時は必ずしもオブジェクト生成時刻と一致せず、パブリッシュ日時とも一致しない オブジェクト名に含まれる日時はパブリッシュ時刻の数秒後となっている。サブスクリプションがトピックからメッセージを受け取って処理した時刻である可能性がある オブジェクトのメタデータ Cloud Storage サブスクリプションによって生成されたオブジェクトは、どのようなものでしょうか。着目すべき点だけを挙げます。 content_type は application/octet-stream タイプ不明、もしくは特に指定しないことを意味する (参考 : IANA - Media Types ) ストレージクラスは Standard (バケットの設定と同じ) 公開アクセスは 無効化 (バケットの設定と同じ) スクリーンショット オブジェクトのメタデータ 改行の扱い 同時間帯に受け取ったメッセージは単一ファイルに改行区切りで書き込まれることが分かりました。では、改行を含むメッセージの場合はどのように処理されるのかを確認するため、以下のようなコマンドを短い間隔で複数回実行しました。 date ' +%Y-%m-%d %H:%M:%S ' ; gcloud pubsub topics publish my-test-topic --message =" Line 1: `date ' +%Y-%m-%d %H:%M:%S ' ` Line 2 Line 3 " 結果は以下のようになりました。 出力順 オブジェクト名 オブジェクト 作成時刻 ファイルの内容 1 test-file- 2023-08-15T03:36:13 +00:00_1046df.txt 12:41:14 Line 1: 2023-08-15 12:36:10 Line 2 Line 3 Line 1: 2023-08-15 12:36:11 Line 2 Line 3 2 test-file- 2023-08-15T03:47:01 +00:00_fd1b17.txt 12:52:01 Line 1: 2023-08-15 12:46:57 Line 2 Line 3 Line 1: 2023-08-15 12:46:57 Line 2 Line 3 Line 1: 2023-08-15 12:47:47 Line 2 Line 3 複数メッセージが改行区切りで1個のテキストファイルに入っています。一つ一つのメッセージにパースする際は、メッセージに明確なデリミタが含まれていないと苦労することになりそうです。あるいは本来的に当機能では、改行を前提としないデータや JSON のような構造を持ったデータが適していると言えそうです。 参考 : ファイル形式 複数サブスクリプション ひとつのトピックに複数の Cloud Storage サブスクリプションをぶらさげた場合、2つのバケットに同時にオブジェクトが生成されます。 トラブルシューティング Cloud Storage サブスクリプションを作成する際、以下のようなエラーメッセージが表示される場合があります。 エラーメッセージ "Cloud Pub/Sub did not have the necessary permissions..." API がエラー「Cloud Pub/Sub did not have the necessary permissions configured to access the provided bucket ${BUCKET_NAME} (or the bucket may not exist). Please verify that the service account service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com was granted the Storage Legacy Bucket Reader and Storage Object Creator roles for the provided bucket. These roles must be granted at the bucket level, not the project level. If you must grant roles at the project level, you may instead grant the Storage Admin role for the project containing the provided bucket.」を返しました エラー文中の ${BUCKET_NAME} は自身のバケット名に、 ${PROJECT_NUMBER} はプロジェクト番号に置き換えてお読みください。 これは Pub/Sub のサービスエージェントが対象の Cloud Storage バケットに対して十分な権限を持っていないことを意味しています。 当記事の Pub/Sub サービスエージェントに権限付与 を参考に、IAM 権限を付与すれば解決します。 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター
G-gen の杉村です。生成 AI を使って Google Workspace における業務をサポートする Duet AI for Google Workspace をプレビューしてみましたので、その機能の一部をご紹介します。今回は Google Slides 編です。 はじめに Duet AI for Google Workspace とは 当記事の注意点 画像の自動生成 複雑な描写 できなかった描写 その他の記事 はじめに Duet AI for Google Workspace とは Duet AI for Google Workspace は、Google のコラボレーションソリューションである Google Workspace において 生成 AI (Generative AI) を使って各種業務をサポートする機能です。以下のようなことが実現できるようになります。 Google Docs (ドキュメント) : 文章の自動生成・フォーマル化・要約・精緻化・言い換え Gmail (メール) : E メールの自動生成・推敲 Google Slides (スライド) : 自然言語を与えると画像を自動生成 Google Sheets (スプレッドシート) : データの分析、ラベル割り当て、タスク計画の生成 Google Meet (Web 会議) : バーチャル背景の生成 2023年8月現在では日本語版は提供されておらず、英語版のみです。 当記事の注意点 当記事では Duet AI for Google Workspace の先行テスタープログラム中に利用した機能をご紹介しています。以下の点にご留意ください。 当記事で紹介する Duet AI for Google Workspace の機能は先行テスタープログラム当時のものであり、今後変更になる可能性があります G-gen 社は検証目的でのみ Duet AI for Google Workspace を用いており、顧客業務には利用していません(検証当時) 画像の自動生成 Google Sheets で Duet AI を使った画像の自動生成を試してみます。 Duet AI が有効化された組織では、画面右側に Help me visualize (画像化を手伝って) というプロンプト入力画面が表示されています。 Help me visualize 以下のようにプロンプトを入力してみました。 An image of Cloud network (クラウドネットワークのイメージ) Add a style というドロップダウンリストもありますが、いったん何も指定せずに Create してみます。すると、以下のように複数の画像が生成されました。画像をクリックすると、スライドに画像が挿入されます。 生成された画像 次は、もっと具体的な指示を出してみます。 A border collie dog with a red collar (赤い首輪をつけたボーダーコリー犬) 以下のように画像が表示されました。ちゃんと、犬種まで再現されています。 生成されたボーダーコリーの画像 以下のように描写を追加してみます。 A border collie dog with a red collar sitting on a floor inside a house (家の中で床に座った、赤い首輪をつけたボーダーコリー犬) 以下のように生成されました。確かに、指示された通りに描写されています。 再度、生成された画像 Add a style というドロップダウンリストから、画像のスタイルを選択してみます。Sketch というスタイルを選択して、再度 Create してみます。 Sketch スタイル スケッチされた絵のようなタッチになりました。テスト時には以下のようなスタイルが選択できました。 Photography Background Vector art Sketch Watercolor Cyberpunk I'm Feeling Lucky スタイル一覧 複雑な描写 以下のように複雑な指示をしています。 A cat jumps and tries to grab the ball, but misses and crashes into the wall. (猫がジャンプしてボールを掴もうとするが、失敗して壁に激突してしまう) ある程度、意図を組んでくれた画像になりました。 複雑な描写 次のプロンプトは少し難しかったようです。 An anthropomorphic on-premise server is fighting an anthropomorphic cloud service (擬人化されたオンプレミスサーバーが、擬人化されたクラウドサービスと戦っている様子) 複雑な描写 (2) できなかった描写 プライベートプレビュー期間中だからでしょうか、以下のような人間に関連するプロンプトを入力したところ For now, we're showing limited results for people. Try something else. (現在のところ、人間に関する結果は制限されています。) とエラーが表示され、画像が生成されませんでした。 A lady sitting on a chair (椅子に座った女性) 人間に関する描写はできない場合がある その他の記事 Duet AI for Google Workspace のその他の機能を、以下の記事でもご紹介しています。 blog.g-gen.co.jp blog.g-gen.co.jp 杉村 勇馬 (記事一覧) 執行役員 CTO / クラウドソリューション部 部長 元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。 Follow @y_sugi_it
アバター