G-gen Tech Blog Google Cloud(旧称GCP)の情報発信を行う技術ブログ 2025-01-15T09:00:00+09:00 ggen-sugimura Hatena::Blog hatenablog://blog/13574176438010059131 Google Workspaceのエンドポイント管理でモバイルデバイスを安全に管理する hatenablog://entry/6802418398306498772 2025-01-15T09:00:00+09:00 2025-01-15T09:00:02+09:00 G-gen の三浦です。当記事では、Google Workspace の エンドポイント管理を使用して、iPhone ã‚„ Android 端末などのモバイル端末を管理する方法を紹介します。 概要 エンドポイント管理とは エンドポイント管理を使用するメリット 前提条件 基本管理と詳細管理 iOS デバイスの詳細管理 検証手順 エンドポイント設定 デバイスの登録(iOS/Android) デバイスの登録確認(iOS/Android) iOS のポリシー設定 Android のポリシー設定 動作確認 iOS の動作確認(ローカル保存不可) Android の動作確認(業務用アプリの配布) Andro… <p>G-gen の三浦です。当記事では、Google Workspace の エンドポイント管理を使用して、iPhone ã‚„ Android 端末などのモバイル端末を管理する方法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a><ul> <li><a href="#エンドポイント管理とは">エンドポイント管理とは</a></li> <li><a href="#エンドポイント管理を使用するメリット">エンドポイント管理を使用するメリット</a></li> </ul> </li> <li><a href="#前提条件">前提条件</a><ul> <li><a href="#基本管理と詳細管理">基本管理と詳細管理</a></li> <li><a href="#iOS-デバイスの詳細管理">iOS デバイスの詳細管理</a></li> </ul> </li> <li><a href="#検証手順">検証手順</a></li> <li><a href="#エンドポイント設定">エンドポイント設定</a><ul> <li><a href="#デバイスの登録iOSAndroid">デバイスの登録(iOS/Android)</a></li> <li><a href="#デバイスの登録確認iOSAndroid">デバイスの登録確認(iOS/Android)</a></li> <li><a href="#iOS-のポリシー設定">iOS のポリシー設定</a></li> <li><a href="#Android-のポリシー設定">Android のポリシー設定</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#iOS-の動作確認ローカル保存不可">iOS の動作確認(ローカル保存不可)</a></li> <li><a href="#Android-の動作確認業務用アプリの配布">Android の動作確認(業務用アプリの配布)</a></li> <li><a href="#Android-の動作確認アカウントワイプ">Android の動作確認(アカウントワイプ)</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090136.png" width="800" height="448" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <h2 id="エンドポイント管理とは">エンドポイント管理とは</h2> <p><strong>エンドポイント管理</strong>は、Google Workspace が提供するデバイス管理機能です。従業員が使用するモバイル端末やパソコンを一元的に管理し、組織のセキュリティポリシーを適用できます。</p> <p>一般的に、このようなデバイス管理の仕組みは <strong>MDM</strong>(Mobile Device Management)と呼ばれます。MDM は、セキュリティポリシーの適用、アプリの配布、デバイスの監視、紛失・盗難時のデータ消去などの機能を提供するツール全般を指します。</p> <ul> <li>参考 : <a href="https://workspace.google.com/intl/ja/products/admin/endpoint/">エンドポイント管理</a></li> </ul> <h2 id="エンドポイント管理を使用するメリット">エンドポイント管理を使用するメリット</h2> <p>Google Workspace のエンドポイント管理を使用すると、以下のようなメリットがあります。</p> <table> <thead> <tr> <th> メリット </th> <th> 詳細 </th> </tr> </thead> <tbody> <tr> <td> Google Workspace とのシームレスな統合 </td> <td> アカウントと端末の管理を一元化できます。<strong>コンテキストアウェア アクセス</strong>で Gmail ã‚„ Google ドライブへの柔軟なアクセス制御も可能です。 </td> </tr> <tr> <td> 追加費用や専用アプリ不要 </td> <td> Business Plus ã‚„ Enterprise プランなどに標準搭載されており、<strong>追加費用や専用アプリは不要</strong>です。 </td> </tr> <tr> <td> Android ゼロタッチ登録と BYOD </td> <td> ゼロタッチ登録で多数の端末を簡単にセットアップできます。また個人所有デバイス(BYOD)においても業務データと個人データを分離できるようになり、セキュリティとプライバシーを両立できます。 </td> </tr> </tbody> </table> <p><strong>ゼロタッチ登録</strong>及び<strong>コンテキストアウェア アクセス</strong>の詳細な設定手順については、以下のドキュメントと記事を参照してください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/topic/13782289?hl=ja">Android のゼロタッチ登録の設定</a></li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcontext-aware-access-with-google-workspace" title="コンテキストアウェアアクセスでGoogle Workspaceのセキュリティを強化してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/context-aware-access-with-google-workspace">blog.g-gen.co.jp</a></cite></p> <h1 id="前提条件">前提条件</h1> <h3 id="基本管理と詳細管理">基本管理と詳細管理</h3> <p>Google Workspace のエンドポイント管理には、<strong>基本管理</strong>と<strong>詳細管理</strong>があります。Google Workspace のエディションによって、どちらの機能が利用できるかが決まります。</p> <table> <thead> <tr> <th> 機能の名称 </th> <th> 詳細 </th> </tr> </thead> <tbody> <tr> <td> <strong>基本管理</strong> </td> <td> デバイス登録、紛失時の Google アカウントリモート削除、<BR>デバイスのセキュリティステータス(例:OS バージョン)<BR>の確認などの基本的な管理機能を提供します。 </td> </tr> <tr> <td> <strong>詳細管理</strong> </td> <td> 組織で許可されていないアプリを禁止する機能やデータ操作制限<BR>など、より高度な制御が可能。個人所有デバイス(BYOD)<BR>やゼロタッチ登録により、柔軟にデバイスを管理できます。</td> </tr> </tbody> </table> <ul> <li>参考 : <a href="https://support.google.com/a/answer/7576736?hl=ja">モバイル管理機能の比較</a></li> </ul> <h3 id="iOS-デバイスの詳細管理">iOS デバイスの詳細管理</h3> <p>iOS デバイスで詳細管理を行うには、Apple プッシュ証明書が必要です。この証明書は Apple Push Certificates Portal で取得できます。詳細は以下のドキュメントを確認してください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/9904735?hl=ja">会社所有の iOS デバイスの管理を設定する</a></li> <li>参考 : <a href="https://support.google.com/a/answer/6080359?hl=ja">Apple プッシュ証明書を設定する</a></li> </ul> <h1 id="検証手順">検証手順</h1> <p>以下の手順でエンドポイント管理を設定し、動作を確認します。</p> <ol> <li><p><strong>デバイスの登録(iOS/Android)</strong><br/> 管理コンソールにデバイスを登録します。</p></li> <li><p><strong>iOS のポリシー設定</strong><br/> Google ドライブやドキュメントの業務データをローカル(例:Files アプリ)に保存できないよう制限します。</p></li> <li><p><strong>Android のポリシー設定</strong><br/> 業務用アプリ(例:Google Gemini)をリモート配布するための設定をします。</p></li> <li><p><strong>iOS 動作確認(ローカル保存不可)</strong><br/> 業務データが Files アプリに保存できないことを確認します。</p></li> <li><p><strong>Android 動作確認(業務用アプリの配布)</strong><br/> 業務用アプリがインストールされ、アンインストールできないことを確認します。</p></li> <li><p><strong>Android 動作確認(アカウントワイプ)</strong><br/> 管理コンソールから端末上の会社で使用している Google アカウントを削除します。</p></li> </ol> <h1 id="エンドポイント設定">エンドポイント設定</h1> <h2 id="デバイスの登録iOSAndroid">デバイスの登録(iOS/Android)</h2> <p>デバイスの Google Chrome で Google(<a href="https://google.co.jp">https://google.co.jp</a>)にアクセスします。</p> <p><figure class="figure-image figure-image-fotolife" title="Googleにアクセス"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090129.png" width="800" height="379" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Googleにアクセス</figcaption></figure></p> <p>会社で使用する Google アカウントでログインすることで、管理コンソールにデバイスが登録されます。</p> <p><figure class="figure-image figure-image-fotolife" title="会社アカウントでログイン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090133.png" width="772" height="367" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>会社アカウントでログイン</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/users/answer/138740?hl=ja">iOS デバイスで Google Workspace を設定する</a></li> <li>参考 : <a href="https://support.google.com/a/users/answer/9370410?hl=ja">Android デバイスで Google Workspace を設定する</a></li> </ul> <h2 id="デバイスの登録確認iOSAndroid">デバイスの登録確認(iOS/Android)</h2> <p>Google Workspace の管理コンソール(<a href="https://admin.google.com">https://admin.google.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/182076?hl=ja">管理コンソールにログインする</a></li> </ul> <p>[デバイス] > [モバイルとエンドポイント] > [デバイス] へ移動し、フィルタから<strong>メールアドレス</strong>でデバイスを抽出し、登録されていることを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="モバイル端末の登録確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090008.png" width="800" height="311" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>モバイル端末の登録確認</figcaption></figure></p> <h2 id="iOS-のポリシー設定">iOS のポリシー設定</h2> <p>[モバイルとエンドポイント] > [設定] > [iOS] > [データ共有] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="データ共有を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090011.png" width="800" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>データ共有を選択</figcaption></figure></p> <p>設定を適用する<strong>組織部門</strong>を選択し、[データ操作] の [編集] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="編集を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090015.png" width="800" height="218" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>編集を選択</figcaption></figure></p> <p>以下を選択し、[保存] または [オーバーライド] を選択します。</p> <ul> <li><strong>Google Workspace のデータが外部と共有される可能性のある操作を iOS で行うことをユーザーに許可しない</strong></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="設定を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090018.png" width="800" height="571" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>設定を選択</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15405201?hl=ja">iOS デバイスでの過失によるデータ漏洩を防止する</a></li> </ul> <h2 id="Android-のポリシー設定">Android のポリシー設定</h2> <p>[モバイルとエンドポイント] > [設定] > [一般] > [全般] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="全般を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090022.png" width="800" height="301" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>全般を選択</figcaption></figure></p> <p>設定を適用する<strong>組織部門</strong>を選択し、[モバイル管理] の [編集] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="編集を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090025.png" width="800" height="177" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>編集を選択</figcaption></figure></p> <p>[カスタム] から <code>Android</code> ã‚’<strong>詳細</strong>に変更し、[保存] または [オーバーライド] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="設定を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090028.png" width="800" height="598" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>設定を選択</figcaption></figure></p> <p>[アプリ] > [ウェブアプリとモバイルアプリ] へ移動し、[アプリを追加] から [限定公開の Android アプリを追加] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="限定公開のAndroidアプリを追加を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090031.png" width="742" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>限定公開のAndroidアプリを追加を選択</figcaption></figure></p> <p>検索バーに <strong>Gemini</strong> と入力し、リストから <strong>Google Gemini</strong> を選択し、[選択] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="Geminiアプリを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090034.png" width="532" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Geminiアプリを選択</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090037.png" width="621" height="197" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>選択</figcaption></figure></p> <p>アプリを適用する対象(全ユーザーまたは特定の組織部門や Google グループ)を選択し、[続行] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="インストール対象を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090040.png" width="800" height="465" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>インストール対象を選択</figcaption></figure></p> <p>以下を選択し、[完了] を選択します。</p> <ul> <li><strong>自動インストール</strong></li> <li><strong>ユーザーがアプリをアンインストールできないようにする</strong></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="アプリへのアクセス方法を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090043.png" width="800" height="448" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリへのアクセス方法を選択</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/2494992?hl=ja">限定公開の Android アプリを Google Play で管理する</a></li> </ul> <h1 id="動作確認">動作確認</h1> <h2 id="iOS-の動作確認ローカル保存不可">iOS の動作確認(ローカル保存不可)</h2> <p>Google ドキュメントアプリを起動し、会社で使っている Google アカウントを選択します。</p> <p>Google ドキュメントファイルを開き、右上の […] を選択します。</p> <p> <figure class="figure-image figure-image-fotolife" title="... を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090102.png" width="800" height="139" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>... を選択</figcaption></figure></p> <p>[共有とエクスポート] > [コピーを送信] > [PDF] > [OK] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="コピーを送信を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090105.png" width="516" height="365" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>コピーを送信を選択</figcaption></figure></p> <p>["ファイル"に保存] を選択すると、<strong>ファイルを共有できません</strong>と表示され、ローカルに保存ができないことを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="ファイルに保存を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090108.png" width="483" height="338" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ファイルに保存を選択</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="ローカルへの保存失敗"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090111.png" width="540" height="242" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ローカルへの保存失敗</figcaption></figure></p> <h2 id="Android-の動作確認業務用アプリの配布">Android の動作確認(業務用アプリの配布)</h2> <p>設定アプリから会社で使っている Google アカウントを選択し、仕事用プロファイルのセットアップを開始します。</p> <p><figure class="figure-image figure-image-fotolife" title="仕事用プロファイルのセットアップ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090052.png" width="230" height="484" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>仕事用プロファイルのセットアップ</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/users/answer/9370410?hl=ja#zippy=%2C%E5%80%8B%E4%BA%BA%E3%81%AE%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%2C%E4%BB%95%E4%BA%8B%E7%94%A8%E3%83%97%E3%83%AD%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">仕事用プロファイルの作成</a></li> </ul> <p>以下メッセージが表示された場合、[モバイルとエンドポイント] > [デバイスの承認] から端末を選択し、[デバイスを承認] を選択します。</p> <p><code>このデバイスは有効になっていません 管理者によるデバイスの承認が必要です。</code></p> <p><figure class="figure-image figure-image-fotolife" title="デバイス未承認エラー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090046.png" width="352" height="243" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>デバイス未承認エラー</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="モバイル端末の承認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090049.png" width="800" height="124" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>モバイル端末の承認</figcaption></figure></p> <p>セットアップが完了すると、設定したアプリが表示されます。業務用アプリはアプリのアイコンに鞄のアイコンが表示されます。</p> <p><figure class="figure-image figure-image-fotolife" title="業務用アプリのインストール確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090055.png" width="340" height="178" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>業務用アプリのインストール確認</figcaption></figure></p> <p>業務アプリは、先の設定の通り、アンインストールしようとすると失敗します。</p> <p><figure class="figure-image figure-image-fotolife" title="業務用アプリのアンインストール不可確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090058.png" width="392" height="590" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>業務用アプリのアンインストール不可確認</figcaption></figure></p> <h2 id="Android-の動作確認アカウントワイプ">Android の動作確認(アカウントワイプ)</h2> <p>[デバイス] > [モバイルとエンドポイント] > [デバイス] から端末を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="Android デバイスを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090117.png" width="800" height="182" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Android デバイスを選択</figcaption></figure></p> <p>[その他] > [アカウントをワイプ] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="アカウントをワイプを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090120.png" width="364" height="528" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アカウントをワイプを選択</figcaption></figure></p> <p>[アカウントをワイプ] を選択し、ワイプを実行します。</p> <p><figure class="figure-image figure-image-fotolife" title="ワイプの実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090114.png" width="536" height="461" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ワイプの実行</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="ワイプ後のデバイスステータス"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090123.png" width="800" height="256" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ワイプ後のデバイスステータス</figcaption></figure></p> <p>インストールした業務アプリが削除されていることを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="業務アプリの削除確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250115/20250115090126.png" width="316" height="180" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>業務アプリの削除確認</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/173390?hl=ja">デバイスから企業データをワイプする</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak BigQuery MLを徹底解説! hatenablog://entry/6802418398318856635 2025-01-14T09:00:00+09:00 2025-01-14T09:05:38+09:00 G-gen の佐々木です。当記事では BigQuery 上で機械学習モデルを作成、評価、実行するための機能である BigQuery ML について解説します。 概要 BigQuery とは BigQuery ML とは BigQuery ML の使用方法 ユーザーインターフェース BigQuery Editions クエリのドライラン BigQuery ML でサポートされるモデル 内部モデル 外部モデル インポートされたモデル リモートモデル ユーザーが Vertex AI でデプロイしたモデル Google の生成 AI モデル タスク固有のソリューション 基本的な SQL ステートメント… <p>G-gen の佐々木です。当記事では BigQuery 上で機械学習モデルを作成、評価、実行するための機能である <strong>BigQuery ML</strong> について解説します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a><ul> <li><a href="#BigQuery-とは">BigQuery とは</a></li> <li><a href="#BigQuery-ML-とは">BigQuery ML とは</a></li> </ul> </li> <li><a href="#BigQuery-ML-の使用方法">BigQuery ML の使用方法</a><ul> <li><a href="#ユーザーインターフェース">ユーザーインターフェース</a></li> <li><a href="#BigQuery-Editions">BigQuery Editions</a></li> <li><a href="#クエリのドライラン">クエリのドライラン</a></li> </ul> </li> <li><a href="#BigQuery-ML-でサポートされるモデル">BigQuery ML でサポートされるモデル</a><ul> <li><a href="#内部モデル">内部モデル</a></li> <li><a href="#外部モデル">外部モデル</a></li> <li><a href="#インポートされたモデル">インポートされたモデル</a></li> <li><a href="#リモートモデル">リモートモデル</a><ul> <li><a href="#ユーザーが-Vertex-AI-でデプロイしたモデル">ユーザーが Vertex AI でデプロイしたモデル</a></li> <li><a href="#Google-の生成-AI-モデル">Google の生成 AI モデル</a></li> <li><a href="#タスク固有のソリューション">タスク固有のソリューション</a></li> </ul> </li> </ul> </li> <li><a href="#基本的な-SQL-ステートメント関数">基本的な SQL ステートメント・関数</a><ul> <li><a href="#CREATE-MODEL-ステートメント">CREATE MODEL ステートメント</a></li> <li><a href="#MLEVALUATE-関数">ML.EVALUATE 関数</a></li> <li><a href="#MLPREDICT-関数">ML.PREDICT 関数</a></li> </ul> </li> <li><a href="#特徴量の前処理">特徴量の前処理</a><ul> <li><a href="#自動前処理">自動前処理</a></li> <li><a href="#手動前処理TRANSFORM-ステートメント">手動前処理(TRANSFORM ステートメント)</a></li> </ul> </li> <li><a href="#モデルのモニタリング">モデルのモニタリング</a></li> <li><a href="#BigQuery-ML-の料金">BigQuery ML の料金</a><ul> <li><a href="#オンデマンド料金">オンデマンド料金</a></li> <li><a href="#BigQuery-Editions-の料金">BigQuery Editions の料金</a></li> <li><a href="#外部モデルの料金">外部モデルの料金</a></li> <li><a href="#リモートモデルの料金">リモートモデルの料金</a></li> </ul> </li> <li><a href="#他の機械学習系プロダクトとの統合">他の機械学習系プロダクトとの統合</a><ul> <li><a href="#Vertex-AI">Vertex AI</a></li> <li><a href="#Colab-Enterprise">Colab Enterprise</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250112/20250112073648.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <h2 id="BigQuery-とは">BigQuery とは</h2> <p><a href="https://cloud.google.com/bigquery/docs/introduction?hl=ja">BigQuery</a> は、Google Cloud のフルマネージド分析用データベース(データウェアハウス)サービスです。インフラ管理不要の分析用データベースを従量課金で使用できます。</p> <p>当記事では BigQuery 自体の説明は割愛します。プロダクトの全容については以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-explained-basics" title="BigQueryを徹底解説!(基本編) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-explained-basics">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-explained-advanced" title="BigQueryを徹底解説!(応用編) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-explained-advanced">blog.g-gen.co.jp</a></cite></p> <h2 id="BigQuery-ML-とは">BigQuery ML とは</h2> <p><strong>BigQuery ML</strong> とは、BigQuery 上で機械学習モデルのトレーニングや予測、評価を行うことができる機能です。BigQuery で使われる標準 SQL 準拠の <strong>GoogleSQL</strong> を使用し、BigQuery 上のデータを使った機械学習が容易に実現できます。</p> <p>通常、機械学習モデルの開発には、機械学習フレームワークに対する高度な知識とプログラミング技術が要求されます。そのような専門的スキルを持つメンバーの確保が難しい場合であっても、BigQuery ML では <strong>SQL の知識があればモデルの開発を行うことができます</strong>。</p> <p>BigQuery ML では、モデルのトレーニングや予測で使用するデータは BigQuery 自体に格納されているものをシームレスに使用することができ、<strong>データの蓄積・モデルの学習・予測の実行が BigQuery 内で完結します。</strong>これにより、モデル開発のための習熟が必要なツールが減り、また大量のデータ移動による時間・料金などのコストを抑えることができます。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja">BigQuery の AI と ML の概要</a></li> </ul> <h1 id="BigQuery-ML-の使用方法">BigQuery ML の使用方法</h1> <h2 id="ユーザーインターフェース">ユーザーインターフェース</h2> <p>BigQuery ML は、以下のユーザーインターフェースで利用することができます。</p> <ul> <li><a href="https://cloud.google.com/bigquery/docs/bigquery-web-ui?hl=ja">Google Cloud コンソール</a></li> <li><a href="https://cloud.google.com/bigquery/docs/bq-command-line-tool?hl=ja">bq コマンドライン ツール</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/rest">BigQuery REST API</a></li> <li><a href="https://cloud.google.com/bigquery/docs/notebooks-introduction?hl=ja">BigQuery に統合された Colab Enterprise ノートブック</a></li> <li>Jupyter ノートブックやビジネス インテリジェンス プラットフォームなどの外部ツール</li> </ul> <p>Google Cloud コンソールから使用すると、BigQuery で通常の SQL を実行するときと同様の使用感で BigQuery ML の機能を利用することができます。</p> <p>5番目の Jupyter ノートブックを使った方法について、以下の記事では、フルマネージドの Jupyter ノートブック環境である <strong>Vertex AI Workbench</strong> から、マジックコマンド <code>%%bigquery</code> で BigQuery ML を使用する例が示されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbuilding-kmeans-model-with-bigquery-ml" title="Vertex AI WorkbenchとBigQuery MLで機械学習モデル(クラスタリング)を構築してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/building-kmeans-model-with-bigquery-ml">blog.g-gen.co.jp</a></cite></p> <h2 id="BigQuery-Editions">BigQuery Editions</h2> <p>BigQuery の課金モードとして<strong>オンデマンド</strong>を選択している場合、BigQuery ML を従量課金で使用することができます。</p> <p>課金モードとして <strong>BigQuery Editions</strong>を選択している場合、BigQuery ML は Enterprise ティアおよび Enterprise Plus ティアでのみ使用することができます(Standard ティアでは使用不可)。</p> <p>BigQuery Editions の詳細については以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-editions-explained" title="BigQuery Editionsを徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-editions-explained">blog.g-gen.co.jp</a></cite></p> <h2 id="クエリのドライラン">クエリのドライラン</h2> <p>BigQuery ML に限らず、BigQuery ではクエリ実行前に<strong>ドライラン</strong>を行うことで、実際に処理を行う前に、処理されるデータ量やオンデマンドで発生する料金を見積ることができます。</p> <p><figure class="figure-image figure-image-fotolife" title="ドライランにより、処理されるデータ量をクエリ実行前に確認できる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090540.png" width="800" height="196" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ドライランにより、処理されるデータ量をクエリ実行前に確認できる</figcaption></figure></p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/running-queries?hl=ja#dry-run">ドライラン</a></li> </ul> <h1 id="BigQuery-ML-でサポートされるモデル">BigQuery ML でサポートされるモデル</h1> <p>以降に紹介するのは2025å¹´1月時点でサポートされているモデルです。最新のサポート状況については以下のリンク先を参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja#supported_models">BigQuery の AI と ML の概要 - サポートされているモデル</a></li> </ul> <h2 id="内部モデル">内部モデル</h2> <p>BigQuery ML の組み込みのモデルとして、以下のモデルを使用して BigQuery 内部でトレーニングを行うことができます。</p> <ul> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-contribution-analysis">貢献度分析(Contribution analysis)</a>(2025å¹´1月現在、プレビュー)</li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm">線形回帰(Linear regression)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm">ロジスティック回帰(Logistic regression)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-kmeans">K 平均法クラスタリング(K-means clustering)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-matrix-factorization">行列分解(Matrix factorization)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-pca">主成分分析(PCA: Principal component analysis)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-time-series">時系列(Time series)</a></li> </ul> <p>モデルの作成時に使用する <code>CREATE MODEL</code> ステートメント(後述)の <code>OPTIONS</code> で、トレーニングに使用するモデルを指定できます。</p> <h2 id="外部モデル">外部モデル</h2> <p>以下のモデルは BigQuery ML の外部にあり、別の AI/ML サービスである <strong>Vertex AI</strong> を使用してトレーニングされます。</p> <ul> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-dnn-models">ディープ ニューラル ネットワーク(DNN: Deep neural network)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-wnd-models">ワイド&ディープ(Wide &amp; Deep)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-autoencoder">オートエンコーダ(Autoencoder)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-boosted-tree">ブーストツリー(Boosted Tree)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-random-forest">ランダム フォレスト(Random forest)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-automl">AutoML</a></li> </ul> <h2 id="インポートされたモデル">インポートされたモデル</h2> <p>BigQuery の外部でトレーニングされたカスタムモデルを Cloud Storage からインポートし、BigQuery ML で予測を実行することができます。Bigquery ML でインポートできるモデルの種類は以下の通りです。</p> <ul> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-onnx">Open Neural Network Exchange(ONNX)</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-tensorflow">TensorFlow</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-tflite">TensorFlow Lite</a></li> <li><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-xgboost">XGBoost</a></li> </ul> <h2 id="リモートモデル">リモートモデル</h2> <h3 id="ユーザーが-Vertex-AI-でデプロイしたモデル">ユーザーが Vertex AI でデプロイしたモデル</h3> <p><a href="https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja#remote_models">リモートモデル</a>では、Vertex AI でデプロイした機械学習モデルを使用して予測を実行することができます。モデルが大きすぎて BigQuery にインポートできない場合などに使用します。</p> <p><figure class="figure-image figure-image-fotolife" title="Vertex AI でデプロイしたモデルをリモートモデルとして使用する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090557.png" width="800" height="386" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Vertex AI でデプロイしたモデルをリモートモデルとして使用する</figcaption></figure></p> <h3 id="Google-の生成-AI-モデル">Google の生成 AI モデル</h3> <p>BigQuery ML からは、<strong>Gemini</strong> 等、Vertex AI で提供される <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models?hl=ja#foundation_models">Google の生成 AI モデル</a>をリモートモデルとして利用できます。</p> <p>以下の記事では、BigQuery ML のリモートモデルで Google 開発の大規模言語モデルである <strong>PaLM 2</strong> を使用して、テキストの感情分析を行っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fusing-palm2-with-bigquery-ml" title="BigQuery MLでVertex AIの基盤モデルPaLM2を呼び出して感情分析してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/using-palm2-with-bigquery-ml">blog.g-gen.co.jp</a></cite></p> <p>リモートモデルとして使用できる生成 AI モデルの最新情報については、以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/generative-ai-overview">Generative AI overview</a></li> </ul> <h3 id="タスク固有のソリューション">タスク固有のソリューション</h3> <p>BigQuery ML から、Google Cloud が用意した、特定のタスクに特化した機械学習モデルの API(<strong>事前トレーニング済み API</strong>)を利用できます。</p> <p>利用可能な事前トレーニング済み API には以下のような種類があります。それぞれ GoogleSQL の関数を使用してリクエストを送信します。</p> <table> <thead> <tr> <th> タスク </th> <th> API の名前 </th> <th> GoogleSQL の関数 </th> </tr> </thead> <tbody> <tr> <td> 自然言語処理 </td> <td> <a href="https://cloud.google.com/natural-language">Cloud Natural Language API</a> </td> <td> <a href="https://cloud.google.com/bigquery/docs/understand-text">ML.UNDERSTAND_TEXT</a> </td> </tr> <tr> <td> 機械翻訳 </td> <td> <a href="https://cloud.google.com/translate">Cloud Translation API</a> </td> <td> <a href="https://cloud.google.com/bigquery/docs/translate-text">ML.TRANSLATE</a> </td> </tr> <tr> <td> 音声文字変換 </td> <td> <a href="https://cloud.google.com/speech-to-text">Speech-to-Text API</a> </td> <td> <a href="https://cloud.google.com/bigquery/docs/transcribe">ML.TRANSCRIBE</a> </td> </tr> <tr> <td> ドキュメント処理 </td> <td> <a href="https://cloud.google.com/document-ai">Document AI API</a> </td> <td> <a href="https://cloud.google.com/bigquery/docs/process-document">ML.PROCESS_DOCUMENT</a> </td> </tr> <tr> <td> コンピュータ ビジョン </td> <td> <a href="https://cloud.google.com/vision">Cloud Vision API</a> </td> <td> <a href="https://cloud.google.com/bigquery/docs/annotate-image">ML.ANNOTATE_IMAGE</a> </td> </tr> </tbody> </table> <h1 id="基本的な-SQL-ステートメント関数">基本的な SQL ステートメント・関数</h1> <p><a href="https://cloud.google.com/bigquery/docs/create-machine-learning-model">公式ドキュメントのチュートリアル</a> を元に、BigQuery ML における基本的な SQL 文を解説します。</p> <p>モデルや場面に応じてどのようなステートメント・関数が使用できるのかは、以下のドキュメントで解説されています。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/e2e-journey">各モデルのエンドツーエンドのユーザー ジャーニー</a></li> </ul> <h2 id="CREATE-MODEL-ステートメント">CREATE MODEL ステートメント</h2> <p>BigQuery ML では、GoogleSQL の <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create">CREATE MODEL</a> ステートメントを使用してモデルのトレーニングを行います。</p> <p>モデルの作成には <code>CREATE MODEL</code> の他に、データセット内に同じ名前のモデルが存在しない場合のみモデルを作成する <code>CREATE MODEL IF NOT EXISTS</code> や、同じ名前のモデルが存在していた場合は置き換える <code>CREATE OR REPLACE MODEL</code> ステートメントが利用できます。</p> <p>以下は、<code>CREATE OR REPLACE MODEL</code> ステートメントを使用して、<code>bqml_tutorial</code> データセット内に <code>sample_model</code> という名前でロジスティック回帰モデルを作成する例です。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>#standardSQL <span class="synStatement">CREATE</span> <span class="synStatement">OR</span> <span class="synIdentifier">REPLACE</span> MODEL `bqml_tutorial.sample_model` OPTIONS(model_type = <span class="synSpecial">'</span><span class="synConstant">logistic_reg</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> <span class="synStatement">SELECT</span> <span class="synSpecial">IF</span>(totals.transactions <span class="synSpecial">IS</span> <span class="synSpecial">NULL</span>, <span class="synConstant">0</span>, <span class="synConstant">1</span>) <span class="synSpecial">AS</span> label, IFNULL(device.operatingSystem, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> os, device.isMobile <span class="synSpecial">AS</span> is_mobile, IFNULL(geoNetwork.country, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> country, IFNULL(totals.pageviews, <span class="synConstant">0</span>) <span class="synSpecial">AS</span> pageviews <span class="synSpecial">FROM</span> `bigquery-<span class="synSpecial">public</span>-data.google_analytics_sample.ga_sessions_*` <span class="synSpecial">WHERE</span> _TABLE_SUFFIX <span class="synStatement">BETWEEN</span> <span class="synSpecial">'</span><span class="synConstant">20160801</span><span class="synSpecial">'</span> <span class="synStatement">AND</span> <span class="synSpecial">'</span><span class="synConstant">20170630</span><span class="synSpecial">'</span> </pre> <p>使用するモデルは <code>OPTIONS</code> の <code>model_type=</code> で設定しています。<code>FROM</code> で指定したデータから、<code>SELECT</code> で指定した特徴量を使用してモデルの学習を行っています。</p> <p>Google Cloud コンソールや <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-train">ML.TRAINING_INFO</a> 関数を使用することで、モデルのトレーニング時の統計情報を確認することもできます。</p> <p><figure class="figure-image figure-image-fotolife" title="クエリの結果としてトレーニング時の統計情報を確認できる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090543.png" width="800" height="385" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クエリの結果としてトレーニング時の統計情報を確認できる</figcaption></figure></p> <p>トレーニングしたモデルの各種評価指標は、モデルの詳細からいつでも確認することができます。</p> <p><figure class="figure-image figure-image-fotolife" title="作成したモデルの各種評価指標を確認する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090547.png" width="800" height="564" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>作成したモデルの各種評価指標を確認する</figcaption></figure></p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/model-overview">モデルの作成</a></li> </ul> <h2 id="MLEVALUATE-関数">ML.EVALUATE 関数</h2> <p>作成したモデルの評価は <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate">ML.EVALUATE</a> 関数で行うことができます。</p> <p>以下の SQL を実行することで、<code>ML.EVALUATE</code> 関数の <code>MODEL</code> 引数で指定したモデルに対して評価を行います。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>#standardSQL <span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> ML.EVALUATE( MODEL `bqml_tutorial.sample_model`, ( <span class="synStatement">SELECT</span> <span class="synSpecial">IF</span>(totals.transactions <span class="synSpecial">IS</span> <span class="synSpecial">NULL</span>, <span class="synConstant">0</span>, <span class="synConstant">1</span>) <span class="synSpecial">AS</span> label, IFNULL(device.operatingSystem, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> os, device.isMobile <span class="synSpecial">AS</span> is_mobile, IFNULL(geoNetwork.country, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> country, IFNULL(totals.pageviews, <span class="synConstant">0</span>) <span class="synSpecial">AS</span> pageviews <span class="synSpecial">FROM</span> `bigquery-<span class="synSpecial">public</span>-data.google_analytics_sample.ga_sessions_*` <span class="synSpecial">WHERE</span> _TABLE_SUFFIX <span class="synStatement">BETWEEN</span> <span class="synSpecial">'</span><span class="synConstant">20170701</span><span class="synSpecial">'</span> <span class="synStatement">AND</span> <span class="synSpecial">'</span><span class="synConstant">20170801</span><span class="synSpecial">'</span> ) ) </pre> <p>コンソール上での出力は以下のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="ML.EVALUATE 関数によるモデルの評価"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090550.png" width="800" height="151" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ML.EVALUATE 関数によるモデルの評価</figcaption></figure></p> <p>評価時に出力される指標はモデルの種類によって異なります。また、<code>ML.CONFUSION_MATRIX</code>(混同行列)や <code>ML.ROC_CURVE</code>(ROC 曲線)などの関数も提供されています。</p> <p>詳細は以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/evaluate-overview">BigQuery ML モデルの評価の概要</a></li> </ul> <h2 id="MLPREDICT-関数">ML.PREDICT 関数</h2> <p>作成したモデルを使用して予測を行うには、<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-predict">ML.PREDICT</a> 関数を使用します。</p> <p>以下のように <code>ML.PREDICT</code> 関数の <code>MODEL</code> 引数で指定したモデルを使用して予測を行います。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>#standardSQL <span class="synStatement">SELECT</span> country, <span class="synIdentifier">SUM</span>(predicted_label) <span class="synSpecial">AS</span> total_predicted_purchases <span class="synSpecial">FROM</span> ML.PREDICT( MODEL `bqml_tutorial.sample_model`, ( <span class="synStatement">SELECT</span> IFNULL(device.operatingSystem, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> os, device.isMobile <span class="synSpecial">AS</span> is_mobile, IFNULL(totals.pageviews, <span class="synConstant">0</span>) <span class="synSpecial">AS</span> pageviews, IFNULL(geoNetwork.country, <span class="synSpecial">&quot;&quot;</span>) <span class="synSpecial">AS</span> country <span class="synSpecial">FROM</span> `bigquery-<span class="synSpecial">public</span>-data.google_analytics_sample.ga_sessions_*` <span class="synSpecial">WHERE</span> _TABLE_SUFFIX <span class="synStatement">BETWEEN</span> <span class="synSpecial">'</span><span class="synConstant">20170701</span><span class="synSpecial">'</span> <span class="synStatement">AND</span> <span class="synSpecial">'</span><span class="synConstant">20170801</span><span class="synSpecial">'</span> ) ) <span class="synSpecial">GROUP</span> <span class="synSpecial">BY</span> country <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> total_predicted_purchases <span class="synSpecial">DESC</span> LIMIT <span class="synConstant">10</span> </pre> <p>コンソール上での出力は以下のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="ML.PREDICT 関数による予測"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250114/20250114090554.png" width="587" height="383" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ML.PREDICT 関数による予測</figcaption></figure></p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/inference-overview">モデル推定の概要</a></li> </ul> <h1 id="特徴量の前処理">特徴量の前処理</h1> <h2 id="自動前処理">自動前処理</h2> <p>BigQuery ML では自動前処理として、モデルのトレーニング時に以下の前処理を自動で行っています。</p> <ul> <li>欠損データの補完</li> <li>値の変換(標準化、ワンホットエンコーディング、タイムスタンプの変換など)</li> </ul> <p>自動前処理の詳細については、以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/auto-preprocessing">自動特徴前処理</a></li> </ul> <h2 id="手動前処理TRANSFORM-ステートメント">手動前処理(TRANSFORM ステートメント)</h2> <p><code>TRANSFORM</code> ステートメントを使用することで、前処理用の関数を使用することができます。</p> <p>たとえば、以下の SQL では、<code>ML.QUANTILE_BUCKETIZE</code> 関数で <code>mother_age</code> 列の<a href="https://developers.google.com/machine-learning/crash-course/numerical-data/binning">バケット化(ビニング)</a>を、<code>ML.FEATURE_CROSS</code> 関数で <code>is_male</code> 列と <code>mother_race</code> 列の<a href="https://developers.google.com/machine-learning/crash-course/categorical-data/feature-crosses">特徴クロス</a>を作成する前処理を行ってからモデルを作成しています。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>#standardSQL <span class="synStatement">CREATE</span> MODEL `bqml_tutorial.natality_model` TRANSFORM( weight_pounds, is_male, gestation_weeks, ML.QUANTILE_BUCKETIZE(mother_age, <span class="synConstant">5</span>) OVER() <span class="synSpecial">AS</span> bucketized_mother_age, <span class="synIdentifier">CAST</span>(mother_race <span class="synSpecial">AS</span> string) <span class="synSpecial">AS</span> mother_race, ML.FEATURE_CROSS( STRUCT( is_male, <span class="synIdentifier">CAST</span>(mother_race <span class="synSpecial">AS</span> STRING) <span class="synSpecial">AS</span> mother_race ) ) is_male_mother_race ) OPTIONS ( model_type = <span class="synSpecial">'</span><span class="synConstant">linear_reg</span><span class="synSpecial">'</span>, input_label_cols = [<span class="synSpecial">'</span><span class="synConstant">weight_pounds</span><span class="synSpecial">'</span>] ) <span class="synSpecial">AS</span> <span class="synStatement">SELECT</span> * <span class="synSpecial">FROM</span> `bigquery-<span class="synSpecial">public</span>-data.samples.natality` <span class="synSpecial">WHERE</span> weight_pounds <span class="synSpecial">IS</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> <span class="synStatement">AND</span> RAND() &lt; <span class="synConstant">0</span>.<span class="synConstant">001</span> </pre> <p>その他、手動前処理に使用できる関数については以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/manual-preprocessing">手動での特徴の前処理</a></li> </ul> <h1 id="モデルのモニタリング">モデルのモニタリング</h1> <p><code>ML.VALIDATE_DATA_SKEW</code> 関数や <code>ML.VALIDATE_DATA_DRIFT</code> 関数を使用することで、トレーニングに使用したデータと、実際のモデル運用時に予測に使用されるデータ(サービングデータ)の統計情報を比較し、<strong>データスキュー</strong>ã‚„<strong>データドリフト</strong>の発生を検知することができます。</p> <p><strong>データスキュー(Data Skew)</strong>とは、トレーニングで使用したデータの分布と、本番環境で提供されるデータの分布が大きく異なっていることにより、モデルの予測性能が下がってしまう現象のことです。トレーニングが適切に行えていない状況であると考えられます。</p> <p><strong>データドリフト(Data Drift)</strong>とは、本番環境で提供されるデータの分布が時間の経過とともに大きく変化してしまうことにより、モデルの予測性能が下がってしまう現象のことです。モデルの劣化と捉えてもいいでしょう。</p> <p>モデルのモニタリングに使用できる関数の種類については、以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/docs/model-monitoring-overview">モデル モニタリングの概要</a></li> </ul> <h1 id="BigQuery-ML-の料金">BigQuery ML の料金</h1> <p>BigQuery ML の料金の詳細および最新情報については、以下のドキュメントを参照してください。</p> <ul> <li>参考:<a href="https://cloud.google.com/bigquery/pricing?hl=en#bqml">BigQuery ML pricing</a></li> </ul> <h2 id="オンデマンド料金">オンデマンド料金</h2> <p>BigQuery の課金モードがオンデマンドの場合、BigQuery で処理されるデータのバイト数に応じて課金が発生します。モデル作成と予測でバイト数あたりの料金単価が異なる点に注意が必要です。</p> <p>たとえば、ロジスティック回帰モデルや線型回帰モデルの作成時のトレーニングでは <code>$375/1TB</code>、評価・予測タスクでは <code>$7.5/1TB</code> の料金が発生します(東京リージョン、2025å¹´1月時点)。</p> <h2 id="BigQuery-Editions-の料金">BigQuery Editions の料金</h2> <p>課金モードとして BigQuery Editions を利用する場合、BigQuery ML の料金は Editions の使用量に含まれます。</p> <p>使用するモデルによって利用される <a href="https://cloud.google.com/bigquery/docs/reservations-intro?hl=ja#assignments">Editions の割り当て</a>が異なり、内部モデルの作成・予測には Editions の <code>QUERY</code> 割り当てが、外部モデルの利用には <code>ML_EXTERNAL</code> が利用されます。</p> <h2 id="外部モデルの料金">外部モデルの料金</h2> <p>BigQuery 外部のモデルを使用してトレーニングを行う外部モデルでは、オンデマンドの料金もしくは BigQuery Editions の料金(BigQuery で処理されるぶんの料金)に加え、<a href="https://cloud.google.com/vertex-ai/pricing?hl=ja">Vertex AI のトレーニング料金</a>も発生します。</p> <h2 id="リモートモデルの料金">リモートモデルの料金</h2> <p>リモートモデルでも外部モデル同様に、 BigQuery で処理されるぶんの料金に加え、リモートモデルとして使用するサービスの料金が適用されます。</p> <p>たとえば、リモートモデルとして Cloud AI Vision API を使用する場合は <a href="https://cloud.google.com/vision/pricing?hl=ja">Cloud AI Vision API の料金</a>が、Vertex AI の基盤モデル(生成 AI モデル)を使用する場合は <a href="https://cloud.google.com/vertex-ai/generative-ai/pricing?hl=ja">Vertex AI の料金</a>が追加で発生します。</p> <h1 id="他の機械学習系プロダクトとの統合">他の機械学習系プロダクトとの統合</h1> <h2 id="Vertex-AI">Vertex AI</h2> <p><a href="https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform?hl=ja">Vertex AI</a> は機械学習モデルの開発に関わる様々な機能が統合されたプロダクトです。</p> <p>Vertex AI には開発した機械学習モデルを集中管理するための <a href="https://cloud.google.com/vertex-ai/docs/model-registry/introduction?hl=ja">Model Registry</a> という機能があり、BigQuery ML で開発したモデルもここで管理することができます。</p> <p>Model Registory で管理されているモデルはバージョニングや評価、デプロイを容易に行うことができます。Vertex AI の <a href="https://cloud.google.com/vertex-ai/docs/general/deployment?hl=ja">Endpoints</a> 機能では、フルマネージドの実行環境にモデルをデプロイし、生成されたエンドポイントを使用してオンラインの予測を実行することができます。</p> <p>Vertex AI の詳細については、以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fvertexai-explained" title="Vertex AI を徹底解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/vertexai-explained">blog.g-gen.co.jp</a></cite></p> <h2 id="Colab-Enterprise">Colab Enterprise</h2> <p><a href="https://cloud.google.com/colab/docs/introduction">Colab Enterprise</a> は、Google Cloud 上に事前構築されたマネージドなノートブック環境を提供するサービスです。</p> <p>Colab Enterprise のノートブックを使用して、ノートブックから BigQuery ML によるタスクを実行することができます。モデルの開発時に Python の機械学習ライブラリを使用した複雑なデータ処理が必要な場合などに活用できます。</p> <p>Colab Enterprise のサービス詳細については、以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcolab-enterprise-and-vertexai-workbench-explained" title="Colab EnterpriseとVertex AI Workbenchを徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/colab-enterprise-and-vertexai-workbench-explained">blog.g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun EvantarcとWorkflowsでイベントドリブンにCloud Run jobsを実行してみた hatenablog://entry/6802418398309256704 2025-01-10T09:00:00+09:00 2025-01-10T09:00:01+09:00 G-gen の出口です。本記事では、Evantarc と Workflows を利用して イベントドリブンに Cloud Run jobs を実行する方法をご紹介します。 概要 Cloud Run functions と Cloud Run jobs 検証の概要 Eventarc Workflows Cloud Storage の準備 Cloud Storage バケットの作成 Cloud Strage サービスエージェントへの権限付与 BigQuery テーブルの作成 Cloud Run jobs の作成 サービスアカウントの作成 Docker コンテナの作成に必要なリソースの作成 main… <p>G-gen の出口です。本記事では、Evantarc と Workflows を利用して イベントドリブンに Cloud Run jobs を実行する方法をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a><ul> <li><a href="#Cloud-Run-functions-と-Cloud-Run-jobs">Cloud Run functions と Cloud Run jobs</a></li> <li><a href="#検証の概要">検証の概要</a></li> <li><a href="#Eventarc">Eventarc</a></li> <li><a href="#Workflows">Workflows</a></li> </ul> </li> <li><a href="#Cloud-Storage-の準備">Cloud Storage の準備</a><ul> <li><a href="#Cloud-Storage-バケットの作成">Cloud Storage バケットの作成</a></li> <li><a href="#Cloud-Strage-サービスエージェントへの権限付与">Cloud Strage サービスエージェントへの権限付与</a></li> </ul> </li> <li><a href="#BigQuery-テーブルの作成">BigQuery テーブルの作成</a></li> <li><a href="#Cloud-Run-jobs-の作成">Cloud Run jobs の作成</a><ul> <li><a href="#サービスアカウントの作成">サービスアカウントの作成</a></li> <li><a href="#Docker-コンテナの作成に必要なリソースの作成">Docker コンテナの作成に必要なリソースの作成</a><ul> <li><a href="#mainpy">main.py</a></li> <li><a href="#requirementstxt">requirements.txt</a></li> <li><a href="#Procfile">Procfile</a></li> </ul> </li> <li><a href="#Artifact-Registry-の作成">Artifact Registry の作成</a></li> <li><a href="#Artifact-Registry-にアップロード">Artifact Registry にアップロード</a></li> <li><a href="#Cloud-Run-jobs-の作成-1">Cloud Run jobs の作成</a></li> </ul> </li> <li><a href="#Workflows-の作成">Workflows の作成</a><ul> <li><a href="#サービスアカウントの設定">サービスアカウントの設定</a></li> <li><a href="#ワークフローの作成">ワークフローの作成</a><ul> <li><a href="#cloud-run-job-workflowyaml">cloud-run-job-workflow.yaml</a></li> </ul> </li> <li><a href="#ワークフローのデプロイ">ワークフローのデプロイ</a></li> </ul> </li> <li><a href="#Eventarc-トリガーの設定">Eventarc トリガーの設定</a><ul> <li><a href="#サービスアカウントの設定-1">サービスアカウントの設定</a></li> <li><a href="#Eventarc-の作成">Eventarc の作成</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213091632.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <h2 id="Cloud-Run-functions-と-Cloud-Run-jobs">Cloud Run functions と Cloud Run jobs</h2> <p>イベントドリブンにデータを処理するには、Cloud Run functions を使った方法などがあります。例えば、Cloud Storage にオブジェクトが格納されたら自動的に Cloud Run functions が起動するような処理を、非常に簡単に実装できます。しかし、Cloud Run functions には最大9分(イベントドリブン関数の場合)の実行時間制限があるなど、いくつかの制約があります。</p> <p>当記事では、<strong>Cloud Run jobs</strong> を使ってイベントドリブンな処理を実現する検証を行いました。Cloud Run jobs には、Cloud Run functions と比較して以下のようなメリットがあります。</p> <ul> <li>最大実行時間が168時間であること(2025å¹´1月現在では24時間を超える処理は Preview)</li> <li>タスクの並列実行数を明示的に指定可能であること</li> </ul> <p>Cloud Run jobs については以下の記事で解説していますので、ご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-run-jobs-explained" title="Cloud Run jobs を徹底解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-run-jobs-explained">blog.g-gen.co.jp</a></cite></p> <p>また以下の記事では、Cloud Storage にテキストファイルが格納されたことを起点として Cloud Run functions を呼び出し、Vertex AI Gemini API で取得したテキストの要約結果を BigQuery に保存する処理を実装しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fgen-ai-with-event-driven-architecture" title="イベンドドリブン×生成AIで日報を自動要約してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/gen-ai-with-event-driven-architecture">blog.g-gen.co.jp</a></cite></p> <p>当記事では、上記記事の Cloud Run functions の部分を Cloud Run jobs に置き換えて、イベントドリブンに Cloud Run jobs を実行する構成を実装します。</p> <h2 id="検証の概要">検証の概要</h2> <p>当記事で行った検証のアーキテクチャは以下の通りです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250110/20250110090011.png" width="800" height="325" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ol> <li>ローカル PC から日報のテキストファイルを Cloud Storage にアップロード</li> <li>ファイルがアップロードされたことを検知して Eventarc トリガーが Workflows ã‚’èµ·å‹•</li> <li>Workflows が受け取ったイベント情報を環境変数にセットして Cloud Run jobs ã‚’èµ·å‹•</li> <li>Cloud Run jobs が Gemini で日報ファイルを要約し、結果を BigQuery テーブルに格納</li> </ol> <h2 id="Eventarc">Eventarc</h2> <p><strong>Evantarc</strong> は Google Cloud でイベントドリブンアーキテクチャを構築するためのフルマネージドサービスです。イベントの発生元から様々な宛先への転送を、サーバレスで容易に構築できます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/eventarc/docs?hl=ja">Eventarc の概要</a></li> <li>参考 : <a href="https://cloud.google.com/eventarc/docs/event-driven-architectures?hl=ja">イベント ドリブン アーキテクチャ</a></li> </ul> <p>以下の記事では Eventarc を使ったアーキテクチャの例が紹介されていますので、ご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fnotify-using-eventarc-for-cloudrun" title="Eventarc + Cloud Run で Google Cloud リソースの作成を Slack 通知する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/notify-using-eventarc-for-cloudrun">blog.g-gen.co.jp</a></cite></p> <h2 id="Workflows">Workflows</h2> <p><strong>Workflows</strong>(または Cloud Workflows)は Google Cloud のフルマネージドでサーバーレスなオーケストレーションサービスです。定義した順番に Cloud Run ã‚„ Cloud Run functions を実行したり、BigQuery でクエリを発行するなど、様々な Google Cloud サービスを実行したり、任意の HTTP エンドポイントにリクエストを送ることができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/workflows/docs/overview?hl=ja">ワークフローの概要</a></li> </ul> <p>以下の記事で Workflows について解説していますので、ご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-workflows-explained" title="Cloud Workflowsを徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-workflows-explained">blog.g-gen.co.jp</a></cite></p> <h1 id="Cloud-Storage-の準備">Cloud Storage の準備</h1> <h2 id="Cloud-Storage-バケットの作成">Cloud Storage バケットの作成</h2> <p>日報ファイルをアップロードするためのバケットを作成します。</p> <p><strong>バケット名に置き換えてください</strong> の部分を、作成されるバケット名に置き換えて、以下のコマンドを実行します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synIdentifier">BUCKET_NAME</span>=<span class="synStatement">&quot;</span><span class="synConstant">作成されるバケット名に置き換えてください</span><span class="synStatement">&quot;</span> gcloud storage buckets create gs://<span class="synPreProc">${BUCKET_NAME}</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 </pre> <h2 id="Cloud-Strage-サービスエージェントへの権限付与">Cloud Strage サービスエージェントへの権限付与</h2> <p>Cloud Storage からのトリガーを作成する場合、Pub/Sub パブリッシャーのロールをプロジェクトの Cloud Storage サービスエージェントに付与する必要があります。</p> <p><strong>プロジェクト ID に置き換えてください</strong> の部分を、ご自身のプロジェクト ID に置き換えて、以下のコマンドを実行します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synIdentifier">PROJECT</span>=<span class="synStatement">&quot;</span><span class="synConstant">プロジェクト ID に置き換えてください</span><span class="synStatement">&quot;</span> <span class="synIdentifier">SERVICE_ACCOUNT</span>=<span class="synStatement">&quot;</span><span class="synPreProc">$(</span><span class="synSpecial">gcloud storage service-agent --project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT})</span><span class="synStatement">&quot;</span> gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">serviceAccount:</span><span class="synPreProc">${SERVICE_ACCOUNT}</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">='</span><span class="synConstant">roles/pubsub.publisher</span><span class="synStatement">'</span> </pre> <h1 id="BigQuery-テーブルの作成">BigQuery テーブルの作成</h1> <p>日報データを格納するための BigQuery テーブルを作成します。</p> <p>以下のコマンドでは、<code>report</code> という名前のデータセットと、<code>daily_report</code> という名前のテーブルを作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># データセットを作成</span> bq <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 mk <span class="synStatement">\</span> <span class="synSpecial">--dataset</span> <span class="synStatement">\</span> <span class="synPreProc">${PROJECT}</span>:report <span class="synComment"># テーブルを作成</span> bq mk <span class="synStatement">\</span> <span class="synSpecial">--table</span> <span class="synStatement">\</span> <span class="synSpecial">--schema</span> date:DATE,name:STRING,text:STRING <span class="synStatement">\</span> <span class="synSpecial">--clustering_fields</span> date,name <span class="synStatement">\</span> <span class="synPreProc">${PROJECT}</span>:report.daily_report </pre> <p> name カラムと date カラムをクラスタ化してテーブルを作成することで、name カラムおよび date カラムでフィルタをかけるクエリを実行したときにスキャン量を削減して、パフォーマンスを向上させることができます。</p> <h1 id="Cloud-Run-jobs-の作成">Cloud Run jobs の作成</h1> <h2 id="サービスアカウントの作成">サービスアカウントの作成</h2> <p>Cloud Run jobs で使用するサービスアカウントを作成します。</p> <p>Cloud Run jobs が Gemini API で文章を要約したり、BigQuery にデータを書き込んだり、ログを出力したりするために、以下のロールを Workflows で使用するサービスアカウントに付与する必要があります。</p> <ul> <li>BigQuery データ編集者(<code>roles/bigquery.dataEditor</code>)</li> <li>BigQuery ジョブユーザー(<code>roles/bigquery.jobUser</code>)</li> <li>Storage オブジェクト閲覧者(<code>roles/storage.objectViewer</code>)</li> <li>Vertex AI ユーザー(<code>roles/aiplatform.user</code>)</li> <li>ログ書き込み(<code>roles/logging.logWriter</code>)</li> </ul> <p>以下のコマンドを実行すると、<code>sa-daily-report-job</code> という名前のサービスアカウントが作成され、その後、必要なロールが付与されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud iam service-accounts create sa-daily-report-job gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/bigquery.dataEditor gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/bigquery.jobUser gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/storage.objectViewer gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/aiplatform.user gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/logging.logWriter </pre> <h2 id="Docker-コンテナの作成に必要なリソースの作成">Docker コンテナの作成に必要なリソースの作成</h2> <p>主要な処理を Python コードで実行する <code>main.py</code>、コード内で利用するパッケージをリスト化した <code>requirements.txt</code>、そして <code>Procfile</code> を作成します。</p> <p><strong>Procfile</strong> とは、コンテナの起動時に呼び出されるプロセスを定義するファイルで、Python で Buildpack を利用する場合においては、ファイルの作成が必須になります。Buildpack を利用すれば、Dockerfile を作成せずにコードをコンテナイメージに変換することができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/docs/buildpacks/overview?hl=ja">Google Cloud の Buildpack</a></li> <li>参考 : <a href="https://cloud.google.com/docs/buildpacks/about-procfile?hl=ja">Procfile について</a></li> </ul> <h3 id="mainpy">main.py</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> vertexai <span class="synPreProc">from</span> vertexai.generative_models <span class="synPreProc">import</span> GenerativeModel, Part, SafetySetting <span class="synPreProc">import</span> os <span class="synPreProc">import</span> argparse <span class="synPreProc">from</span> datetime <span class="synPreProc">import</span> datetime <span class="synPreProc">import</span> logging <span class="synPreProc">from</span> google.cloud <span class="synPreProc">import</span> bigquery, storage <span class="synPreProc">import</span> google.cloud.logging PROJECT_ID = os.environ.get(<span class="synConstant">&quot;PROJECT_ID&quot;</span>) REGION = os.environ.get(<span class="synConstant">&quot;REGION&quot;</span>) DATASET_ID = os.environ.get(<span class="synConstant">&quot;DATASET_ID&quot;</span>) TABLE_ID = os.environ.get(<span class="synConstant">&quot;TABLE_ID&quot;</span>) TABLE_NAME = f<span class="synConstant">&quot;{PROJECT_ID}.{DATASET_ID}.{TABLE_ID}&quot;</span> INPUT_BUCKET = os.environ.get(<span class="synConstant">&quot;INPUT_BUCKET&quot;</span>) INPUT_FILE = os.environ.get(<span class="synConstant">&quot;INPUT_FILE&quot;</span>) <span class="synComment"># Vertex AI の初期化</span> vertexai.init(project=PROJECT_ID, location=REGION) <span class="synComment"># Cloud Logging クライアントのインスタンス化</span> logger_client = google.cloud.logging.Client() logger_client.setup_logging() logger = logging.getLogger() logger.setLevel(logging.DEBUG) <span class="synComment"># Cloud Storage クライアントのインスタンス化</span> storage_client = storage.Client() <span class="synComment"># Cloud Storage からファイルを読んで Gemini に要約させる関数</span> <span class="synStatement">def</span> <span class="synIdentifier">summarize_text_from_file</span>() -&gt; <span class="synIdentifier">str</span>: <span class="synStatement">try</span>: <span class="synComment"># Cloud Storage にあるファイルのテキストを読み取る</span> bucket = storage_client.bucket(INPUT_BUCKET) blob = bucket.blob(INPUT_FILE) file_content = blob.download_as_string() text = file_content.decode(<span class="synConstant">&quot;utf-8&quot;</span>) <span class="synStatement">except</span> <span class="synType">Exception</span> <span class="synStatement">as</span> e: logger.error(f<span class="synConstant">&quot;Error during reading file: {e}&quot;</span>) <span class="synStatement">raise</span> <span class="synStatement">try</span>: <span class="synComment"># Gemini に要約させる</span> model = GenerativeModel(<span class="synConstant">&quot;gemini-1.5-flash-002&quot;</span>) generation_config = { <span class="synConstant">&quot;max_output_tokens&quot;</span>: <span class="synConstant">500</span>, <span class="synConstant">&quot;temperature&quot;</span>: <span class="synConstant">0.1</span>, <span class="synConstant">&quot;top_p&quot;</span>: <span class="synConstant">0.1</span>, } response = model.generate_content( f<span class="synConstant">&quot;&quot;&quot;以下の文章を要約してください:</span><span class="synSpecial">\n</span><span class="synConstant">{file_content}</span><span class="synSpecial">\n</span><span class="synConstant">要約:</span><span class="synSpecial">\n</span><span class="synConstant">&quot;&quot;&quot;</span>, generation_config=generation_config ) <span class="synStatement">except</span> <span class="synType">Exception</span> <span class="synStatement">as</span> e: logger.error(f<span class="synConstant">&quot;Error during summarization: {e}&quot;</span>) <span class="synStatement">raise</span> <span class="synStatement">return</span> response.candidates[<span class="synConstant">0</span>].content.parts[<span class="synConstant">0</span>].text <span class="synComment"># BigQuery のテーブルにデータを挿入する関数</span> <span class="synStatement">def</span> <span class="synIdentifier">insert_into_bigquery</span>(summary_text: <span class="synIdentifier">str</span>): <span class="synStatement">try</span>: <span class="synComment"># ファイルの名前から日付と名前を取得する</span> <span class="synIdentifier">file</span> = INPUT_FILE.split(<span class="synConstant">&quot;/&quot;</span>)[-<span class="synConstant">1</span>] <span class="synComment"># フォルダ部分を消す</span> date_str, name_txt = <span class="synIdentifier">file</span>.split(<span class="synConstant">&quot;_&quot;</span>) name = name_txt.split(<span class="synConstant">&quot;.&quot;</span>)[<span class="synConstant">0</span>] <span class="synStatement">try</span>: date_object = datetime.strptime(date_str, <span class="synConstant">'%Y%m%d'</span>) formatted_date = date_object.strftime(<span class="synConstant">&quot;%Y-%m-%d&quot;</span>) <span class="synStatement">except</span> <span class="synType">ValueError</span>: <span class="synStatement">raise</span> <span class="synType">ValueError</span>(<span class="synConstant">&quot;Invalid filename date format. Expected YYYYMMDD.&quot;</span>) client = bigquery.Client(project=PROJECT_ID) table_ref = client.get_table(f<span class="synConstant">&quot;{TABLE_NAME}&quot;</span>) <span class="synComment"># 重複にならないように、データを挿入する</span> query = f<span class="synConstant">&quot;&quot;&quot;MERGE {TABLE_NAME} t</span> <span class="synConstant"> USING (</span> <span class="synConstant"> SELECT CAST('{formatted_date}' AS DATE) AS date, </span> <span class="synConstant"> '{name}' AS name, </span> <span class="synConstant"> '''{summary_text}''' AS text) i</span> <span class="synConstant"> ON t.date = i.date AND t.name = i.name</span> <span class="synConstant"> WHEN MATCHED THEN</span> <span class="synConstant"> UPDATE SET text = i.text</span> <span class="synConstant"> WHEN NOT MATCHED THEN</span> <span class="synConstant"> INSERT (date, name, text) VALUES (i.date, i.name, i.text)&quot;&quot;&quot;</span> query_job = client.query(query) <span class="synStatement">try</span>: query_job.result() logger.debug(f<span class="synConstant">&quot;{INPUT_FILE} insert successful.&quot;</span>) <span class="synStatement">except</span> <span class="synType">Exception</span> <span class="synStatement">as</span> e: logger.error(f<span class="synConstant">&quot;{INPUT_FILE} insert failed: {e}&quot;</span>) <span class="synStatement">raise</span> <span class="synStatement">except</span> <span class="synType">Exception</span> <span class="synStatement">as</span> e: logger.error(f<span class="synConstant">&quot;An unexpected error occurred while insert into bigquery: {e}&quot;</span>) <span class="synStatement">raise</span> <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: summary_result = summarize_text_from_file() insert_into_bigquery(summary_result) </pre> <h3 id="requirementstxt">requirements.txt</h3> <pre class="code lang-python" data-lang="python" data-unlink>google-cloud-aiplatform==<span class="synConstant">1.73</span>.<span class="synConstant">0</span> google-cloud-bigquery==<span class="synConstant">3.25</span>.<span class="synConstant">0</span> google-cloud-logging==<span class="synConstant">3.11</span>.<span class="synConstant">2</span> </pre> <h3 id="Procfile">Procfile</h3> <p>Buildpacks では web プロセスを定義することが必須です。web プロセスを定義しなかった場合、 <code>web process not found in Procfile</code> というエラーが発生します。<br/> ただし、今回は HTTP トラフィックを受信する必要がないので、実際には web プロセスは使用されません。</p> <pre class="code" data-lang="" data-unlink>web: echo &#34;no web&#34; python: python</pre> <h2 id="Artifact-Registry-の作成">Artifact Registry の作成</h2> <p>コンテナイメージを保存するための Artifact Registry 標準リポジトリを作成します。</p> <p>以下のコマンドでは、<code>my-repo</code> という名前のリポジトリを作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud artifacts repositories create my-repo <span class="synStatement">\</span> <span class="synSpecial">--repository-format</span><span class="synStatement">=</span>docker <span class="synStatement">\</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 </pre> <h2 id="Artifact-Registry-にアップロード">Artifact Registry にアップロード</h2> <p>Buildpack を使用してコンテナイメージをビルドし、作成した Artifact Registry リポジトリにプッシュします。</p> <p>以下のコマンドでは、ソースコードをビルドし、<code>my-repo</code> リポジトリに <code>daily-report-job</code> というイメージ名でプッシュします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud builds submit <span class="synSpecial">--pack</span> <span class="synIdentifier">image</span>=asia-northeast1-docker.pkg.dev/<span class="synPreProc">${PROJECT}</span>/my-repo/daily-report-job </pre> <h2 id="Cloud-Run-jobs-の作成-1">Cloud Run jobs の作成</h2> <p>以下のコマンドでは、<code>daily-report-job</code> という名前の Cloud Run jobs を作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud run <span class="synStatement">jobs</span> create daily-report-job <span class="synStatement">\</span> <span class="synSpecial">--image</span><span class="synStatement">=</span>asia-northeast1-docker.pkg.dev/<span class="synPreProc">${PROJECT}</span>/my-repo/daily-report-job:latest <span class="synStatement">\</span> <span class="synSpecial">--command</span><span class="synStatement">=</span>python <span class="synStatement">\</span> <span class="synSpecial">--args</span><span class="synStatement">=</span>main.py <span class="synStatement">\</span> <span class="synSpecial">--region</span><span class="synStatement">=</span>asia-northeast1 <span class="synStatement">\</span> <span class="synSpecial">--service-account</span><span class="synStatement">=</span>sa-daily-report-job@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synSpecial">--set-env-vars</span><span class="synStatement">=</span><span class="synIdentifier">INPUT_BUCKET</span>=<span class="synPreProc">${BUCKET_NAME}</span>,<span class="synIdentifier">INPUT_FILE</span>=input_file.txt,<span class="synIdentifier">PROJECT_ID</span>=<span class="synPreProc">${PROJECT}</span>,<span class="synIdentifier">DATASET_ID</span>=report,<span class="synIdentifier">TABLE_ID</span>=daily_report </pre> <p>環境変数 <code>INPUT_BUCKET</code> および <code>INPUT_FILE</code> は、実際には Workflows がジョブを起動する際に送られてきたイベント情報を利用してオーバーライドされます。</p> <h1 id="Workflows-の作成">Workflows の作成</h1> <h2 id="サービスアカウントの設定">サービスアカウントの設定</h2> <p>Workflows で使用するサービスアカウントを作成します。</p> <p>Workflows は環境変数をオーバーライドして Cloud Run jobs を起動し、実行結果を受け取るために、以下のロールを Workflows で使用するサービスアカウントに付与する必要があります。</p> <ul> <li>Cloud Run デベロッパー(<code>roles/run.developer</code>)</li> </ul> <p>なお環境変数をオーバーライドして Cloud Run jobs を起動するロールとして、上記の他に「オーバーライドを使用する Cloud Run ジョブ エグゼキュータ(<code>roles/run.jobsExecutorWithOverrides</code>)」ロールがありますが、こちらだと実行結果を受け取るために必要な <code>run.executions.get</code> 権限が不足しているため、上記のロールとしています。</p> <p>以下のコマンドを実行すると、<code>sa-cloud-run-job-workflow</code> という名前のサービスアカウントが作成され、その後、必要なロールが付与されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud iam service-accounts create sa-cloud-run-job-workflow gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-cloud-run-job-workflow@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/run.developer </pre> <h2 id="ワークフローの作成">ワークフローの作成</h2> <p>Cloud Run jobs を実行するためのワークフローを、<code>cloud-run-job-workflow.yaml</code> という YAML ファイルに定義します。</p> <h3 id="cloud-run-job-workflowyaml">cloud-run-job-workflow.yaml</h3> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">main</span><span class="synSpecial">:</span> <span class="synIdentifier">params</span><span class="synSpecial">:</span> <span class="synSpecial">[</span>event<span class="synSpecial">]</span> <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">init</span><span class="synSpecial">:</span> <span class="synIdentifier">assign</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">project_id</span><span class="synSpecial">:</span> ${sys.get_env(&quot;GOOGLE_CLOUD_PROJECT_ID&quot;)} <span class="synStatement">- </span><span class="synIdentifier">event_bucket</span><span class="synSpecial">:</span> ${event.data.bucket} <span class="synStatement">- </span><span class="synIdentifier">event_file</span><span class="synSpecial">:</span> ${event.data.name} <span class="synStatement">- </span><span class="synIdentifier">job_name</span><span class="synSpecial">:</span> daily-report-job <span class="synStatement">- </span><span class="synIdentifier">job_location</span><span class="synSpecial">:</span> asia-northeast1 <span class="synStatement">- </span><span class="synIdentifier">run_job</span><span class="synSpecial">:</span> <span class="synIdentifier">call</span><span class="synSpecial">:</span> googleapis.run.v1.namespaces.jobs.run <span class="synIdentifier">args</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> ${&quot;namespaces/&quot; + project_id + <span class="synConstant">&quot;/jobs/&quot;</span> + job_name} <span class="synIdentifier">location</span><span class="synSpecial">:</span> ${job_location} <span class="synIdentifier">body</span><span class="synSpecial">:</span> <span class="synIdentifier">overrides</span><span class="synSpecial">:</span> <span class="synIdentifier">containerOverrides</span><span class="synSpecial">:</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> INPUT_BUCKET <span class="synIdentifier">value</span><span class="synSpecial">:</span> ${event_bucket} <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> INPUT_FILE <span class="synIdentifier">value</span><span class="synSpecial">:</span> ${event_file} <span class="synIdentifier">result</span><span class="synSpecial">:</span> job_execution <span class="synStatement">- </span><span class="synIdentifier">finish</span><span class="synSpecial">:</span> <span class="synIdentifier">return</span><span class="synSpecial">:</span> ${job_execution} </pre> <h2 id="ワークフローのデプロイ">ワークフローのデプロイ</h2> <p>以下のコマンドを実行してワークフローをデプロイします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud workflows deploy cloud-run-job-workflow <span class="synStatement">\</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 <span class="synStatement">\</span> <span class="synSpecial">--source</span><span class="synStatement">=</span>cloud-run-job-workflow.yaml <span class="synSpecial">--service-account</span><span class="synStatement">=</span>serviceAccount:sa-cloud-run-job-workflow@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com </pre> <h1 id="Eventarc-トリガーの設定">Eventarc トリガーの設定</h1> <h2 id="サービスアカウントの設定-1">サービスアカウントの設定</h2> <p>Eventarc で使用するサービスアカウントを作成します。</p> <p>Eventarc は Cloud Storage からイベントを受信して Workflows を起動するため、以下のロールを Eventarc で使用するサービスアカウントに付与する必要があります。</p> <ul> <li>Eventarc イベント受信者(<code>roles/eventarc.eventReceiver</code>)</li> <li>ワークフロー起動元(<code>roles/workflows.invoker</code>)</li> </ul> <p>以下のコマンドを実行すると、サービスアカウント <code>sa-cloud-run-job-workflow-trigger</code> が作成され、そのサービスアカウントに必要な権限が付与されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud iam service-accounts create sa-cloud-run-job-workflow-trigger gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-cloud-run-job-workflow-trigger@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/eventarc.eventReceiver gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=</span>serviceAccount:sa-cloud-run-job-workflow-trigger@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=</span>roles/workflows.invoker </pre> <h2 id="Eventarc-の作成">Eventarc の作成</h2> <p>以下のコマンドで、Eventarc トリガーを <code>cloud-run-job-workflow-trigger</code> という名前で作成します。</p> <p>このコマンドでは、さきほど作成したサービスアカウント <code>sa-cloud-run-job-workflow-trigger</code> を指定したり、<code>destination-workflow</code> オプションで宛先のワークフローである <code>cloud-run-job-workflow</code> を指定しています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud eventarc triggers create cloud-run-job-workflow-trigger <span class="synStatement">\</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 <span class="synStatement">\</span> <span class="synSpecial">--destination-workflow</span><span class="synStatement">=</span>cloud-run-job-workflow <span class="synStatement">\</span> <span class="synSpecial">--destination-workflow-location</span><span class="synStatement">=</span>asia-northeast1 <span class="synStatement">\</span> <span class="synSpecial">--event-filters</span><span class="synStatement">=&quot;</span><span class="synConstant">type=google.cloud.storage.object.v1.finalized</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--event-filters</span><span class="synStatement">=&quot;</span><span class="synConstant">bucket=</span><span class="synPreProc">${BUCKET_NAME}</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--service-account</span><span class="synStatement">=</span>sa-cloud-run-job-workflow-trigger@<span class="synPreProc">${PROJECT}</span>.iam.gserviceaccount.com </pre> <h1 id="動作確認">動作確認</h1> <p>まずは、日報のテキストファイルを用意します。例として、以下のテキストが書き込まれたテキストファイル <code>20241231_山田太郎.txt</code> を作成します。</p> <pre class="code" data-lang="" data-unlink>今日の業務内容: 午前: 昨日取得したオンラインストアの顧客行動ログデータ(ç´„5TB)のBigQueryへのロード作業を実施。Dataflowパイプラインを用いて、パーティショニングとクラスタリングを行い、クエリパフォーマンスの最適化を図った。ロード完了後、データの整合性を確認し、データ品質に問題がないことを検証した。ロード時間は予想通り約3時間であったが、一部データの重複が確認されたため、重複データ削除クエリを記述し実行。約1%の重複データが削除された。 午後: BigQuery上で顧客セグメンテーションのためのSQLクエリを開発・実行。購買頻度、平均購入額、最終購入日などを基に、&#34;高頻度購入者&#34;, &#34;低頻度低額購入者&#34;, &#34;休眠顧客&#34; の3つのセグメントに分類するクエリを作成した。各セグメントの人口統計データ(年齢、性別など)との関連性を分析するために、ユーザー属性テーブルと結合し分析を実施。 分析結果を可視化するために、Looker Studioを用いたダッシュボードを作成開始。本日中に主要指標の表示まで完了させた。 その他: プロジェクトXの今後の分析計画についてチームリーダーとミーティングを実施。 顧客チャーン予測モデル構築のためのデータ準備について議論し、必要なデータ項目とデータソースを特定した。来週から機械学習モデルの構築に着手する予定。 また、BigQueryの料金を監視し、コスト最適化のための検討を開始した。パーティショニングとクラスタリングの効果を検証し、更なる最適化の可能性を探る。 課題と問題点: データログに含まれる一部の顧客IDに重複が見られた。データ収集元でのデータクレンジングの必要性を指摘し、関係部署への報告を検討している。 Looker Studioダッシュボードの作成に時間がかかっている。より効率的な可視化ツールの検討が必要かもしれない。 明日の予定: プロジェクトX: 顧客チャーン予測モデル構築のためのデータ準備開始。必要なデータの抽出と前処理を行う。 プロジェクトY (準備段階): プロジェクトYの要件定義書作成に向けて、関係者との打ち合わせを行う。 コメント: 本日、プロジェクトXのデータ分析が大きく進展した。BigQueryとDataflowパイプラインを用いたデータ処理は効率的であった。しかし、データ品質に関する課題も浮き彫りになったため、関係部署との連携を強化し、データクオリティ向上に努める必要がある。</pre> <p>この日報ファイルを Cloud Storage にアップロードします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>gcloud storage <span class="synStatement">cp</span> 20241231_山田太郎.txt gs://<span class="synPreProc">${BUCKET_NAME}</span> </pre> <p>BigQuery を見ると、テーブルに日報のデータが書き込まれていることが確認できました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250110/20250110090014.png" width="800" height="283" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下は、要約後の文章です。</p> <pre class="code" data-lang="" data-unlink>このログは、BigQueryとDataflowを用いたデータ処理に関する報告です。 **午前:** 5TBのオンラインストリームデータのBigQueryへのロード作業を実施。Dataflowパイプラインを用いてパーティショニングとクレンジングを行い、クエリの最適化を実現しました。処理時間は予想通り約3時間でしたが、一部データの欠損が確認され、1%のデータが復旧不能でした。 **午後:** BigQuery上で、課金タイプ、平均課金額、最終課金日などを基に、「高課金ユーザー」、「低課金ユーザー」、「潜在顧客」の3つのセグメントに分類するSQLクエリを作成・実行しました。各セグメントのユーザー属性(年齢、性別など)との関連性を分析し、Looker Studioで視覚化しました。 **その他:** プロジェクトXの今後の分析計画として、チャーン予測モデルとプロモーション施策のデータ整備を決定しました。BigQueryのログを監視し、コスト最適化のための改善策を検討します。パーティショニングとクレンジングのポイントを明確化し、今後のデータ品質向上に繋げます。 **課題と問題点:** 一部の顧客IDにデータ欠損が見つかりました。データの完全性確保のため、データクレンジングとモニタリングの強化が必要です。Looker Studioでのダッシュボード作成に時間がかかっています。より効率的な可視化方法の検討が必要です。 **今後の予定:** プロジェクトXでは、チャーン予測のためのデータ整備と必要なデータの抽出・前処理を行います。プロジェクトY(チャーン予測モデル)では、要件定義書を作成し、関係者との打ち合わせを行います。 全体として、データ処理は概ね成功しましたが、データ欠損や可視化の効率性といった課題が残っており、今後の改善が必要であることが報告されています。</pre> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-deguchi/profile_128x128.png?1730365677);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">出口 晋太朗 <a href="https://blog.g-gen.co.jp/archive/author/ggen-kataiwa">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2024å¹´7月にG-genに入社。<br>福岡在住で、Google Cloud をマスターするため日々エンジニアとして修行中。<br> </div> </div> </div> </div> ggen-deguchi Gemini APIへのリクエストでエラーコード429「Resource exhausted, please try again later.」 hatenablog://entry/6802418398318530416 2025-01-09T10:15:00+09:00 2025-01-09T10:15:00+09:00 G-genの杉村です。Vertex API 経由で Gemini モデルへ API リクエストを送信する際に、エラーコード 429 で Resource exhausted, please try again later. というエラーが頻繁に発生しました。その原因と対処法を紹介します。 事象 原因 対処法 2つの対処案 エクスポネンシャルバックオフ Provisioned Throughput Provisioned Throughput とは GSU 購入期間 購入方法と料金 考慮事項 事象 Vertex API 経由で Gemini モデルへ API リクエストを送信した際、エラーコード… <p>G-genの杉村です。Vertex API 経由で Gemini モデルへ API リクエストを送信する際に、エラーコード 429 で <code>Resource exhausted, please try again later.</code> というエラーが頻繁に発生しました。その原因と対処法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#事象">事象</a></li> <li><a href="#原因">原因</a></li> <li><a href="#対処法">対処法</a><ul> <li><a href="#2つの対処案">2つの対処案</a></li> <li><a href="#エクスポネンシャルバックオフ">エクスポネンシャルバックオフ</a></li> <li><a href="#Provisioned-Throughput">Provisioned Throughput</a><ul> <li><a href="#Provisioned-Throughput-とは">Provisioned Throughput とは</a></li> <li><a href="#GSU">GSU</a></li> <li><a href="#購入期間">購入期間</a></li> <li><a href="#購入方法と料金">購入方法と料金</a></li> <li><a href="#考慮事項">考慮事項</a></li> </ul> </li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108220357.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="事象">事象</h1> <p>Vertex API 経由で Gemini モデルへ API リクエストを送信した際、エラーコード 429 で <code>Resource exhausted, please try again later.</code> というエラーが発生しました。レスポンス内の status は <code>RESOURCE_EXHAUSTED</code> です。</p> <p>しばらくして再試行するとリクエストが成功するときがありますが、しばしば同じエラーとなります。</p> <h1 id="原因">原因</h1> <p>このエラーは、処理のためのリソースが Google 側で枯渇することを防ぐため、Google によって API 利用が制限されていることを意味しています。Google は随時、物理インフラストラクチャを強化していますが、Gemini API は多くのユーザーに利用されているため、しばしばこのメッセージが表示されることがあります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/error-code-429?hl=ja">エラーコード 429</a></li> </ul> <h1 id="対処法">対処法</h1> <h2 id="2つの対処案">2つの対処案</h2> <p>次のいずれかの対処法が考えられます。</p> <ol> <li>アプリケーションに<strong>エクスポネンシャルバックオフ</strong>(exponential backoff、指数バックオフ)を実装する</li> <li><strong>Provisioned Throughput</strong>(プロビジョニングされたスループット)を購入する</li> </ol> <h2 id="エクスポネンシャルバックオフ">エクスポネンシャルバックオフ</h2> <p><code>1.</code> の<strong>エクスポネンシャルバックオフ</strong>(指数バックオフ)は、クラウドサービスの API リクエストを使用するアプリケーションを実装する際に一般的な手法です。</p> <p>エクスポネンシャルバックオフでは、API リクエストがサーバー側エラーや一時的な障害で失敗した場合に、待機時間を1秒、2秒、4秒、8秒...のようにべき乗しながら再試行を繰り返します。</p> <p>再試行を無限に繰り返さないよう、試行回数が規定の回数に達してもリクエストが成功しない場合、エラー終了させるよう実装します。このようにリトライ回数に上限を設けることを truncated exponential backoff(切り捨て型エクスポネンシャルバックオフ)とも呼びます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/storage/docs/retry-strategy?hl=ja#exponential-backoff">指数バックオフ アルゴリズム</a></li> </ul> <p>この手法を取ることにより、リージョンで多くのユーザーからの API リクエストが輻輳してリソースが枯渇している場合でも、しばらく待ってから再試行することで、最終的に API リクエストが成功する可能性を高められます。</p> <h2 id="Provisioned-Throughput">Provisioned Throughput</h2> <h3 id="Provisioned-Throughput-とは">Provisioned Throughput とは</h3> <p><code>2.</code> の <strong>Provisioned Throughput</strong>(プロビジョニングされたスループット)とは、Gemini ã‚„ Claude の API スループットを事前に予約購入しておき、固定金額で利用する月額サブスクリプションサービスです。</p> <p>Provisioned Throughput は、事前にモデルとロケーション(リージョン)を指定して購入します。サポートされているモデルは gemini-1.5-flash、gemini-1.5-pro、imagen-3.0-generate-001、Anthropic Claude 3.5 Sonnet などです。対象モデルの一覧は以下のドキュメントを参照してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput?hl=ja">プロビジョニングされたスループット</a></li> </ul> <p>前述のエクスポネンシャルバックオフはリソース枯渇に対する根本的な対処にはなっていませんが、Provisioned Throughput を購入する方法では、従量課金利用よりもリソースが優先して確保されます。重要な本番環境アプリケーションでの Gemini の利用や、月額利用料金を固定したい場合などに利用を検討します。</p> <h3 id="GSU">GSU</h3> <p>Provisioned Throughput は、<strong>GSU</strong>(Generative AI Scale Unit)という単位で購入します。1 GSU は、例えば Gemini 1.5 Pro の場合、800文字/秒です。</p> <p>この秒あたりの文字数について、1文字のインプットは1文字としてカウントされますが、例えば Gemini 1.5 Pro の場合、1文字のアウトプットは3文字としてカウントされます。また1枚の画像は1,052文字としてカウントされます。この消費文字数を<strong>バーンダウン率</strong>といいます。</p> <p>1 GSU あたりのスループット(文字/秒)やバーンダウン率はモデルによって異なるので、以下のドキュメントを参照してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput?hl=ja#supported-models">サポートされているモデル</a></li> </ul> <h3 id="購入期間">購入期間</h3> <p>Provisioned Throughput は1ヶ月、3ヶ月、1年の期間で購入できます。自動更新を有効にすることで、更新作業を省略することができます。一度購入すると、コミットした期間は注文をキャンセルすることはできません。</p> <p>購入時にはモデルを指定する必要があり、生成 AI では頻繁に新しいモデルが登場することを考慮すると、1年単位の購入には慎重になるべきと考えられます。ただし以下のドキュメントでは、同じリージョン内でかつ同じ事業者から提供されているモデルであれば、バージョンの切り替えができることも示されています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput?hl=ja#considerations">定期購入を開始する前に考慮すべきこと</a></li> </ul> <h3 id="購入方法と料金">購入方法と料金</h3> <p>購入は Google Cloud コンソールの「プロビジョニングされたスループット」画面(<a href="https://console.cloud.google.com/vertex-ai/provisioned-throughput">https://console.cloud.google.com/vertex-ai/provisioned-throughput</a>)から購入することができます。</p> <p>同画面でモデル、リージョン、GSU 数、期間を入力すると、料金見積もりが表示されます。</p> <p>購入に必要な権限などの詳細は、以下のドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput?hl=ja#purchase-provisioned-throughput">プロビジョニングされたスループットを購入する</a></li> </ul> <h3 id="考慮事項">考慮事項</h3> <p>考慮事項としては、以下が挙げられます。</p> <ul> <li>実際のスループットが Provisioned Throughput の注文量を超えると、超過分は従量課金で処理される</li> <li>未使用のスループットは翌月に繰り越せない</li> </ul> <p>その他の考慮事項については以下のドキュメントを注意深くご参照ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/provisioned-throughput?hl=ja#considerations">定期購入を開始する前に考慮すべきこと</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura 組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。への対処法 hatenablog://entry/6802418398311609267 2025-01-08T09:00:00+09:00 2025-01-08T09:30:07+09:00 G-gen の武井です。当記事では IAM ポリシーを編集しようとした際に、組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。 と表示されてエラーになったときの対処法を紹介します。 事象とメッセージ 原因 対処方法 対処手順 顧客 ID の確認 IAM 権限の確認 組織、フォルダまたはプロジェクトを選択 組織のポリシー画面へ遷移 制約の編集画面へ遷移 制約を編集 結果の確認 最後に ワークアラウンド 関連記事 事象とメッセージ Google Cloud(旧称 GCP)で、IAM ポリシーを編集し… <p>G-gen の武井です。当記事では IAM ポリシーを編集しようとした際に、<code>組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。</code> と表示されてエラーになったときの対処法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#事象とメッセージ">事象とメッセージ</a></li> <li><a href="#原因">原因</a></li> <li><a href="#対処方法">対処方法</a></li> <li><a href="#対処手順">対処手順</a><ul> <li><a href="#顧客-ID-の確認">顧客 ID の確認</a></li> <li><a href="#IAM-権限の確認">IAM 権限の確認</a></li> <li><a href="#組織フォルダまたはプロジェクトを選択">組織、フォルダまたはプロジェクトを選択</a></li> <li><a href="#組織のポリシー画面へ遷移">組織のポリシー画面へ遷移</a></li> <li><a href="#制約の編集画面へ遷移">制約の編集画面へ遷移</a></li> <li><a href="#制約を編集">制約を編集</a></li> </ul> </li> <li><a href="#結果の確認">結果の確認</a></li> <li><a href="#最後に">最後に</a><ul> <li><a href="#ワークアラウンド">ワークアラウンド</a></li> <li><a href="#関連記事">関連記事</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241217/20241217090310.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="事象とメッセージ">事象とメッセージ</h1> <p>Google Cloud(旧称 GCP)で、IAM ポリシーを編集し、Google アカウントに IAM ロールを紐づけようとした際に、以下のメッセージが表示され、編集が失敗しました。</p> <p><figure class="figure-image figure-image-fotolife" title="組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090004.png" width="671" height="406" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。</figcaption></figure></p> <blockquote><p>IAM ポリシーの更新に失敗しました<br/> 組織のポリシー「ドメインで制限された共有」(constraints/iam.allowedPolicyMemberDomains)が適用されています。ポリシーでプリンシパルとして追加できるのは、許可されたドメインのプリンシパルのみです。プリンシパルのメールアドレスを修正して、もう一度お試しください。共有先のドメインの制限の詳細</p> <p>リクエスト ID: (æ•°å­—)</p></blockquote> <h1 id="原因">原因</h1> <p>この事象は、組織ポリシーの制約 <code>iam.allowedPolicyMemberDomains</code> が組織レベル、フォルダレベルまたはプロジェクトレベルで有効化されているときに発生します。</p> <p><code>iam.allowedPolicyMemberDomains</code> は、<strong>許可されていない組織に所属する Google アカウントへの権限付与を禁止する制約</strong>です。例として、<code>g-gen.co.jp</code> という組織の Google Cloud プロジェクトで、<code>example.com</code>(他組織)のプリンシパルに対して IAM ロールを付与しようとするケースが該当します。</p> <ul> <li>参考:<a href="https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains?hl=ja">ドメイン別の ID の制限</a></li> </ul> <p>この制約は、2024年初頭以降に作成された Google Cloud 組織ではデフォルトで有効化されています。それ以前に作成された組織でも、管理者が明示的にこの制約を有効化している場合は、この事象が発生します。</p> <ul> <li>参考:<a href="https://cloud.google.com/resource-manager/docs/secure-by-default-organizations?hl=ja#organization_policies_enforced_on_organization_resources">組織リソースに適用される組織のポリシー</a></li> </ul> <p>なお組織のポリシーとは、セキュリティや統制の向上のために、所定のルールを Google Cloud 環境全体に適用する仕組みのことです。組織のポリシーの詳細は、以下の記事を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Forganization-policy-explained" title="組織のポリシーを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/organization-policy-explained">blog.g-gen.co.jp</a></cite></p> <h1 id="対処方法">対処方法</h1> <p>組織ポリシーの制約 <code>iam.allowedPolicyMemberDomains</code> はリスト型の制約で、デフォルトでは自組織のみが許可されています。</p> <p>したがって、<strong>他組織の Google アカウントに IAM ロールを付与したい</strong>場合はこの制約の許可リストに、<strong>その組織を明示的に追加</strong>する必要があります。</p> <p>組織ポリシーの制約は、組織レベル、フォルダレベル、プロジェクトレベルで適用することができ、親リソースのポリシーは子リソースに<strong>継承</strong>されます。ただし、明示的に設定することで、子リソース側で親リソースの制約をオーバーライド(上書き)することも可能です。</p> <p>よって、取り得る選択肢としては、以下のいずれかになります。</p> <ol> <li><code>iam.allowedPolicyMemberDomains</code> ã‚’<strong>組織レベル</strong>で編集する</li> <li><code>iam.allowedPolicyMemberDomains</code> ã‚’<strong>フォルダレベル</strong>でオーバーライドして編集する</li> <li><code>iam.allowedPolicyMemberDomains</code> ã‚’<strong>プロジェクトレベル</strong>でオーバーライドして編集する</li> </ol> <p>上記のうち <code>1.</code>、<code>2.</code> の場合、組織全体もしくはフォルダ全体で影響が及びますが、<code>3.</code> の影響範囲は当該プロジェクトのみです。</p> <p>ご自身の環境構成と照らし合わせ、影響範囲を十分に理解したうえで適切なスコープで設定いただくことを推奨します。</p> <h1 id="対処手順">対処手順</h1> <h2 id="顧客-ID-の確認">顧客 ID の確認</h2> <p>許可リストに外部組織を追加するには、その組織の<code>顧客 ID</code>を把握しておく必要があります。</p> <p>以下を参考に追加対象組織の顧客 ID を取得してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains?hl=ja#retrieving_customer_id">Google Workspace お客様 ID の取得</a> (<code>gcloud / API</code>から取得)</li> <li>参考 : <a href="https://support.google.com/a/answer/10070793?hl=ja">顧客 ID の確認</a> (<code>Admin コンソール</code>から取得)</li> </ul> <p>また、以下の記事も参考にしてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fhow-to-retrieve-organization-id" title="Google Cloud組織の組織IDや顧客IDを調べる方法 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/how-to-retrieve-organization-id">blog.g-gen.co.jp</a></cite></p> <h2 id="IAM-権限の確認">IAM 権限の確認</h2> <p>当手順を実施するには、操作する Google アカウント、もしくはアカウントが所属するグループが、組織レベルで<strong>組織ポリシー管理者</strong>(<code>roles/orgpolicy.policyAdmin</code>)ロールを持っている必要があります。</p> <p>組織ポリシー管理者を付与できる最も下位レベルのリソースは「組織」です。よって、フォルダやプロジェクトレベルで制約をオーバーライドする場合でも、組織レベルで組織ポリシー管理者ロールを持っている必要があります。</p> <p>作業者の Google アカウントが必要な権限を持っていない場合は、組織レベルで IAM ロール「組織ポリシー管理者」を付与してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/access-control-org?hl=ja">IAM を使用した組織リソースのアクセス制御</a></li> </ul> <h2 id="組織フォルダまたはプロジェクトを選択">組織、フォルダまたはプロジェクトを選択</h2> <p>Google Cloud コンソールにログインし、プロジェクトセレクターをクリックして、制約を無効化を適用する組織、フォルダ、またはプロジェクトを選択します。</p> <p>当記事の「対処方法」をよくお読みになり、制約の編集位置を決めたうえで選択してください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090007.png" width="800" height="367" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="組織のポリシー画面へ遷移">組織のポリシー画面へ遷移</h2> <p>コンソール上部の検索ボックスに「組織のポリシー」と入力し、サジェストされた<code>組織のポリシー</code>を選択します。</p> <p>または、<code>IAM と管理</code>画面から直接遷移しても構いません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090011.png" width="800" height="345" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="制約の編集画面へ遷移">制約の編集画面へ遷移</h2> <p>制約一覧の上部のフィルタに <code>constraints/iam.allowedPolicyMemberDomains</code> を入力し、フィルタ結果の中から <code>Domain restricted sharing</code> をクリックして編集画面へ遷移します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090014.png" width="800" height="511" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="制約を編集">制約を編集</h2> <p><code>ポリシーを管理</code>をクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090018.png" width="800" height="536" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下の順でルールを追加し、最後に<code>ポリシーを設定</code>をクリックします。</p> <table> <thead> <tr> <th style="text-align:left;"> # </th> <th style="text-align:left;"> é …ç›® </th> <th style="text-align:left;"> 設定値 </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 1 </td> <td style="text-align:left;"> ポリシーのソース </td> <td style="text-align:left;"> <code>親のポリシーをオーバーライドする</code> </td> </tr> <tr> <td style="text-align:left;"> 2 </td> <td style="text-align:left;"> ポリシーの適用 </td> <td style="text-align:left;"> <code>親と結合する</code> ※親の設定を上書きする場合は<code>交換</code>を選択する </td> </tr> <tr> <td style="text-align:left;"> 3 </td> <td style="text-align:left;"> ポリシーの値 </td> <td style="text-align:left;"> <code>カスタム</code>を選択する </td> </tr> <tr> <td style="text-align:left;"> 4 </td> <td style="text-align:left;"> ポリシータイプ </td> <td style="text-align:left;"> <code>許可</code>を選択する </td> </tr> <tr> <td style="text-align:left;"> 5 </td> <td style="text-align:left;"> カスタム値 </td> <td style="text-align:left;"> <code>顧客ID</code>を入力する </td> </tr> </tbody> </table> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090022.png" width="595" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="結果の確認">結果の確認</h1> <p>設定が完了すると、以下のような表示になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20250108/20250108090026.png" width="800" height="680" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="最後に">最後に</h1> <h2 id="ワークアラウンド">ワークアラウンド</h2> <p>組織ポリシーの変更が難しい場合は、Google グループに外部組織のメンバーを追加し、そのグループに権限を付与することでドメイン制限の制約を回避することも可能です。</p> <p>詳細は以下の公式ドキュメントをご確認ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains?hl=ja#google_groups">Google グループ</a></li> </ul> <h2 id="関連記事">関連記事</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Ftrouble-shoot-service-account-key-creation-is-disabled" title="「サービス アカウント キーの作成が無効になっています」への対処法 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/trouble-shoot-service-account-key-creation-is-disabled">blog.g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-yutakei/20220512/20220512214329.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">武井 祐介 <a href="https://blog.g-gen.co.jp/archive/author/ggen-yutakei">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2025 選出。IaC ã‚„ CI/CD 周りのサービスやプロダクトが興味分野です。</p> <p class="sw-profile__txt">趣味はロードバイク、ロードレースやサッカー観戦です。</p> <!-- 以下の行を追加 --> <a href="https://twitter.com/ggenyutakei?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ggenyutakei</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-yutakei 2024å¹´12月のイチオシGoogle Cloudアップデート hatenablog://entry/6802418398312333970 2025-01-06T09:00:00+09:00 2025-01-06T09:00:03+09:00 G-gen の杉村です。2024å¹´12月のイチオシ Google Cloud(旧称 GCP)アップデートをまとめてご紹介します。記載は全て、記事公開当時のものですのでご留意ください。 はじめに Google フォームで新しい権限「Responder(回答者)」が利用可能に Vertex AI Search で gemini-1.5-flash-002-high-fidelity(Preview) Google Deepmind、大規模世界モデル Genie 2 を発表 Parameter Manager が Preview 公開 画像生成モデル「Imagen 3」が一般公開 Gemini 2.… <p>G-gen の杉村です。2024å¹´12月のイチオシ Google Cloud(旧称 GCP)アップデートをまとめてご紹介します。記載は全て、記事公開当時のものですのでご留意ください。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Google-フォームで新しい権限Responder回答者が利用可能に">Google フォームで新しい権限「Responder(回答者)」が利用可能に</a></li> <li><a href="#Vertex-AI-Search-で-gemini-15-flash-002-high-fidelityPreview">Vertex AI Search で gemini-1.5-flash-002-high-fidelity(Preview)</a></li> <li><a href="#Google-Deepmind大規模世界モデル-Genie-2-を発表">Google Deepmind、大規模世界モデル Genie 2 を発表</a></li> <li><a href="#Parameter-Manager-が-Preview-公開">Parameter Manager が Preview 公開</a></li> <li><a href="#画像生成モデルImagen-3が一般公開">画像生成モデル「Imagen 3」が一般公開</a></li> <li><a href="#Gemini-20-が発表">Gemini 2.0 が発表</a></li> <li><a href="#BigQuery-のクロスリージョンデータセットレプリケーションが-GA">BigQuery のクロスリージョンデータセットレプリケーションが GA</a></li> <li><a href="#BigQuery-で-BigQuery-Managed-Disaster-Recovery-が-GA">BigQuery で BigQuery Managed Disaster Recovery が GA</a></li> <li><a href="#VPC-SC-の-IngressEgress-rules-の-Google-グループ指定が-GA">VPC SC の Ingress/Egress rules の Google グループ指定が GA</a></li> <li><a href="#Google-Workspace-で-NotebookLM-Plus-が利用可能に">Google Workspace で NotebookLM Plus が利用可能に</a></li> <li><a href="#新サービス-Google-Agentspace-が発表">新サービス Google Agentspace が発表</a></li> <li><a href="#Compute-Engine-で-Windows-Server-2025-が利用可能に">Compute Engine で Windows Server 2025 が利用可能に</a></li> <li><a href="#Cloud-IAM-で-Principal-access-boundary-policies-が-Preview--GA">Cloud IAM で Principal access boundary policies が Preview → GA</a></li> <li><a href="#Looker-Studio-のデータソース編集画面でデータのプレビューが可能に">Looker Studio のデータソース編集画面でデータのプレビューが可能に</a></li> <li><a href="#全エディションで-AppSheet-管理画面が利用可能に">全エディションで AppSheet 管理画面が利用可能に</a></li> <li><a href="#Gemini-20-Flash-Thinking-の試験運用版が-Google-AI-Studio-で公開">Gemini 2.0 Flash Thinking の試験運用版が Google AI Studio で公開</a></li> <li><a href="#Google-ドライブで動画をアップロード後すぐに再生できるように">Google ドライブで動画をアップロード後すぐに再生できるように</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240603/20240603200204.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>当記事では、毎月の Google Cloud(旧称 GCP)や Google Workspace(旧称 GSuite)のアップデートのうち、特に重要なものをまとめます。</p> <p>また当記事は、Google Cloud に関するある程度の知識を前提に記載されています。前提知識を得るには、ぜひ以下の記事もご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcontents-for-google-cloud-learners" title="Google Cloud サービスカット学習コンテンツ集 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/contents-for-google-cloud-learners">blog.g-gen.co.jp</a></cite></p> <p>リンク先の公式ガイドは、英語版で表示しないと最新情報が反映されていない場合がありますためご注意ください。</p> <h1 id="Google-フォームで新しい権限Responder回答者が利用可能に">Google フォームで新しい権限「Responder(回答者)」が利用可能に</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/12/add-granular-control-to-google-forms.html">Adding granular control options for who can respond to Google Forms</a> (2024-12-03)</p> <p>Google フォームで新しい権限「Responder(回答者)」が利用可能に。</p> <p>従来はフォームをインターネット公開するか、回答可能な人を絞るときは特定ドメインにしか限定できなかった。今後はアカウントやグループに限定して公開することが可能になった。</p> <h1 id="Vertex-AI-Search-で-gemini-15-flash-002-high-fidelityPreview">Vertex AI Search で gemini-1.5-flash-002-high-fidelity(Preview)</h1> <p><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen?hl=en#high-fidelity-models">High fidelity models</a> (2024-12-04)</p> <p>Vertex AI Search で gemini-1.5-flash-002-high-fidelity モデルが Preview 公開。</p> <p>gemini-1.5-flash-002-high-fidelity モデルとは、コンテキストベースの質問に最適化された RAG 用モデル。正確性や安全性を重視したチューニングがされている。金融、ヘルスケアなど正確性が重要な用途を想定。</p> <h1 id="Google-Deepmind大規模世界モデル-Genie-2-を発表">Google Deepmind、大規模世界モデル Genie 2 を発表</h1> <p><a href="https://deepmind.google/discover/blog/genie-2-a-large-scale-foundation-world-model/">Genie 2: A large-scale foundation world model</a> (2024-12-04)</p> <p>Google Deepmind が、基盤世界モデル(A large-scale foundation world model)Genie 2 を発表した。</p> <p>アクション制御可能でプレイ可能な 3D 環境を生成できる。生成したワールドは、キーボードとマウス入力を使用して、人間または AI エージェントによってプレイできる。一人称視点、アイソメトリック ビュー、三人称運転ビデオなど、さまざまな環境を生成可能。</p> <h1 id="Parameter-Manager-が-Preview-公開">Parameter Manager が Preview 公開</h1> <p><a href="https://cloud.google.com/secret-manager/parameter-manager/docs/overview?hl=en">Parameter Manager overview</a> (2024-12-06)</p> <p>Secret Manager の派生機能として Parameter Manager が Preview 公開。</p> <p>環境設定値を集中管理する仕組み。シークレット管理も可能だが、Secret Manager には存在する「rotation schedules」が無いなどの差異がある。現在 gcloud/REST のみで提供。 シークレット情報は Secret Manager で、その他の環境固有設定値は Parameter Manager で管理することが想定される</p> <p>Parameter Manager から Secret Manager のシークレットを参照することもでき、ちょうど AWS Secret Manager と Parameter Store の関係に似ている。</p> <h1 id="画像生成モデルImagen-3が一般公開">画像生成モデル「Imagen 3」が一般公開</h1> <p><a href="https://cloud.google.com/vertex-ai/generative-ai/docs/image/overview?hl=en">Imagen on Vertex AI | AI Image Generator</a> (2024-12-10)</p> <p>画像生成モデル「Imagen 3」が一般公開された。これまでは許可リスト制だったがVertex AI経由で全ユーザーが利用可能になった。以下のモデルが利用可能。</p> <ul> <li>imagen-3.0-generate-001</li> <li>imagen-3.0-fast-generate-001</li> </ul> <p>ただし画像の編集や few-shot learning が可能な以下のモデルは引き続き、許可制。</p> <ul> <li>imagen-3.0-capability</li> </ul> <h1 id="Gemini-20-が発表">Gemini 2.0 が発表</h1> <p><a href="https://news.mynavi.jp/techplus/article/20241212-3084231/">Google、「Gemini 2.0」を発表、AIモデルは”エージェント時代”に</a> (2024-12-12)</p> <p>Google が生成AIモデル Gemini の最新版、Gemini 2.0 を発表。</p> <p>マルチモーダル対応がさらに強化。<code>gemini-2.0-flash-exp</code> が Gemini アプリや Vertex AI Studio、Google AI Studio で既に使用可能になっている。</p> <h1 id="BigQuery-のクロスリージョンデータセットレプリケーションが-GA">BigQuery のクロスリージョンデータセットレプリケーションが GA</h1> <p><a href="https://cloud.google.com/bigquery/docs/data-replication?hl=en">Cross-region dataset replication</a> (2024-12-11)</p> <p>BigQuery でクロスリージョン データセットレプリケーションが Preview → GA。</p> <p>別リージョンにデータを非同期で複製し、データの堅牢性と可用性を高められる。データセットのリージョン間移行にも利用可能。</p> <p>ただしプライマリリージョンが障害時、セカンダリリージョンは Read Only になる。詳細は以下の記事も参照。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fusing-bigquery-cross-region-replication" title="BigQueryのクロスリージョン・データセットレプリケーションを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/using-bigquery-cross-region-replication">blog.g-gen.co.jp</a></cite></p> <h1 id="BigQuery-で-BigQuery-Managed-Disaster-Recovery-が-GA">BigQuery で BigQuery Managed Disaster Recovery が GA</h1> <p><a href="https://cloud.google.com/bigquery/docs/managed-disaster-recovery?hl=en">Managed disaster recovery</a> (2024-12-11)</p> <p>BigQuery で BigQuery Managed Disaster Recovery が Preview → GA。</p> <p>リージョン障害のときにデータのみならずコンピュートリソース予約もフェイルオーバし、書き込みを含むワークロードを継続できる。</p> <h1 id="VPC-SC-の-IngressEgress-rules-の-Google-グループ指定が-GA">VPC SC の Ingress/Egress rules の Google グループ指定が GA</h1> <p><a href="https://cloud.google.com/vpc-service-controls/docs/release-notes?hl=en#December_11_2024">VPC Service Controls release notes - December 11, 2024</a> (2024-12-11)</p> <p>VPC Service Controls 境界で Ingress/Egress rules での Google グループ指定が Preview → GA。</p> <p>従来は Google アカウントを直接指定する必要があったが、グループ指定ができるようになり、運用の煩雑さがやや解消される。</p> <h1 id="Google-Workspace-で-NotebookLM-Plus-が利用可能に">Google Workspace で NotebookLM Plus が利用可能に</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/12/notebooklm-plus-gemini-for-google-workspace-users.html">NotebookLM Plus now available to Google Workspace customers</a> (2024-12-13)</p> <p>Google Workspace で NotebookLM Plus が利用可能に。もともと無料で利用できた NotebookLM の有償版。Plus では様々な機能、制限緩和、データの保護が追加される。</p> <p>NotebookLM は自分専用のAIノートブック。データをアップロードして生成AIに読み込ませ生成テキストや自分のメモも記述しておける。分析や資料作成、情報整理などに利用できる。</p> <p>要Geminiアドオンライセンス。</p> <h1 id="新サービス-Google-Agentspace-が発表">新サービス Google Agentspace が発表</h1> <p><a href="https://cloud.google.com/blog/products/ai-machine-learning/bringing-ai-agents-to-enterprises-with-google-agentspace?hl=en">Introducing Google Agentspace: Bringing AI agents and AI-powered search to enterprises</a> (2024-12-14)</p> <p>新サービス Google Agentspace が発表。Early accessに申込可能。以下の機能を備える。</p> <ol> <li>自社データをアップロードしてAIから利用できる NotebookLM Plus</li> <li>コンフルや Google ドライブ、SharePoint 等から検索できるエンタープライズサーチ</li> <li>人の代わりにタスクをこなすエージェント</li> </ol> <h1 id="Compute-Engine-で-Windows-Server-2025-が利用可能に">Compute Engine で Windows Server 2025 が利用可能に</h1> <p><a href="https://cloud.google.com/compute/docs/images/os-details?hl=en#windows_server">Windows Server</a> (2024-12-16)</p> <p>Compute Engine で Windows Server 2025 が利用可能になった。EoS(イメージ廃止日)は 2034-10-10。</p> <p>なおその前の Windows Server 2022 の EoS は 2031-10-14。</p> <h1 id="Cloud-IAM-で-Principal-access-boundary-policies-が-Preview--GA">Cloud IAM で Principal access boundary policies が Preview → GA</h1> <p><a href="https://cloud.google.com/iam/docs/principal-access-boundary-policies?hl=en">Principal access boundary policies</a> (2024-12-17)</p> <p>Cloud IAM で Principal access boundary policies が Preview → GA。</p> <p>自組織のプリンシパルがどのリソースにアクセスできるかの境界(boundary)を設けられる。アクセス先を自組織のリソースに限定したり、特定フォルダ内に限定したりできる。</p> <h1 id="Looker-Studio-のデータソース編集画面でデータのプレビューが可能に">Looker Studio のデータソース編集画面でデータのプレビューが可能に</h1> <p><a href="https://support.google.com/looker-studio/answer/15446333">Preview your data</a> (2024-12-17)</p> <p>Looker Studio でデータソースの編集画面でデータ内容をプレビューできるようになった。</p> <p>BigQuery、Google スプレッドシート、Looker、Excel、CSV に対応。</p> <h1 id="全エディションで-AppSheet-管理画面が利用可能に">全エディションで AppSheet 管理画面が利用可能に</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/12/appsheet-admin-console-general-availability.html">Now generally available: Monitor and manage AppSheet usage in your organization with the AppSheet Admin console</a> (2024-12-18)</p> <p>Google Workspace の全エディションで AppSheet の管理画面が利用可能になった。</p> <p>アプリの利用状況、誰がアプリをたくさん作っているか、ライセンス数、などが横断で閲覧できる管理画面。</p> <h1 id="Gemini-20-Flash-Thinking-の試験運用版が-Google-AI-Studio-で公開">Gemini 2.0 Flash Thinking の試験運用版が Google AI Studio で公開</h1> <p><a href="https://ai.google.dev/gemini-api/docs/thinking-mode?hl=ja">Gemini 2.0 Flash の思考モード</a> (2024-12-19)</p> <p>Gemini 2.0 Flash Thinking の試験運用版(gemini-2.0-flash-thinking-exp-1219)が Vertex AI(Generative AI on Vertex AI)と Google AI Studio で公開。</p> <p>このモデルでは、生成結果だけでなく、生成に至った「思考」プロセスも生成して表示する。</p> <h1 id="Google-ドライブで動画をアップロード後すぐに再生できるように">Google ドライブで動画をアップロード後すぐに再生できるように</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/12/release-notes-12-20-2024.html">Google Workspace Updates Weekly Recap - December 20, 2024</a> (2024-12-20)</p> <p>Google ドライブで動画をアップロード後、すぐに再生できるようになった。</p> <p>これまではアップロード後に数分〜数十分、処理の時間が必要だった。</p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura Compute EngineインスタンスにローカルKubernetesクラスタを構築する(Minikube) hatenablog://entry/6802418398302598666 2024-12-27T09:00:00+09:00 2024-12-27T09:00:02+09:00 G-gen の佐々木です。当記事ではコンテナ オーケストレーション ツールである Kubenretes の学習用のため、Minikube を使って Compute Engine(Google Compute Engine、GCE)仮想マシン上にローカル Kubernetes クラスタを構築していきます。 はじめに 当記事の目的 Minikube とは Compute Engine インスタンスの作成 作業の概要 シェル変数の設定 VPC・サブネットの作成 VPC の作成 サブネットの作成 インスタンスの作成 Minikube の要件について インスタンスの作成 ファイアウォールルールの設定 イ… <p>G-gen の佐々木です。当記事ではコンテナ オーケストレーション ツールである Kubenretes の学習用のため、Minikube を使って Compute Engine(Google Compute Engine、GCE)仮想マシン上にローカル Kubernetes クラスタを構築していきます。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#当記事の目的">当記事の目的</a></li> <li><a href="#Minikube-とは">Minikube とは</a></li> </ul> </li> <li><a href="#Compute-Engine-インスタンスの作成">Compute Engine インスタンスの作成</a><ul> <li><a href="#作業の概要">作業の概要</a></li> <li><a href="#シェル変数の設定">シェル変数の設定</a></li> <li><a href="#VPCサブネットの作成">VPC・サブネットの作成</a><ul> <li><a href="#VPC-の作成">VPC の作成</a></li> <li><a href="#サブネットの作成">サブネットの作成</a></li> </ul> </li> <li><a href="#インスタンスの作成">インスタンスの作成</a><ul> <li><a href="#Minikube-の要件について">Minikube の要件について</a></li> <li><a href="#インスタンスの作成-1">インスタンスの作成</a></li> </ul> </li> <li><a href="#ファイアウォールルールの設定">ファイアウォールルールの設定</a></li> <li><a href="#インスタンスに-SSH-接続">インスタンスに SSH 接続</a><ul> <li><a href="#コンソールからインスタンスに接続GUI-の場合">コンソールからインスタンスに接続(GUI の場合)</a></li> <li><a href="#gcloud-コマンドで-SSH-接続CLI-の場合">gcloud コマンドで SSH 接続(CLI の場合)</a></li> </ul> </li> </ul> </li> <li><a href="#Docker-のインストール">Docker のインストール</a><ul> <li><a href="#Minikube-の-driver-について">Minikube の driver について</a></li> <li><a href="#パッケージリストの更新">パッケージリストの更新</a></li> <li><a href="#インストール">インストール</a><ul> <li><a href="#APT-リポジトリのセットアップ">APT リポジトリのセットアップ</a></li> <li><a href="#Docker-のインストール-1">Docker のインストール</a></li> </ul> </li> <li><a href="#Docker-の動作確認">Docker の動作確認</a></li> <li><a href="#クリーンアップ">クリーンアップ</a></li> </ul> </li> <li><a href="#Minikube-のインストール">Minikube のインストール</a><ul> <li><a href="#APT-リポジトリのセットアップ-1">APT リポジトリのセットアップ</a></li> <li><a href="#インストール-1">インストール</a></li> </ul> </li> <li><a href="#Minikube-の実行">Minikube の実行</a><ul> <li><a href="#Minikube-実行ユーザーを-docker-グループに追加">Minikube 実行ユーザーを docker グループに追加</a></li> <li><a href="#Minikube-の実行-1">Minikube の実行</a></li> <li><a href="#Pod-の作成">Pod の作成</a></li> <li><a href="#Pod-の公開">Pod の公開</a></li> <li><a href="#クリーンアップ-1">クリーンアップ</a></li> </ul> </li> <li><a href="#バックアップの取得">バックアップの取得</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241111/20241111071615.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="当記事の目的">当記事の目的</h2> <p>当記事では <strong>Minikube</strong> という OSS(オープンソースソフトウェア)を使用して、Compute Engine の仮想マシン(インスタンス)上に学習用の <strong>Kubernetes クラスタ</strong>を構築する方法を紹介します。</p> <p>Kubernetes はコンテナ オーケストレーション ツールのデファクトスタンダードであり、マネージドな Kubernetes クラスタを提供する <strong>Google Kubernetes Engine(GKE)</strong>は Google Cloud における代表的なサービスの一つです。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/kubernetes-explained">Kubernetes の基本を解説 - G-gen Tech Blog</a></li> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/gke-explained">Google Kubernetes Engine(GKE)を徹底解説 - G-gen Tech Blog</a></li> </ul> <p>Kubernetes はコンテナの運用管理のための非常に強力なツールである反面、独自の用語や設定ファイル、高頻度のバージョンアップなど、学習コストが高いことで知られています。当記事の内容は、<strong>Kubernetes に入門するための簡易的な学習環境を、低コストで用意する</strong>ことを目的としています。</p> <p>学習環境として Compute Engine を用いるメリットとして、使用しないときはインスタンスを停止して料金を節約できる点や、マシンイメージ等を使用してバックアップを取得し、必要に応じて手軽にリストアすることができる点があります。</p> <p>なお、GKE では請求先アカウントにつき月額 $74.40 の無料枠が提供されています。実際の GKE クラスタを使用して学習を行いたい場合は、Autopilot クラスタでこの無料枠を利用してみるのもよいでしょう。</p> <ul> <li>参考 : <a href="https://cloud.google.com/kubernetes-engine/pricing?hl=ja#cluster_management_fee_and_free_tier">クラスタ管理手数料と無料枠</a></li> </ul> <p>ただし、GKE は膨大な量のログを Cloud Logging に出力するため、Cloud Logging の料金にも注意を払う必要があります。</p> <p>また、以下の記事では <strong>Terraform</strong> を使用して Autopilot モードの GKE クラスタを作成する方法を紹介していますので、参考にしてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fprivate-gke-made-with-terraform" title="Google Kubernetes Engine(GKE)の限定公開クラスタをTerraformで作成する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/private-gke-made-with-terraform">blog.g-gen.co.jp</a></cite></p> <h2 id="Minikube-とは">Minikube とは</h2> <p>Minikube はローカル環境で Kubernetes を実行するためのツールです。Minikube を使うと、仮想マシン上にシングルノードの Kubernetes クラスタを構築することができます。Minikube では Kubernetes の全ての機能を使用できるわけではありませんが、基本的な動作の確認や開発環境として利用することができます。</p> <p>当記事では、以下の公式チュートリアルを元に Minikube をインストールし、クラスタの構築を行います。</p> <ul> <li>参考 : <a href="https://kubernetes.io/ja/docs/setup/learning-environment/minikube/">Minikubeを使用してローカル環境でKubernetesを動かす</a></li> <li>参考 : <a href="https://minikube.sigs.k8s.io/docs/start/?arch=%2Flinux%2Fx86-64%2Fstable%2Fdebian+package">minikube start</a></li> </ul> <h1 id="Compute-Engine-インスタンスの作成">Compute Engine インスタンスの作成</h1> <h2 id="作業の概要">作業の概要</h2> <p>Google Cloud プロジェクトに Compute Engine インスタンスを作成します。</p> <p>インスタンスは VPC 内のサブネットに作成する必要があるため、それらのリソースを先に作成し、その中にインスタンスを作成します。</p> <p>そして、インスタンス内で作業する際に VPC の外部から接続できるように、接続を許可するファイアウォールルールを設定しておきます。</p> <p><figure class="figure-image figure-image-fotolife" title="当記事で作成する Compute Engine 環境の構成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241227/20241227090012.png" width="800" height="401" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>当記事で作成する Compute Engine 環境の構成</figcaption></figure></p> <p>当記事では <strong>gcloud コマンド</strong>を用いてリソースの作成を行っていきます。gcloud コマンドのインストールについては以下のドキュメントを参照してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/docs/install?hl=ja">gcloud CLI をインストールする</a></li> </ul> <p>また、Google Cloud コンソールから利用できる <strong>Cloud Shell</strong>(ブラウザベースのターミナル環境)には gcloud コマンドがプリインストールされているため、以降の作業をそのまま実施することができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/shell/docs/using-cloud-shell?hl=ja">Cloud Shell を使用する</a></li> </ul> <h2 id="シェル変数の設定">シェル変数の設定</h2> <p>コマンドで何度か使用する値をシェル変数に格納しておきます。当記事では <code>SUFFIX</code> の値を <strong>minikube</strong> として進めていきます。<code>PROJECT</code> にはリソースを作成するプロジェクトの ID を、<code>REGION</code> には <strong>asia-northeast1</strong> などのリージョンを指定します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synIdentifier">SUFFIX</span>=<span class="synSpecial">{</span>適当な値<span class="synSpecial">}</span> <span class="synComment"># 当記事では minikube </span> <span class="synIdentifier">PROJECT</span>=<span class="synSpecial">{</span>プロジェクトID<span class="synSpecial">}</span> <span class="synIdentifier">REGION</span>=<span class="synSpecial">{</span>リソースを作成するリージョン<span class="synSpecial">}</span> </pre> <h2 id="VPCサブネットの作成">VPC・サブネットの作成</h2> <h3 id="VPC-の作成">VPC の作成</h3> <p>以下のコマンドで VPC を作成します。サブネットを手動で作成するため、<code>--subnet-mode</code> フラグで <code>custom</code> を指定します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># VPC を作成する</span> $ gcloud compute networks create vpc-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--subnet-mode</span><span class="synStatement">=</span>custom <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT}</span> </pre> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/gcloud/reference/compute/networks/create">gcloud compute networks create(コマンドリファレンス)</a></li> </ul> <h3 id="サブネットの作成">サブネットの作成</h3> <p>作成した VPC を指定し、その中にサブネットを作成します。<code>--range</code> フラグではサブネットに割り当てるプライベート IP アドレスの範囲を CIDR で指定します。当記事では <code>192.168.144.0/28</code> を割り当てています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># サブネットを作成する</span> $ gcloud compute networks subnets create subnet-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--network</span><span class="synStatement">=</span>vpc-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--region</span><span class="synStatement">=</span><span class="synPreProc">${REGION}</span> <span class="synStatement">\</span> <span class="synSpecial">--range</span><span class="synStatement">=</span><span class="synConstant">192</span>.<span class="synConstant">168</span>.<span class="synConstant">144</span>.<span class="synConstant">0</span>/<span class="synConstant">28</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT}</span> </pre> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/gcloud/reference/compute/networks/subnets/create">gcloud compute networks subnets create(コマンドリファレンス)</a></li> </ul> <h2 id="インスタンスの作成">インスタンスの作成</h2> <h3 id="Minikube-の要件について">Minikube の要件について</h3> <p><a href="https://minikube.sigs.k8s.io/docs/start/?arch=%2Flinux%2Fx86-64%2Fstable%2Fdebian+package#what-youll-need">公式チュートリアル</a>によると、Minikube のリソース要件は以下のようになっています。</p> <ul> <li>2つ以上の CPU</li> <li>2 GB 以上のメモリ容量</li> <li>20 GB 以上のディスク領域</li> </ul> <p>たとえばメモリが不足している場合、Minikube を実行しようとしても、以下のようにエラーが出て終了してしまいます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># メモリ不足の場合、Minikube を実行できない</span> $ minikube <span class="synStatement">start</span> <span class="synSpecial">--driver</span><span class="synStatement">=</span>docker 😄 minikube v1.<span class="synConstant">34</span>.<span class="synConstant">0</span> on Debian <span class="synConstant">12</span>.<span class="synConstant">7</span> <span class="synPreProc">(</span><span class="synSpecial">amd64</span><span class="synPreProc">)</span> ✨ Using the docker driver based on user configuration â›” Exiting due to RSRC_INSUFFICIENT_CONTAINER_MEMORY: docker only has 969MiB available, <span class="synStatement">less</span> than the required 1800MiB <span class="synStatement">for</span> Kubernetes </pre> <p>当記事ではメモリ容量にある程度余裕があるマシンタイプでインスタンスを作成します。</p> <p>マシンタイプは簡単に変更することができるため、まずは小さめのマシンタイプで試してみて、足りなければリソースを増やしてもよいでしょう。</p> <ul> <li>参考 : <a href="https://cloud.google.com/compute/docs/instances/changing-machine-type-of-stopped-instance?hl=ja#changing_machine_type">コンピューティング インスタンスのマシンタイプの編集 - マシンタイプを変更する</a></li> </ul> <h3 id="インスタンスの作成-1">インスタンスの作成</h3> <p>前の手順で作成した VPC とサブネットを指定し、Compute Engine インスタンスを作成します。</p> <p>当記事では以下の設定値でインスタンスを作成します。</p> <table> <thead> <tr> <th> é …ç›® </th> <th> gcloud コマンドのフラグ </th> <th> 値 </th> <th> 備考 </th> </tr> </thead> <tbody> <tr> <td> インスタンス名 </td> <td> </td> <td> vm-${SUFFIX} </td> <td> </td> </tr> <tr> <td> VPC </td> <td> <code>--network</code> </td> <td> vpc-${SUFFIX} </td> <td> </td> </tr> <tr> <td> サブネット </td> <td> <code>--subnet</code> </td> <td> subnet-${SUFFIX} </td> <td> </td> </tr> <tr> <td> OS イメージ </td> <td> <code>--image-family</code><br><code>--image-project</code> </td> <td> debian-12<br>debian-cloud </td> <td> <strong>以降の手順はここで指定した OS を前提とする点に注意</strong> </td> </tr> <tr> <td> マシンタイプ </td> <td> <code>--machine-type</code> </td> <td> e2-medium </td> <td> 2 vCPU、メモリ4GB<br>必要に応じて変更可(<a href="https://cloud.google.com/compute/docs/instances/changing-machine-type-of-stopped-instance?hl=ja">参考</a>) </td> </tr> <tr> <td> ディスクサイズ </td> <td> --boot-disk-size </td> <td> 20GB </td> <td> Minikube のリソース要件に準拠 </td> </tr> <tr> <td> ネットワークタグ </td> <td> <code>--tags</code> </td> <td> ssh </td> <td> 後で作成するファイアウォールルールをインスタンスに紐付ける際に使用 </td> </tr> </tbody> </table> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Compute Engine インスタンスを作成する</span> $ gcloud compute instances create vm-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--network</span><span class="synStatement">=</span>vpc-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--subnet</span><span class="synStatement">=</span>subnet-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--zone</span><span class="synStatement">=</span><span class="synPreProc">${REGION}</span>-a <span class="synStatement">\</span> <span class="synSpecial">--image-family</span><span class="synStatement">=</span>debian-12 <span class="synStatement">\</span> <span class="synSpecial">--image-project</span><span class="synStatement">=</span>debian-cloud <span class="synStatement">\</span> <span class="synSpecial">--machine-type</span><span class="synStatement">=</span>e2-medium <span class="synStatement">\</span> <span class="synSpecial">--boot-disk-size</span><span class="synStatement">=</span>20GB <span class="synStatement">\</span> <span class="synSpecial">--tags</span><span class="synStatement">=</span>ssh <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT}</span> </pre> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/gcloud/reference/compute/instances/create">gcloud compute instances create(コマンドリファレンス)</a></li> </ul> <h2 id="ファイアウォールルールの設定">ファイアウォールルールの設定</h2> <p>作成したインスタンスに SSH でアクセスできるように、VPC に内向きのファイアウォールルールを作成します。<code>--target-tags</code> フラグでインスタンスに設定したものと同じタグを指定することで、このルールをインスタンスに紐付けることができます。</p> <p>なお、当記事では便宜上 <code>--source-ranges</code> フラグ、つまりアクセス元の IP アドレス範囲を <strong>0.0.0.0/0</strong>(任意の IP アドレス)に設定していますが、セキュリティを考慮して自身の PC の IP アドレス等を設定することもできます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># SSH を許可するファイアウォールルールを作成する</span> $ gcloud compute firewall-rules create vpc-<span class="synPreProc">${SUFFIX}</span>-allow-ssh <span class="synStatement">\</span> <span class="synSpecial">--direction</span><span class="synStatement">=</span>INGRESS <span class="synStatement">\</span> <span class="synSpecial">--source-ranges</span><span class="synStatement">=</span><span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>/<span class="synConstant">0</span> <span class="synStatement">\</span> <span class="synSpecial">--allow</span><span class="synStatement">=</span>tcp:22 <span class="synStatement">\</span> <span class="synSpecial">--target-tags</span><span class="synStatement">=</span>ssh <span class="synStatement">\</span> <span class="synSpecial">--network</span><span class="synStatement">=</span>vpc-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT}</span> </pre> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/gcloud/reference/compute/firewall-rules/create">gcloud compute firewall-rules create(コマンドリファレンス)</a></li> </ul> <h2 id="インスタンスに-SSH-接続">インスタンスに SSH 接続</h2> <h3 id="コンソールからインスタンスに接続GUI-の場合">コンソールからインスタンスに接続(GUI の場合)</h3> <p>Minikube をインストールするため、作成したインスタンスに SSH 接続します。</p> <p>Google Cloud コンソールからインスタンスに SSH 接続する場合、インスタンス一覧画面で「<strong>SSH</strong>」を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="Google Cloud コンソールからインスタンスに SSH 接続する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241227/20241227090016.png" width="800" height="156" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Google Cloud コンソールからインスタンスに SSH 接続する</figcaption></figure></p> <h3 id="gcloud-コマンドで-SSH-接続CLI-の場合">gcloud コマンドで SSH 接続(CLI の場合)</h3> <p>gcloud では、以下のコマンドを使用してインスタンスに SSH 接続できます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># インスタンスに SSH 接続する</span> $ gcloud compute ssh vm-<span class="synPreProc">${SUFFIX}</span> <span class="synStatement">\</span> <span class="synSpecial">--zone</span><span class="synStatement">=</span><span class="synPreProc">${REGION}</span>-a <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT}</span> </pre> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/gcloud/reference/compute/ssh">gcloud compute ssh(コマンドリファレンス)</a></li> </ul> <h1 id="Docker-のインストール">Docker のインストール</h1> <h2 id="Minikube-の-driver-について">Minikube の driver について</h2> <p>Minikube では動作環境(driver)として Docker ã‚„ VirtualBox など、いくつかの選択肢が提供されています。</p> <ul> <li>参考 : <a href="https://minikube.sigs.k8s.io/docs/drivers/">Drivers</a></li> </ul> <p>当記事では推奨 driver の1つである Docker を使用して構築を進めていきます。</p> <p>以下の Docker 公式ドキュメントの手順に沿って、Docker をインストールしていきます。</p> <ul> <li>参考 : <a href="https://docs.docker.com/engine/install/debian/">Install Docker Engine on Debian</a></li> </ul> <h2 id="パッケージリストの更新">パッケージリストの更新</h2> <p>以降の手順については、<strong>SSH 接続した Compute Engine VM 上でコマンドを実行</strong>してください。</p> <p>まずは、APT のパッケージを最新化しておきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># パッケージリストを最新の状態にする</span> $ sudo apt update <span class="synComment"># パッケージの最新化(時間がかかる可能性あり)</span> $ sudo apt upgrade <span class="synSpecial">-y</span> </pre> <h2 id="インストール">インストール</h2> <h3 id="APT-リポジトリのセットアップ">APT リポジトリのセットアップ</h3> <p>まず、Docker パッケージの検証に必要な GPG Key を用意します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Docker のダウンロードに必要なパッケージをインストールする</span> $ sudo apt install ca-certificates curl <span class="synComment"># keyrings ディレクトリのパーミッションを設定する</span> $ sudo install <span class="synSpecial">-m</span> <span class="synConstant">0755</span> <span class="synSpecial">-d</span> /etc/apt/keyrings <span class="synComment"># Docker 公式の GPG Key をダウンロードして keyrings ディレクトリに格納する</span> $ sudo curl <span class="synSpecial">-fsSL</span> https://download.docker.com/linux/debian/gpg <span class="synSpecial">-o</span> /etc/apt/keyrings/docker.asc <span class="synComment"># GPG Key のパーミッションを変更する</span> $ sudo <span class="synStatement">chmod</span> a+<span class="synStatement">r</span> /etc/apt/keyrings/docker.asc </pre> <p>apt コマンドのパッケージ取得元のリポジトリとして Docker 関連のリポジトリを追加します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Docker のリポジトリを追加する</span> $ <span class="synStatement">echo</span><span class="synConstant"> \</span> <span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">deb [arch=</span><span class="synPreProc">$(</span><span class="synSpecial">dpkg --print-architecture</span><span class="synPreProc">)</span><span class="synConstant"> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \</span> <span class="synConstant"> </span><span class="synPreProc">$(</span><span class="synSpecial">. /etc/os-release </span><span class="synStatement">&amp;&amp;</span><span class="synSpecial"> </span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synPreProc">$VERSION_CODENAME</span><span class="synStatement">&quot;</span><span class="synPreProc">)</span><span class="synConstant"> stable</span><span class="synStatement">&quot;</span><span class="synConstant"> </span>| <span class="synStatement">\</span> sudo tee /etc/apt/sources.list.d/docker.list <span class="synStatement">&gt;</span> /dev/null </pre> <h3 id="Docker-のインストール-1">Docker のインストール</h3> <p>Docker の実行に必要なパッケージをインストールします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># パッケージリストを更新する</span> $ sudo apt update <span class="synComment"># Docker の実行に必要なパッケージをインストールする</span> $ sudo apt install <span class="synSpecial">-y</span> docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin </pre> <h2 id="Docker-の動作確認">Docker の動作確認</h2> <p>Docker で適当なコンテナを実行してみます。ここでは Docker 公式コンテナイメージの <a href="https://hub.docker.com/_/hello-world">hello-world</a> を使用します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># hello-world コンテナの起動</span> $ sudo docker run <span class="synSpecial">--name</span> hello hello-world -------------------- 出力例 <span class="synSpecial">--------------------</span> Unable to <span class="synStatement">find</span> image <span class="synStatement">'</span><span class="synConstant">hello-world:latest</span><span class="synStatement">'</span> locally latest: Pulling from library/hello-world c1ec31eb5944: Pull <span class="synStatement">complete</span> Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348 Status: Downloaded newer image <span class="synStatement">for</span> hello-world:latest Hello from Docker<span class="synStatement">!</span> This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: <span class="synConstant">1</span>. The Docker client contacted the Docker <span class="synStatement">daemon</span>. <span class="synConstant">2</span>. The Docker <span class="synStatement">daemon</span> pulled the <span class="synStatement">&quot;</span><span class="synConstant">hello-world</span><span class="synStatement">&quot;</span> image from the Docker Hub. <span class="synPreProc">(</span>amd64<span class="synPreProc">)</span> <span class="synConstant">3</span>. The Docker <span class="synStatement">daemon</span> created a new container from that image which runs the executable that produces the output you are currently reading. <span class="synConstant">4</span>. The Docker <span class="synStatement">daemon</span> streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run <span class="synSpecial">-it</span> ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ </pre> <h2 id="クリーンアップ">クリーンアップ</h2> <p>動作確認用の hello-world コンテナと、そのコンテナイメージを削除していきます。</p> <p>hello-world コンテナは停止した状態で残っています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># コンテナ一覧を確認する</span> $ sudo docker container <span class="synStatement">ls</span> <span class="synSpecial">-a</span> -------------------- 出力例 <span class="synSpecial">--------------------</span> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0f6492b1ab35 hello-world <span class="synStatement">&quot;</span><span class="synConstant">/hello</span><span class="synStatement">&quot;</span> <span class="synConstant">48</span> seconds ago Exited <span class="synPreProc">(</span><span class="synConstant">0</span><span class="synPreProc">)</span> <span class="synConstant">47</span> seconds ago hello </pre> <p>また、コンテナ実行に使用されたコンテナイメージもローカルにダウンロードされているため、これを削除していきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># コンテナイメージの一覧を確認する</span> $ sudo docker image <span class="synStatement">ls</span> -------------------- 出力例 <span class="synSpecial">--------------------</span> REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest d2c94e258dcb <span class="synConstant">18</span> months ago <span class="synConstant">13</span>.3kB </pre> <p>以下のコマンドで、停止したコンテナとコンテナイメージを削除します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># コンテナを削除する</span> $ sudo docker container <span class="synStatement">rm</span> hello <span class="synComment"># hello-world コンテナイメージを削除する</span> $ sudo docker image <span class="synStatement">rm</span> hello-world:latest </pre> <h1 id="Minikube-のインストール">Minikube のインストール</h1> <h2 id="APT-リポジトリのセットアップ-1">APT リポジトリのセットアップ</h2> <p>まず、Kubernetes のリポジトリを APT のパッケージ取得元として登録します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Kubernetes のリポジトリを登録</span> $ <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /</span><span class="synStatement">&quot;</span><span class="synConstant"> </span>| sudo tee /etc/apt/sources.list.d/kubernetes.list </pre> <p>パッケージの検証に使用する GPG Key をダウンロードします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># GPG Key のダウンロード</span> curl <span class="synSpecial">-fsSL</span> https://pkgs.k8s.io/core:/stable:/v1.<span class="synConstant">28</span>/deb/Release.key | sudo gpg <span class="synSpecial">--dearmor</span> <span class="synSpecial">-o</span> /etc/apt/keyrings/kubernetes-apt-keyring.gpg </pre> <p>改めてパッケージリストを更新します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># パッケージリストを更新する</span> $ sudo apt update </pre> <ul> <li>参考 : <a href="https://kubernetes.io/blog/2023/08/15/pkgs-k8s-io-introduction/#how-to-migrate">How to migrate to the Kubernetes community-owned repositories?</a></li> </ul> <h2 id="インストール-1">インストール</h2> <p>Minikube のパッケージをダウンロードし、<code>dpkg</code> コマンドでインストールを実行します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Minikube のパッケージをダウンロードする</span> $ curl <span class="synSpecial">-LO</span> https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb <span class="synComment"># Minikube をインストールする</span> $ sudo dpkg <span class="synSpecial">-i</span> minikube_latest_amd64.deb -------------------- 出力例 <span class="synSpecial">--------------------</span> Selecting previously unselected package minikube. <span class="synPreProc">(</span><span class="synSpecial">Reading database ... </span><span class="synConstant">73229</span><span class="synSpecial"> files and directories currently installed.</span><span class="synPreProc">)</span> Preparing to unpack minikube_latest_amd64.deb ... Unpacking minikube <span class="synPreProc">(</span><span class="synConstant">1</span><span class="synSpecial">.</span><span class="synConstant">34</span><span class="synSpecial">.0-0</span><span class="synPreProc">)</span> ... Setting up minikube <span class="synPreProc">(</span><span class="synConstant">1</span><span class="synSpecial">.</span><span class="synConstant">34</span><span class="synSpecial">.0-0</span><span class="synPreProc">)</span> ... </pre> <h1 id="Minikube-の実行">Minikube の実行</h1> <h2 id="Minikube-実行ユーザーを-docker-グループに追加">Minikube 実行ユーザーを docker グループに追加</h2> <p>Minikube を実行するユーザーを docker グループに所属させます。</p> <p>この手順をスキップすると、Minikube 実行時に以下のような権限エラーが発生してしまいます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ minikube <span class="synStatement">start</span> <span class="synSpecial">--driver</span><span class="synStatement">=</span>docker 😄 minikube v1.<span class="synConstant">34</span>.<span class="synConstant">0</span> on Debian <span class="synConstant">12</span>.<span class="synConstant">7</span> <span class="synPreProc">(</span><span class="synSpecial">amd64</span><span class="synPreProc">)</span> ✨ Using the docker driver based on user configuration 💣 Exiting due to PROVIDER_DOCKER_NEWGRP: <span class="synStatement">&quot;</span><span class="synConstant">docker version --format &lt;no value&gt;-&lt;no value&gt;:&lt;no value&gt;</span><span class="synStatement">&quot;</span> <span class="synStatement">exit</span> <span class="synStatement">status</span> 1: permission denied <span class="synStatement">while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get &quot;</span><span class="synConstant">http://%2Fvar%2Frun%2Fdocker.sock/v1.47/version</span><span class="synStatement">&quot;: dial unix /var/run/docker.sock: connect: permission denied</span> <span class="synStatement">💡 Suggestion: Add your user to the '</span><span class="synConstant">docker</span><span class="synStatement">' group: '</span><span class="synConstant">sudo usermod -aG docker $USER &amp;&amp; newgrp docker</span><span class="synStatement">'</span> <span class="synStatement">📘 Documentation: https://docs.docker.com/engine/install/linux-postinstall/</span> </pre> <p><code>Suggestion:</code> の項目に記載されているコマンドを実行し、現在仮想マシンのログインに使用しているユーザーを docker グループに追加します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 現在のユーザーを docker グループに追加する</span> $ sudo usermod <span class="synSpecial">-aG</span> docker <span class="synPreProc">$USER</span> &amp;&amp; <span class="synStatement">newgrp</span> docker </pre> <h2 id="Minikube-の実行-1">Minikube の実行</h2> <p><code>minikube start</code> コマンドで Minikube を実行します。<code>--driver</code> フラグで Docker をドライバとして設定しています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ minikube <span class="synStatement">start</span> <span class="synSpecial">--driver</span><span class="synStatement">=</span>docker -------------------- 出力例 <span class="synSpecial">--------------------</span> 😄 minikube v1.<span class="synConstant">34</span>.<span class="synConstant">0</span> on Debian <span class="synConstant">12</span>.<span class="synConstant">8</span> <span class="synPreProc">(</span><span class="synSpecial">amd64</span><span class="synPreProc">)</span> ✨ Using the docker driver based on user configuration 📌 Using Docker driver with root privileges 👍 Starting <span class="synStatement">&quot;</span><span class="synConstant">minikube</span><span class="synStatement">&quot;</span> primary control-plane node <span class="synError">in</span> <span class="synStatement">&quot;</span><span class="synConstant">minikube</span><span class="synStatement">&quot;</span> cluster 🚜 Pulling base image v0.<span class="synConstant">0</span>.<span class="synConstant">45</span> ... 💾 Downloading Kubernetes v1.<span class="synConstant">31</span>.<span class="synConstant">0</span> preload ... <span class="synStatement">&gt;</span> preloaded-images-k8s-v18-v1...: <span class="synConstant">326</span>.<span class="synConstant">69</span> MiB / <span class="synConstant">326</span>.<span class="synConstant">69</span> MiB <span class="synConstant">100</span>.<span class="synConstant">00</span>% <span class="synConstant">37</span>.<span class="synConstant">80</span> M <span class="synStatement">&gt;</span> gcr.io/k8s-minikube/kicbase...: <span class="synConstant">487</span>.<span class="synConstant">90</span> MiB / <span class="synConstant">487</span>.<span class="synConstant">90</span> MiB <span class="synConstant">100</span>.<span class="synConstant">00</span>% <span class="synConstant">46</span>.<span class="synConstant">45</span> M 🔥 Creating docker container <span class="synPreProc">(</span><span class="synIdentifier">CPUs</span>=<span class="synConstant">2</span><span class="synSpecial">, </span><span class="synIdentifier">Memory</span>=<span class="synSpecial">2200MB</span><span class="synPreProc">)</span> ... 🐳 Preparing Kubernetes v1.<span class="synConstant">31</span>.<span class="synConstant">0</span> on Docker <span class="synConstant">27</span>.<span class="synConstant">2</span>.<span class="synConstant">0</span> ... â–ª Generating certificates and keys ... â–ª Booting up control plane ... â–ª Configuring RBAC rules ... 🔗 Configuring bridge CNI <span class="synPreProc">(</span><span class="synSpecial">Container Networking Interface</span><span class="synPreProc">)</span> ... 🔎 Verifying Kubernetes components... â–ª Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner, default-storageclass 💡 kubectl not found. If you need it, try: <span class="synStatement">'</span><span class="synConstant">minikube kubectl -- get pods -A</span><span class="synStatement">'</span> 🏄 Done! kubectl is now configured to use <span class="synStatement">&quot;</span><span class="synConstant">minikube</span><span class="synStatement">&quot;</span> cluster and <span class="synStatement">&quot;</span><span class="synConstant">default</span><span class="synStatement">&quot;</span> namespace by default </pre> <p>Minikube の状態は <code>minikube status</code> コマンドで確認できます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Minikube の状態を確認する</span> $ minikube <span class="synStatement">status</span> -------------------- 出力例 <span class="synSpecial">--------------------</span> minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: Configured </pre> <p>一般に Kubernetes の管理操作には <code>kubectl</code> コマンドを使用しますが、Minikube では <code>minikube kubectl</code> を使用します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Minikube のノードを確認する</span> $ minikube kubectl <span class="synSpecial">--</span> get nodes -------------------- 出力例 <span class="synSpecial">--------------------</span> NAME STATUS ROLES AGE VERSION minikube Ready control-plane 11m v1.<span class="synConstant">31</span>.<span class="synConstant">0</span> </pre> <p>毎回 minikube の部分からコマンドを入力するのは手間なので、エイリアスを設定して kubectl だけでコマンドを実行できるようにします。エイリアスは <code>.bashrc</code> ファイルに設定しておきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># エイリアスを設定する(.bashrc に追記)</span> $ <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">alias kubectl='minikube kubectl --'</span><span class="synStatement">&quot;</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> .bashrc <span class="synComment"># .bashrc の追記内容を反映する</span> $ <span class="synStatement">source</span> .bashrc <span class="synComment"># エイリアスで実行できることを確認する</span> $ kubectl get nodes -------------------- 出力例 <span class="synSpecial">--------------------</span> NAME STATUS ROLES AGE VERSION minikube Ready control-plane 13m v1.<span class="synConstant">31</span>.<span class="synConstant">0</span> </pre> <h2 id="Pod-の作成">Pod の作成</h2> <p>Minikube のクラスタを実行できたので、Kubernetes で管理できる最小単位のコンピューティング リソースである Pod を作成してみます。</p> <p>vim 等のエディタを使用して、<code>sample-pod.yaml</code> として以下のマニフェストファイルを作成します。この Pod は、Web サーバである nginx のコンテナを実行します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># sample-pod.yaml</span> <span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Pod <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">labels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> sample <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">containers</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">image</span><span class="synSpecial">:</span> nginx:1.27 <span class="synIdentifier">ports</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">containerPort</span><span class="synSpecial">:</span> <span class="synConstant">80</span> </pre> <p><code>kubectl apply</code> コマンドでマニフェストファイルをクラスタに適用します。これにより、YAML ファイルに記載した設定内容の Pod が Minikube クラスタ上で実行されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># マニフェストファイルをクラスタに適用して Pod を作成する</span> $ kubectl apply <span class="synSpecial">-f</span> sample-pod.yaml </pre> <p><code>kubectl get pods</code> で Pod の一覧を取得します。先程マニフェストファイルを適用した Pod が実行されています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Pod の一覧を取得する</span> $ kubectl get pods -------------------- 出力例 <span class="synSpecial">--------------------</span> NAME READY STATUS RESTARTS AGE nginx <span class="synConstant">1</span>/<span class="synConstant">1</span> Running <span class="synConstant">0</span> 2m15s </pre> <h2 id="Pod-の公開">Pod の公開</h2> <p>Service リソースとして NodePort を作成して、先程作成した Pod の nginx コンテナに Minikube クラスタの外部から接続できるようにします。</p> <p>Pod 同様、Service もマニフェストファイルから作成できますが、ここでは簡易的に <code>kubectl expose</code> コマンドで作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># NodePort を作成して Pod を公開する</span> $ kubectl expose pod/nginx <span class="synSpecial">--type</span><span class="synStatement">=</span>NodePort <span class="synSpecial">--port</span><span class="synStatement">=</span><span class="synConstant">80</span> </pre> <p><code>kubectl get services</code> コマンドで Service リソースの一覧を確認します。NodePort タイプの Service が作成されています(2行目)。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Service の一覧を取得する</span> $ kubectl get services -------------------- 出力例 <span class="synSpecial">--------------------</span> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT<span class="synPreProc">(</span><span class="synSpecial">S</span><span class="synPreProc">)</span> AGE kubernetes ClusterIP <span class="synConstant">10</span>.<span class="synConstant">96</span>.<span class="synConstant">0</span>.<span class="synConstant">1</span> <span class="synStatement">&lt;</span>none<span class="synStatement">&gt;</span> <span class="synConstant">443</span>/TCP 49m nginx NodePort <span class="synConstant">10</span>.<span class="synConstant">110</span>.<span class="synConstant">131</span>.<span class="synConstant">236</span> <span class="synStatement">&lt;</span>none<span class="synStatement">&gt;</span> 80:30134/TCP 116s </pre> <p><code>minikube service nginx --url</code> で NodePort にアクセスするための URL を取得できるため、この URL にアクセスしてみます。ここまで手順通りにリソースを作成していれば、Pod 内の nginx コンテナからレスポンスが返ってきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ curl <span class="synPreProc">$(</span><span class="synSpecial">minikube service nginx --url</span><span class="synPreProc">)</span> -------------------- 出力例 <span class="synSpecial">--------------------</span> <span class="synStatement">&lt;</span>!DOCTYPE html<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>html<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>head<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>title<span class="synStatement">&gt;</span>Welcome to nginx!<span class="synStatement">&lt;</span>/title<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>style<span class="synStatement">&gt;</span> html <span class="synSpecial">{</span> color-scheme: light dark<span class="synStatement">;</span> <span class="synSpecial">}</span> body <span class="synSpecial">{</span> width: 35em<span class="synStatement">;</span> margin: <span class="synConstant">0</span> auto<span class="synStatement">;</span> font-family: Tahoma, Verdana, Arial, sans-serif<span class="synStatement">;</span> <span class="synSpecial">}</span> <span class="synStatement">&lt;</span>/style<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/head<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>body<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>h<span class="synStatement">1&gt;</span>Welcome to nginx!<span class="synStatement">&lt;</span>/h<span class="synStatement">1&gt;</span> <span class="synStatement">&lt;</span>p<span class="synStatement">&gt;</span>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.<span class="synStatement">&lt;</span>/p<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>p<span class="synStatement">&gt;</span>For online documentation and support please refer to <span class="synStatement">&lt;</span>a <span class="synIdentifier">href</span>=<span class="synStatement">&quot;</span><span class="synConstant">http://nginx.org/</span><span class="synStatement">&quot;&gt;</span>nginx.org<span class="synStatement">&lt;</span>/a<span class="synStatement">&gt;</span>.<span class="synStatement">&lt;</span>br/<span class="synStatement">&gt;</span> Commercial support is available at <span class="synStatement">&lt;</span>a <span class="synIdentifier">href</span>=<span class="synStatement">&quot;</span><span class="synConstant">http://nginx.com/</span><span class="synStatement">&quot;&gt;</span>nginx.com<span class="synStatement">&lt;</span>/a<span class="synStatement">&gt;</span>.<span class="synStatement">&lt;</span>/p<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>p<span class="synStatement">&gt;&lt;</span>em<span class="synStatement">&gt;</span>Thank you <span class="synStatement">for</span> using nginx.<span class="synStatement">&lt;</span>/em<span class="synStatement">&gt;&lt;</span>/p<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/body<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/html<span class="synStatement">&gt;</span> </pre> <h2 id="クリーンアップ-1">クリーンアップ</h2> <p>動作確認用に作成した各リソースを削除します。</p> <p>Service リソースを <code>kubectl delete</code> コマンドで削除します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Service を削除する</span> $ kubectl delete services nginx </pre> <p>Pod はマニフェストファイルから作成したので、<code>kubectl delete</code> コマンドで <code>-f</code> フラグを使用し、リソース作成時に使用したマニフェストファイルを指定します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Pod を削除する</span> $ kubectl delete <span class="synSpecial">-f</span> sample-pod.yaml </pre> <h1 id="バックアップの取得">バックアップの取得</h1> <p>Minikube を構築したインスタンスのバックアップを取得しておくと、学習中に環境を壊してしまった場合などに容易に復元することができます。</p> <p>以下の記事で Compute Engine のマシンイメージの取得方法、およびマシンイメージからのインスタンスの復元方法を解説しているので、こちらの手順を参考にバックアップを取得しておくとよいでしょう。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/postgresql-on-compute-engine#%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E5%8F%96%E5%BE%97%E3%81%A8%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%AE%E5%BE%A9%E5%85%83">Compute EngineインスタンスにPostgreSQLサーバを構築する - バックアップの取得とインスタンスの復元</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun コネクテッドシートの抽出機能で行数制限を10万行以上にする方法 hatenablog://entry/6802418398309303995 2024-12-26T09:00:00+09:00 2024-12-26T09:00:01+09:00 G-gen の堂原です。当記事では、Google スプレッドシート(Google Sheets)の機能であるコネクテッドシートで、データの抽出機能を使う際、行数制限が10万行までしか選べない場合の対処法を紹介します。 コネクテッドシートとは 概要 データの抽出 事象 解決方法 コネクテッドシートとは 概要 コネクテッドシート(Connected Sheets)は、Google スプレッドシートの機能です。コネクテッドシートを用いると、Google Cloud(旧称 GCP)のデータ分析サービスである BigQuery のテーブルやビューを Google スプレッドシート上で可視化、分析できます… <p>G-gen の堂原です。当記事では、<strong>Google スプレッドシート</strong>(Google Sheets)の機能である<strong>コネクテッドシート</strong>で、<strong>データの抽出</strong>機能を使う際、行数制限が<strong>10万行までしか選べない</strong>場合の対処法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#コネクテッドシートとは">コネクテッドシートとは</a><ul> <li><a href="#概要">概要</a></li> <li><a href="#データの抽出">データの抽出</a></li> </ul> </li> <li><a href="#事象">事象</a></li> <li><a href="#解決方法">解決方法</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241206/20241206092656.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="コネクテッドシートとは">コネクテッドシートとは</h1> <h2 id="概要">概要</h2> <p><strong>コネクテッドシート</strong>(Connected Sheets)は、<strong>Google スプレッドシート</strong>の機能です。コネクテッドシートを用いると、Google Cloud(旧称 GCP)のデータ分析サービスである <strong>BigQuery</strong> のテーブルやビューを Google スプレッドシート上で可視化、分析できます。</p> <p>詳しくは以下の記事をご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fconnected-sheets" title="Connected Sheets で始めるデータ分析 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/connected-sheets">blog.g-gen.co.jp</a></cite></p> <h2 id="データの抽出">データの抽出</h2> <p><strong>データの抽出</strong>は、BigQuery のデータを<strong>スプレッドシートに取り込む</strong>機能です。データの抽出を行うと<strong>データの処理がスプレッドシート上で完結する</strong>ため、BigQuery の料金を抑えることが出来ます。反対にデータの抽出を行わないと、関数を用いてデータを計算するとき等に都度 BigQuery にリクエストが発行されるため、BigQuery のスキャン料金が発生します。</p> <p>データの抽出機能では、<strong>最大 500,000 è¡Œ</strong>のデータを抽出することが可能です。ただし、データサイズは 10 MB 以下、総セル数は 5,000,000 以下という制限もあります。</p> <ul> <li>参考 : <a href="https://support.google.com/docs/answer/9703214?hl=en&amp;sjid=14484867265077306784-AP#zippy=%2C%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E6%8A%BD%E5%87%BA%E3%81%99%E3%82%8B%2Cpull-data-into-an-extract:~:text=Pull%20data%20into%20an%20extract">Analyze &amp; refresh BigQuery data in Google Sheets using Connected Sheets > Pull data into an extract</a></li> </ul> <h1 id="事象">事象</h1> <p>データの抽出機能は、先述の通り最大 500,000 行のデータ抽出が可能です。</p> <p>しかし2024å¹´12月現在、Google スプレッドシートの設定画面でデータの抽出を設定しようとすると、行数制限が <strong>100,000 行までしか選択できません</strong>。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241226/20241226090003.png" width="800" height="427" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>もちろん、この状態で設定を適用しても、100,000 行までしかデータは出力されません。なお当記事の検証では kaggle で公開されている <a href="https://www.kaggle.com/datasets/sdolezel/black-friday">Black Friday</a> のデータセットを用いており、レコード数は 550,068 行です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241226/20241226090008.png" width="800" height="273" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>全ての Google Workspace 環境でこのような状況が見られるかは未確認ですが、当社が所有する複数の Google Workspace アカウントでは、2024å¹´12月現在、いずれも同様の事象が発生しました。</p> <h1 id="解決方法">解決方法</h1> <p>問題となっている行数制限の設定箇所には、実は<strong>直接数字を書き込むことができます</strong>。</p> <p><figure class="figure-image figure-image-fotolife" title="手動で「123,456」を書き込んだ例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241226/20241226090012.png" width="334" height="507" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>手動で「123,456」を書き込んだ例</figcaption></figure></p> <p>そのため、<strong>100,000 行以上のレコードを表示させたい場合は手動で数字を書き換える</strong>必要があります。この例では直接「500,000」と書き込むことで、下図のように 500,000 行までレコードを表示させることができました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241226/20241226090015.png" width="800" height="604" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ただし先述の通り、データサイズは 10 MB 以下、総セル数は 5,000,000 以下という制限も同時に適用されるため、どれかに抵触した場合はそれが上限となります。</p> <p><figure class="figure-image figure-image-fotolife" title="セル数が 5,000,000 を超えた際のエラー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241226/20241226090019.png" width="695" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>セル数が 5,000,000 を超えた際のエラー</figcaption></figure></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-ryu-dohara/profile_256x256.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">堂原 竜希<a href="https://blog.g-gen.co.jp/archive/author/ggen-ryu-dohara">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部データアナリティクス課。2023å¹´4月より、G-genにジョイン。</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2023, 2024, 2025に選出 (2024年はRookie of the year、2025年はFellowにも選出)。休みの日はだいたいゲームをしているか、時々自転車で遠出をしています。</p> <a href="https://twitter.com/ryu_dohara?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ryu_dohara</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-ryu-dohara Direct VPC Egress経由でCloud NATを利用するとCloud NATのモニタリング指標が記録されない hatenablog://entry/6802418398305516993 2024-12-25T09:00:00+09:00 2024-12-25T09:00:02+09:00 G-gen の佐々木です。当記事では、Direct VPC Egress を経由して Cloud NAT を利用する際の注意点として、Cloud NAT のモニタリング指標が Cloud Monitoring に記録されない仕様について解説します。 Direct VPC Egress 経由の Cloud NAT 利用 制限事項 想定される問題 対策 Cloud NAT のログを有効にする Cloud NAT のポート割り当て数に余裕をもたせる サーバーレス VPC アクセスを利用する Direct VPC Egress 経由の Cloud NAT 利用 Cloud Run ã‚„ Cloud Ru… <p>G-gen の佐々木です。当記事では、Direct VPC Egress を経由して Cloud NAT を利用する際の注意点として、<strong>Cloud NAT のモニタリング指標が Cloud Monitoring に記録されない仕様</strong>について解説します。</p> <ul class="table-of-contents"> <li><a href="#Direct-VPC-Egress-経由の-Cloud-NAT-利用">Direct VPC Egress 経由の Cloud NAT 利用</a></li> <li><a href="#制限事項">制限事項</a></li> <li><a href="#想定される問題">想定される問題</a></li> <li><a href="#対策">対策</a><ul> <li><a href="#Cloud-NAT-のログを有効にする">Cloud NAT のログを有効にする</a></li> <li><a href="#Cloud-NAT-のポート割り当て数に余裕をもたせる">Cloud NAT のポート割り当て数に余裕をもたせる</a></li> <li><a href="#サーバーレス-VPC-アクセスを利用する">サーバーレス VPC アクセスを利用する</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241225/20241225090011.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="Direct-VPC-Egress-経由の-Cloud-NAT-利用">Direct VPC Egress 経由の Cloud NAT 利用</h1> <p>Cloud Run ã‚„ Cloud Run functions(旧称 Cloud Functiions)などのサーバーレス コンピューティング サービスでは、処理を行う際にだけ実行環境を起動し、処理をしないときは実行環境を停止することができます。このため実行環境が起動するたびに、実行環境の <strong>IP アドレスは変わってしまいます</strong>。</p> <p>これらの実行環境から、接続元 IP アドレスが制限されている外部の Web API 等へリクエストを送信する場合、実行環境の外部 IP アドレスを固定する必要がでてきます。このとき、<strong>Cloud NAT</strong> を使用することで、外部アクセスに使用される IP アドレスを固定することができます。Cloud NAT は VPC に紐付いたリソースであるため、Cloud Run 等から VPC に接続するためには <strong>Direct VPC Egress</strong> を使用します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/nat/docs/overview?hl=ja">Cloud NAT の概要</a></li> <li>参考 : <a href="https://cloud.google.com/run/docs/configuring/connecting-vpc?hl=ja#direct-vpc">ダイレクト VPC 下り(外向き)</a></li> </ul> <p>Cloud Run で Direct VPC Egress を使用して Cloud NAT で外部 IP アドレスを固定する方法については、以下の記事で解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-run-using-static-ip-via-direct-vpc-egress" title="Cloud Runから固定IPでインターネット接続する(Direct VPC Egress編) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-run-using-static-ip-via-direct-vpc-egress">blog.g-gen.co.jp</a></cite></p> <p>また、Direct VPC Egress の詳細については以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloudrun-direct-vpc-egress" title="Cloud RunのDirect VPC Egressを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloudrun-direct-vpc-egress">blog.g-gen.co.jp</a></cite></p> <h1 id="制限事項">制限事項</h1> <p>Cloud Run の公式ドキュメントには Direct VPC Egress の制限事項が記載されているほか、Cloud NAT のドキュメントにも制限が記載されています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/run/docs/configuring/vpc-direct-vpc#limitations">Direct VPC egress with a VPC network - Limitations</a></li> <li>参考 : <a href="https://cloud.google.com/run/docs/configuring/vpc-direct-vpc#limitations">Cloud NAT product interactions - Direct VPC egress interactions</a></li> </ul> <p>後者のドキュメントには、以下のような制限が記載されています。</p> <ol> <li>Direct VPC Egress の <a href="https://cloud.google.com/nat/docs/monitoring?hl=ja#gateway-metrics">Cloud NAT 指標</a>は Cloud Monitoring にエクスポートされない</li> <li>Direct VPC Egress の Cloud NAT ログには、発信元の Cloud Run サービス、リビジョン、ジョブの名前は表示されない</li> <li>Direct VPC Egress では<a href="https://cloud.google.com/nat/docs/private-nat?hl=ja">プライベート NAT</a> が使用できない</li> </ol> <p>当記事ではこれらの3つの制限のうち、<strong>1</strong> のモニタリングに関する制限について掘り下げます。</p> <h1 id="想定される問題">想定される問題</h1> <p>前述の 1 の制限は、Direct VPC Egress 経由で Cloud Run ã‚„ Cloud Run functions から Cloud NAT が利用された場合、その利用状況が Cloud Monitoring で可視化できないことを意味しています。</p> <p><figure class="figure-image figure-image-fotolife" title="Direct VPC Egress経由でCloud NATを使用すると、Cloud NATのモニタリング指標が表示されない"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241225/20241225090005.png" width="800" height="362" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Direct VPC Egress経由でCloud NATを使用すると、Cloud NATのモニタリング指標が表示されない</figcaption></figure></p> <p>この場合、Cloud NAT に高負荷が原因で問題が生じたときの原因調査が難しくなります。たとえば以下の記事で紹介しているケースでは、Cloud Run からの全てのアウトバウンド トラフィックが意図せず Cloud NAT に向かってしまったことで、<strong>Cloud NAT が利用できるポート数が上限に達してしまい</strong>、接続エラーが多発する状況になりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-run-to-sql-access-failed-via-nat" title="Cloud RunからCloud SQLへの通信がCloud NATを経由してしまう事象とその解決策 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-run-to-sql-access-failed-via-nat">blog.g-gen.co.jp</a></cite></p> <p>このケースにおける Cloud NAT の高負荷は設定ミスによって起こったものですが、リクエストの急増により Cloud Run がスケールした場合など、通常の利用時にも発生する可能性はあります。本来 Cloud NAT のポート使用状況は Cloud Monitoring で可視化できますが、Direct VPC Egress 経由のトラフィックについては、この指標が記録されません。</p> <h1 id="対策">対策</h1> <h2 id="Cloud-NAT-のログを有効にする">Cloud NAT のログを有効にする</h2> <p>Cloud NAT のログは作成時のデフォルトでは無効になっていますが、これを有効化することで、エラーログから Cloud NAT の異常を検知できる可能性があります。</p> <p>ただし、前述の制限事項に記載したように、Direct VPC Egress の Cloud NAT ログには発信元の情報が記録されない点には注意が必要です。</p> <p><figure class="figure-image figure-image-fotolife" title="Cloud NATのログを有効化する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241225/20241225090008.png" width="413" height="280" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud NATのログを有効化する</figcaption></figure></p> <h2 id="Cloud-NAT-のポート割り当て数に余裕をもたせる">Cloud NAT のポート割り当て数に余裕をもたせる</h2> <p>Cloud NAT には利用できるポート数を動的に変更する<strong>動的ポートの割り当て</strong>機能があります。しかし、動的ポートの割り当てを使うと、ポート数がスケールアウトするタイミングでパケットがドロップしてしまう問題があります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/nat/docs/ports-and-addresses?hl=ja#dynamic-port">動的ポートの割り当て</a></li> <li>参考 : <a href="https://cloud.google.com/nat/docs/troubleshooting?hl=ja#dpa-drops">動的ポート割り当てが構成されているときにパケットがドロップされる</a></li> </ul> <p>動的ポートの割り当てを使用しない場合、静的にポートの割り当てが行われます。デフォルトでは<strong>64</strong>個のポートが利用できますが、手動で変更することができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/nat/docs/ports-and-addresses?hl=ja#static-port">静的ポートの割り当て</a></li> </ul> <p>Direct VPC Egress を使用しているときは Cloud NAT の指標が利用できないため、適切なポート数を検討することも難しいですが、問題が起こったときにポート数を増やす選択肢があることは理解しておくとよいでしょう。</p> <h2 id="サーバーレス-VPC-アクセスを利用する">サーバーレス VPC アクセスを利用する</h2> <p><strong>サーバーレス VPC アクセス</strong>経由で Cloud NAT を使用する場合、Direct VPC Egress と比較して料金やパフォーマンス面のデメリットはありますが、当記事で解説したモニタリングに関する問題は発生しません。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vpc/docs/serverless-vpc-access?hl=ja">サーバーレス VPC アクセス</a></li> </ul> <p>Cloud Run でサーバーレス VPC アクセスを使用して Cloud NAT で外部 IP アドレスを固定する方法については、以下の記事で解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloudrun-using-static-ip" title="Cloud Runから固定IPでインターネット接続する(サーバーレスVPCアクセス編) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloudrun-using-static-ip">blog.g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun å…¨Geminiプロダクトを徹底解説! hatenablog://entry/6802418398309377607 2024-12-24T09:00:00+09:00 2024-12-24T09:20:15+09:00 G-gen の米川です。Google が開発した大規模言語モデル Gemini は、その高い性能と多岐にわたるプロダクト展開で注目を集めています。当記事では、Gemini プロダクトの全貌を網羅的に解説します。 はじめに 生成 AI 基盤モデル としての Gemini モデルとは Gemini のモデルファミリー Gemini モデルのバージョン Gemini プロダクト Gemini アプリ Gemini アプリとは データ保護 Gemini Advanced Gems Gemini for Google Workspace Gemini for Google Workspace とは サイ… <p>G-gen の米川です。Google が開発した大規模言語モデル <strong>Gemini</strong> は、その高い性能と多岐にわたるプロダクト展開で注目を集めています。当記事では、Gemini プロダクトの全貌を網羅的に解説します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#生成-AI-基盤モデル-としての-Gemini">生成 AI 基盤モデル としての Gemini</a><ul> <li><a href="#モデルとは">モデルとは</a></li> <li><a href="#Gemini-のモデルファミリー">Gemini のモデルファミリー</a></li> <li><a href="#Gemini-モデルのバージョン">Gemini モデルのバージョン</a></li> </ul> </li> <li><a href="#Gemini-プロダクト">Gemini プロダクト</a><ul> <li><a href="#Gemini-アプリ">Gemini アプリ</a><ul> <li><a href="#Gemini-アプリとは">Gemini アプリとは</a></li> <li><a href="#データ保護">データ保護</a></li> <li><a href="#Gemini-Advanced">Gemini Advanced</a></li> <li><a href="#Gems">Gems</a></li> </ul> </li> <li><a href="#Gemini-for-Google-Workspace">Gemini for Google Workspace</a><ul> <li><a href="#Gemini-for-Google-Workspace-とは">Gemini for Google Workspace とは</a></li> <li><a href="#サイドパネル">サイドパネル</a></li> <li><a href="#Gemini-for-Google-Workspace-アドオン">Gemini for Google Workspace アドオン</a></li> </ul> </li> <li><a href="#Gemini-for-Google-Cloud">Gemini for Google Cloud</a><ul> <li><a href="#Gemini-for-Google-Cloud-とは">Gemini for Google Cloud とは</a></li> <li><a href="#機能一覧">機能一覧</a></li> <li><a href="#料金">料金</a></li> </ul> </li> <li><a href="#Generative-AI-on-Vertex-AI">Generative AI on Vertex AI</a><ul> <li><a href="#Generative-AI-on-Vertex-AI-とは">Generative AI on Vertex AI とは</a></li> <li><a href="#データの保護">データの保護</a></li> <li><a href="#ユースケース">ユースケース</a></li> <li><a href="#その他のプロダクト">その他のプロダクト</a></li> <li><a href="#料金-1">料金</a></li> </ul> </li> <li><a href="#Gemini-API">Gemini API</a><ul> <li><a href="#Gemini-API-とは">Gemini API とは</a></li> <li><a href="#データの保護-1">データの保護</a></li> <li><a href="#料金-2">料金</a></li> </ul> </li> </ul> </li> <li><a href="#導入すべき-Gemini-プロダクト">導入すべき Gemini プロダクト</a><ul> <li><a href="#生成-AI-で社内業務を効率化したい場合">生成 AI で社内業務を効率化したい場合</a></li> <li><a href="#自社データを効率的に検索したり生成-AI-に質問に答えさせたい場合">自社データを効率的に検索したり生成 AI に質問に答えさせたい場合</a></li> <li><a href="#自社の新サービスに生成-AI-を組み込む場合">自社の新サービスに生成 AI を組み込む場合</a></li> <li><a href="#システム開発を効率化したい場合">システム開発を効率化したい場合</a></li> </ul> </li> <li><a href="#ビジネス導入における注意点">ビジネス導入における注意点</a><ul> <li><a href="#生成-AI-のビジネス適用">生成 AI のビジネス適用</a></li> <li><a href="#生成-AI-は確率エンジンであることを理解する">生成 AI は確率エンジンであることを理解する</a></li> <li><a href="#生成-AI-に向いてる業務--向いてない業務">生成 AI に向いてる業務 / 向いてない業務</a><ul> <li><a href="#向いている業務">向いている業務</a></li> <li><a href="#向いていない業務">向いていない業務</a></li> </ul> </li> <li><a href="#セキュリティ">セキュリティ</a><ul> <li><a href="#データ保護-1">データ保護</a></li> <li><a href="#生成-AI-アプリへの攻撃">生成 AI アプリへの攻撃</a></li> <li><a href="#不適切な生成コンテンツ">不適切な生成コンテンツ</a></li> </ul> </li> </ul> </li> <li><a href="#導入事例">導入事例</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241224/20241224074638.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p><strong>Gemini</strong> は、Google が開発した生成 AI 基盤モデル、およびそれを利用した生成 AI プロダクト群です。Gemini を用いたプロダクトには、以下があります。</p> <table> <thead> <tr> <th> プロダクト名 </th> <th> 概要 </th> </tr> </thead> <tbody> <tr> <td> <strong>Gemini アプリ</strong> </td> <td> ブラウザやモバイルアプリから利用な生成 AI チャットアプリ </td> </tr> <tr> <td> <strong>Gemini for Google Workspace</strong> </td> <td> Google Workspace に組み込まれた業務補助 AI </td> </tr> <tr> <td> <strong>Gemini for Google Cloud</strong> </td> <td> Google Cloud 上の開発を効率化するツール群 </td> </tr> <tr> <td> <strong>Generative AI on Vertex AI</strong> </td> <td> Google Cloud の Vertex AI API 経由で Gemini モデルを呼び出し </td> </tr> <tr> <td> <strong>Gemini API</strong> </td> <td> Google AI Studio の API 経由で Gemini モデルを呼び出し </td> </tr> </tbody> </table> <p>それぞれに異なる機能や特徴があり、ビジネスシーンに合わせて最適なプロダクトを選択できます。当記事ではこれらを <strong>Gemini プロダクト</strong>と呼称して、それぞれ紹介します。</p> <p><figure class="figure-image figure-image-fotolife" title="Gemini プロダクト一覧"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221140556.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini プロダクト一覧</figcaption></figure></p> <h1 id="生成-AI-基盤モデル-としての-Gemini">生成 AI 基盤モデル としての Gemini</h1> <h2 id="モデルとは">モデルとは</h2> <p>まず、機械学習における<strong>モデル</strong>とは、大量のデータからパターンやルールを学習し、特定のタスクを実行できるようになった仕組みのことを指します。</p> <p>例えば画像認識モデルは、たくさんの猫の画像データから、“猫らしさ” を学習することで、人が教えることなく初めて見る猫の画像でも「これは猫だ」と認識できます。</p> <p>Gemini も機械学習モデルの1つです。Gemini は<strong>マルチモーダル</strong>な生成 AI モデルです。マルチモーダルなモデルとは、テキスト、画像、音声、動画など、<strong>複数の種類の情報を理解し、コンテンツを生成する</strong>ことができることを指します。</p> <p>当記事で紹介する Gemini プロダクトは、この Gemini モデルを用いています。</p> <h2 id="Gemini-のモデルファミリー">Gemini のモデルファミリー</h2> <p><figure class="figure-image figure-image-fotolife" title="Gemini モデルファミリー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221141608.png" width="841" height="333" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini モデルファミリー</figcaption></figure></p> <p>Gemini のモデルには複数の種類があり、それぞれ得意なタスクや能力が異なります。Gemini アプリや Gemini for Google Workspace に組み込まれているモデルや、Google Cloud から API 経由で利用できるモデルには、以下のようなものがあります。</p> <p><strong>Gemini Ultra</strong><br/> Gemini ファミリーの中でも最も高性能なモデルです。複雑な推論や高度なコーディングなど、専門的な知識を必要とするタスクに優れています。</p> <p><strong>Gemini Pro</strong><br/> 幅広いタスクに対応できる汎用性の高いモデルです。文章生成、翻訳、質疑応答など、様々な用途で利用できます。</p> <p><strong>Gemini Flash</strong><br/> 高速な応答速度を誇るモデルです。レイテンシが重要なアプリケーションに最適です。</p> <p><strong>Gemini Nano</strong><br/> 軽量なモデルです。スマートフォンなどのデバイス上で動作するように設計されており、限られた計算資源でも効率的に動作します。</p> <p>これらのモデルは、Gemini プロダクトに組み込まれています。私たちユーザーから明示的にモデルの種類が選択できるプロダクトもあれば、Google が最適なモデルを選択して組み込み済みのこともあります。</p> <h2 id="Gemini-モデルのバージョン">Gemini モデルのバージョン</h2> <p>上記のモデルに加えて、Gemini には<strong>バージョン</strong>という概念があります。バージョンは、モデルの改善や機能追加が行われるたびに更新されていきます。</p> <p>例えば2024å¹´12月現在、Gemini Pro の一般利用可能なバージョンは Gemini 1.0 Pro と Gemini 1.5 Pro の2つです。</p> <p>1.0 から 1.5 へのアップデートにより、コンテキストウィンドウ(一度に処理できる情報量、単位は<strong>トークン</strong>)がより大きくなったり、推論能力、コード生成能力が向上しました。</p> <p>2024å¹´12月には、<strong>Gemini 2.0</strong> が発表されました。2024å¹´12月現在、Google Cloud(Vertex AI)等で Gemini 2.0 Flash の試験運用版が利用可能であるほか、Gemini アプリでも 2.0 の試験運用版が既に利用可能になっています。</p> <ul> <li>参考 : <a href="https://blog.google/intl/ja-jp/company-news/technology/google-gemini-ai-update-december-2024/">Gemini 2.0: エージェント時代に向けた新しい AI モデル</a></li> <li>参考 : <a href="https://developers.googleblog.com/en/the-next-chapter-of-the-gemini-era-for-developers/">The next chapter of the Gemini era for developers</a></li> </ul> <h1 id="Gemini-プロダクト">Gemini プロダクト</h1> <h2 id="Gemini-アプリ">Gemini アプリ</h2> <p><figure class="figure-image figure-image-fotolife" title="Gemini アプリはチャットツール"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221142026.png" width="1200" height="696" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini アプリはチャットツール</figcaption></figure></p> <h3 id="Gemini-アプリとは">Gemini アプリとは</h3> <p><strong>Gemini アプリ</strong>(Gemini app)とは、以下の2つのチャットプロダクトの総称です。</p> <ol> <li>Gemini ウェブアプリ(<code>gemini.google.com</code> のこと。かつて Bard と呼ばれていた Web ブラウザ向け生成 AI チャット)</li> <li>スマートフォン向け生成 AI チャットアプリ(Android および iOS 向け)</li> </ol> <p>いずれも、Google の生成 AI 基盤モデルである Gemini を基盤としたチャットアプリケーションで、Google アカウントさえあれば<strong>無料で利用できます</strong>。</p> <ul> <li>参考 : <a href="https://gemini.google.com/">gemini.google.com</a></li> <li>参考 : <a href="https://support.google.com/gemini">Gemini アプリ ヘルプ</a></li> </ul> <h3 id="データ保護">データ保護</h3> <p>Gemini アプリは、Google アカウントがあれば誰でも無料で利用できます。ただし、無償の Google アカウントで Gemini アプリを使う場合、入力したデータは Google によって<strong>サービス改善のために利用される</strong>場合があります。</p> <p>一方で以下のエディションの Google Workspace で管理されたアカウントであれば「<strong>エンタープライズグレードのデータ保護</strong>」が適用されます。この場合、データは Google によって<strong>サービス改善のために利用されることはなく</strong>、人間のレビュワーによって見られることもありません。</p> <ul> <li>Business Starter / Business Standard / Business Plus</li> <li>Enterprise Starter / Enterprise Standard / Enterprise Plus</li> <li>Essentials</li> <li>Enterprise Essentials / Enterprise Essentials Plus</li> <li>Frontline Starter / Frontline Standard</li> <li>Nonprofits</li> </ul> <p>さらに Google Workspace では、Gemini アプリを利用できるユーザーを限定したり、逆に組織全体で利用可能にするなど、利用可否のコントロールも可能です。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/14130944#zippy=%2Cgemini%2Cgemini-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%A7%E3%81%99%E3%81%8B">Gemini for Google Workspace に関するよくある質問 - Gemini アプリとは何ですか?</a></li> <li>参考 : <a href="https://support.google.com/a/answer/14571493">Gemini アプリをオンまたはオフにする</a></li> </ul> <h3 id="Gemini-Advanced">Gemini Advanced</h3> <p><strong>Gemini Advanced</strong> は、様々な追加機能を含む、Gemini の有償版です。Gemini Advanced では、Gemini アプリで最新版の Gemini モデルを選択できるようになったり、情報レポートを簡単に作成できる Deep Research、長文テキストやファイルの分析、Gmail ã‚„ Google ドキュメントとのシームレスな連携などが利用可能です。</p> <p>個人の場合、個人向けの有償 Google サービスである Google One AI プレミアムプランに加入することで利用可能になります。Google Workspace の場合、Gemini for Google Workspace アドオンを購入すると利用可能になります。</p> <ul> <li>参考 : <a href="https://gemini.google/advanced/">Gemini Advanced</a></li> <li>参考 : <a href="https://support.google.com/gemini/answer/14517446">Gemini Advanced にアップグレードする</a></li> </ul> <h3 id="Gems">Gems</h3> <p>Gemini ウェブアプリの拡張機能として、<strong>Gems</strong> があります。Gems は Gemini ウェブアプリをカスタマイズするための機能です。2024å¹´8月28日に、Gemini Advanced ユーザー向けに公開されました。</p> <p>例えば、YouTube 動画の要約を表示するのに特化した Gems や、画像からテキストを抽出することに特化した Gems などを作成することができます。</p> <p>詳細は以下の記事を参考にしてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fgems-explained" title="Geminiウェブアプリのカスタマイズ機能「Gems」を徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/gems-explained">blog.g-gen.co.jp</a></cite></p> <h2 id="Gemini-for-Google-Workspace">Gemini for Google Workspace</h2> <p><figure class="figure-image figure-image-fotolife" title="Gemini for Google Workspace は業務補助ツール"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221142448.png" width="1142" height="292" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini for Google Workspace は業務補助ツール</figcaption></figure></p> <h3 id="Gemini-for-Google-Workspace-とは">Gemini for Google Workspace とは</h3> <p><strong>Gemini for Google Workspace</strong> とは、Google Workspace にアドオンして利用する AI アシスタント機能です。利用には、Gemini for Google Workspace アドオンの追加購入が必要です。</p> <p>Gemini の強力な AI 技術が Gmail、Google ドキュメント、Google スライド、Google スプレッドシートなど、普段使い慣れた Google Workspace アプリに統合されることで、業務が効率化されます。</p> <p>また Gemini for Google Workspace では<strong>エンタープライズグレードのデータ保護</strong>が適用されており、データは Google によって<strong>サービス改善のために利用されることはなく</strong>、人間のレビュワーによって<strong>見られることもない</strong>ため、安心して業務に利用することができます。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/13623623">Gemini for Google Workspace</a></li> <li>参考 : <a href="https://support.google.com/a/answer/14130944">Gemini for Google Workspace に関するよくある質問</a></li> </ul> <h3 id="サイドパネル">サイドパネル</h3> <p>Gemini for Google Workspace では、<strong>サイドパネル</strong>を通して Gemini が利用できます。</p> <p>Gemini が統合されている Google Workspace アプリ(Google ドキュメント、Google スプレッドシートなど)では、Gemini アイコンが表示されます。例えば Google ドキュメントの場合、画面右上に Gemini アイコンがあります。このアイコンをクリックすると、サイドパネルにプロンプト入力画面が表示され、Gemini に指示を出すことができます。Gemini は指示を受け取ると、数秒でコンテンツを生成してくれます。</p> <p><figure class="figure-image figure-image-fotolife" title="Google ドキュメント上の Gemini"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241224/20241224090007.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Google ドキュメント上の Gemini サイドパネル</figcaption></figure></p> <h3 id="Gemini-for-Google-Workspace-アドオン">Gemini for Google Workspace アドオン</h3> <p>Gemini for Google Workspace の利用には、<strong>Gemini for Google Workspace アドオン</strong>の追加購入が必要です。Gemini for Google Workspace アドオンは Gemini アドオンとも呼ばれ、以下の種類があります(価格は2024å¹´12月現在)。</p> <table> <thead> <tr> <th> Gemini for GWS<br>エディション </th> <th> 料金(ユーザー/月) </th> <th> 主な機能 </th> <th> ユースケース </th> </tr> </thead> <tbody> <tr> <td> <strong>Gemini Business </strong> </td> <td> 年間契約 : 2,260円<br>フレキシブル : 2,712円 </td> <td> - Gmail、ドキュメント、スプレッドシート、スライドでのAI支援<br> - 企業向け Gemini アプリの利用<br> </td> <td> - 情報収集、ブレスト、コンテンツ作成、デザイン作成など日常的な業務効率化<br> - 簡単なデータ分析 </td> </tr> <tr> <td> <strong>Gemini Enterprise</strong> </td> <td> 年間契約 : 3,400円<br>フレキシブル : 4,080円 </td> <td> - Gemini Business の全機能に加え、高度な機能<br> - Meet の議事録作成、リアルタイム翻訳<br> - Chat の会話要約 </td> <td> - 高度な業務効率化<br> - 大規模なデータ分析と可視化<br> - 専門的なレポート作成<br> - 複雑なタスクの自動化<br> - 組織全体の生産性向上 </td> </tr> <tr> <td> <strong>AI Security</strong> </td> <td> 年間契約 : 1,356円<br>フレキシブル : 1,130円 </td> <td> - AI を活用したセキュリティ強化(機密保持のための自動ラベル付け)に限定 </td> <td> - サイバーセキュリティ脅威の検知と防御<br> - フィッシングメールの検出<br> - マルウェアの検知<br> - 不正アクセスの防止<br> - データ漏洩の防止 </td> </tr> <tr> <td> <strong>AI Meetings and Messaging</strong> </td> <td> 年間契約 : 1,356円<br>フレキシブル : 1,130円 </td> <td> - AI を活用した会議とメッセージングの効率化に限定 </td> <td> - 会議の自動要約<br> - 会議中のリアルタイム翻訳<br> - 会議後のアクションアイテムの自動生成<br> - チャットの要約・自動要約 </td> </tr> </tbody> </table> <p>アドオンの違いや詳細については、以下を参考にしてください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/14700766">Gemini for Google Workspace アドオンの比較</a></li> </ul> <h2 id="Gemini-for-Google-Cloud">Gemini for Google Cloud</h2> <p><figure class="figure-image figure-image-fotolife" title="Gemini for Google Cloud は開発補助ツール"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221143037.png" width="1200" height="260" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini for Google Cloud は開発補助ツール</figcaption></figure></p> <h3 id="Gemini-for-Google-Cloud-とは">Gemini for Google Cloud とは</h3> <p><strong>Gemini for Google Cloud</strong> は、Google Cloud 上での開発に役に立つ、開発者向けの AI アシスタント機能です。ソースコードの自動生成、データ分析の効率化、セキュリティの強化などが可能です。</p> <p>アプリケーション開発者はもちろん、データサイエンティストやビジネスアナリスト、セキュリティ担当者など、様々な Google Cloud ユーザーのオペレーション・開発を支援します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/gemini/docs/overview?hl=ja">Gemini for Google Cloud の概要</a></li> </ul> <h3 id="機能一覧">機能一覧</h3> <p>Gemini for Google Cloud には以下の機能が含まれています。</p> <table> <thead> <tr> <th> 機能名 </th> <th> 概要 </th> </tr> </thead> <tbody> <tr> <td> Gemini in BigQuery </td> <td> データ分析、可視化、SQL ã‚„ Python のコード生成などを支援</td> </tr> <tr> <td> Gemini Code Assist </td> <td> IDE と連携して利用。ソースコード開発、デプロイ、トラブルシューティングを支援</td> </tr> <tr> <td> Gemini in Colab Enterprise </td> <td> Colab EnterpriseノートブックでのPythonコード生成を支援</td> </tr> <tr> <td> Gemini in Databases </td> <td> データベース管理、セキュリティ向上などを支援</td> </tr> <tr> <td> Gemini in Looker </td> <td> Looker(Google Cloud コア)や Looker Studio Pro でデータ可視化や解釈を支援</td> </tr> <tr> <td> Gemini in Security Command Center </td> <td> セキュリティに関する検索クエリ生成、ケース解釈、攻撃パス把握を支援</td> </tr> </tbody> </table> <p>以下の当社記事も参考にしてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Ftrial-for-gemini-in-bigquery" title="自然言語でデータ分析ができるGemini in BigQuery(データキャンバス)を試してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/trial-for-gemini-in-bigquery">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Ftrial-for-gemini-in-database" title="Gemini in Databaseを使ってみた(Cloud SQL Studioç·¨) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/trial-for-gemini-in-database">blog.g-gen.co.jp</a></cite></p> <h3 id="料金">料金</h3> <p>Gemini for Google Cloud を利用するには、<strong>Gemini Code Assist サブスクリプション</strong>を購入して、ユーザーに割り当てます。ライセンスを割り当てられたユーザーは、Gemini in BigQuery、Gemini in Databases、Gemini in Colab Enterprise などの機能が利用可能になります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/gemini/docs/discover/set-up-gemini?hl=ja">Gemini Code Assist を設定する</a></li> </ul> <p>Gemini Code Assist サブスクリプションには Standard と Enterprise の2エディションがあり、どちらを選ぶかによって付随する機能が異なります。以下は、2024å¹´12月現在の価格です。最新の価格は、必ず公式ドキュメントを参照してください。</p> <table> <thead> <tr> <th> エディション </th> <th> 料金(月額) </th> <th> 料金(12ヶ月コミット) </th> </tr> </thead> <tbody> <tr> <td> Standard </td> <td> $22.80 / 月 / 人 </td> <td> $19.0 / 月 / 人 </td> </tr> <tr> <td> Enterprise </td> <td> $54.0 / 月 / 人 </td> <td> $45.0 / 月 / 人 </td> </tr> </tbody> </table> <ul> <li>参考:<a href="https://cloud.google.com/products/gemini/pricing?hl=ja">Gemini for Google Cloud の料金</a></li> </ul> <p>また、Gemini in BigQuery のみ利用したい場合、<strong>BigQuery Editions の Enterprise Plus エディション</strong>を有効化することで利用可能になります。</p> <p>こちらの場合、Gemini Code Assist をサブスクライブする必要はありません。またこちらの利用方法の場合、SQL ã‚„ Python のコード生成、可視化補助など Gemini Code Assist に含まれる Gemini in BigQuery のすべての機能に加えて、パーティショニングとクラスタリングのレコメンデーションやマテリアライズド・ビューのレコメンデーションなど、追加の生成 AI 機能も利用可能になります。</p> <h2 id="Generative-AI-on-Vertex-AI">Generative AI on Vertex AI</h2> <p><figure class="figure-image figure-image-fotolife" title="Generative AI on Vertex AI では Google Cloud 経由で Gemini を利用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221143829.png" width="1014" height="294" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Generative AI on Vertex AI では Google Cloud 経由で Gemini を利用</figcaption></figure></p> <h3 id="Generative-AI-on-Vertex-AI-とは">Generative AI on Vertex AI とは</h3> <p><strong>Generative AI on Vertex AI</strong> とは、Google Cloud の AI/ML プラットフォームプロダクトである <strong>Vertex AI</strong> の REST API を通して、Gemini などの生成 AI モデルを利用する手法のことです。アプリケーション開発者は Vertex AI API を通して Gemini モデルにプロンプトを入力し、レスポンスを得ることができます。</p> <p>これにより、Gemini を自社開発のアプリケーションに組み込むことができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview?hl=ja">Generative AI on Vertex AI</a></li> </ul> <p>Vertex AI API は、HTTPS での呼び出しや、Python ã‚„ Java などの各プログラミング言語用の公式クライアントライブラリ、また BigQuery ML などから利用できます。</p> <p>Google Cloud プロダクトですので、認証・認可は IAM によって管理されており、また課金も Google Cloud 利用料として請求されます。</p> <h3 id="データの保護">データの保護</h3> <p>Vertex AI API 経由で Gemini に入力されるプロンプトやチューニングデータは保護されており、データが Google によってサービス改善に利用されることはありません。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance?hl=ja">生成 AI とデータ ガバナンス</a></li> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/multimodal-faqs?hl=ja">Gemini API に関するよくある質問</a></li> </ul> <h3 id="ユースケース">ユースケース</h3> <p>以下の当社記事では、Vertex AI API 経由で Gemini を呼び出すことで生成 AI アプリケーションを開発した事例を紹介しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fmultimodal-chat-app-with-gemini" title="Geminiでマルチモーダル対応の生成AIチャットアプリを爆速で作ってみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/multimodal-chat-app-with-gemini">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fgoogle-maps-gemini-review-analysis" title="BigQueryとGemini 1.5 Proによるラーメン店クチコミの定量分析 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/google-maps-gemini-review-analysis">blog.g-gen.co.jp</a></cite></p> <h3 id="その他のプロダクト">その他のプロダクト</h3> <p>Google Cloud には Vertex AI API 経由での Gemini 呼び出しのほか、Gemini を利用した各種プロダクトがあります。</p> <p><strong>Vertex AI Agent Builder</strong> は Vertex AI の派生プロダクトの1つです。このプロダクトの Vertex AI Search 機能により、RAG 構成(生成 AI により生成されたコンテンツをデータにより根拠づけするアーキテクチャ)を簡単に構成したり、Google クオリティの企業データ検索(エンタープライズサーチ)を容易に構築できます。</p> <p>以下の記事も参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fvertex-ai-search-and-conversation-explained" title="Vertex AI Agent Builderを徹底解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/vertex-ai-search-and-conversation-explained">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcomparing-rag-architecture-across-cloud-vendors" title="生成AIのRAG構成を大手3社(AWS、Azure、Google Cloud)で徹底比較してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/comparing-rag-architecture-across-cloud-vendors">blog.g-gen.co.jp</a></cite></p> <h3 id="料金-1">料金</h3> <p>Generative AI on Vertex AI での Gemini 利用の料金は、入力したデータと出力したデータのボリュームに応じた従量課金です。固定料金は発生しません。</p> <p>以下は、料金単価の一部抜粋です。情報は2024å¹´12月現在のものですので、必ず最新の公式ドキュメントをご参照ください。</p> <table> <thead> <tr> <th> モデル </th> <th> タイプ </th> <th> 単価(入力トークンが128K以下の場合)</th> </tr> </thead> <tbody> <tr> <td> Gemini 1.5 Flash </td> <td> テキスト入力 </td> <td> $0.00001875 / 1,000 文字 </td> </tr> <tr> <td> Gemini 1.5 Flash </td> <td> 画像入力 </td> <td> $0.00002 / 画像 </td> </tr> <tr> <td> Gemini 1.5 Flash </td> <td> テキスト出力 </td> <td> $0.000075 / 1,000 文字 </td> </tr> </tbody> </table> <ul> <li>参考:<a href="https://cloud.google.com/vertex-ai/generative-ai/pricing?hl=ja#google_models">Vertex AI の料金</a></li> </ul> <p>なお、一般的な生成 AI 基盤モデルサービスでは入力データの量をトークンという単位で計測し、トークン単位での課金となります。一方の Gemini では、入力文字数や画像の枚数などで計測するため見積もりが容易なほか、特に日本語においてはトークン数での計測よりも安価になる傾向にあります。</p> <h2 id="Gemini-API">Gemini API</h2> <p><figure class="figure-image figure-image-fotolife" title="Gemini API では Google AI Studio 経由で Gemini を利用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241221/20241221144043.png" width="1014" height="294" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Gemini API では Google AI Studio 経由で Gemini を利用</figcaption></figure></p> <h3 id="Gemini-API-とは">Gemini API とは</h3> <p><strong>Gemini API</strong> は、個人利用や小規模デベロッパー向けの、API 経由で Gemini モデルを呼び出し可能なプロダクトです。Google Cloud とは独立しており、単一サービスとして提供されています。Gemini API は利用規約に従い、商用利用することができます。</p> <p>Gemini API は <strong>Google AI Studio</strong> という AI 開発用プラットフォーム経由で提供されており、Google Cloud の Generative AI on Vertex AI と同じく REST API やクライアントライブラリ経由で利用できます。</p> <p>Gemini API には無料枠があり、一定のレート制限のもと利用可能です。有償版は、リクエストや生成コンテンツのボリュームに基づいた従量課金です。</p> <p>Google Cloud とは独立しているため、認証は IAM ではなく、Google AI Studio から発行する API キーで行われます。</p> <ul> <li>参考 : <a href="https://ai.google.dev/gemini-api/docs?hl=ja">Gemini API を使ってみる</a></li> <li>参考 : <a href="https://ai.google.dev/aistudio">Google AI Studio</a></li> </ul> <h3 id="データの保護-1">データの保護</h3> <p>Gemini API を無料枠で利用する場合、入力したデータや生成されたコンテンツは、Google の<strong>サービス改善に利用されたり</strong>、<strong>人間のレビュワーに見られる</strong>可能性があります。Google はこれを利用規約に明記しており、機密情報や個人情報を送信しないことを求めています。</p> <p>Gemini API の有償版を利用する場合はデータが保護され、サービス改善に利用されたり、人間のレビュワーに見られることはありません。</p> <ul> <li>参考 : <a href="https://ai.google.dev/gemini-api/terms?hl=ja">Gemini API 追加利用規約</a></li> </ul> <h3 id="料金-2">料金</h3> <p>Google AI Studio 経由での Gemini API は、入力したデータと出力したデータのボリュームに応じた従量課金です。ただし、Google Cloud の Generative AI on Vertex AI で利用する場合とは異なる料金設定がされており、文字数や画像の枚数ではなく、トークン量に応じた課金です。</p> <ul> <li>参考 : <a href="https://ai.google.dev/pricing">料金モデル</a></li> </ul> <h1 id="導入すべき-Gemini-プロダクト">導入すべき Gemini プロダクト</h1> <h3 id="生成-AI-で社内業務を効率化したい場合">生成 AI で社内業務を効率化したい場合</h3> <p>社内業務を効率化したい場合は、<strong>Gemini アプリ</strong>ã‚„ <strong>Gemini for Google Workspace</strong> の導入を検討します。</p> <p>これらのプロダクトにより、以下のような効果が期待できます。</p> <ul> <li>個人やチームの生産性向上</li> <li>メール、ドキュメント作成、プレゼンテーション作成、情報収集などを効率化</li> </ul> <p>ほとんどの Google Workspace のエディションでは Gemini アプリがデータ保護付きで利用可能になっており、<a href="https://gemini.google.com/">gemini.google.com</a> にアクセスすることですぐに業務利用することができます。</p> <h3 id="自社データを効率的に検索したり生成-AI-に質問に答えさせたい場合">自社データを効率的に検索したり生成 AI に質問に答えさせたい場合</h3> <p>自社の大量のドキュメント類の中から必要なデータを効率的に検索したり、生成 AI に要約させたい場合、Google Cloud プロダクトの1つである<strong>Vertex AI Agent Builder</strong>(Vertex AI Search)を使います。</p> <p>蓄積された大量の自社データをもとに生成 AI にコンテンツを生成させたり、日本語での質問に答えさせたりすることもできます。</p> <h3 id="自社の新サービスに生成-AI-を組み込む場合">自社の新サービスに生成 AI を組み込む場合</h3> <p>自社のアプリに生成 AI を組み込んだり、顧客へ提供するサービスに生成 AI を活用したい場合、<strong>Generative AI on Vertex AI</strong> を使います。</p> <p>自社アプリから Vertex AI 経由で Gemini を呼び出し、プロンプトを入力して、生成結果を得ることができます。システム開発の知識さえあれば、機械学習の知識がなくとも、Vertex AI ã‚„ Vertex AI Agent Builder(Vertex AI Search)の API 呼び出しにより高品質な検索や RAG を実現することができます。</p> <h3 id="システム開発を効率化したい場合">システム開発を効率化したい場合</h3> <p>システム開発を効率化したい場合、コード生成補助機能などを備えた <strong>Gemini for Google Cloud</strong> を使います。</p> <p>Gemini for Google Cloud の1機能である Gemini Code Assist は、月額サブスクリプション制であり、高度な開発補助機能を備えています。</p> <h1 id="ビジネス導入における注意点">ビジネス導入における注意点</h1> <h2 id="生成-AI-のビジネス適用">生成 AI のビジネス適用</h2> <p>OpenAI 社が2022å¹´11月に生成 AI チャットボット Chat GPT を公開してから、瞬く間に生成 AI ブームが巻き起こりました。2023年には多くの企業が、生成 AI のビジネス利用を試みる PoC(Proof of Concept)を行い、2024年には実際に業務で利用する企業も増えました。</p> <p>生成 AI ブームに乗り遅れまいと、2025年も多くの企業が生成 AI の PoC や、業務への適用を試みるものと考えられます。しかし、生成 AI は銀の弾丸(万能薬)ではありません。以下に説明する性質を適切に理解し、ビジネスに適用することを検討してください。</p> <h2 id="生成-AI-は確率エンジンであることを理解する">生成 AI は確率エンジンであることを理解する</h2> <p>Gemini を含む生成 AI は、大量のデータから学習し、確率的に<strong>最もそれらしい回答を生成</strong>する「確率エンジン」です。そのため、完璧な回答を返すとは限りませんし、毎回同じコンテンツが生成されるとも限りません。</p> <p>この点を理解した上で、Gemini を活用する業務とそうでない業務を見極める必要があります。これを理解していないと、精度向上に報われない労力を注ぎ続けることになってしまいます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/transform/ja/prompt-probability-data-and-the-gen-ai-mindset">The Prompt: 確率、データ、そして生成 AI に向き合うマインドセットとは</a></li> </ul> <h2 id="生成-AI-に向いてる業務--向いてない業務">生成 AI に向いてる業務 / 向いてない業務</h2> <h3 id="向いている業務">向いている業務</h3> <p>前述の性質から、生成 AI は以下のような業務領域を得意としています。</p> <p><strong>創造的な作業</strong><br/> 新しいアイデアの創出、文章やコードの作成、デザイン、翻訳など</p> <p><strong>情報収集、分析</strong><br/> 大量のデータの要約、トレンド分析、レポート作成など</p> <p><strong>コミュニケーション</strong><br/> 高度な正確性が求められない顧客対応、社内コミュニケーション、教育など</p> <p><strong>反復的な作業</strong><br/> データ入力、議事録作成、単純な質問への回答など</p> <h3 id="向いていない業務">向いていない業務</h3> <p>反対に、以下のような業務には向いていません。</p> <p><strong>高度な判断や意思決定</strong><br/> 専門知識や倫理観が求められる業務</p> <p><strong>正確性が求められる業務</strong><br/> 医療診断、金融取引、法律相談など</p> <p><strong>セキュリティ上、高度にセンシティブな業務</strong><br/> 重要な個人情報や機密情報を含み、非常に高度なセキュリティ上の考慮が必要な業務</p> <h2 id="セキュリティ">セキュリティ</h2> <h3 id="データ保護-1">データ保護</h3> <p>生成 AI の業務利用では、入力したプロンプトや生成されたコンテンツが、生成 AI サービス提供事業者によってどう扱われるかに十分注意する必要があります。</p> <p>多くの場合、無償の生成 AI プロダクトでは、入出力データが事業者の<strong>サービス改善のために利用されます</strong>。これを防ぐには、有償版を購入し、<strong>オプトアウト</strong>と呼ばれる「事業者によって入出力データがサービス改善に用いられないようにする」オプションを有効化する必要があります。</p> <p>Gemini の場合、無償版の Gemini アプリや無償版の Gemini API(Google AI Studio)では、入出力データがサービス改善に利用されることが利用規約に明記されています。</p> <p>一方で、Gemini for Google Workspace ã‚„ Generative AI on Vertex AI(Google Cloud)では、入出力データにエンタープライズグレードのデータ保護が適用され、サービス改善などには利用されません。</p> <p>この点をよく理解し、利用規約などを確認してください。</p> <h3 id="生成-AI-アプリへの攻撃">生成 AI アプリへの攻撃</h3> <p>特に自社アプリに生成 AI を組み込んで一般ユーザー向けに公開する場合、生成 AI の脆弱性を突く攻撃手法である<strong>プロンプトインジェクション</strong>などに十分注意する必要があります。</p> <p>プロンプトインジェクションは、生成 AI に悪意を持って工夫したプロンプトを投入し、本来ユーザーがしるべきではない情報等を答えさせる手法です。これにより、機密情報やシステムの内部構造が漏洩するリスクがあります。</p> <p>「アプリケーション内部構造におけるデータのアクセス権限設計」「システム側プロンプトの工夫」「レスポンスへのフィルタ設定」「生成 AI が担当する機能範囲の調整」など、適切な対処を行うことでリスクを低減することができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai?hl=ja">Generative AI on Vertex AI - 安全に使用するためのベスト プラクティス</a></li> <li>参考 : <a href="https://ai.google.dev/gemini-api/docs/safety-guidance?hl=ja">Google AI Studio - 安全に関するガイダンス</a></li> </ul> <h3 id="不適切な生成コンテンツ">不適切な生成コンテンツ</h3> <p>生成 AI は確率論的な仕組みであるため、不適切な生成コンテンツが生成される可能性を否定できません。特に外部に公開する可能性のある生成 AI を組み込んだ自社アプリでは、政治、宗教、性的なコンテンツ、差別的な発言、ブランドイメージを毀損するようなコンテンツなどが生成されるリスクを低減する必要があります。</p> <p>システムプロンプトを工夫することでそういった生成を抑止したり、Gemini では<strong>安全フィルタ</strong>によってそのようなコンテンツが返答されることを防ぐことができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/safety-system-instructions?hl=ja">安全に関するシステム指示</a></li> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters?hl=ja">安全フィルタを構成する</a></li> </ul> <h1 id="導入事例">導入事例</h1> <p>業種や業態を問わず、様々な企業が Gemini を導入し、業務効率化や顧客満足度を向上しています。具体的な導入事例は、以下の記事を参考にしてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fg-gen.co.jp%2Fcase-studies%2F21%2F" title="名古屋鉄道株式会社様 - 導入事例 - 株式会社G-gen(ジージェン)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://g-gen.co.jp/case-studies/21/">g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fg-gen.co.jp%2Fcase-studies%2F22%2F" title="東洋建設株式会社様 - 導入事例 - 株式会社G-gen(ジージェン)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://g-gen.co.jp/case-studies/22/">g-gen.co.jp</a></cite></p> <p>G-gen 社の提供する「Generative AI 活用支援ソリューション」では、Google Cloud のスペシャリストエンジニアが、貴社の Gemini 活用を支援します。開発を内製化する場合と、外注する場合の両方で活用いただけます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fg-gen.co.jp%2Fservices%2Fgenerative-ai-utilization-support-solution.html" title="Google Cloud ではじめる Generative AI 活用支援ソリューション - 株式会社G-gen(ジージェン)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://g-gen.co.jp/services/generative-ai-utilization-support-solution.html">g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-s-yonekawa/20240109/20240109102504.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">米川 佐満人 <a href="https://blog.g-gen.co.jp/archive/author/ggen-s-yonekawa">(記事一覧)</a></p> <p class="sw-profile__txt">プラットフォームエンジニアリング本部 営業部 営業2課 å…¼ 粉もん事業所</p> <p class="sw-profile__txt">2022å¹´7月にG-genにジョイン。 <p class="sw-profile__txt">モットーは「クラウドで、関西を、もっと働きやすく」</p> <p class="sw-profile__txt">課題解決に向けた提案&お客様との伴走プロジェクトにモチベーションを感じる日々。現在 Google Cloud 全資格コンプリート目指して奮闘中(あと1つ)。ですが、本職は 光の戦士@FFXIV です。</p> </div> </div> </div> </div> ggen-s-yonekawa Microsoft TeamsからGoogle Chatへのデータ移行を検証してみた hatenablog://entry/6802418398313085167 2024-12-23T09:00:00+09:00 2024-12-23T09:32:03+09:00 G-gen の三浦です。当記事では2024å¹´12月17日にベータ版として公開された Microsoft Teams から Google Chat への移行ツールの検証結果をご紹介します。 概要 Microsoft Teams からのデータ移行 とは 前提条件 制約 検証概要 検証環境 検証の流れ 設定手順 [Microsoft 365] Teams のグループ ID を確認 [Google Workspace] 移行用の csv ファイルの準備 [Google Workspace] データ移行の実施 [Microsoft 365] 移行後に、Teams で新規のメッセージを送信 [Google… <p>G-gen の三浦です。当記事では2024å¹´12月17日にベータ版として公開された Microsoft Teams から Google Chat への移行ツールの検証結果をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a><ul> <li><a href="#Microsoft-Teams-からのデータ移行-とは">Microsoft Teams からのデータ移行 とは</a></li> <li><a href="#前提条件">前提条件</a></li> <li><a href="#制約">制約</a></li> </ul> </li> <li><a href="#検証概要">検証概要</a><ul> <li><a href="#検証環境">検証環境</a></li> <li><a href="#検証の流れ">検証の流れ</a></li> </ul> </li> <li><a href="#設定手順">設定手順</a><ul> <li><a href="#Microsoft-365-Teams-のグループ-ID-を確認">[Microsoft 365] Teams のグループ ID を確認</a></li> <li><a href="#Google-Workspace-移行用の-csv-ファイルの準備">[Google Workspace] 移行用の csv ファイルの準備</a></li> <li><a href="#Google-Workspace-データ移行の実施">[Google Workspace] データ移行の実施</a></li> <li><a href="#Microsoft-365-移行後にTeams-で新規のメッセージを送信">[Microsoft 365] 移行後に、Teams で新規のメッセージを送信</a></li> <li><a href="#Google-Workspace-差分移行の実施">[Google Workspace] 差分移行の実施</a></li> <li><a href="#Google-Workspace-移行を完了しスペースを展開">[Google Workspace] 移行を完了し、スペースを展開</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223093150.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <h2 id="Microsoft-Teams-からのデータ移行-とは">Microsoft Teams からのデータ移行 とは</h2> <p><strong>Teams からのメッセージの移行</strong>機能は、Google Workspace の管理機能であり、Microsoft Teams(以下、Teams)のチャンネルのメッセージを Google Chat(以下、Chat)のスペースに移行することができます。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15010566?hl=ja">Teams からのメッセージの移行について(ベータ版)</a></li> </ul> <h2 id="前提条件">前提条件</h2> <p>2024å¹´12月現在、本機能はベータ版であり、正式リリースされていません。ベータ版機能の<strong>本番環境での利用は非推奨</strong>のため、テストや検証で使用してください。詳細は以下の利用規約をご確認ください。</p> <ul> <li>参考 : <a href="https://workspace.google.com/terms/service-terms">Google Workspace サービス固有の利用規約</a></li> </ul> <p>当機能で移行を実施するには、Google Workspace 側では特権管理者ロールが、Teams 側ではグローバル管理者ロールが必要です。</p> <h2 id="制約">制約</h2> <p>当機能には以下のような制限があります。</p> <ul> <li>チーム内のメッセージのみ移行可能です。ユーザー間の個別チャットやダイレクトメッセージは移行できません。</li> <li>Teams の「チーム」は Chat の「スペース」に変換されます。ただし、元の権限(標準、プライベート)はすべて制限付きスペースに変換されます。制限付きスペースは後から変更可能です。</li> </ul> <p>移行に関する制限の詳細は、以下公式ドキュメントをご確認ください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15011136?hl=ja">チャットの移行で移行されるデータ(ベータ版)</a></li> </ul> <h1 id="検証概要">検証概要</h1> <h2 id="検証環境">検証環境</h2> <p>検証環境は以下のとおりです。実際の移行ケースを想定し、Teams と Chat のドメインおよびユーザー情報を統一した環境で検証しました。</p> <table> <thead> <tr> <th> プラットフォーム </th> <th> ドメイン名 </th> <th> ユーザー名 </th> <th> ライセンス </th> </tr> </thead> <tbody> <tr> <td> Google Workspace </td> <td> miurak-test.com </td> <td> [email protected] </td> <td> Google Workspace Business Standard </td> </tr> <tr> <td> Microsoft 365 </td> <td> miurak-test.com </td> <td> [email protected] </td> <td> Microsoft 365 Business Basic </td> </tr> </tbody> </table> <p>Teams のチームは以下のとおりです。</p> <table> <thead> <tr> <th> 親ディレクトリ </th> <th> チーム名 </th> <th> 種類 </th> <th> 所有者 </th> </tr> </thead> <tbody> <tr> <td> 既定のディレクトリ </td> <td> 一般 </td> <td> 標準 </td> <td> [email protected] </td> </tr> <tr> <td> 既定のディレクトリ </td> <td> teams-channel-private </td> <td> プライベート </td> <td> [email protected] </td> </tr> <tr> <td> 既定のディレクトリ </td> <td> teams-channel-public </td> <td> 標準 </td> <td> [email protected] </td> </tr> </tbody> </table> <p><figure class="figure-image figure-image-fotolife" title="チーム設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090005.png" width="566" height="469" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>チーム設定</figcaption></figure></p> <h2 id="検証の流れ">検証の流れ</h2> <p>以下の手順でデータの移行を実施します。</p> <table> <thead> <tr> <th> é …ç›® </th> <th> 作業 </th> <th> プラットフォーム </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> Teams のグループ ID を確認 </td> <td> Microsoft 365 </td> </tr> <tr> <td> 2 </td> <td> 移行用の csv ファイルの準備 </td> <td> Google Workspace </td> </tr> <tr> <td> 3 </td> <td> データ移行の実施 </td> <td> Google Workspace </td> </tr> <tr> <td> 4 </td> <td> 移行後に、Teams で新規のメッセージを送信 </td> <td> Microsoft 365 </td> </tr> <tr> <td> 5 </td> <td> 差分移行の実施 </td> <td> Google Workspace </td> </tr> <tr> <td> 6 </td> <td> 移行を完了し、スペースを展開 </td> <td> Google Workspace </td> </tr> </tbody> </table> <h1 id="設定手順">設定手順</h1> <h2 id="Microsoft-365-Teams-のグループ-ID-を確認">[Microsoft 365] Teams のグループ ID を確認</h2> <p>Microsoft Teams 管理センター(<a href="https://admin.teams.microsoft.com">https://admin.teams.microsoft.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://learn.microsoft.com/ja-jp/microsoftteams/manage-teams-in-modern-portal">Microsoft Teams 管理センターでチームを管理する</a></li> </ul> <p>[Teams] > [チームを管理] > [エクスポート] から、チーム情報を csv 形式でエクスポートします。</p> <p><figure class="figure-image figure-image-fotolife" title="エクスポートを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090011.png" width="800" height="232" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エクスポートを選択</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="エクスポートを実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090014.png" width="375" height="144" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エクスポートを実行</figcaption></figure></p> <p>csv を確認し、<strong>Groups Id</strong> を控えてください。 <figure class="figure-image figure-image-fotolife" title="グループIDの確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090017.png" width="800" height="243" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>グループIDの確認</figcaption></figure></p> <h2 id="Google-Workspace-移行用の-csv-ファイルの準備">[Google Workspace] 移行用の csv ファイルの準備</h2> <p>Google Workspace の管理コンソール(<a href="https://admin.google.com">https://admin.google.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/182076?hl=ja">管理コンソールにログインする</a></li> </ul> <p>[データ] > [データのインポートとエクスポート] > [データ移行(新規)] へ移動し、ステップ 2 の [サンプル csv をダウンロード] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="移行用のサンプル csv のダウンロード"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090020.png" width="701" height="539" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>移行用のサンプル csv のダウンロード</figcaption></figure></p> <p>ダウンロードした csv を開き、<strong>Source MicrosoftTeamsID</strong> の箇所に、前の手順で確認した Teams の <strong>Groups Id</strong> を入力し、保存します。 <figure class="figure-image figure-image-fotolife" title="移行用の csv ファイルの編集"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090024.png" width="453" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>移行用の csv ファイルの編集</figcaption></figure></p> <h2 id="Google-Workspace-データ移行の実施">[Google Workspace] データ移行の実施</h2> <p>チャットの移行の [ステップ 1] で [Microsoft アカウントに接続] を選択し、移行ツールに権限を付与します。</p> <p><figure class="figure-image figure-image-fotolife" title="Microsoftアカウントに接続"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090026.png" width="511" height="246" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Microsoftアカウントに接続</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="Microsoftアカウントを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090029.png" width="315" height="277" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Microsoftアカウントを選択</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="アクセス許可内容を確認して承諾"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090032.png" width="326" height="531" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセス許可内容を確認して承諾</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="接続を確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090035.png" width="468" height="194" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>接続を確認</figcaption></figure></p> <p>[ステップ 2] の [移行マップの csv をアップロード] を選択し、前の手順で作成した csv ファイルを選択します。 <figure class="figure-image figure-image-fotolife" title="作成したcsvのアップロード"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090038.png" width="620" height="505" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>作成したcsvのアップロード</figcaption></figure></p> <p>[ステップ 3] は、Teams と Chat のユーザー名が異なる場合のみ実施します。今回は同じため、省略します。</p> <p>例: Microsoft 365 のユーザーが <code>[email protected]</code> で、このユーザーを Google Workspace の <code>[email protected]</code> に関連付けたい場合に実施します。</p> <p><figure class="figure-image figure-image-fotolife" title="ユーザーIDの関連付け"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090049.png" width="800" height="236" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ユーザーIDの関連付け</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15009958?hl=ja#zippy=%2C%E3%82%B9%E3%83%86%E3%83%83%E3%83%97-id-%E3%83%9E%E3%83%83%E3%83%97%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%81%A6%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%AA%E5%A0%B4%E5%90%88">ステップ 4: ID マップを作成してアップロードする(必要な場合)</a></li> </ul> <p>[ステップ 4] で以下を選択して [保存] してください。</p> <ul> <li>メッセージの移行開始日:Teams のメッセージを Chat へ移行する開始日を選択します。</li> <li>マッピングされていない ID:有効化 <ul> <li>ID の移行元ドメインを保持する:Teams と Chat のドメインが同じ場合はこちらを選択</li> <li>ID にターゲットドメインを使用する:Teams と Chat のドメインが異なる場合はこちらを選択</li> </ul> </li> </ul> <p><figure class="figure-image figure-image-fotolife" title="移行設定の選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090041.png" width="800" height="406" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>移行設定の選択</figcaption></figure></p> <p>[ステップ 5] の [移行を開始] を選択します。 <figure class="figure-image figure-image-fotolife" title="移行の開始"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090045.png" width="800" height="186" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>移行の開始</figcaption></figure></p> <p>移行が完了したことを確認します。詳細は [移行レポート] または [概要レポート] をエクスポートすることで確認できます。</p> <p>※ この時点では、ユーザー側に移行したスペースは表示されません。差分を含めた移行作業をすべて完了し、最後に [スペースをロールアウト] することで表示されます。</p> <p><figure class="figure-image figure-image-fotolife" title="移行完了確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090052.png" width="800" height="386" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>移行完了確認</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="概要レポート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090055.png" width="800" height="161" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>概要レポート</figcaption></figure></p> <h2 id="Microsoft-365-移行後にTeams-で新規のメッセージを送信">[Microsoft 365] 移行後に、Teams で新規のメッセージを送信</h2> <p>データの移行後に Teams のチャンネルで新規のメッセージを送信します。 <figure class="figure-image figure-image-fotolife" title="差分検知用のメッセージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090058.png" width="662" height="385" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>差分検知用のメッセージ</figcaption></figure></p> <h2 id="Google-Workspace-差分移行の実施">[Google Workspace] 差分移行の実施</h2> <p>チャットの移行から [ステップ 5] の [差分移行を実行] を選択します。 <figure class="figure-image figure-image-fotolife" title="差分移行を実施"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090102.png" width="800" height="101" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>差分移行を実施</figcaption></figure></p> <p>処理が成功したことを確認します。 <figure class="figure-image figure-image-fotolife" title="差分移行の成功"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090114.png" width="800" height="328" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>差分移行の成功</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="概要レポート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090117.png" width="800" height="162" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>概要レポート</figcaption></figure></p> <h2 id="Google-Workspace-移行を完了しスペースを展開">[Google Workspace] 移行を完了し、スペースを展開</h2> <p>すべての移行が完了したら、[スペースをロールアウト] を選択します。 <figure class="figure-image figure-image-fotolife" title="スペースのロールアウトを実施"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090105.png" width="800" height="126" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スペースのロールアウトを実施</figcaption></figure></p> <p>注意事項を確認したうえで、[スペースをロールアウト] を実行します。この操作を行うと、Teams 側で新たに追加されたメッセージや変更内容は Chat に移行されません。事前に十分に確認したうえで進めてください。</p> <p>注意事項を確認したうえで、[スペースをロールアウト] を実行します。特に以下の点にご注意ください</p> <ul> <li>この操作は、移行開始から30日以内に完了する必要があります。 <ul> <li>30日を過ぎると、移行を最初からやり直す必要があります。</li> </ul> </li> <li>ロールアウト後、Teams 側でのメッセージや変更は再移行できません。</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="注意事項の確認とロールアウト実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090108.png" width="533" height="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>注意事項の確認とロールアウト実行</figcaption></figure></p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15064266?hl=ja&amp;ref_topic=15316138&amp;sjid=8562262943053197089-AP#zippy=%2C%E6%89%8B%E9%A0%86-%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%A8%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">手順 3: ユーザーがスペースとメッセージを利用できるようにする</a></li> </ul> <p>スペースの公開が完了したことを確認します。 <figure class="figure-image figure-image-fotolife" title="公開確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090111.png" width="800" height="128" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>公開確認</figcaption></figure></p> <p>Chat を確認し、差分用のメッセージも含めて移行できることを確認します。 <figure class="figure-image figure-image-fotolife" title="Google Chat でのデータ移行確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223090120.png" width="800" height="461" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Google Chat でのデータ移行確認</figcaption></figure></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak インベントリレポートを使ったGoogle ドライブのセキュリティリスク管理 hatenablog://entry/6802418398306504165 2024-12-20T09:00:00+09:00 2024-12-23T09:33:30+09:00 G-gen の三浦です。当記事では、Google ドライブのインベントリレポート機能を使ったセキュリティリスクの管理方法を紹介します。 概要 ドライブインベントリとは 前提条件 設定の概要 設定手順 [Google Cloud] BigQuery データセットの作成 [Google Workspace] インベントリレポートの有効化 [Google Cloud] レポートデータの確認 データ抽出例 サンプルクエリ①:アクセス権が「リンクを知っているインターネット上の誰もがアクセスできる」ファイルの抽出 サンプルクエリ②:特定のユーザーがオーナーとなっているファイルを抽出(マイドライブも含む) … <p>G-gen の三浦です。当記事では、Google ドライブのインベントリレポート機能を使ったセキュリティリスクの管理方法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a><ul> <li><a href="#ドライブインベントリとは">ドライブインベントリとは</a></li> <li><a href="#前提条件">前提条件</a></li> </ul> </li> <li><a href="#設定の概要">設定の概要</a></li> <li><a href="#設定手順">設定手順</a><ul> <li><a href="#Google-Cloud-BigQuery-データセットの作成">[Google Cloud] BigQuery データセットの作成</a></li> <li><a href="#Google-Workspace-インベントリレポートの有効化">[Google Workspace] インベントリレポートの有効化</a></li> <li><a href="#Google-Cloud-レポートデータの確認">[Google Cloud] レポートデータの確認</a></li> </ul> </li> <li><a href="#データ抽出例">データ抽出例</a><ul> <li><a href="#サンプルクエリアクセス権がリンクを知っているインターネット上の誰もがアクセスできるファイルの抽出">サンプルクエリ①:アクセス権が「リンクを知っているインターネット上の誰もがアクセスできる」ファイルの抽出</a></li> <li><a href="#サンプルクエリ特定のユーザーがオーナーとなっているファイルを抽出マイドライブも含む">サンプルクエリ②:特定のユーザーがオーナーとなっているファイルを抽出(マイドライブも含む)</a></li> <li><a href="#サンプルクエリ組織外のドメインと共有されているファイルを抽出">サンプルクエリ③:組織外のドメインと共有されているファイルを抽出</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241223/20241223093312.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <h2 id="ドライブインベントリとは">ドライブインベントリとは</h2> <p>Google ドライブの<strong>インベントリレポート機能</strong>は、組織内の Google ドライブや共有ドライブの利用状況を把握し、管理者がデータを監査・管理するための機能です。</p> <p>この機能を使えば、ドライブ内のファイル情報やアクセス権、更新日時を週次で BigQuery にエクスポートできます。これにより、<strong>データ漏洩リスクの軽減</strong>ã‚„<strong>利用状況の可視化</strong>が可能です。</p> <ul> <li>参考 : <a href="https://support.google.com/a/topic/15486865?hl=ja">BigQuery のドライブ インベントリ</a></li> <li>参考 : <a href="https://support.google.com/a/answer/15485686?hl=ja#zippy=%2C%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%81%A8%E5%AE%9A%E7%BE%A9">ドライブのインベントリのエクスポート スキーマ</a></li> </ul> <h2 id="前提条件">前提条件</h2> <p>ドライブインベントリレポート機能は、以下の Google Workspace エディションで使用できます。</p> <ul> <li>Enterprise Standard</li> <li>Enterprise Plus</li> <li>Education Standard</li> <li>Education Plus</li> <li>Enterprise Essentials Plus</li> <li>Cloud Identity Premium</li> </ul> <p>詳細は以下のドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15141054?hl=ja">組織のドライブのインベントリをエクスポートする</a></li> </ul> <h1 id="設定の概要">設定の概要</h1> <p>以下の手順でインベントリレポートを設定し、動作を確認します。</p> <table> <thead> <tr> <th> 順番 </th> <th> 作業場所 </th> <th> 作業名 </th> <th> 内容 </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> Google Cloud </td> <td> BigQuery データセット作成 </td> <td> インベントリレポートをエクスポートする BigQuery データセットを作成します。 </td> </tr> <tr> <td> 2 </td> <td> Google Workspace </td> <td> インベントリレポートの有効化 </td> <td> インベントリレポートを有効化します。 </td> </tr> <tr> <td> 3 </td> <td> Google Cloud </td> <td> レポートデータの確認 </td> <td> BigQuery にエクスポートされたデータを確認します。 </td> </tr> </tbody> </table> <h1 id="設定手順">設定手順</h1> <h2 id="Google-Cloud-BigQuery-データセットの作成">[Google Cloud] BigQuery データセットの作成</h2> <p>BigQuery のデータセットを作成します。Google Workspace で<strong>データ リージョン ポリシー</strong>を使用している場合は、BigQuery のリージョンをポリシーで指定したリージョンと同一にします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/14310028?hl=ja">データの地理的な場所を選択する</a></li> </ul> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 環境変数を設定</span> <span class="synIdentifier">PROJECT_ID</span>=<span class="synStatement">&quot;</span><span class="synConstant">my_project</span><span class="synStatement">&quot;</span> <span class="synComment"># Google Cloud プロジェクト ID を設定</span> <span class="synIdentifier">BQ_DATASET</span>=<span class="synStatement">&quot;</span><span class="synConstant">my_dataset</span><span class="synStatement">&quot;</span> <span class="synComment"># BigQuery のデータセット名を設定</span> <span class="synIdentifier">BQ_LOCATION</span>=<span class="synStatement">&quot;</span><span class="synConstant">US</span><span class="synStatement">&quot;</span> <span class="synComment"># BigQuery のリージョンを設定</span> <span class="synIdentifier">GWS_USER</span>=<span class="synStatement">&quot;</span><span class="synConstant">[email protected]</span><span class="synStatement">&quot;</span> <span class="synComment"># Google Workspace 管理者アカウントを設定</span>   <span class="synComment"># BigQuery データセットを作成</span> bq <span class="synSpecial">--project_id</span><span class="synStatement">=</span><span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> mk <span class="synSpecial">--location</span><span class="synStatement">=</span><span class="synPreProc">$BQ_LOCATION</span> <span class="synStatement">\</span> <span class="synPreProc">$BQ_DATASET</span>   <span class="synComment"># Google Workspace 管理者アカウントに BigQuery の編集権限を付与</span> gcloud projects add-iam-policy-binding <span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">user:</span><span class="synPreProc">$GWS_USER</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synConstant">roles/bigquery.dataEditor</span><span class="synStatement">&quot;</span>   <span class="synComment"># Google Workspace 管理者アカウントに IAM の管理権限を付与</span> gcloud projects add-iam-policy-binding <span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">user:</span><span class="synPreProc">$GWS_USER</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synConstant">roles/resourcemanager.projectIamAdmin</span><span class="synStatement">&quot;</span> </pre> <h2 id="Google-Workspace-インベントリレポートの有効化">[Google Workspace] インベントリレポートの有効化</h2> <p>Google Workspace の管理コンソール(<a href="https://admin.google.com">https://admin.google.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/182076?hl=ja">管理コンソールにログインする</a></li> </ul> <p>[レポート] > [データ統合] へ移動し、[ドライブのインベントリのエクスポート] を選択します。 <figure class="figure-image figure-image-fotolife" title="データ統合"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090012.png" width="800" height="416" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>データ統合</figcaption></figure></p> <p>以下を設定し、[保存] を選択します。</p> <ul> <li>ドライブのインベントリ レポートの Google BigQuery へのエクスポートを有効にする:<code>有効化</code></li> <li>BigQuery のプロジェクト ID:<code>プロジェクト ID</code></li> <li>プロジェクト内の既存のデータセット:<code>データセット名</code></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="エクスポート先の設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090015.png" width="800" height="281" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エクスポート先の設定</figcaption></figure></p> <p>エクスポートの有効化から初回のエクスポートまでは1~2週間かかります。2回目以降は週次でデータが更新されます。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15141054?hl=ja">組織のドライブのインベントリをエクスポートする</a></li> </ul> <p>エクスポートが完了しない場合、管理者のログイベントを確認し、エラーの有無をご確認ください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15141054?hl=ja&amp;ref_topic=15486865#zippy=%2C%E3%83%89%E3%83%A9%E3%82%A4%E3%83%96%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%99%E3%83%B3%E3%83%88%E3%83%AA%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AB%E9%96%A2%E9%80%A3%E3%81%99%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88">ドライブのインベントリのエクスポートに関連するイベント</a></li> </ul> <h2 id="Google-Cloud-レポートデータの確認">[Google Cloud] レポートデータの確認</h2> <p>Google Cloud コンソールからログインし、検索バーに<code>BigQuery</code>と入力し、[BigQuery] を選択します。 <figure class="figure-image figure-image-fotolife" title="BigQuery検索"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090018.png" width="711" height="181" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>BigQuery検索</figcaption></figure></p> <p>データセットアイコンを選択し、<code>[inventory]</code> という名前のテーブルが表示されることを確認します。 <figure class="figure-image figure-image-fotolife" title="インベントリの確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090022.png" width="514" height="389" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>インベントリの確認</figcaption></figure></p> <h1 id="データ抽出例">データ抽出例</h1> <h2 id="サンプルクエリアクセス権がリンクを知っているインターネット上の誰もがアクセスできるファイルの抽出"><strong>サンプルクエリ①</strong>:アクセス権が「リンクを知っているインターネット上の誰もがアクセスできる」ファイルの抽出</h2> <ul> <li><strong>想定ユースケース</strong> <ul> <li>誤って外部共有されているファイルの検出</li> <li>データ漏洩リスクの高いファイルの特定</li> <li>監査対応のための外部共有ファイルの一覧作成</li> </ul> </li> </ul> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> id <span class="synSpecial">AS</span> file_id, <span class="synIdentifier">CONCAT</span>(<span class="synSpecial">'</span><span class="synConstant">https://drive.google.com/file/d/</span><span class="synSpecial">'</span>, id, <span class="synSpecial">'</span><span class="synConstant">/view</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> file_url, <span class="synComment">-- ファイルのURLを生成</span> title <span class="synSpecial">AS</span> file_name, <span class="synComment">-- ファイル名</span> owner.<span class="synIdentifier">user</span>.email <span class="synSpecial">AS</span> owner_email, <span class="synComment">-- オーナーのメールアドレス</span> perm.email <span class="synSpecial">AS</span> shared_with_email, <span class="synComment">-- 共有相手のメールアドレス(anyone の場合は null)</span> perm.role <span class="synSpecial">AS</span> shared_role, <span class="synComment">-- 共有役割(anyone の場合は null)</span> perm.<span class="synSpecial">type</span> <span class="synSpecial">AS</span> shared_type <span class="synComment">-- 共有タイプ</span> <span class="synSpecial">FROM</span> `my_project.my_dataset.inventory`, <span class="synComment">-- データセットを指定</span> UNNEST(<span class="synSpecial">access</span>.permissions) <span class="synSpecial">AS</span> perm <span class="synComment">-- permissions を展開</span> <span class="synSpecial">WHERE</span> perm.<span class="synSpecial">type</span> = <span class="synSpecial">'</span><span class="synConstant">ANYONE</span><span class="synSpecial">'</span> <span class="synComment">-- 共有タイプが anyone のファイルを抽出</span> <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> id; <span class="synComment">-- file_id でソート</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="実行結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090025.png" width="800" height="257" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>実行結果</figcaption></figure></p> <h2 id="サンプルクエリ特定のユーザーがオーナーとなっているファイルを抽出マイドライブも含む"><strong>サンプルクエリ②</strong>:特定のユーザーがオーナーとなっているファイルを抽出(マイドライブも含む)</h2> <ul> <li><strong>想定ユースケース</strong> <ul> <li>退職者のデータ整理と引継ぎ</li> <li>特定ユーザーのファイルアクセス状況の確認</li> <li>重要データのユーザー単位での管理</li> </ul> </li> </ul> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> child.id <span class="synSpecial">AS</span> file_id, <span class="synComment">-- ファイルID</span> child.title <span class="synSpecial">AS</span> file_name, <span class="synComment">-- ファイル名</span> child.owner.<span class="synIdentifier">user</span>.email <span class="synSpecial">AS</span> owner_email, <span class="synComment">-- オーナーのメールアドレス</span> child.org_unit_path <span class="synSpecial">AS</span> org_unit, <span class="synComment">-- 所属組織単位</span> parent.title <span class="synSpecial">AS</span> parent_folder_name, <span class="synComment">-- 親フォルダ名</span> child.trashed <span class="synSpecial">AS</span> is_trashed, <span class="synComment">-- ゴミ箱に入っているか (true:ゴミ箱入り)</span> child.mime_type, <span class="synComment">-- MIMEタイプ</span> child.size_bytes / (<span class="synConstant">1024</span> * <span class="synConstant">1024</span>) <span class="synSpecial">AS</span> file_size_mb, <span class="synComment">-- ファイルサイズ(MB)</span> child.create_time_micros <span class="synSpecial">AS</span> created_time, <span class="synComment">-- 作成日時(マイクロ秒)</span> child.last_modified_time_micros <span class="synSpecial">AS</span> last_modified_time <span class="synComment">-- 最終更新日時(マイクロ秒)</span> <span class="synSpecial">FROM</span> `my_project.my_dataset.inventory` <span class="synSpecial">AS</span> child <span class="synComment">-- データセットを指定</span> <span class="synSpecial">LEFT</span> <span class="synSpecial">JOIN</span> `my_project.my_dataset.inventory` <span class="synSpecial">AS</span> parent <span class="synComment">-- 親フォルダ情報を取得するために自己結合</span> <span class="synSpecial">ON</span> child.parent = parent.id <span class="synComment">-- 親フォルダのIDで結合</span> <span class="synSpecial">WHERE</span> child.owner.<span class="synIdentifier">user</span>.email = <span class="synSpecial">'</span><span class="synConstant">[email protected]</span><span class="synSpecial">'</span> <span class="synComment">-- 抽出したいユーザーのメールアドレスを指定</span> <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> child.last_modified_time_micros <span class="synSpecial">DESC</span>; <span class="synComment">-- 最終更新日時で降順ソート</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="実行結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090028.png" width="800" height="285" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>実行結果</figcaption></figure></p> <h2 id="サンプルクエリ組織外のドメインと共有されているファイルを抽出"><strong>サンプルクエリ③</strong>:組織外のドメインと共有されているファイルを抽出</h2> <ul> <li><strong>想定ユースケース</strong> <ul> <li>組織外とのファイル共有状況の把握</li> <li>ファイル共有ポリシー違反の検出</li> <li>外部ドメインとのデータ共有範囲の監視</li> </ul> </li> </ul> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> id <span class="synSpecial">AS</span> file_id, <span class="synComment">-- ファイルID</span> title <span class="synSpecial">AS</span> file_name, <span class="synComment">-- ファイル名</span> owner.<span class="synIdentifier">user</span>.email <span class="synSpecial">AS</span> owner_email, <span class="synComment">-- オーナーのメールアドレス</span> perm.email <span class="synSpecial">AS</span> shared_with_email, <span class="synComment">-- 共有相手のメールアドレス</span> perm.domain <span class="synSpecial">AS</span> shared_with_domain, <span class="synComment">-- 共有相手のドメイン</span> perm.role <span class="synSpecial">AS</span> shared_role <span class="synComment">-- 共有役割</span> <span class="synSpecial">FROM</span> `my_project.my_dataset.inventory`, <span class="synComment">-- データセットを指定</span> UNNEST(<span class="synSpecial">access</span>.permissions) <span class="synSpecial">AS</span> perm <span class="synComment">-- permissions を展開</span> <span class="synSpecial">WHERE</span> perm.domain <span class="synStatement">NOT</span> <span class="synStatement">IN</span> (<span class="synSpecial">'</span><span class="synConstant">example.com</span><span class="synSpecial">'</span>) <span class="synComment">-- 自社ドメイン以外と共有されているファイルを抽出</span> <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> shared_with_domain, file_name; <span class="synComment">-- ドメイン、ファイル名でソート</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="実行結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241220/20241220090031.png" width="800" height="317" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>実行結果</figcaption></figure></p> <p>上記以外にも公式ドキュメントにサンプルクエリがありますので、ご確認ください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/15485686?hl=ja#findable&amp;label&amp;storage&amp;largest&amp;cse&amp;sharing">クエリの例</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak 新しく追加されたCloud Run実行用の事前定義ロールを解説 hatenablog://entry/6802418398312677366 2024-12-19T09:00:00+09:00 2024-12-19T09:05:20+09:00 2024å¹´12月17日より、Cloud Run を呼び出すための権限を持つ3つの事前定義ロールが新たに利用可能となりました。当記事ではロールの詳細や、従来から利用されてきた事前定義ロールとの違いなどを解説します。 はじめに 新たな事前定義ロール Cloud Run サービス起動元 Cloud Run ジョブ エグゼキュータ オーバーライドを使用する Cloud Run ジョブ エグゼキュータ Cloud Run 起動元ロールとの比較 権限内容の比較 Cloud Run jobs のキャンセル、オーバーライドに関して 参考リンク はじめに 2024å¹´12月17日より、Cloud Run を呼び出… <p>2024å¹´12月17日より、Cloud Run を呼び出すための権限を持つ3つの事前定義ロールが新たに利用可能となりました。当記事ではロールの詳細や、従来から利用されてきた事前定義ロールとの違いなどを解説します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#新たな事前定義ロール">新たな事前定義ロール</a><ul> <li><a href="#Cloud-Run-サービス起動元">Cloud Run サービス起動元</a></li> <li><a href="#Cloud-Run-ジョブ-エグゼキュータ">Cloud Run ジョブ エグゼキュータ</a></li> <li><a href="#オーバーライドを使用する-Cloud-Run-ジョブ-エグゼキュータ">オーバーライドを使用する Cloud Run ジョブ エグゼキュータ</a></li> </ul> </li> <li><a href="#Cloud-Run-起動元ロールとの比較">Cloud Run 起動元ロールとの比較</a><ul> <li><a href="#権限内容の比較">権限内容の比較</a></li> <li><a href="#Cloud-Run-jobs-のキャンセルオーバーライドに関して">Cloud Run jobs のキャンセル、オーバーライドに関して</a></li> </ul> </li> <li><a href="#参考リンク">参考リンク</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241219/20241219090521.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>2024å¹´12月17日より、Cloud Run を呼び出すための権限を持つ以下の3つの事前定義ロールが新たに利用可能となりました。</p> <ol> <li>Cloud Run サービス起動元(<code>roles/run.servicesInvoker</code>)</li> <li>Cloud Run ジョブ エグゼキュータ(<code>roles/run.jobsExecutor</code>)</li> <li>オーバーライドを使用する Cloud Run ジョブ エグゼキュータ(<code>roles/run.jobsExecutorWithOverrides</code>)</li> </ol> <p>当記事ではロールの詳細や、従来から利用されてきた事前定義ロールとの違いなどを解説します。</p> <h1 id="新たな事前定義ロール">新たな事前定義ロール</h1> <h2 id="Cloud-Run-サービス起動元">Cloud Run サービス起動元</h2> <p><strong>Cloud Run サービス起動元</strong>(<code>roles/run.servicesInvoker</code>、英名 Cloud Run Service Invoker)ロールは以下の権限のみが付与された事前定義ロールで、Cloud Run services のサービス呼び出し、および Cloud Run functions の関数呼び出しを可能にします。このロールを持っていても Cloud Run jobs のジョブ実行はできません。</p> <ul> <li>run.routes.invoke</li> </ul> <h2 id="Cloud-Run-ジョブ-エグゼキュータ">Cloud Run ジョブ エグゼキュータ</h2> <p><strong>Cloud Run ジョブ エグゼキュータ</strong>(<code>roles/run.jobsExecutor</code>、英名 Cloud Run Jobs Executor)ロールは以下の2つの権限が付与された事前定義ロールで、Cloud Run jobs のジョブ実行とジョブのキャンセルを可能にします。このロールを持っていても、Cloud Run services のサービス呼び出し、および Cloud Run functions の関数呼び出しはできません。</p> <ul> <li>run.jobs.run</li> <li>run.executions.cancel</li> </ul> <h2 id="オーバーライドを使用する-Cloud-Run-ジョブ-エグゼキュータ">オーバーライドを使用する Cloud Run ジョブ エグゼキュータ</h2> <p><strong>オーバーライドを使用する Cloud Run ジョブ エグゼキュータ</strong>(<code>roles/run.jobsExecutorWithOverrides</code>、英名 Cloud Run Jobs Executor With Overrides)ロールは以下の3つの権限が付与された事前定義ロールで、Cloud Run jobs のジョブ実行、ジョブのキャンセルのほか、<strong>ジョブ構成をオーバーライドしたジョブの実行</strong>が可能です。</p> <ul> <li>run.jobs.run</li> <li>run.executions.cancel</li> <li>run.jobs.runWithOverrides</li> </ul> <p>ジョブ構成をオーバーライドした実行の詳細については以下の記事で解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloudrun-jobs-override-job-configuration" title="Cloud Run jobsでジョブ構成をオーバーライドしてジョブを実行する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloudrun-jobs-override-job-configuration">blog.g-gen.co.jp</a></cite></p> <h1 id="Cloud-Run-起動元ロールとの比較">Cloud Run 起動元ロールとの比較</h1> <h2 id="権限内容の比較">権限内容の比較</h2> <p>従来、Cloud Run のサービス・関数・ジョブの実行については、<strong>Cloud Run èµ·å‹•å…ƒ</strong>(<code>roles/run.invoker</code>)ロールの使用が推奨されていました。この事前定義ロールには、以下の2つの権限が付与されています。これらの権限により、サービス・関数・ジョブのいずれも実行することができます。</p> <ul> <li>run.routes.invoke</li> <li>run.jobs.run</li> </ul> <p>新しい事前定義ロール、特に「Cloud Run サービス起動元」と「Cloud Run ジョブ エグゼキュータ」の2つは、従来からある Cloud Run èµ·å‹•å…ƒ ロールの役割を分割したような形になっています。これにより、「サービス・関数の呼び出しのみができるプリンシパル」と「ジョブの実行のみができるプリンシパル」といった<strong>最小権限の原則</strong>を意識した権限管理ができるようになります。</p> <h2 id="Cloud-Run-jobs-のキャンセルオーバーライドに関して">Cloud Run jobs のキャンセル、オーバーライドに関して</h2> <p>従来の Cloud Run 起動元ロールには、Cloud Run jobs のジョブ実行をキャンセルするための権限(run.executions.cancel)や、ジョブ構成をオーバーライドするための権限(run.jobs.runWithOverrides)がありません。そのため、ジョブのキャンセルやオーバーライドを行いたい場合は <strong>Cloud Run 開発者</strong>(roles/run.developer)ロールが必要でした。</p> <p>しかし、Cloud Run 開発者 ロールは Cloud Run の作成、更新、削除の権限も持っているため、プリンシパルにジョブの実行に関することだけをさせたい場合、過剰な権限を与えてしまうことになります。</p> <p>新たに追加された「Cloud Run ジョブ エグゼキュータ」および「オーバーライドを使用する Cloud Run ジョブ エグゼキュータ」ロールでは、ジョブの実行時に必要となる権限<strong>のみ</strong>が付与されています。</p> <p>そのため、たとえばワークフローから環境によって構成をオーバーライドしたジョブを実行するようなケースで、ワークフローが使用するサービスアカウントなどのプリンシパルに対して過剰な権限を持たせずに済みます。</p> <h1 id="参考リンク">参考リンク</h1> <ul> <li><a href="https://cloud.google.com/run/docs/reference/iam/roles">Cloud Run IAM roles(公式ドキュメント)</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun Google Workspaceã‚’IdPとしてSlackにSSOする方法 hatenablog://entry/6802418398305013187 2024-12-18T09:00:00+09:00 2024-12-18T09:00:07+09:00 G-gen の三浦です。当記事では Google Workspace(Cloud Identity)を使用して、Slack にシングルサインオン(以下、SSO)を設定する方法を紹介します。 基礎知識 シングルサインオン(SSO)とは SAML 認証とは SAML 認証の流れ Google Workspace の SSO 対応するアプリ Google Workspace ã‚’ IdP とするメリット 対応エディション 検証の概要 検証作業 [Google Workspace] カスタム SAML アプリの作成 [Google Workspace] カスタム SAML アプリのユーザー設定 [Sla… <p>G-gen の三浦です。当記事では Google Workspace(Cloud Identity)を使用して、Slack にシングルサインオン(以下、SSO)を設定する方法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#基礎知識">基礎知識</a><ul> <li><a href="#シングルサインオンSSOとは">シングルサインオン(SSO)とは</a></li> <li><a href="#SAML-認証とは">SAML 認証とは</a></li> <li><a href="#SAML-認証の流れ">SAML 認証の流れ</a></li> </ul> </li> <li><a href="#Google-Workspace-の-SSO">Google Workspace の SSO</a><ul> <li><a href="#対応するアプリ">対応するアプリ</a></li> <li><a href="#Google-Workspace-ã‚’-IdP-とするメリット">Google Workspace ã‚’ IdP とするメリット</a></li> <li><a href="#対応エディション">対応エディション</a></li> </ul> </li> <li><a href="#検証の概要">検証の概要</a></li> <li><a href="#検証作業">検証作業</a><ul> <li><a href="#Google-Workspace-カスタム-SAML-アプリの作成">[Google Workspace] カスタム SAML アプリの作成</a></li> <li><a href="#Google-Workspace-カスタム-SAML-アプリのユーザー設定">[Google Workspace] カスタム SAML アプリのユーザー設定</a></li> <li><a href="#Slack-SAML-認証設定">[Slack] SAML 認証設定</a></li> <li><a href="#Slack-Slack-への直接アクセス確認">[Slack] Slack への直接アクセス確認</a></li> <li><a href="#Google-Workspace-Google-Workspace-アプリ経由の確認">[Google Workspace] Google Workspace アプリ経由の確認</a></li> <li><a href="#Google-Workspace-コンテキストアウェアアクセスの設定手順確認">[Google Workspace] コンテキストアウェアアクセスの設定手順確認</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241123/20241123114423.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="基礎知識">基礎知識</h1> <h2 id="シングルサインオンSSOとは">シングルサインオン(SSO)とは</h2> <p><strong>シングルサインオン</strong>(SSO)とは、一度の認証で複数のアプリケーションやサービスを利用できるようにするための仕組みです。利用者が複数のサービスを利用するとき、通常はサービスごとに ID とパスワードを入力します。これを1度で済むようにする仕組みが SSO です。</p> <p>SSO を実装することで、以下の効果が得られます。</p> <ul> <li><p><strong>利便性向上</strong><br/> ユーザーはアプリケーションごとにパスワードを入力する必要がなくなります。</p></li> <li><p><strong>セキュリティ強化</strong><br/> パスワードの管理が簡素化され、セキュリティリスクを軽減できます。</p></li> </ul> <h2 id="SAML-認証とは">SAML 認証とは</h2> <p>SSO を実現するプロトコルの一つに <strong>SAML</strong>(Security Assertion Markup Language)があります。SAML は、認証情報を安全にやり取りするための標準規格です。現在では SAML 2.0 が標準とされています。</p> <p>SAML による認証情報のやりとりを理解するためには、以下の2つの役割を理解する必要があります。</p> <ul> <li><p><strong>IdP</strong>(Identity Provider)<br/> アイデンティティ(アカウント)を保存したり、管理する役割。SSO では認証を担う。当記事では <strong>Google Workspace</strong> が該当。</p></li> <li><p><strong>SP</strong>(Service Provider)<br/> 認証済みのユーザーが実際に利用するサービス。今回の例では <strong>Slack</strong> が該当。</p></li> </ul> <h2 id="SAML-認証の流れ">SAML 認証の流れ</h2> <p>Slack を例に取ると、ユーザーから見た認証の流れは以下のようになります。</p> <ol> <li>ユーザーが Slack(SP) にアクセス</li> <li>Google Workspace(IdP)にリダイレクトされ、認証が行われる</li> <li>Slack にリダイレクトされ、ログインが完了</li> </ol> <h1 id="Google-Workspace-の-SSO">Google Workspace の SSO</h1> <h2 id="対応するアプリ">対応するアプリ</h2> <p>Google Workspace ã‚’ IdP として利用すれば、多くのクラウドサービスで SAML 認証を使用したシングルサインオンを実現できます。</p> <p>例えば、以下のようなサービスが「統合対応 SAML アプリ」としてネイティブに対応しています。</p> <ul> <li>Amazon Web Services</li> <li>Notion</li> <li>ServiceNow</li> <li>Tableau</li> <li>Zendesk</li> </ul> <p>またネイティブに対応していないサービスでも、SAML 2.0 規格に準拠していれば、「カスタム SAML アプリ」として SSO 設定が可能です。</p> <ul> <li>参考 : <a href="https://support.google.com/a/table/9217027?hl=ja">統合対応 SAML アプリの一覧</a></li> <li>参考 : <a href="https://support.google.com/a/answer/6087519?hl=ja">カスタム SAML アプリを設定する</a></li> </ul> <h2 id="Google-Workspace-ã‚’-IdP-とするメリット">Google Workspace ã‚’ IdP とするメリット</h2> <p>Google Workspace ã‚’ IdP として利用することで、SSO の効果をさらに高める以下の利点があります。</p> <ul> <li><p><strong>Google アカウントの統一管理</strong><br/> Google アカウントを認証基盤として使用するため、複数のアカウントやパスワードを管理する必要がなくなり、ユーザーの利便性が向上。</p></li> <li><p><strong>セキュリティ強化</strong><br/> 多要素認証(Google Authenticator やセキュリティキーの利用)やコンテキストアウェアアクセス(IP アドレスやデバイスによるアクセス制御)を活用し、企業のセキュリティ要件やリスク管理方針に応じた柔軟な認証ポリシーを設定可能。</p></li> <li><p><strong>幅広いサービスとの連携</strong><br/> Google Workspace を通じて、Slack などの外部サービスだけでなく、Google Workspace 内部のサービス(Google Drive、Google Meet など)へのアクセスも一元管理。</p></li> </ul> <p>2段階認証やコンテキストアウェア アクセスについては、以下の公式ドキュメントも参考にしてください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/175197?hl=ja">2 段階認証プロセスでビジネスを保護する</a></li> <li>参考 : <a href="https://support.google.com/a/answer/9275380?hl=ja">コンテキストアウェア アクセスでビジネスを保護する</a></li> </ul> <h2 id="対応エディション">対応エディション</h2> <p>Google Workspace では、Essentials Starter 以外の全エディションで、Google Workspace ã‚’ IdP とした SSO を構成できます。</p> <p>Cloud Identity でも同様に、Free と Premium の両エディションで、Cloud Identity ã‚’ IdP とした SSO を構成できます。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/6043385?hl=ja">Google Workspace の各エディションの比較</a></li> <li>参考 : <a href="https://support.google.com/cloudidentity/answer/7431902?hl=ja">Cloud Identity の機能とエディションの比較</a></li> </ul> <h1 id="検証の概要">検証の概要</h1> <p>以下の手順で SSO を設定し、動作を確認します。</p> <table> <thead> <tr> <th> 順番 </th> <th> 作業場所 </th> <th> 作業名 </th> <th> 内容 </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> Google Workspace </td> <td> カスタム SAML アプリの作成 </td> <td> Google Workspace 側で SAML 認証を設定 </td> </tr> <tr> <td> 2 </td> <td> Google Workspace </td> <td> カスタム SAML アプリのユーザー設定 </td> <td> アプリを利用する対象(組織、グループ)を設定 </td> </tr> <tr> <td> 3 </td> <td> Slack </td> <td> SAML 認証設定 </td> <td> Slack 側で SAML 認証を設定 </td> </tr> <tr> <td> 4 </td> <td> Slack </td> <td> Slack への直接アクセス確認 </td> <td> Google Workspace にログインしていない状態で、SAML 認証が正しく動作するかを確認 </td> </tr> <tr> <td> 5 </td> <td> Google Workspace </td> <td> Google Workspace アプリ経由の確認 </td> <td> Google Workspace のカスタム SAML アプリを利用して、SSO が正しく設定されているかを確認 </td> </tr> <tr> <td> 6 </td> <td> Google Workspace </td> <td> コンテキストアウェアアクセスの設定手順確認 </td> <td> アクセス制限(IP アドレスやデバイス制御)の設定手順を確認 </td> </tr> </tbody> </table> <ul> <li>参考 : <a href="https://slack.com/intl/ja-jp/help/articles/204078066-Google-Workspace-%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%AA%E3%83%B3">Google Workspace シングルサインオン</a></li> <li>参考 : <a href="https://support.google.com/a/answer/6357481?hl=ja">Slack クラウド アプリケーション</a></li> </ul> <p>なお前提として、Slack では Business+ または Enterprise Grid プランでのみ、SAML ベースの SSO を利用できます。Free プランや Pro プランでは利用できませんのでご注意ください。</p> <ul> <li>参考 : <a href="https://app.slack.com/plans/T01LMH91KNF?geocode=ja-jp">チームに合ったプランを選択しましょう</a></li> </ul> <h1 id="検証作業">検証作業</h1> <h2 id="Google-Workspace-カスタム-SAML-アプリの作成">[Google Workspace] カスタム SAML アプリの作成</h2> <p>Google Workspace の管理コンソール(<a href="https://admin.google.com">https://admin.google.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/182076?hl=ja">管理コンソールにログインする</a></li> </ul> <p>[アプリ] > [ウェブアプリとモバイルアプリ] > [アプリを追加] > [カスタム SAML アプリの追加] を選択します。 <figure class="figure-image figure-image-fotolife" title="カスタム SAML アプリの追加"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010154.png" width="668" height="445" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>カスタム SAML アプリの追加</figcaption></figure></p> <p>アプリ名を入力し、必要に応じてアイコンを添付します。入力が終わったら [続行] を選択します。 <figure class="figure-image figure-image-fotolife" title="アプリ名の入力"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010048.png" width="573" height="362" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリ名の入力</figcaption></figure></p> <p>管理者は、Slack 側に登録するために以下の3点を控えておき、[続行] を選択します。</p> <ul> <li><strong>SSO の URL</strong></li> <li><strong>エンティティ ID</strong></li> <li><strong>証明書(右側のダウンロードアイコンを選択してダウンロードします)</strong></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="IdP 設定の確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010052.png" width="658" height="611" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>IdP 設定の確認</figcaption></figure></p> <p>以下を入力し、[続行] を選択します。</p> <ul> <li><strong>ACS の URL</strong>:<code>https://${{Slack URL}}/sso/saml</code></li> <li><strong>エンティティ ID</strong>:<code>https://slack.com</code></li> <li><strong>署名付き応答</strong>:有効化(チェックを入れる)</li> <li><strong>名前 ID</strong>:<code>[Basic Information] &gt; [Primary email]</code></li> </ul> <p>Slack URL は、以下のドキュメントを参照して確認してください。</p> <ul> <li>参考 : <a href="https://slack.com/intl/ja-jp/help/articles/221769328-Slack-URL-%E3%81%BE%E3%81%9F%E3%81%AF-ID-%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">Slack URL または ID を確認する</a></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="SP 設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010055.png" width="775" height="562" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SP 設定</figcaption></figure></p> <p>[マップングを追加] から以下の通りに設定し、[完了] を選択します。</p> <ul> <li><code>[Basic Information] &gt; [Primary email]</code>:<code>User.Email</code></li> <li><code>[Basic Information] &gt; [First name]</code>:<code>first_name</code></li> <li><code>[Basic Information] &gt; [Last Name]</code>:<code>last_name</code></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="属性のマッピング"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010059.png" width="698" height="535" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>属性のマッピング</figcaption></figure></p> <h2 id="Google-Workspace-カスタム-SAML-アプリのユーザー設定">[Google Workspace] カスタム SAML アプリのユーザー設定</h2> <p>作成したアプリの [ユーザー アクセス] を選択します。 <figure class="figure-image figure-image-fotolife" title="ユーザーアクセスを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010103.png" width="800" height="285" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ユーザーアクセスを選択</figcaption></figure></p> <p>SAML 認証を利用する対象(<strong>組織全体</strong>または<strong>特定の組織部門</strong>または <strong>Google グループ</strong>)を選択し、[オン] > [保存(またはオーバーライド)] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="SAML アプリの有効化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010106.png" width="800" height="214" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML アプリの有効化</figcaption></figure></p> <h2 id="Slack-SAML-認証設定">[Slack] SAML 認証設定</h2> <p>管理者アカウントにて、[ワークスペース名] > [ツールと設定] > [ワークスペースの設定] を選択します。 <figure class="figure-image figure-image-fotolife" title="ワークスペースの設定を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010110.png" width="450" height="422" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ワークスペースの設定を選択</figcaption></figure></p> <p>[認証] > [認証] から SAML 認証の [設定する] を選択します。 <figure class="figure-image figure-image-fotolife" title="SAML 認証設定を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010113.png" width="800" height="407" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML 認証設定を選択</figcaption></figure></p> <p>以下の通りに設定し、詳細設定の [開く] を選択します。</p> <ul> <li><strong>SAML 2.0 エンドポイント (HTTP)</strong>:<code>SSO の URL</code></li> <li><strong>ID プロバイダ発行者</strong>:<code>エンティティ ID</code></li> <li><strong>公開証明書</strong>:ダウンロードした証明書ファイルをテキストエディタで開き、その内容をコピーして貼り付けます。</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="SAML 認証設定1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010117.png" width="667" height="586" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML 認証設定1</figcaption></figure></p> <p>以下を選択し、[設定を保存する] を選択します。</p> <ul> <li><strong>サービスプロバイダ発行者</strong>:カスタム SAML アプリで設定した<code>エンティティ ID</code></li> <li><strong>署名付きレスポンス</strong>:有効化(チェックを入れる)</li> <li><strong>ワークスペースの認証が必要なメンバー</strong>:SAML 認証が必要な対象を選択します。</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="SAML 認証設定2"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010121.png" width="661" height="407" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML 認証設定2</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="SAML 認証設定3"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010124.png" width="622" height="505" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML 認証設定3</figcaption></figure></p> <p>SAML 認証が有効化されたことを確認します。 <figure class="figure-image figure-image-fotolife" title="有効化確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010127.png" width="523" height="378" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>有効化確認</figcaption></figure></p> <h2 id="Slack-Slack-への直接アクセス確認">[Slack] Slack への直接アクセス確認</h2> <p>Google Workspace にログインしていない状態で、Slack の URL(例: <code>https://${{Slack URL}}</code>)にアクセスします。 <figure class="figure-image figure-image-fotolife" title="Slack URL へアクセス"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010138.png" width="544" height="92" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Slack URL へアクセス</figcaption></figure></p> <p>[SAML でサインイン] を選択します。 <figure class="figure-image figure-image-fotolife" title="SAML でサインインを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010141.png" width="426" height="319" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SAML でサインインを選択</figcaption></figure></p> <p>Google のログイン画面が表示されるため、アカウント及びパスワードを入力して [次へ] を選択します。 <figure class="figure-image figure-image-fotolife" title="Google ログイン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010144.png" width="800" height="280" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Google ログイン</figcaption></figure></p> <p>認証が完了すると Slack にリダイレクトされ、ログインできることを確認します。 <figure class="figure-image figure-image-fotolife" title="ログイン確認(直接アクセス)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010147.png" width="680" height="575" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログイン確認(直接アクセス)</figcaption></figure></p> <h2 id="Google-Workspace-Google-Workspace-アプリ経由の確認">[Google Workspace] Google Workspace アプリ経由の確認</h2> <p>Google Workspace にログインした状態で、[アプリ] > [カスタム SAML アプリ] を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="カスタム SAML アプリを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010131.png" width="344" height="404" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>カスタム SAML アプリを選択</figcaption></figure></p> <p>Slack にログインできることを確認します。 <figure class="figure-image figure-image-fotolife" title="ログイン確認(カスタム SAML アプリ)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010134.png" width="470" height="462" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログイン確認(カスタム SAML アプリ)</figcaption></figure></p> <p>Google Workspace の管理画面から、ログを確認する方法も検証します。</p> <p>[レポート] > [監査と調査] > [SAML ログイベント] から SAML 認証に関するログを確認できます。 <figure class="figure-image figure-image-fotolife" title="ログ確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010150.png" width="800" height="414" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログ確認</figcaption></figure></p> <h2 id="Google-Workspace-コンテキストアウェアアクセスの設定手順確認">[Google Workspace] コンテキストアウェアアクセスの設定手順確認</h2> <p>[セキュリティ] > [アクセスとデータ管理] > [コンテキストアウェアアクセス] > [アプリにアクセスレベルを割り当てる] を選択します。 <figure class="figure-image figure-image-fotolife" title="アプリにアクセスレベルを割り当てる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010157.png" width="800" height="445" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリにアクセスレベルを割り当てる</figcaption></figure></p> <p>アクセスレベルを適用する対象として <strong>カスタム SAML アプリ</strong> を選択します。[割り当て] を選択することで、アクセスレベルを設定できます。 <figure class="figure-image figure-image-fotolife" title="カスタム SAML アプリへの割り当て"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241218/20241218010200.png" width="578" height="591" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>カスタム SAML アプリへの割り当て</figcaption></figure></p> <p>コンテキストアウェアアクセスの詳細な設定手順やアクセスレベルの作成方法については、以下の記事を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcontext-aware-access-with-google-workspace" title="コンテキストアウェアアクセスでGoogle Workspaceのセキュリティを強化してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/context-aware-access-with-google-workspace">blog.g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak コンテキストアウェアアクセスでGoogle Workspaceのセキュリティを強化してみた hatenablog://entry/6802418398304281076 2024-12-16T09:00:00+09:00 2024-12-16T09:00:03+09:00 G-gen の三浦です。当記事では、コンテキストアウェア アクセス(CAA)を使って Google ドライブ等の Google Workspace アプリケーションへのアクセスを制御する方法を紹介します。 コンテキストアウェア アクセスとは 前提条件 検証内容 動作確認 モニターモードの設定 動作確認(モニターモード) アクセスレベル変更(アクティブモード) 動作確認(アクティブモード) 複合条件の設定 複合条件の動作確認(アクティブモード) コンテキストアウェア アクセスとは コンテキストアウェア アクセス(以降、CAA)は、IP アドレスやデバイスの状態などのコンテキスト(背景情報)に基づ… <p>G-gen の三浦です。当記事では、コンテキストアウェア アクセス(CAA)を使って Google ドライブ等の Google Workspace アプリケーションへのアクセスを制御する方法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#コンテキストアウェア-アクセスとは">コンテキストアウェア アクセスとは</a></li> <li><a href="#前提条件">前提条件</a></li> <li><a href="#検証内容">検証内容</a></li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#モニターモードの設定">モニターモードの設定</a></li> <li><a href="#動作確認モニターモード">動作確認(モニターモード)</a></li> <li><a href="#アクセスレベル変更アクティブモード">アクセスレベル変更(アクティブモード)</a></li> <li><a href="#動作確認アクティブモード">動作確認(アクティブモード)</a></li> <li><a href="#複合条件の設定">複合条件の設定</a></li> <li><a href="#複合条件の動作確認アクティブモード">複合条件の動作確認(アクティブモード)</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241118/20241118101127.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="コンテキストアウェア-アクセスとは">コンテキストアウェア アクセスとは</h1> <p>コンテキストアウェア アクセス(以降、CAA)は、IP アドレスやデバイスの状態などのコンテキスト(背景情報)に基づいてアクセスを制御する、Google Workspace の機能です。</p> <p>Google ドライブ、Gmail、Google カレンダー、Looker Studio などに条件付きでアクセス制御を適用できます。</p> <p><strong>使用例</strong> :</p> <ul> <li><p><strong>IP アドレス制限</strong> : 社内ネットワークの IP アドレスからのみ Google ドライブへのアクセスを許可し、社外(自宅や公共 Wi-Fi)からの利用を禁止する。</p></li> <li><p><strong>デバイス制限</strong> : 会社支給のモバイルデバイス(iPhone、Android)からのみ Gmail へのアクセスを許可し、私用デバイスからの利用を禁止する。</p></li> </ul> <p>参考 : <a href="https://support.google.com/a/answer/12645308?hl=ja">コンテキストアウェア アクセスの概要</a></p> <h1 id="前提条件">前提条件</h1> <p>CAA は、Google Workspace(Cloud Identity)の特定のエディション(Frontline Standard、Enterprise Standard、Enterprise Plus、Cloud Identity Premium 等)で利用できます。</p> <p>詳細は以下のドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/9275380?hl=ja">コンテキストアウェア アクセスでビジネスを保護する</a></li> </ul> <h1 id="検証内容">検証内容</h1> <p>以下の手順で CAA を設定し、動作を確認します。</p> <ol> <li><p><strong>モニターモードの設定</strong><br/> 社内 IP アドレスのみにアクセスを制限するルールを作成し、モニターモード(検知のみでブロックしない)で設定します。</p></li> <li><p><strong>動作確認(モニターモード)</strong><br/> 社内外のアクセス状況を確認し、ログを確認します。</p></li> <li><p><strong>アクセスレベル変更(アクティブモード)</strong><br/> アクティブモードに切り替え、条件外のアクセスをブロックするように設定します。</p></li> <li><p><strong>動作確認(アクティブモード)</strong><br/> 社内 IP アドレスからアクセス可能で、社外からはブロックされることを確認します。</p></li> <li><p><strong>複合条件の設定</strong><br/> 社内 IP アドレスに加え、多要素認証(MFA)を利用している場合のみ許可する条件を設定します。</p></li> <li><p><strong>複合条件の動作確認(アクティブモード)</strong><br/> 条件に合致しないアクセスがブロックされることを確認します。</p></li> </ol> <h1 id="動作確認">動作確認</h1> <h2 id="モニターモードの設定">モニターモードの設定</h2> <p>Google Workspace の管理コンソール(URL : <a href="https://admin.google.com">https://admin.google.com</a>)にログインします。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/182076?hl=ja">管理コンソールにログインする</a></li> </ul> <p>[セキュリティ] > [アクセスとデータ管理] > [コンテキストアウェア アクセス] に移動します。 <figure class="figure-image figure-image-fotolife" title="コンテキストアウェア アクセスへ移動"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090101.png" width="444" height="272" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>コンテキストアウェア アクセスへ移動</figcaption></figure></p> <p>CAA が無効な場合は、[有効にする] を選択して有効化します。その後 [アクセスレベル] を選択します。 <figure class="figure-image figure-image-fotolife" title="有効化とアクセスレベルの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090104.png" width="442" height="243" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>有効化とアクセスレベルの選択</figcaption></figure></p> <p>[アクセスレベルを作成] を選択します。 <figure class="figure-image figure-image-fotolife" title="[アクセスレベルを作成] を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090006.png" width="800" height="116" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセスレベルを作成を選択</figcaption></figure></p> <p>以下を設定し、[作成] を選択します。</p> <ul> <li> <strong>アクセスレベル名</strong>:任意の名前を入力します。</li> <li> <strong>条件</strong>:[基本] > [IP サブネット] を選択し、社内 IP アドレスを入力します。</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="1つ目のアクセスレベルの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090055.png" width="789" height="650" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>1つ目のアクセスレベルの作成</figcaption></figure></p> <p>[アプリに割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="アプリに割り当てを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090058.png" width="491" height="244" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリに割り当てを選択</figcaption></figure></p> <p>適用する対象(ユーザーまたはグループまたは組織部門)とアプリを選択し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="適用対象の選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090009.png" width="777" height="592" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>適用対象の選択</figcaption></figure></p> <p>アクセスレベルを選択し、「監視」にチェックを入れて「続行」を選択します。<strong>モニターモード(監視)</strong>では、アクセスレベルの影響範囲をログで確認でき、ブロックは行われません。</p> <p>なおアクセスレベルは複数選択できますが、<strong>OR 条件</strong>で動作するため、いずれかのアクセスレベルを満たす場合は接続が許可されます。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/9261439?hl=ja">アプリにコンテキストアウェア アクセスレベルを割り当てる</a> <figure class="figure-image figure-image-fotolife" title="アクセスレベルとモードの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090012.png" width="724" height="569" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセスレベルとモードの選択</figcaption></figure></li> </ul> <p>[続行] を選択します。 <figure class="figure-image figure-image-fotolife" title="続行を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090111.png" width="525" height="517" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>続行を選択</figcaption></figure></p> <p>内容を確認し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="モニターモードの適用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090052.png" width="690" height="531" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>モニターモードの適用</figcaption></figure></p> <h2 id="動作確認モニターモード">動作確認(モニターモード)</h2> <p>社内および社外から Google ドライブ、Google カレンダー、Gmail にアクセスします。この段階では、どちらからもアクセス可能です。 <figure class="figure-image figure-image-fotolife" title="アクセス確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090015.png" width="800" height="215" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセス確認</figcaption></figure></p> <p>[レポート] > [監査と調査] > [コンテキストアウェア アクセスのログイベント] を選択します。 <figure class="figure-image figure-image-fotolife" title="ログイベントの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090018.png" width="263" height="563" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログイベントの選択</figcaption></figure></p> <p>以下の条件で検索し、モニターモードでブロックされたユーザーのログを確認します。意図しないブロックが発生していないか確認してください。</p> <ul> <li><code>イベント</code> <strong>次に一致</strong> <code>アクセス拒否(モニターモード)</code></li> <li><code>アクセスレベルの適用</code> <strong>次の文字を含む</strong> <code>アクセスレベル名</code></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="モニターモードのログ確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090020.png" width="800" height="414" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>モニターモードのログ確認</figcaption></figure></p> <h2 id="アクセスレベル変更アクティブモード">アクセスレベル変更(アクティブモード)</h2> <p>[セキュリティ] > [コンテキストアウェア アクセス] へ移動し、 [アプリにアクセスレベルを割り当てる] を選択します。 <figure class="figure-image figure-image-fotolife" title="アプリにアクセスレベルを割り当てるを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090106.png" width="498" height="413" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリにアクセスレベルを割り当てるを選択</figcaption></figure></p> <p>アクセスレベルを適用する対象とアプリを選択し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="割り当てを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090109.png" width="241" height="404" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>割り当てを選択</figcaption></figure></p> <p><code>監視</code>のチェックを外し、<code>アクティブ</code>のチェックを入れて [続行] を選択します。 <figure class="figure-image figure-image-fotolife" title="アクティブモードへの変更"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090023.png" width="800" height="433" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクティブモードへの変更</figcaption></figure></p> <p>[続行] を選択し、内容を確認し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="続行を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090111.png" width="525" height="517" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>続行を選択</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="アクティブモードの適用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090027.png" width="775" height="562" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクティブモードの適用</figcaption></figure></p> <h2 id="動作確認アクティブモード">動作確認(アクティブモード)</h2> <p>社内 IP アドレス及び社外 IP アドレスからアクセスします。社内 IP アドレスでは正常にアクセスでき、社外 IP アドレスではアクセスがブロックされることを確認します。 <figure class="figure-image figure-image-fotolife" title="ブロック確認(IP サブネット)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090114.png" width="396" height="414" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ブロック確認(IP サブネット)</figcaption></figure></p> <h2 id="複合条件の設定">複合条件の設定</h2> <p>[セキュリティ] > [コンテキストアウェア アクセス] に移動し、[アクセスレベル] > [アクセスレベルを作成] を選択します。 <figure class="figure-image figure-image-fotolife" title="アクセスレベルを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090117.png" width="466" height="295" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセスレベルを選択</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="[アクセスレベルを作成] を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090006.png" width="800" height="116" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセスレベルを作成を選択</figcaption></figure></p> <p>以下を設定し [作成] を選択します。この条件により、<strong>MFA 認証がされていないとアクセスがブロック</strong>されます。条件式の詳細については、以下のドキュメントを参照してください。</p> <ul> <li> <strong>アクセスレベル名</strong>:任意の名前を入力します。</li> <li><p> <strong>条件</strong>:[詳細] > <code>request.auth.claims.crd_str.mfa == true</code></p></li> <li><p>参考 : <a href="https://cloud.google.com/access-context-manager/docs/custom-access-level-spec?hl=ja">カスタム アクセスレベルの仕様</a></p></li> <li>参考 : <a href="https://support.google.com/a/answer/11368990?hl=ja">詳細モードでのコンテキストアウェア アクセスの例</a></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="2つ目のアクセスレベルの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090030.png" width="732" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>2つ目のアクセスレベルの作成</figcaption></figure></p> <p>[終了] を選択します。 <figure class="figure-image figure-image-fotolife" title="終了を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090120.png" width="490" height="245" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>終了を選択</figcaption></figure></p> <p>[コンテキストアウェア アクセス] > [アクセスレベル] へ移動し、1つ目と2つ目のルールの<code>CEL名</code>を確認し、控えます。 <figure class="figure-image figure-image-fotolife" title="CEL名の確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090122.png" width="630" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CEL名の確認</figcaption></figure></p> <p>[アクセスレベルを作成] を選択します。 <figure class="figure-image figure-image-fotolife" title="[アクセスレベルを作成] を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090006.png" width="800" height="116" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>[アクセスレベルを作成] を選択</figcaption></figure></p> <p>以下を設定し [作成] を選択します。この条件により、<strong>複数の条件(IP アドレス制限と多要素認証(MFA))を同時に満たす場合のみ</strong>アクセスが許可されます。</p> <ul> <li> <strong>アクセスレベル名</strong>:任意の名前を入力します。</li> <li> <strong>条件</strong>:[詳細] > <code>levels.${{1つ目のアクセスレベルの CEL 名}} &amp;&amp; levels.${{2つ目のアクセスレベルの CEL 名}}</code></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="3つ目のアクセスレベルの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090032.png" width="733" height="569" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>3つ目のアクセスレベルの作成</figcaption></figure></p> <p>[セキュリティ] > [コンテキストアウェア アクセス] へ移動し、 [アプリにアクセスレベルを割り当てる] を選択します。 <figure class="figure-image figure-image-fotolife" title="アプリにアクセスレベルを割り当てるを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090106.png" width="498" height="413" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリにアクセスレベルを割り当てるを選択</figcaption></figure></p> <p>アクセスレベルを適用する対象とアプリを選択し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="割り当てを選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090109.png" width="241" height="404" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>割り当てを選択</figcaption></figure></p> <p>適用済みの1つ目のアクセスレベルを削除し、3つ目のアクセスレベルを選択し、<code>アクティブ</code>のチェックを入れて、[続行] を選択します。</p> <p><strong>本番環境へ適用する場合は、まず [監視] のみにチェックを入れてモニターモードで影響がないことを確認した上で、アクティブモードに切り替えることを推奨します。</strong> <figure class="figure-image figure-image-fotolife" title="1つ目のアクセスレベルの削除"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090035.png" width="800" height="175" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>1つ目のアクセスレベルの削除</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="3つ目のアクセスレベルの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090038.png" width="800" height="448" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>3つ目のアクセスレベルの選択</figcaption></figure></p> <p>[続行] を選択し、内容を確認し、[割り当て] を選択します。 <figure class="figure-image figure-image-fotolife" title="続行を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090111.png" width="525" height="517" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>続行を選択</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="3つ目のアクセスレベルの適用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090041.png" width="800" height="541" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>3つ目のアクセスレベルの適用</figcaption></figure></p> <h2 id="複合条件の動作確認アクティブモード">複合条件の動作確認(アクティブモード)</h2> <p>社内の IP アドレスかつ MFA が無効化されているアカウントからアクセスを確認し、以下画面が表示されることを確認します。 <figure class="figure-image figure-image-fotolife" title="MFA が無効なアカウント"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090125.png" width="446" height="413" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>MFA が無効なアカウント</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="ブロック確認(複合条件)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090044.png" width="358" height="275" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ブロック確認(複合条件)</figcaption></figure></p> <p>[レポート] > [監査と調査] > [コンテキストアウェア アクセスのログイベント] を選択します。 <figure class="figure-image figure-image-fotolife" title="ログイベントの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090018.png" width="263" height="563" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログイベントの選択</figcaption></figure></p> <p>以下の条件で検索し、アクティブモードで拒否された接続ログを確認します。<code>アクセスレベルの不足</code>を確認することで、どのアクセスレベルでブロックされたかを特定できます。</p> <ul> <li><code>イベント</code> <strong>次に一致</strong> <code>アクセスが拒否されました</code></li> <li><code>アクセスレベルの適用</code> <strong>次の文字を含む</strong> <code>アクセスレベル名</code></li> </ul> <p><figure class="figure-image figure-image-fotolife" title="ログの確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090047.png" width="671" height="482" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログの確認</figcaption></figure></p> <p>アカウントのMFAを有効化後、ログアウトし、MFAを利用して再度ログインします。</p> <p>ログイン時に<code>このデバイスでは次回から表示しない</code>を選択しないでください。選択すると、次回以降のログインで MFA 認証が省略され、CAA によってアクセスがブロックされます。</p> <p>万が一選択してしまった場合は、以下のドキュメントを参照してログイン Cookie をリセットしてください。</p> <ul> <li>参考 : <a href="https://support.google.com/a/answer/178854?hl=ja">管理対象の Google アカウントからログアウトする</a> <figure class="figure-image figure-image-fotolife" title="MFA を利用してログイン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090128.png" width="522" height="240" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>MFA を利用してログイン</figcaption></figure></li> </ul> <p>Googleドライブにアクセスし、正常に表示されることを確認します。 <figure class="figure-image figure-image-fotolife" title="アクセス確認 "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241216/20241216090049.png" width="800" height="96" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アクセス確認</figcaption></figure></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak Privileged Access Manager(PAM)ã‚’Terraformで管理する hatenablog://entry/6802418398305005654 2024-12-13T09:00:00+09:00 2024-12-13T09:05:03+09:00 G-gen の武井です。当記事では Privileged Access Manager ã‚’ Terraform で管理する方法について紹介します。 はじめに 概要 Privileged Access Manager (PAM) PAM に必要な権限 利用資格の管理 利用資格の利用 (申請、承認) 全体構成 連携方式 ソースコード Direct Workload Identity および GitHub Actions ワークフロー Terraform ディレクトリ構成 env 配下 (呼び出し側) modules 配下 (モジュール) デプロイ terraform plan terraform … <p>G-gen の武井です。当記事では Privileged Access Manager ã‚’ Terraform で管理する方法について紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#概要">概要</a></li> <li><a href="#Privileged-Access-Manager-PAM">Privileged Access Manager (PAM)</a></li> <li><a href="#PAM-に必要な権限">PAM に必要な権限</a><ul> <li><a href="#利用資格の管理">利用資格の管理</a></li> <li><a href="#利用資格の利用-申請承認">利用資格の利用 (申請、承認)</a></li> </ul> </li> </ul> </li> <li><a href="#全体構成">全体構成</a></li> <li><a href="#連携方式">連携方式</a></li> <li><a href="#ソースコード">ソースコード</a><ul> <li><a href="#Direct-Workload-Identity-および-GitHub-Actions-ワークフロー">Direct Workload Identity および GitHub Actions ワークフロー</a></li> <li><a href="#Terraform">Terraform</a><ul> <li><a href="#ディレクトリ構成">ディレクトリ構成</a></li> <li><a href="#env-配下-呼び出し側">env 配下 (呼び出し側)</a></li> <li><a href="#modules-配下-モジュール">modules 配下 (モジュール)</a></li> </ul> </li> </ul> </li> <li><a href="#デプロイ">デプロイ</a><ul> <li><a href="#terraform-plan">terraform plan</a></li> <li><a href="#terraform-apply">terraform apply</a></li> <li><a href="#リソース">リソース</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#申請">申請</a></li> <li><a href="#承認">承認</a></li> <li><a href="#権限付与">権限付与</a></li> <li><a href="#権限はく奪">権限はく奪</a></li> <li><a href="#再申請">再申請</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090505.png" width="800" height="449" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="概要">概要</h2> <p>当記事では Google Cloud における一時的な IAM 権限付与を実現する仕組みである <strong>Privileged Access Manager</strong> (以降、PAM) を、Terraform と GitHub Actions による CI/CD で管理する方法を紹介します。</p> <p>当記事で実現するのは、PAM の仕組みのデプロイです。API の有効化、サービスエージェントへの権限付与、利用資格 (entitlements) の作成などを Terraform で行うことで、デプロイ以降は PAM を使った承認フローにより、組織の IAM 権限を管理することができるようになります。</p> <h2 id="Privileged-Access-Manager-PAM">Privileged Access Manager (PAM)</h2> <p>PAM の詳細は以下の記事で解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fiam-privileged-access-manager" title="Privileged Access Manager(PAM)を解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/iam-privileged-access-manager">blog.g-gen.co.jp</a></cite></p> <p>PAM での権限管理の仕組みを端的にまとめると、<strong>利用資格</strong> (entitlements) という設定情報にもとづき、一時的な権限の付与を行うものです。</p> <p>利用資格(entitlements)は PAM のオブジェクトであり、承認フローと言い換えることもできます。利用資格には「付与する IAM ロール」「権限を付与する最大時間」「誰が権限をリクエストできるか」「誰がリクエストを承認できるか」「誰が通知を受け取るか」などを定義できます。</p> <p>承認フローは以下のようになります。</p> <ol> <li>申請者は<code>必要な権限、期間、その理由</code>を利用資格に明記し、承認者に提出する</li> <li>承認者は、その妥当性を確認し申請を<code>承認もしくは否認</code>する</li> <li>承認された場合、申請者に対し一定期間権限が付与される</li> <li>所定の期間が経過すると、申請者に付与されていた権限は自動的にはく奪される</li> </ol> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090612.png" width="800" height="643" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="PAM-に必要な権限">PAM に必要な権限</h2> <p>PAM に必要な権限 (IAM ロール) については以下の通りです。</p> <ul> <li>参考:<a href="https://cloud.google.com/iam/docs/pam-permissions-and-setup?hl=ja">Privileged Access Manager の権限と設定</a></li> </ul> <h3 id="利用資格の管理">利用資格の管理</h3> <p>利用資格を<code>管理(作成、更新、削除)</code>するプリンシパルには、<strong>Privileged Access Manager 管理者</strong> (<code>roles/privilegedaccessmanager.admin</code>) が必要です。</p> <p>また、利用資格を<strong>組織ツリーの中のどこで</strong>利用するかによって、以下のいずれかの権限が必要です。</p> <ul> <li>組織全体:セキュリティ管理者(<code>roles/iam.securityAdmin</code>)</li> <li>フォルダ:フォルダ IAM 管理者(<code>roles/resourcemanager.folderIamAdmin</code>)</li> <li>プロジェクト:Project IAM 管理者(<code>roles/resourcemanager.projectIamAdmin</code>)</li> </ul> <h3 id="利用資格の利用-申請承認">利用資格の利用 (申請、承認)</h3> <p>利用資格を用いて、権限付与を申請する、あるいは申請を承認するプリンシパルには、<strong>Privileged Access Manager 閲覧者</strong> (<code>roles/privilegedaccessmanager.viewer</code>) が必要です。</p> <h1 id="全体構成">全体構成</h1> <p>当記事では利用資格を <strong>Terraform と GitHub Actions</strong> で管理します。</p> <p>利用資格は<code>組織/フォルダ/プロジェクトレベル</code>で設定可能ですが、今回は組織とプロジェクトレベルに PAM をデプロイします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090509.png" width="800" height="514" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="連携方式">連携方式</h1> <p>Google Cloud と GitHub Actions の連携には <strong>Direct Workload Identity</strong> を使用します。</p> <p>サービスアカウントキーやサービスアカウントの権限を借用する従来方式とは異なり、Workload Identity プールに必要な IAM 権限を直接付与します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/workload-identity-federation">Workload Identity Federation</a></li> </ul> <h1 id="ソースコード">ソースコード</h1> <h2 id="Direct-Workload-Identity-および-GitHub-Actions-ワークフロー">Direct Workload Identity および GitHub Actions ワークフロー</h2> <p>以下の記事で Direct Workload Identity を作成する bash スクリプトと GitHub Actions のワークフローを掲載しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcreate-direct-workload-identity-for-gha-terraform" title="Google CloudとGitHub Actions(Terraform)を連携するDirect Workload Identityを作成するbashスクリプト - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/create-direct-workload-identity-for-gha-terraform">blog.g-gen.co.jp</a></cite></p> <p>なお、上記の記事に掲載したスクリプトで作成される Workload Identity プールに対しては、組織レベルで以下の IAM ロールを付与しており、利用資格の管理に必要な権限を包含しています。</p> <ul> <li>オーナー (<code>roles/owner</code>)</li> <li>組織管理者 (<code>roles/resourcemanager.organizationAdmin</code>)</li> </ul> <h2 id="Terraform">Terraform</h2> <p>PAM のソースコードは以下のディレクトリ構成にもとづきます。</p> <ul> <li>参考:<a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/privileged_access_manager_entitlement">google_privileged_access_manager_entitlement</a></li> </ul> <h3 id="ディレクトリ構成">ディレクトリ構成</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>. ├── env │ ├── Test_Environment │ │ └── yutakei │ │ ├── backend.tf │ │ ├── locals.tf │ │ ├── main.tf │ │ └── versions.tf │ └── organization │ ├── backend.tf │ ├── locals.tf │ ├── main.tf │ └── versions.tf └── modules ├── apis │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── pam ├── main.tf ├── outputs.tf └── variables.tf </pre> <h3 id="env-配下-呼び出し側">env 配下 (呼び出し側)</h3> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># backend.tf</span> <span class="synType">terraform</span> <span class="synSpecial">{</span> <span class="synType">backend</span> <span class="synConstant">&quot;gcs&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">bucket</span> = <span class="synConstant">&quot;common-tfstate&quot;</span> <span class="synIdentifier">prefix</span> = <span class="synConstant">&quot;terraform/organization/state&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># locals.tf</span> <span class="synType">locals</span> <span class="synSpecial">{</span> <span class="synIdentifier">organization_id</span> = <span class="synConstant">&quot;1234567890&quot;</span> <span class="synComment"># 利用申請(entitlements)の設定</span> <span class="synIdentifier">entitlements</span> = <span class="synSpecial">{</span> <span class="synIdentifier">pam_org1</span> = <span class="synSpecial">{</span> <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-organization-acm-demo&quot;</span> <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> <span class="synIdentifier">eligible_users</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Organization&quot;</span> <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/organizations/1234567890&quot;</span> <span class="synIdentifier">roles</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;roles/accesscontextmanager.gcpAccessReader&quot;</span>, <span class="synConstant">&quot;roles/accesscontextmanager.policyReader&quot;</span> <span class="synSpecial">]</span> <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> <span class="synIdentifier">approvers</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># main.tf</span> <span class="synComment"># 組織レベルでPAMを有効にするには、PAMサービスアカウントにPAMサービスエージェントロールが必要</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_organization_iam_member&quot;</span> <span class="synConstant">&quot;pam_service_agent&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">org_id</span> = local.organization_id <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/privilegedaccessmanager.serviceAgent&quot;</span> <span class="synIdentifier">member</span> = <span class="synConstant">&quot;serviceAccount:service-org-$</span><span class="synSpecial">{</span>local.organization_id<span class="synSpecial">}</span><span class="synConstant">@gcp-sa-pam.iam.gserviceaccount.com&quot;</span> <span class="synSpecial">}</span> <span class="synType">module</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;../../modules/pam&quot;</span> <span class="synIdentifier">entitlements</span> = local.entitlements <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;organizations/$</span><span class="synSpecial">{</span>local.organization_id<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synIdentifier">location</span> = <span class="synConstant">&quot;global&quot;</span> <span class="synSpecial">}</span> <span class="synComment"># versions.tf</span> <span class="synType">terraform</span> <span class="synSpecial">{</span> <span class="synIdentifier">required_version</span> = <span class="synConstant">&quot;~&gt; 1.9.7&quot;</span> <span class="synType">required_providers</span> <span class="synSpecial">{</span> <span class="synIdentifier">google</span> = <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;hashicorp/google&quot;</span> <span class="synIdentifier">version</span> = <span class="synConstant">&quot;~&gt; 6.6.0&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> } <span class="synType">provider</span> <span class="synConstant">&quot;google&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">user_project_override</span> = <span class="synConstant">true</span> <span class="synSpecial">}</span> </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># backend.tf</span> <span class="synType">terraform</span> <span class="synSpecial">{</span> <span class="synType">backend</span> <span class="synConstant">&quot;gcs&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">bucket</span> = <span class="synConstant">&quot;common-tfstate&quot;</span> <span class="synIdentifier">prefix</span> = <span class="synConstant">&quot;terraform/yutakei/state&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># locals.tf</span> <span class="synType">locals</span> <span class="synSpecial">{</span> <span class="synIdentifier">project_id</span> = <span class="synConstant">&quot;yutakei&quot;</span> <span class="synIdentifier">apis</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;privilegedaccessmanager.googleapis.com&quot;</span>, <span class="synSpecial">]</span> <span class="synComment"># 利用申請(entitlements)の設定</span> <span class="synIdentifier">entitlements</span> = <span class="synSpecial">{</span> <span class="synIdentifier">pam1</span> = <span class="synSpecial">{</span> <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-yutakei-bigquery-demo&quot;</span> <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> <span class="synIdentifier">eligible_users</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Project&quot;</span> <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/projects/yutakei&quot;</span> <span class="synIdentifier">roles</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;roles/bigquery.jobUser&quot;</span>, <span class="synConstant">&quot;roles/bigquery.dataViewer&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> <span class="synIdentifier">approvers</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synIdentifier">pam2</span> = <span class="synSpecial">{</span> <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-yutakei-gcs-demo&quot;</span> <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> <span class="synIdentifier">eligible_users</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Project&quot;</span> <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/projects/yutakei&quot;</span> <span class="synIdentifier">roles</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;roles/storage.admin&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> <span class="synIdentifier">approvers</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;user:[email protected]&quot;</span><span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># main.tf</span> <span class="synType">module</span> <span class="synConstant">&quot;apis&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;../../../modules/apis&quot;</span> <span class="synIdentifier">project_id</span> = local.project_id <span class="synIdentifier">apis</span> = local.apis <span class="synSpecial">}</span> <span class="synType">module</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;../../../modules/pam&quot;</span> <span class="synIdentifier">entitlements</span> = local.entitlements <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;projects/$</span><span class="synSpecial">{</span>local.project_id<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synIdentifier">location</span> = <span class="synConstant">&quot;global&quot;</span> <span class="synSpecial">}</span> <span class="synComment"># versions.tf</span> 割愛 </pre> <h3 id="modules-配下-モジュール">modules 配下 (モジュール)</h3> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># main.tf</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_privileged_access_manager_entitlement&quot;</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">for_each</span> = var.entitlements <span class="synIdentifier">entitlement_id</span> = each.value.entitlement_id <span class="synIdentifier">location</span> = var.location <span class="synIdentifier">max_request_duration</span> = each.value.max_request_duration <span class="synIdentifier">parent</span> = var.parent <span class="synType">requester_justification_config</span> <span class="synSpecial">{</span> <span class="synType">unstructured</span> <span class="synSpecial">{}</span> <span class="synSpecial">}</span> <span class="synType">eligible_users</span> <span class="synSpecial">{</span> <span class="synIdentifier">principals</span> = each.value.eligible_users <span class="synSpecial">}</span> <span class="synType">privileged_access</span> <span class="synSpecial">{</span> <span class="synType">gcp_iam_access</span> <span class="synSpecial">{</span> <span class="synIdentifier">resource_type</span> = each.value.resource_type <span class="synIdentifier">resource</span> = each.value.resource <span class="synComment"># 複数のrole_bindingsを生成</span> <span class="synType">dynamic</span> <span class="synConstant">&quot;role_bindings&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">for_each</span> = each.value.roles <span class="synType">content</span> <span class="synSpecial">{</span> <span class="synIdentifier">role</span> = role_bindings.value <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">approval_workflow</span> <span class="synSpecial">{</span> <span class="synType">manual_approvals</span> <span class="synSpecial">{</span> <span class="synIdentifier">require_approver_justification</span> = each.value.require_approver_justification <span class="synType">steps</span> <span class="synSpecial">{</span> <span class="synIdentifier">approvals_needed</span> = each.value.approvals_needed <span class="synType">approvers</span> <span class="synSpecial">{</span> <span class="synIdentifier">principals</span> = each.value.approvers <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># outputs.tf</span> <span class="synType">output</span> <span class="synConstant">&quot;entitlement_ids&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;List of entitlement IDs created&quot;</span> <span class="synIdentifier">value</span> = <span class="synSpecial">[</span><span class="synStatement">for</span> entitlement <span class="synStatement">in</span> google_privileged_access_manager_entitlement.pam : entitlement.entitlement_id<span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;entitlements&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;A map of entitlement configurations&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">map</span>(<span class="synType">object</span>(<span class="synSpecial">{</span> <span class="synIdentifier">entitlement_id</span> = <span class="synType">string</span> <span class="synIdentifier">max_request_duration</span> = <span class="synType">string</span> <span class="synIdentifier">eligible_users</span> = <span class="synType">list</span>(<span class="synType">string</span>) <span class="synIdentifier">resource_type</span> = <span class="synType">string</span> <span class="synIdentifier">resource</span> = <span class="synType">string</span> <span class="synIdentifier">roles</span> = <span class="synType">list</span>(<span class="synType">string</span>) <span class="synIdentifier">require_approver_justification</span> = <span class="synType">bool</span> <span class="synIdentifier">approvals_needed</span> = <span class="synType">number</span> <span class="synIdentifier">approvers</span> = <span class="synType">list</span>(<span class="synType">string</span>) <span class="synSpecial">}</span>)) <span class="synSpecial">}</span> <span class="synComment"># variables.tf</span> <span class="synType">variable</span> <span class="synConstant">&quot;parent&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;Parent resource (e.g., project, folder, or organization)&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">string</span> <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;location&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;Location for the entitlement&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">string</span> <span class="synIdentifier">default</span> = <span class="synConstant">&quot;global&quot;</span> <span class="synSpecial">}</span> </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># main.tf</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_project_service&quot;</span> <span class="synConstant">&quot;apis&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">for_each</span> = <span class="synIdentifier">toset</span>(var.apis) <span class="synIdentifier">project</span> = var.project_id <span class="synIdentifier">service</span> = each.value <span class="synIdentifier">disable_on_destroy</span> = <span class="synConstant">false</span> <span class="synSpecial">}</span> <span class="synComment"># APIの有効化には時間がかかるため、待機時間を設定</span> <span class="synType">resource</span> <span class="synConstant">&quot;null_resource&quot;</span> <span class="synConstant">&quot;delay&quot;</span> <span class="synSpecial">{</span> <span class="synType">provisioner</span> <span class="synConstant">&quot;local-exec&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">command</span> = <span class="synConstant">&quot;sleep 180&quot;</span> <span class="synSpecial">}</span> <span class="synIdentifier">depends_on</span> = <span class="synSpecial">[</span>google_project_service.apis<span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synComment"># outputs.tf</span> <span class="synType">output</span> <span class="synConstant">&quot;enabled_apis&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;List of enabled APIs for the project&quot;</span> <span class="synIdentifier">value</span> = <span class="synSpecial">[</span><span class="synStatement">for</span> service <span class="synStatement">in</span> google_project_service.apis : service.id<span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synComment"># variables.tf</span> <span class="synType">variable</span> <span class="synConstant">&quot;apis&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;List of APIs to enable&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">list</span>(<span class="synType">string</span>) <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;project_id&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;The ID of the project to create resources in&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">string</span> <span class="synSpecial">}</span> </pre> <h1 id="デプロイ">デプロイ</h1> <h2 id="terraform-plan">terraform plan</h2> <p>GitHub Actions (<code>terraform plan</code>) の実行結果です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090513.png" width="800" height="293" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># 組織向けのワークフロー(terraform plan)</span> Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: <span class="synComment"># google_organization_iam_member.pam_service_agent will be created</span> + resource <span class="synConstant">&quot;google_organization_iam_member&quot;</span> <span class="synConstant">&quot;pam_service_agent&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">etag</span> = (known after apply) + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">member</span> = <span class="synConstant">&quot;serviceAccount:[email protected]&quot;</span> + <span class="synIdentifier">org_id</span> = <span class="synConstant">&quot;1234567890&quot;</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/privilegedaccessmanager.serviceAgent&quot;</span> <span class="synSpecial">}</span> <span class="synComment"># module.pam.google_privileged_access_manager_entitlement.pam[&quot;pam_org1&quot;] will be created</span> + resource <span class="synConstant">&quot;google_privileged_access_manager_entitlement&quot;</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">create_time</span> = (known after apply) + <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-organization-acm-demo&quot;</span> + <span class="synIdentifier">etag</span> = (known after apply) + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">location</span> = <span class="synConstant">&quot;global&quot;</span> + <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> + <span class="synIdentifier">name</span> = (known after apply) + <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;organizations/1234567890&quot;</span> + <span class="synIdentifier">state</span> = (known after apply) + <span class="synIdentifier">update_time</span> = (known after apply) + approval_workflow <span class="synSpecial">{</span> + manual_approvals <span class="synSpecial">{</span> + <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> + steps <span class="synSpecial">{</span> + <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> + approvers <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + eligible_users <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> + privileged_access <span class="synSpecial">{</span> + gcp_iam_access <span class="synSpecial">{</span> + <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/organizations/1234567890&quot;</span> + <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Organization&quot;</span> + role_bindings <span class="synSpecial">{</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/accesscontextmanager.gcpAccessReader&quot;</span> <span class="synSpecial">}</span> + role_bindings <span class="synSpecial">{</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/accesscontextmanager.policyReader&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + requester_justification_config <span class="synSpecial">{</span> + unstructured <span class="synSpecial">{}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> Plan: <span class="synConstant">2</span> to add, <span class="synConstant">0</span> to change, <span class="synConstant">0</span> to destroy. </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># プロジェクト向けのワークフロー(terraform plan)</span> Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: <span class="synComment"># module.apis.google_project_service.apis[&quot;privilegedaccessmanager.googleapis.com&quot;] will be created</span> + resource <span class="synConstant">&quot;google_project_service&quot;</span> <span class="synConstant">&quot;apis&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">disable_on_destroy</span> = <span class="synConstant">false</span> + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">project</span> = <span class="synConstant">&quot;yutakei&quot;</span> + <span class="synIdentifier">service</span> = <span class="synConstant">&quot;privilegedaccessmanager.googleapis.com&quot;</span> <span class="synSpecial">}</span> <span class="synComment"># module.apis.null_resource.delay will be created</span> + resource <span class="synConstant">&quot;null_resource&quot;</span> <span class="synConstant">&quot;delay&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">id</span> = (known after apply) <span class="synSpecial">}</span> <span class="synComment"># module.pam.google_privileged_access_manager_entitlement.pam[&quot;pam1&quot;] will be created</span> + resource <span class="synConstant">&quot;google_privileged_access_manager_entitlement&quot;</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">create_time</span> = (known after apply) + <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-yutakei-bigquery-demo&quot;</span> + <span class="synIdentifier">etag</span> = (known after apply) + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">location</span> = <span class="synConstant">&quot;global&quot;</span> + <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> + <span class="synIdentifier">name</span> = (known after apply) + <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;projects/yutakei&quot;</span> + <span class="synIdentifier">state</span> = (known after apply) + <span class="synIdentifier">update_time</span> = (known after apply) + approval_workflow <span class="synSpecial">{</span> + manual_approvals <span class="synSpecial">{</span> + <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> + steps <span class="synSpecial">{</span> + <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> + approvers <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + eligible_users <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> + privileged_access <span class="synSpecial">{</span> + gcp_iam_access <span class="synSpecial">{</span> + <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/projects/yutakei&quot;</span> + <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Project&quot;</span> + role_bindings <span class="synSpecial">{</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/bigquery.jobUser&quot;</span> <span class="synSpecial">}</span> + role_bindings <span class="synSpecial">{</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/bigquery.dataViewer&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + requester_justification_config <span class="synSpecial">{</span> + unstructured <span class="synSpecial">{}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment"># module.pam.google_privileged_access_manager_entitlement.pam[&quot;pam2&quot;] will be created</span> + resource <span class="synConstant">&quot;google_privileged_access_manager_entitlement&quot;</span> <span class="synConstant">&quot;pam&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">create_time</span> = (known after apply) + <span class="synIdentifier">entitlement_id</span> = <span class="synConstant">&quot;pam-yutakei-gcs-demo&quot;</span> + <span class="synIdentifier">etag</span> = (known after apply) + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">location</span> = <span class="synConstant">&quot;global&quot;</span> + <span class="synIdentifier">max_request_duration</span> = <span class="synConstant">&quot;3600s&quot;</span> + <span class="synIdentifier">name</span> = (known after apply) + <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;projects/yutakei&quot;</span> + <span class="synIdentifier">state</span> = (known after apply) + <span class="synIdentifier">update_time</span> = (known after apply) + approval_workflow <span class="synSpecial">{</span> + manual_approvals <span class="synSpecial">{</span> + <span class="synIdentifier">require_approver_justification</span> = <span class="synConstant">true</span> + steps <span class="synSpecial">{</span> + <span class="synIdentifier">approvals_needed</span> = <span class="synConstant">1</span> + approvers <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + eligible_users <span class="synSpecial">{</span> + <span class="synIdentifier">principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;user:[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> + privileged_access <span class="synSpecial">{</span> + gcp_iam_access <span class="synSpecial">{</span> + <span class="synIdentifier">resource</span> = <span class="synConstant">&quot;//cloudresourcemanager.googleapis.com/projects/yutakei&quot;</span> + <span class="synIdentifier">resource_type</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com/Project&quot;</span> + role_bindings <span class="synSpecial">{</span> + <span class="synIdentifier">role</span> = <span class="synConstant">&quot;roles/storage.admin&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> + requester_justification_config <span class="synSpecial">{</span> + unstructured <span class="synSpecial">{}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> Plan: <span class="synConstant">4</span> to add, <span class="synConstant">0</span> to change, <span class="synConstant">0</span> to destroy. </pre> <h2 id="terraform-apply">terraform apply</h2> <p>GitHub Actions (<code>terraform apply</code>) の実行結果です。戻り値は先ほど同様のため割愛します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090517.png" width="800" height="291" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="リソース">リソース</h2> <p>組織では <code>PAM サービスアカウントに対する IAM Policy</code>と<code>利用資格</code>がデプロイされました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090520.png" width="800" height="279" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090524.png" width="800" height="255" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>プロジェクトでも<code>利用資格</code>がデプロイされました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090527.png" width="800" height="255" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="動作確認">動作確認</h1> <h2 id="申請">申請</h2> <p>申請者のアカウントで PAM の管理画面にアクセスし、<code>権限付与をリクエスト</code> をクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090531.png" width="800" height="226" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下3項目を入力し、<code>権限付与をリクエスト</code>をクリックすると、承認フローが承認者へと進みます。</p> <ul> <li><code>権限付与の期間</code> (必須、最大時間が1時間の場合、30分/45分/1時間から選択できる)</li> <li><code>理由</code> (å¿…é ˆ)</li> <li><code>通知の受信者</code> (任意、省略しても利用資格で設定した承認者にメール通知が行われる)</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090534.png" width="800" height="548" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>承認されるまでの間、当該利用資格のステータスは <code>Approval Awaited</code> となります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090538.png" width="800" height="206" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span>`</p> <h2 id="承認">承認</h2> <p>承認者のアカウントで PAM の管理画面にアクセスすると、申請者からの承認フローが回ってきたことがわかります。</p> <p>以下のメール通知が届きます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090550.png" width="800" height="698" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><code>承認 / 拒否</code>をクリックし申請内容を確認します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090542.png" width="800" height="204" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>コメント欄 (å¿…é ˆ) に承認する旨を入力し、<code>承認</code>をクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090545.png" width="800" height="565" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="権限付与">権限付与</h2> <p>承認者には以下のメール通知が届きます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090554.png" width="800" height="784" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>利用資格のステータスも <code>Approval Awaited &gt; Active</code> となっており、権限付与の残り時間も表示されています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090558.png" width="800" height="207" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>IAM Policy の管理画面を確認すると、今回利用資格の中で定義した2つのロールが PAM によって付与されたことがわかります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090601.png" width="800" height="206" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="権限はく奪">権限はく奪</h2> <p>申請時に希望した付与時間 (今回は30分) が経過すると、先ほどまで付与されていたロールが PAM によってはく奪されていることがわかります。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090608.png" width="800" height="189" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="再申請">再申請</h2> <p>利用資格のステータスが <code>Available</code> (申請開始前の状態) に戻っており、必要な際には再度同じ利用資格を使って申請が行えます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241213/20241213090605.png" width="800" height="212" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-yutakei/20220512/20220512214329.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">武井 祐介 <a href="https://blog.g-gen.co.jp/archive/author/ggen-yutakei">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2025 選出。IaC ã‚„ CI/CD 周りのサービスやプロダクトが興味分野です。</p> <p class="sw-profile__txt">趣味はロードバイク、ロードレースやサッカー観戦です。</p> <!-- 以下の行を追加 --> <a href="https://twitter.com/ggenyutakei?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ggenyutakei</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-yutakei Google CloudとGitHub Actions(Terraform)を連携するDirect Workload Identityを作成するbashスクリプト hatenablog://entry/6802418398303133454 2024-12-11T09:00:00+09:00 2024-12-11T09:00:03+09:00 G-gen の武井です。当記事では Google Cloud と GitHub Actions (Terraform) を連携する Direct Workload Identity を作成する bash スクリプトを紹介します。 はじめに 概要 以前の記事との違い 制限事項 前提条件 免責事項 ソースコード スクリプトの使い方 認証 変数設定 実行 リソースの確認 Workload Identity プール・プロバイダー サービスアカウント Workload Identity プールの IAM Policy 構成 ソースコード (Terraform) Terraform ディレクトリ構成 ワー… <p>G-gen の武井です。当記事では Google Cloud と GitHub Actions (Terraform) を連携する Direct Workload Identity を作成する bash スクリプトを紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#概要">概要</a></li> <li><a href="#以前の記事との違い">以前の記事との違い</a></li> <li><a href="#制限事項">制限事項</a></li> <li><a href="#前提条件">前提条件</a></li> <li><a href="#免責事項">免責事項</a></li> </ul> </li> <li><a href="#ソースコード">ソースコード</a></li> <li><a href="#スクリプトの使い方">スクリプトの使い方</a><ul> <li><a href="#認証">認証</a></li> <li><a href="#変数設定">変数設定</a></li> <li><a href="#実行">実行</a></li> <li><a href="#リソースの確認">リソースの確認</a><ul> <li><a href="#Workload-Identity-プールプロバイダー">Workload Identity プール・プロバイダー</a></li> <li><a href="#サービスアカウント">サービスアカウント</a></li> <li><a href="#Workload-Identity-プールの-IAM-Policy">Workload Identity プールの IAM Policy</a></li> </ul> </li> <li><a href="#構成">構成</a></li> <li><a href="#ソースコード-Terraform">ソースコード (Terraform)</a><ul> <li><a href="#Terraform-ディレクトリ構成">Terraform ディレクトリ構成</a></li> <li><a href="#ワークフロー-terraformyaml">ワークフロー (terraform.yaml)</a></li> <li><a href="#envdemo-配下-呼び出し側">env/demo 配下 (呼び出し側)</a></li> <li><a href="#modulesapis-配下-モジュール">modules/apis 配下 (モジュール)</a></li> </ul> </li> <li><a href="#プルリクエスト-terraform-plan">プルリクエスト (terraform plan)</a></li> <li><a href="#マージ-terraform-apply">マージ (terraform apply)</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090037.png" width="800" height="449" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="概要">概要</h2> <p>当記事で紹介するのは、Google Cloud と GitHub Actions (Terraform) との連携に必要な <strong>Direct Workload Identity</strong> リソースを作成する bash スクリプトです。</p> <h2 id="以前の記事との違い">以前の記事との違い</h2> <p>以前執筆した記事で紹介したのは、<code>サービスアカウントの権限を借用する形式</code>の Workload Identity リソースを作成するスクリプトです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcreate-workload-identity-for-gha-terraform" title="Google CloudとGitHub Actions(Terraform)を連携するWorkload Identityを作成するbashスクリプト - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/create-workload-identity-for-gha-terraform">blog.g-gen.co.jp</a></cite></p> <p>今回ご紹介するのは、<strong><code>Workload Identity プールに必要な権限 (IAM ロール) を直接付与する形式</code></strong>の Workload Identity リソースを作成するスクリプトです。</p> <p>この方式は、サービスアカウントの払い出しやサービスアカウントを借用するための権限付与が必要ないため、従来よりもセキュアな連携が可能で、Google Cloud、ならびに GitHub の公式ドキュメント上でも推奨されています。</p> <ul> <li>参考:<a href="https://cloud.google.com/iam/docs/workload-identity-federation?hl=ja#access_management">アクセス管理</a></li> <li>参考:<a href="https://github.com/google-github-actions/auth?tab=readme-ov-file#preferred-direct-workload-identity-federation">(Preferred) Direct Workload Identity Federation</a></li> </ul> <h2 id="制限事項">制限事項</h2> <p>推奨される形式ではあるものの、Direct Workload Identity には<strong>対応可能なプロダクトや機能に制限があります。</strong></p> <p>対応していないプロダクトやその機能を管理したい場合、従来形式 (サービスアカウントの権限を借用する形式) の Workload Identity をご利用ください。</p> <ul> <li>参考:<a href="https://cloud.google.com/iam/docs/federated-identity-supported-services?hl=ja">ID 連携: プロダクトと制限事項</a></li> </ul> <h2 id="前提条件">前提条件</h2> <p>当 bash スクリプトは、<code>Debian GNU/Linux 12 (bookworm)</code> 上で開発され、動作確認されています。</p> <p>また、以下のソフトウェアがインストールされていることが前提です。カッコ内は開発時のバージョンです。</p> <ul> <li>gcloud(<code>Google Cloud SDK 486.0.0</code>)</li> </ul> <p>スクリプト実行時は、実行先のプロジェクトに対して gcloud CLI を認証する必要があります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/sdk/docs/authorizing?hl=ja#user-account">ユーザー アカウントを使用して認可する</a></li> <li>参考 : <a href="https://cloud.google.com/sdk/docs/authorizing?hl=ja#service-account">サービス アカウントを使用して承認する</a></li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fdifference-of-gloud-auth-commands" title="gcloud auth loginとgcloud auth application-default loginの違いとは? - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/difference-of-gloud-auth-commands">blog.g-gen.co.jp</a></cite></p> <h2 id="免責事項">免責事項</h2> <p>当記事で紹介するプログラムのソースコードは、ご自身の責任のもと、使用、引用、改変、再配布して構いません。</p> <p>ただし、同ソースコードが原因で発生した不利益やトラブルについては、当社は一切の責任を負いません。</p> <h1 id="ソースコード">ソースコード</h1> <p>前述の <code>免責事項</code> をご理解のうえ、ご利用ください。</p> <p><strong>init.sh</strong></p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment">#!/bin/bash</span> <span class="synComment"># エラーハンドリング: エラーが発生したらスクリプトを終了</span> <span class="synStatement">set</span><span class="synIdentifier"> </span><span class="synSpecial">-e</span> <span class="synComment"># 変数の設定</span> <span class="synIdentifier">PROJECT_ID</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># プロジェクトID (ex: gha-demo-prj) </span> <span class="synIdentifier">PROJECT_NUMBER</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># プロジェクト番号 (ex: 1234567890)</span> <span class="synIdentifier">ORGANIZATION_ID</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># プロジェクトの組織ID (ex: 0123456789)</span> <span class="synIdentifier">WORKLOAD_IDENTITY_POOL</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># Workload Identityプール名 (ex: gha-demo-pool)</span> <span class="synIdentifier">WORKLOAD_IDENTITY_PROVIDER</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># Workload Identityプロバイダ名 (ex: gha-demo-provider)</span> <span class="synIdentifier">GITHUB_REPO</span>=<span class="synStatement">&quot;&quot;</span> <span class="synComment"># GitHubリポジトリ名 (ex: gha-demo-org/gha-demo-repo)</span> <span class="synComment"># ログ出力関数</span> <span class="synIdentifier">log() {</span> <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">[INFO] </span><span class="synPreProc">$1</span><span class="synStatement">&quot;</span> <span class="synIdentifier">}</span> <span class="synIdentifier">log_error() {</span> <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">[ERROR] </span><span class="synPreProc">$1</span><span class="synStatement">&quot;</span><span class="synConstant"> </span><span class="synStatement">&gt;&amp;2</span> <span class="synIdentifier">}</span> <span class="synComment"># 1. IAM Credential API を有効化</span> <span class="synStatement">if !</span> gcloud services list <span class="synSpecial">--enabled</span> <span class="synSpecial">--filter</span><span class="synStatement">=&quot;</span><span class="synConstant">name:iamcredentials.googleapis.com</span><span class="synStatement">&quot;</span> <span class="synSpecial">--format</span><span class="synStatement">=&quot;</span><span class="synConstant">value(name)</span><span class="synStatement">&quot;</span> <span class="synStatement">|</span> <span class="synStatement">grep</span> <span class="synStatement">&quot;</span><span class="synConstant">iamcredentials.googleapis.com</span><span class="synStatement">&quot;</span> <span class="synStatement">&gt;</span>/dev/null <span class="synConstant">2</span><span class="synStatement">&gt;&amp;</span><span class="synConstant">1</span><span class="synStatement">;</span> <span class="synStatement">then</span> log <span class="synStatement">&quot;</span><span class="synConstant">IAM Credential API を有効にしています...</span><span class="synStatement">&quot;</span> gcloud services <span class="synStatement">enable</span> iamcredentials.googleapis.com <span class="synSpecial">--project</span><span class="synStatement">=&quot;</span><span class="synPreProc">$PROJECT_ID</span><span class="synStatement">&quot;</span> <span class="synStatement">else</span> log <span class="synStatement">&quot;</span><span class="synConstant">IAM Credential API は既に有効化されています</span><span class="synStatement">&quot;</span> <span class="synStatement">fi</span> <span class="synComment"># 2. Workload Identity プールの作成</span> <span class="synStatement">if !</span> gcloud iam workload-identity-pools describe <span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span> <span class="synSpecial">--location</span><span class="synStatement">=&quot;</span><span class="synConstant">global</span><span class="synStatement">&quot;</span> <span class="synSpecial">--project</span><span class="synStatement">=&quot;</span><span class="synPreProc">$PROJECT_ID</span><span class="synStatement">&quot;</span> <span class="synStatement">&gt;</span>/dev/null <span class="synConstant">2</span><span class="synStatement">&gt;&amp;</span><span class="synConstant">1</span><span class="synStatement">;</span> <span class="synStatement">then</span> log <span class="synStatement">&quot;</span><span class="synConstant">Workload Identity プールを作成中: </span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> gcloud iam workload-identity-pools create <span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span> \ <span class="synSpecial">--project</span><span class="synStatement">=&quot;</span><span class="synPreProc">$PROJECT_ID</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--location</span><span class="synStatement">=&quot;</span><span class="synConstant">global</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--display-name</span><span class="synStatement">=&quot;</span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> <span class="synStatement">else</span> log <span class="synStatement">&quot;</span><span class="synConstant">Workload Identity プールは既に存在します: </span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> <span class="synStatement">fi</span> <span class="synComment"># 3. Workload Identity プロバイダの作成</span> <span class="synStatement">if !</span> gcloud iam workload-identity-pools providers describe <span class="synPreProc">$WORKLOAD_IDENTITY_PROVIDER</span> <span class="synSpecial">--workload-identity-pool</span><span class="synStatement">=&quot;</span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> <span class="synSpecial">--location</span><span class="synStatement">=&quot;</span><span class="synConstant">global</span><span class="synStatement">&quot;</span> <span class="synSpecial">--project</span><span class="synStatement">=&quot;</span><span class="synPreProc">$PROJECT_ID</span><span class="synStatement">&quot;</span> <span class="synStatement">&gt;</span>/dev/null <span class="synConstant">2</span><span class="synStatement">&gt;&amp;</span><span class="synConstant">1</span><span class="synStatement">;</span> <span class="synStatement">then</span> log <span class="synStatement">&quot;</span><span class="synConstant">Workload Identity プロバイダを作成中: </span><span class="synPreProc">$WORKLOAD_IDENTITY_PROVIDER</span><span class="synStatement">&quot;</span> gcloud iam workload-identity-pools providers create-oidc <span class="synPreProc">$WORKLOAD_IDENTITY_PROVIDER</span> \ <span class="synSpecial">--project</span><span class="synStatement">=&quot;</span><span class="synPreProc">$PROJECT_ID</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--location</span><span class="synStatement">=&quot;</span><span class="synConstant">global</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--workload-identity-pool</span><span class="synStatement">=&quot;</span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--display-name</span><span class="synStatement">=&quot;</span><span class="synPreProc">$WORKLOAD_IDENTITY_PROVIDER</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--issuer-uri</span><span class="synStatement">=&quot;</span><span class="synConstant">https://token.actions.githubusercontent.com</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--attribute-mapping</span><span class="synStatement">=&quot;</span><span class="synConstant">google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--attribute-condition</span><span class="synStatement">=&quot;</span><span class="synConstant">assertion.repository=='</span><span class="synPreProc">$GITHUB_REPO</span><span class="synConstant">'</span><span class="synStatement">&quot;</span> <span class="synStatement">else</span> log <span class="synStatement">&quot;</span><span class="synConstant">Workload Identity プロバイダは既に存在します: </span><span class="synPreProc">$WORKLOAD_IDENTITY_PROVIDER</span><span class="synStatement">&quot;</span> <span class="synStatement">fi</span> <span class="synComment"># 4. 組織レベルでのロール付与</span> log <span class="synStatement">&quot;</span><span class="synConstant">組織レベルでのロール付与の確認</span><span class="synStatement">&quot;</span> <span class="synStatement">for</span> role <span class="synStatement">in</span> <span class="synStatement">&quot;</span><span class="synConstant">roles/resourcemanager.organizationAdmin</span><span class="synStatement">&quot;</span> <span class="synStatement">&quot;</span><span class="synConstant">roles/owner</span><span class="synStatement">&quot;</span>; <span class="synStatement">do</span> <span class="synStatement">if !</span> gcloud organizations get-iam-policy <span class="synPreProc">$ORGANIZATION_ID</span> <span class="synSpecial">--flatten</span><span class="synStatement">=&quot;</span><span class="synConstant">bindings[].members</span><span class="synStatement">&quot;</span> <span class="synSpecial">--filter</span><span class="synStatement">=&quot;</span><span class="synConstant">bindings.members:principalSet://iam.googleapis.com/projects/</span><span class="synPreProc">$PROJECT_NUMBER</span><span class="synConstant">/locations/global/workloadIdentityPools/</span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synConstant"> AND bindings.role:</span><span class="synPreProc">$role</span><span class="synStatement">&quot;</span> <span class="synSpecial">--format</span><span class="synStatement">=&quot;</span><span class="synConstant">value(bindings.role)</span><span class="synStatement">&quot;</span> <span class="synStatement">|</span> <span class="synStatement">grep</span> <span class="synStatement">&quot;</span><span class="synPreProc">$role</span><span class="synStatement">&quot;</span> <span class="synStatement">&gt;</span>/dev/null <span class="synConstant">2</span><span class="synStatement">&gt;&amp;</span><span class="synConstant">1</span><span class="synStatement">;</span> <span class="synStatement">then</span> log <span class="synStatement">&quot;</span><span class="synPreProc">$role</span><span class="synConstant"> ã‚’Workload Identity プールに付与中: </span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> gcloud organizations add-iam-policy-binding <span class="synPreProc">$ORGANIZATION_ID</span> \ <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">principalSet://iam.googleapis.com/projects/</span><span class="synPreProc">$PROJECT_NUMBER</span><span class="synConstant">/locations/global/workloadIdentityPools/</span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synConstant">/attribute.repository/</span><span class="synPreProc">$GITHUB_REPO</span><span class="synStatement">&quot;</span> \ <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synPreProc">$role</span><span class="synStatement">&quot;</span> <span class="synStatement">else</span> log <span class="synStatement">&quot;</span><span class="synPreProc">$role</span><span class="synConstant"> は既にWorkload Identity プールに付与されています: </span><span class="synPreProc">$WORKLOAD_IDENTITY_POOL</span><span class="synStatement">&quot;</span> <span class="synStatement">fi</span> <span class="synStatement">done</span> log <span class="synStatement">&quot;</span><span class="synConstant">Direct Workload Identity 設定が完了しました。</span><span class="synStatement">&quot;</span> </pre> <h1 id="スクリプトの使い方">スクリプトの使い方</h1> <h2 id="認証">認証</h2> <p>まずは実行先のプロジェクトに対して gcloud CLI の認証を通します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 実行先プロジェクトの確認</span> $ gcloud config list <span class="synStatement">[</span>core<span class="synStatement">]</span> account <span class="synStatement">=</span> [email protected] disable_usage_reporting <span class="synStatement">=</span> True project <span class="synStatement">=</span> gha-demo-prj Your active configuration is: <span class="synStatement">[</span>gha-demo-prj<span class="synStatement">]</span> <span class="synComment"># gcloud CLI の認証</span> $ gcloud auth login ~~中略~~ You are now logged <span class="synError">in</span> as <span class="synStatement">[</span>[email protected]<span class="synStatement">]</span>. Your current project is <span class="synStatement">[</span>gha-demo-prj<span class="synStatement">]</span>. </pre> <h2 id="変数設定">変数設定</h2> <p><code>7~12行目</code>の変数に環境情報を入力します。</p> <p>※ 当スクリプトでは、サービスアカウントの変数定義はありません。</p> <h2 id="実行">実行</h2> <p>スクリプトに実行権限を付与して実行します。</p> <p>※ 当スクリプトでは、以下のリソースは作成しません。</p> <ul> <li><code>サービスアカウント</code></li> <li><code>サービスアカウントを借用するための IAM Policy</code></li> <li><code>Workload Identity プールとサービスアカウントの紐づけ</code></li> </ul> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 実行権限付与</span> $ <span class="synStatement">chmod</span> <span class="synSpecial">+x</span> init.sh $ <span class="synStatement">ls</span> <span class="synSpecial">-l</span> -rwxr-xr-x <span class="synConstant">1</span> test-user test-user <span class="synConstant">3784</span> Nov <span class="synConstant">12</span> 14:27 init.sh </pre> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># スクリプト実行</span> $ ./init.sh <span class="synStatement">[</span>INFO<span class="synStatement">]</span> IAM Credential API は既に有効化されています <span class="synStatement">[</span>INFO<span class="synStatement">]</span> Workload Identity プールを作成中: gha-demo-pool Created workload identity pool <span class="synStatement">[</span>gha-demo-pool<span class="synStatement">]</span>. <span class="synStatement">[</span>INFO<span class="synStatement">]</span> Workload Identity プロバイダを作成中: gha-demo-provider Created workload identity pool provider <span class="synStatement">[</span>gha-demo-provider<span class="synStatement">]</span>. <span class="synStatement">[</span>INFO<span class="synStatement">]</span> 組織レベルでのロール付与の確認 <span class="synStatement">[</span>INFO<span class="synStatement">]</span> roles/resourcemanager.organizationAdmin ã‚’Workload Identity プールに付与中: gha-demo-pool Updated IAM policy <span class="synStatement">for</span> organization <span class="synStatement">[</span><span class="synConstant">0123456789</span><span class="synStatement">]</span>. ~~中略~~ <span class="synStatement">[</span>INFO<span class="synStatement">]</span> roles/owner ã‚’Workload Identity プールに付与中: gha-demo-pool Updated IAM policy <span class="synStatement">for</span> organization <span class="synStatement">[</span><span class="synConstant">0123456789</span><span class="synStatement">]</span>. ~~中略~~ <span class="synStatement">[</span>INFO<span class="synStatement">]</span> Workload Identity 設定が完了しました。 </pre> <h2 id="リソースの確認">リソースの確認</h2> <h3 id="Workload-Identity-プールプロバイダー">Workload Identity プール・プロバイダー</h3> <p>以下のように作成されます。</p> <p><figure class="figure-image figure-image-fotolife" title="Workload Identity プール (1/2)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090011.png" width="800" height="330" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload Identity プール (1/2)</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="Workload Identity プール (2/2)、サービスアカウントを使用していない"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090014.png" width="800" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload Identity プール (2/2)、サービスアカウントを使用していない</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="Workload Identity プロバイダー (1/2)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090018.png" width="800" height="490" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload Identity プロバイダー (1/2)</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="Workload Identity プロバイダー (2/2)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090021.png" width="800" height="553" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload Identity プロバイダー (2/2)</figcaption></figure></p> <h3 id="サービスアカウント">サービスアカウント</h3> <p>前述の通り、Direct Workload Identity にサービスアカウントは不要なため、当スクリプトでは作成しません。</p> <h3 id="Workload-Identity-プールの-IAM-Policy">Workload Identity プールの IAM Policy</h3> <p>以下のように作成されます。<br/> ※ IAM ロールは適用先のセキュリティポリシーに応じて調整してください。</p> <p><figure class="figure-image figure-image-fotolife" title="組織レベルの IAM Policy"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090047.png" width="800" height="357" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>組織レベルの IAM Policy</figcaption></figure></p> <h2 id="構成">構成</h2> <p>当スクリプトで作成される Workload Identity を使い、Google Cloud プロジェクトに対する <code>terraform plan</code> ã‚„ <code>terraform apply</code> を、GitHub Actions で自動化します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241028/20241028090051.png" width="800" height="323" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ワークフローや Terraform ソースコードは次項に記載のものを使用します。<br/> ご利用される場合は、前述の <code>免責事項</code> をご理解のうえ、ご利用ください。</p> <h2 id="ソースコード-Terraform">ソースコード (Terraform)</h2> <h3 id="Terraform-ディレクトリ構成">Terraform ディレクトリ構成</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>. ├── .github │ └── workflows │ └── terraform.yaml ├── env │ └── demo │ ├── backend.tf │ ├── locals.tf │ ├── main.tf │ └── versions.tf ├── modules │ └── apis │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── .gitignore ├── init.sh └── README.md </pre> <h3 id="ワークフロー-terraformyaml">ワークフロー (terraform.yaml)</h3> <p>以下の値をご自身の環境で作成したリソースに置き換えてください。</p> <ul> <li><code>38行目</code>: Workload Identity プロバイダー</li> </ul> <p>Direct Workload Identity では、<code>google-github-actions/auth@v2</code> でサービスアカウントを定義する必要はありません。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">name</span><span class="synSpecial">:</span> terraform <span class="synComment"># main ブランチへの Pull request と Merge</span> <span class="synIdentifier">on</span><span class="synSpecial">:</span> <span class="synIdentifier">pull_request</span><span class="synSpecial">:</span> <span class="synIdentifier">branches</span><span class="synSpecial">:</span> <span class="synStatement">- </span>main <span class="synIdentifier">push</span><span class="synSpecial">:</span> <span class="synIdentifier">branches</span><span class="synSpecial">:</span> <span class="synStatement">- </span>main <span class="synComment"># ジョブ (GitHUb runners で実行)</span> <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">terraform-workflow</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">id-token</span><span class="synSpecial">:</span> write <span class="synIdentifier">contents</span><span class="synSpecial">:</span> read <span class="synIdentifier">pull-requests</span><span class="synSpecial">:</span> write <span class="synIdentifier">strategy</span><span class="synSpecial">:</span> <span class="synIdentifier">matrix</span><span class="synSpecial">:</span> <span class="synComment"> # tf_working_dir に main.tf (呼び出し側) のディレクトリを指定</span> <span class="synIdentifier">tf_working_dir</span><span class="synSpecial">:</span> <span class="synStatement">- </span>./env/demo <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/checkout@v4 <span class="synIdentifier">name</span><span class="synSpecial">:</span> Checkout <span class="synIdentifier">id</span><span class="synSpecial">:</span> checkout <span class="synComment"> # Workload Identity 連携</span> <span class="synComment"> # https://cloud.google.com/iam/docs/using-workload-identity-federation#generate-automatic</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synConstant">'auth'</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synConstant">'Authenticate to Google Cloud'</span> <span class="synIdentifier">uses</span><span class="synSpecial">:</span> <span class="synConstant">'google-github-actions/auth@v2'</span> <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">workload_identity_provider</span><span class="synSpecial">:</span> <span class="synConstant">'projects/1234567890/locations/global/workloadIdentityPools/gha-demo-pool/providers/gha-demo-provider'</span> <span class="synComment"> # https://github.com/marketplace/actions/setup-tfcmt </span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> shmokmt/actions-setup-tfcmt@v2 <span class="synIdentifier">name</span><span class="synSpecial">:</span> Setup tfcmt <span class="synComment"> # https://github.com/marketplace/actions/setup-github-comment</span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> shmokmt/actions-setup-github-comment@v2 <span class="synIdentifier">name</span><span class="synSpecial">:</span> Setup github-comment <span class="synComment"> # https://github.com/actions/setup-node</span> <span class="synComment"> # https://github.com/hashicorp/setup-terraform/issues/84</span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/setup-node@v4 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">node-version</span><span class="synSpecial">:</span> <span class="synConstant">'18'</span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> hashicorp/setup-terraform@v3 <span class="synIdentifier">name</span><span class="synSpecial">:</span> Setup terraform <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform fmt <span class="synIdentifier">id</span><span class="synSpecial">:</span> fmt <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd ${{ matrix.tf_working_dir }} terraform fmt -recursive <span class="synIdentifier">continue-on-error</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform Init <span class="synIdentifier">id</span><span class="synSpecial">:</span> init <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd ${{ matrix.tf_working_dir }} terraform init -upgrade <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform Validate <span class="synIdentifier">id</span><span class="synSpecial">:</span> validate <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd ${{ matrix.tf_working_dir }} terraform validate <span class="synComment"> # main ブランチへ pull request した際に terraform plan を実行</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform Plan <span class="synIdentifier">id</span><span class="synSpecial">:</span> plan <span class="synIdentifier">if</span><span class="synSpecial">:</span> github.event_name == <span class="synConstant">'pull_request'</span> <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd ${{ matrix.tf_working_dir }} export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} tfcmt -var target:${{ matrix.tf_working_dir }} plan -- terraform plan --parallelism=50 github-comment hide -condition <span class="synConstant">'Comment.Body contains &quot;No changes.&quot;'</span> <span class="synIdentifier">continue-on-error</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synComment"> # terraform status で失敗した際に workflow を停止</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform Plan Status <span class="synIdentifier">id</span><span class="synSpecial">:</span> status <span class="synIdentifier">if</span><span class="synSpecial">:</span> steps.plan.outcome == <span class="synConstant">'failure'</span> <span class="synIdentifier">run</span><span class="synSpecial">:</span> exit <span class="synConstant">1</span> <span class="synComment"> # main ブランチへ push した際に terraform apply を実行</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Terraform Apply <span class="synIdentifier">id</span><span class="synSpecial">:</span> apply <span class="synIdentifier">if</span><span class="synSpecial">:</span> github.ref == <span class="synConstant">'refs/heads/main'</span> <span class="synType">&amp;&amp;</span> github.event_name == <span class="synConstant">'push'</span> <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd ${{ matrix.tf_working_dir }} export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} tfcmt -var target:${{ matrix.tf_working_dir }} apply -- terraform apply -auto-approve -input=<span class="synConstant">false</span> --parallelism=50 </pre> <h3 id="envdemo-配下-呼び出し側">env/demo 配下 (呼び出し側)</h3> <pre class="code lang-tf" data-lang="tf" data-unlink># backend.tf terraform <span class="synSpecial">{</span> backend &quot;<span class="synConstant">gcs</span>&quot; <span class="synSpecial">{</span> bucket <span class="synStatement">=</span> &quot;<span class="synConstant">gha-demo-prj-tfstate</span>&quot; prefix <span class="synStatement">=</span> &quot;<span class="synConstant">terraform/state</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> # locals.tf locals <span class="synSpecial">{</span> project_id <span class="synStatement">=</span> &quot;<span class="synConstant">gha-demo-prj</span>&quot; apis <span class="synStatement">=</span> <span class="synSpecial">[</span> &quot;<span class="synConstant">artifactregistry.googleapis.com</span>&quot;, &quot;<span class="synConstant">cloudapis.googleapis.com</span>&quot;, &quot;<span class="synConstant">cloudasset.googleapis.com</span>&quot;, &quot;<span class="synConstant">cloudresourcemanager.googleapis.com</span>&quot;, &quot;<span class="synConstant">iam.googleapis.com</span>&quot;, &quot;<span class="synConstant">iamcredentials.googleapis.com</span>&quot;, &quot;<span class="synConstant">servicemanagement.googleapis.com</span>&quot;, &quot;<span class="synConstant">serviceusage.googleapis.com</span>&quot;, &quot;<span class="synConstant">sts.googleapis.com</span>&quot;, <span class="synSpecial">]</span> <span class="synSpecial">}</span> # main.tf module &quot;<span class="synConstant">apis</span>&quot; <span class="synSpecial">{</span> source <span class="synStatement">=</span> &quot;<span class="synConstant">../../modules/apis</span>&quot; project_id <span class="synStatement">=</span> local.project_id apis <span class="synStatement">=</span> local.apis <span class="synSpecial">}</span> # versions.tf terraform <span class="synSpecial">{</span> required_version <span class="synStatement">=</span> &quot;<span class="synConstant">~&gt; 1.9.7</span>&quot; required_providers <span class="synSpecial">{</span> google <span class="synStatement">=</span> <span class="synSpecial">{</span> source <span class="synStatement">=</span> &quot;<span class="synConstant">hashicorp/google</span>&quot; <span class="synStatement">version</span> <span class="synStatement">=</span> &quot;<span class="synConstant">~&gt; 6.6.0</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> provider &quot;<span class="synConstant">google</span>&quot; <span class="synSpecial">{</span> user_project_override <span class="synStatement">=</span> true <span class="synSpecial">}</span> </pre> <h3 id="modulesapis-配下-モジュール">modules/apis 配下 (モジュール)</h3> <pre class="code lang-tf" data-lang="tf" data-unlink># main.tf resource &quot;<span class="synConstant">google_project_service</span>&quot; &quot;<span class="synConstant">apis</span>&quot; <span class="synSpecial">{</span> for_each <span class="synStatement">=</span> toset<span class="synSpecial">(</span>var.apis<span class="synSpecial">)</span> project <span class="synStatement">=</span> var.project_id service <span class="synStatement">=</span> each.value disable_on_destroy <span class="synStatement">=</span> false <span class="synSpecial">}</span> resource &quot;<span class="synConstant">null_resource</span>&quot; &quot;<span class="synConstant">delay</span>&quot; <span class="synSpecial">{</span> provisioner &quot;<span class="synConstant">local-exec</span>&quot; <span class="synSpecial">{</span> command <span class="synStatement">=</span> &quot;<span class="synConstant">sleep 180</span>&quot; <span class="synSpecial">}</span> depends_on <span class="synStatement">=</span> <span class="synSpecial">[</span>google_project_service.apis<span class="synSpecial">]</span> <span class="synSpecial">}</span> # outputs.tf output &quot;<span class="synConstant">enabled_apis</span>&quot; <span class="synSpecial">{</span> description <span class="synStatement">=</span> &quot;<span class="synConstant">List of enabled APIs for the project</span>&quot; value <span class="synStatement">=</span> <span class="synSpecial">[</span><span class="synStatement">for</span> service in google_project_service.apis <span class="synStatement">:</span> service.id<span class="synSpecial">]</span> <span class="synSpecial">}</span> # variables.tf variable &quot;<span class="synConstant">apis</span>&quot; <span class="synSpecial">{</span> description <span class="synStatement">=</span> &quot;<span class="synConstant">List of APIs to enable</span>&quot; type <span class="synStatement">=</span> <span class="synStatement">list</span><span class="synSpecial">(</span>string<span class="synSpecial">)</span> <span class="synSpecial">}</span> variable &quot;<span class="synConstant">project_id</span>&quot; <span class="synSpecial">{</span> description <span class="synStatement">=</span> &quot;<span class="synConstant">The ID of the project to create resources in</span>&quot; type <span class="synStatement">=</span> string <span class="synSpecial">}</span> </pre> <h2 id="プルリクエスト-terraform-plan">プルリクエスト (terraform plan)</h2> <p>Direct Workload Identity でも、main ブランチへのプルリクエストをトリガーに <code>terraform plan</code> が実行されました。<br/> ※ プルリクエストの場合、<code>terraform apply</code> はスキップされます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090025.png" width="800" height="277" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090027.png" width="800" height="545" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <figure class="figure-image figure-image-fotolife" title="プルリクエストをトリガーに terraform plan が自動実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090044.png" width="800" height="707" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プルリクエストをトリガーに terraform plan が自動実行</figcaption></figure></p> <h2 id="マージ-terraform-apply">マージ (terraform apply)</h2> <p>Direct Workload Identity でも、main ブランチへのマージをトリガーに <code>terraform apply</code> が実行されました。<br/> ※ マージの場合、<code>terraform plan</code> はスキップされます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090031.png" width="800" height="272" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090034.png" width="800" height="544" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <figure class="figure-image figure-image-fotolife" title="マージをトリガーに terraform apply が自動実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241211/20241211090041.png" width="800" height="574" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>マージをトリガーに terraform apply が自動実行</figcaption></figure></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-yutakei/20220512/20220512214329.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">武井 祐介 <a href="https://blog.g-gen.co.jp/archive/author/ggen-yutakei">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2025 選出。IaC ã‚„ CI/CD 周りのサービスやプロダクトが興味分野です。</p> <p class="sw-profile__txt">趣味はロードバイク、ロードレースやサッカー観戦です。</p> <!-- 以下の行を追加 --> <a href="https://twitter.com/ggenyutakei?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ggenyutakei</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-yutakei GoogleドライブをデータソースとするVertex AI SearchアプリでPythonからの検索結果がゼロになる場合の対処法 hatenablog://entry/6802418398309090409 2024-12-09T09:00:00+09:00 2024-12-09T09:00:03+09:00 G-gen の堂原です。当記事では、Google ドライブをデータソースとする Vertex AI Search アプリに対して、Python から検索を行う際に検索結果が0件になってしまう場合の対処法について紹介します。 はじめに 検索が失敗するケース Google Cloud APIs のチャンネルが v1alpha 以外の場合 サービスアカウントを用いる場合 対処法 Python Client サンプルコード ポイント ライブラリのチャンネル指定 credentials Requests ライブラリを用いての直接アクセス サンプルコード ポイント はじめに 当記事では、Google Cl… <p>G-gen の堂原です。当記事では、<strong>Google ドライブ</strong>をデータソースとする <strong>Vertex AI Search</strong> アプリに対して、Python から検索を行う際に検索結果が0件になってしまう場合の対処法について紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#検索が失敗するケース">検索が失敗するケース</a><ul> <li><a href="#Google-Cloud-APIs-のチャンネルが-v1alpha-以外の場合">Google Cloud APIs のチャンネルが v1alpha 以外の場合</a></li> <li><a href="#サービスアカウントを用いる場合">サービスアカウントを用いる場合</a></li> </ul> </li> <li><a href="#対処法">対処法</a></li> <li><a href="#Python-Client">Python Client</a><ul> <li><a href="#サンプルコード">サンプルコード</a></li> <li><a href="#ポイント">ポイント</a><ul> <li><a href="#ライブラリのチャンネル指定">ライブラリのチャンネル指定</a></li> </ul> </li> <li><a href="#credentials">credentials</a></li> </ul> </li> <li><a href="#Requests-ライブラリを用いての直接アクセス">Requests ライブラリを用いての直接アクセス</a><ul> <li><a href="#サンプルコード-1">サンプルコード</a></li> <li><a href="#ポイント-1">ポイント</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241206/20241206081549.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>当記事では、Google Cloud(旧称 GCP)が提供する検索エンジンサービスである <strong>Vertex AI Search</strong> において、<strong>Google ドライブ</strong>をデータソースとする Vertex AI Search アプリに対して、Python から検索を行う方法について紹介します。</p> <p>Vertex AI Search アプリを Python から検索するには、次の 2 つの方法があります。</p> <ol> <li><a href="https://cloud.google.com/python/docs/reference/discoveryengine/latest">Python Client</a> を用いる方法</li> <li>Requests ライブラリを用いて直接 <a href="https://cloud.google.com/generative-ai-app-builder/docs/reference/rest">Google Cloud APIs</a> にアクセスする方法</li> </ol> <p>しかし実装方法によっては、検索結果が0件になってしまう場合があります。</p> <h1 id="検索が失敗するケース">検索が失敗するケース</h1> <h2 id="Google-Cloud-APIs-のチャンネルが-v1alpha-以外の場合">Google Cloud APIs のチャンネルが v1alpha 以外の場合</h2> <p>当記事を執筆した2024å¹´12月現在、Google ドライブをデータソースとする Vertex AI Search アプリへの検索は、「Python Client を用いる方法」「直接 Google Cloud APIs にアクセスする方法」の両方とも、<strong>v1alpha</strong> でのみ期待通り動作します。一方で、v1 または v1beta を用いた場合は、検索結果が0件になります。</p> <p>Google Cloud APIs において v1alpha とは、API のバージョンを示すチャンネルの1つです。Google Cloud APIs は基本的に、v1alpha、v1beta、v1 という順で開発が進みます。v1alpha の機能は予告なく削除される可能性があるため、本番環境での利用は非推奨です。</p> <ul> <li>参考 : <a href="https://cloud.google.com/apis/design/versioning?hl=ja">バージョニング</a></li> </ul> <h2 id="サービスアカウントを用いる場合">サービスアカウントを用いる場合</h2> <p>Google Cloud APIs には通常、Google アカウントまたはサービスアカウントの認証情報を使用してアクセスします。</p> <p>ただし、2024å¹´12月現在では、<strong>サービスアカウントを用いて</strong> Google ドライブをデータソースとする Vertex AI Search アプリへの検索を行った場合、<strong>サーバ側のエラー</strong>(500 Internal Server Error)が発生します。</p> <h1 id="対処法">対処法</h1> <p>当事象に対する2024å¹´12月現在の対処法は、以下のとおりです。</p> <ol> <li>v1alpha チャンネルのクライアントライブラリを使用する</li> <li>サービスアカウントではなく、Google アカウントの認証情報を使用する</li> </ol> <h1 id="Python-Client">Python Client</h1> <h2 id="サンプルコード">サンプルコード</h2> <p>Python Client を使用する場合のサンプルコードは次のとおりです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> google.cloud.discoveryengine_v1alpha <span class="synPreProc">import</span> SearchServiceClient, SearchRequest <span class="synPreProc">from</span> google.protobuf.json_format <span class="synPreProc">import</span> MessageToDict PROJECT_ID = <span class="synConstant">&quot;xxx&quot;</span> <span class="synComment"># Google Cloud プロジェクト ID</span> VERTEX_AI_APP_ID = <span class="synConstant">&quot;xxx&quot;</span> <span class="synComment"># Vertex AI Search アプリの ID</span> client = SearchServiceClient(credentials=credentials) serving_config = f<span class="synConstant">&quot;projects/{PROJECT_ID}/locations/global/collections/default_collection/engines/{VERTEX_AI_APP_ID}/servingConfigs/default_serving_config&quot;</span> content_search_spec = SearchRequest.ContentSearchSpec( <span class="synComment"># スニペットを出力させない</span> snippet_spec=SearchRequest.ContentSearchSpec().SnippetSpec( return_snippet=<span class="synIdentifier">False</span> ), <span class="synComment"># 要約文を出力させる</span> summary_spec=SearchRequest.ContentSearchSpec().SummarySpec( summary_result_count=<span class="synConstant">3</span>, include_citations=<span class="synIdentifier">False</span>, <span class="synComment"># Gemini Proを用いるように指定</span> model_spec=SearchRequest.ContentSearchSpec().SummarySpec().ModelSpec( version=<span class="synConstant">&quot;gemini-1.5-flash-001/answer_gen/v1&quot;</span> ) ) ) <span class="synComment"># Vertex AI Searchにクエリを投げる</span> response = client.search( SearchRequest( serving_config=serving_config, query=<span class="synConstant">&quot;G-genとは?&quot;</span>, page_size=<span class="synConstant">3</span>, content_search_spec=content_search_spec ) ) <span class="synComment"># 要約文を標準出力</span> <span class="synIdentifier">print</span>(response.summary.summary_text) <span class="synComment"># 検索結果を標準出力</span> <span class="synStatement">for</span> r <span class="synStatement">in</span> response.results: r_dct = MessageToDict(r._pb) <span class="synIdentifier">print</span>(r_dct) </pre> <ul> <li>参考 : <a href="https://cloud.google.com/python/docs/reference/discoveryengine/latest/google.cloud.discoveryengine_v1beta.services.search_service.SearchServiceClient">Class SearchServiceClient</a></li> </ul> <h2 id="ポイント">ポイント</h2> <h3 id="ライブラリのチャンネル指定">ライブラリのチャンネル指定</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> google.cloud.discoveryengine_v1alpha <span class="synPreProc">import</span> SearchServiceClient, SearchRequest </pre> <p>上記のように、google-cloud-discoveryengine をインポートする際のチャンネル指定は <code>_v1alpha</code> を明示的に指定する必要があります。チャンネルを指定しない以下のようなインポート文だと、<a href="#%E6%A4%9C%E7%B4%A2%E3%81%8C%E5%A4%B1%E6%95%97%E3%81%99%E3%82%8B%E3%82%B1%E3%83%BC%E3%82%B9">検索が失敗するケース</a>に記載の通り、検索結果が0件になります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 未指定</span> <span class="synPreProc">from</span> google.cloud.discoveryengine <span class="synPreProc">import</span> SearchServiceClient, SearchRequest <span class="synComment"># v1 指定</span> <span class="synPreProc">from</span> google.cloud.discoveryengine_v1 <span class="synPreProc">import</span> SearchServiceClient, SearchRequest <span class="synComment"># v1beta 指定</span> <span class="synPreProc">from</span> google.cloud.discoveryengine_v1beta <span class="synPreProc">import</span> SearchServiceClient, SearchRequest </pre> <h2 id="credentials">credentials</h2> <p><code>client = SearchServiceClient(credentials=credentials)</code> でパラメータとして与える認証情報はサービスアカウントのものではなく、Google アカウントのものにする必要があります。</p> <p>Google アカウントの認証情報の場合は変数の型が <code>google.oauth2.credentials.Credentials</code> に、サービスアカウントの認証情報の場合は変数の型が <code>google.oauth2.service_account.Credentials</code> になります。</p> <h1 id="Requests-ライブラリを用いての直接アクセス">Requests ライブラリを用いての直接アクセス</h1> <h2 id="サンプルコード-1">サンプルコード</h2> <p>Requests ライブラリを使用して Google Cloud APIs に直接アクセスする場合のサンプルコードは次のとおりです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> requests PROJECT_NUMBER = <span class="synConstant">&quot;xxx&quot;</span> <span class="synComment"># Google Cloud プロジェクト番号</span> VERTEX_AI_APP_ID = <span class="synConstant">&quot;xxx&quot;</span> <span class="synComment"># Vertex AI Search アプリの ID</span> <span class="synComment"># APIのURL</span> url = f<span class="synConstant">&quot;https://discoveryengine.googleapis.com/v1alpha/projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/engines/{VERTEX_AI_APP_ID}/servingConfigs/default_search&quot;</span> <span class="synComment"># リクエストヘッダ</span> headers = { <span class="synConstant">&quot;Authorization&quot;</span>: <span class="synConstant">&quot;Bearer &quot;</span> + credentials.token, <span class="synConstant">&quot;Content-Type&quot;</span>: <span class="synConstant">&quot;application/json&quot;</span>, } <span class="synComment"># リクエストボディ</span> session = f<span class="synConstant">&quot;projects/{PROJECT_NUMBER}/locations/global/collections/default_collection/engines/{VERTEX_AI_APP_ID}/sessions/-&quot;</span> data = { <span class="synConstant">&quot;query&quot;</span>: <span class="synConstant">&quot;G-genとは?&quot;</span>, <span class="synConstant">&quot;pageSize&quot;</span>: <span class="synConstant">3</span>, <span class="synConstant">&quot;contentSearchSpec&quot;</span>: { <span class="synConstant">&quot;snippetSpec&quot;</span>: { <span class="synConstant">&quot;returnSnippet&quot;</span>: <span class="synIdentifier">False</span> }, <span class="synConstant">&quot;extractiveContentSpec&quot;</span>: { <span class="synConstant">&quot;maxExtractiveAnswerCount&quot;</span>: <span class="synConstant">1</span> } }, <span class="synConstant">&quot;session&quot;</span>: session } <span class="synComment"># 検索リクエスト送信</span> response = requests.post(f<span class="synConstant">&quot;{url}:search&quot;</span>, headers=headers, json=data) <span class="synComment"># 検索結果を標準出力</span> <span class="synStatement">for</span> r <span class="synStatement">in</span> response.json().get(<span class="synConstant">&quot;results&quot;</span>): <span class="synIdentifier">print</span>(r) data = { <span class="synConstant">&quot;query&quot;</span>: { <span class="synConstant">&quot;text&quot;</span>: <span class="synConstant">&quot;G-genとは?&quot;</span>, <span class="synConstant">&quot;queryId&quot;</span>: response.json().get(<span class="synConstant">&quot;sessionInfo&quot;</span>).get(<span class="synConstant">&quot;queryId&quot;</span>) }, <span class="synConstant">&quot;session&quot;</span>: response.json().get(<span class="synConstant">&quot;sessionInfo&quot;</span>).get(<span class="synConstant">&quot;name&quot;</span>), <span class="synConstant">&quot;answerGenerationSpec&quot;</span>: { <span class="synConstant">&quot;modelSpec&quot;</span>: { <span class="synConstant">&quot;modelVersion&quot;</span>: <span class="synConstant">&quot;gemini-1.5-flash-001/answer_gen/v1&quot;</span> } } } <span class="synComment"># 要約リクエスト送信</span> response = requests.post(f<span class="synConstant">&quot;{url}:answer&quot;</span>, headers=headers, json=data) <span class="synComment"># 要約文を標準出力</span> <span class="synIdentifier">print</span>(response.json().get(<span class="synConstant">&quot;answer&quot;</span>).get(<span class="synConstant">&quot;answerText&quot;</span>)) </pre> <ul> <li>参考 : <a href="https://cloud.google.com/generative-ai-app-builder/docs/reference/rest/v1alpha/projects.locations.collections.dataStores.servingConfigs/search">Method: projects.locations.collections.dataStores.servingConfigs.search</a></li> </ul> <h2 id="ポイント-1">ポイント</h2> <p>Requests ライブラリを用いて直接 Google Cloud APIs にアクセスパターンでも、重要なポイントは Python Client を用いる場合と変わりません。</p> <ul> <li>API の URL のチャンネル指定を v1alpha にする</li> <li>ヘッダの Authorization に含むトークンは Google アカウントのアクセストークンとする</li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-ryu-dohara/profile_256x256.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">堂原 竜希<a href="https://blog.g-gen.co.jp/archive/author/ggen-ryu-dohara">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部データアナリティクス課。2023å¹´4月より、G-genにジョイン。</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2023, 2024, 2025に選出 (2024年はRookie of the year、2025年はFellowにも選出)。休みの日はだいたいゲームをしているか、時々自転車で遠出をしています。</p> <a href="https://twitter.com/ryu_dohara?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ryu_dohara</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-ryu-dohara Cloud Run functionsからVPC Service Controls境界内へのアクセスを許可する方法 hatenablog://entry/6801883189105732428 2024-12-06T09:00:00+09:00 2024-12-06T09:00:02+09:00 G-gen の堂原です。本記事では Google Cloud(旧称 GCP)の Cloud Run functions(旧 Cloud Functions)から、VPC Service Controls 境界の中のリソースへアクセスさせる方法について紹介します。 はじめに 本記事の趣旨 VPC Service Controls Cloud Run functions ポイント アクセスの成否 VPC 経由でのリクエストが必要な理由 VPC のアクセス可能なサービス はじめに 本記事の趣旨 本記事では、Cloud Run functions から、VPC Service Controls で保護… <p>G-gen の堂原です。本記事では Google Cloud(旧称 GCP)の <strong>Cloud Run functions</strong>(旧 Cloud Functions)から、<strong>VPC Service Controls</strong> 境界の中のリソースへアクセスさせる方法について紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#本記事の趣旨">本記事の趣旨</a></li> <li><a href="#VPC-Service-Controls">VPC Service Controls</a></li> <li><a href="#Cloud-Run-functions">Cloud Run functions</a></li> </ul> </li> <li><a href="#ポイント">ポイント</a></li> <li><a href="#アクセスの成否">アクセスの成否</a></li> <li><a href="#VPC-経由でのリクエストが必要な理由">VPC 経由でのリクエストが必要な理由</a></li> <li><a href="#VPC-のアクセス可能なサービス">VPC のアクセス可能なサービス</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241118/20241118094242.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="本記事の趣旨">本記事の趣旨</h2> <p>本記事では、Cloud Run functions から、VPC Service Controls で保護された Google Cloud プロジェクトのリソースにアクセスする方法や、どのような設定でアクセスが成功または失敗するのかについて解説します。</p> <h2 id="VPC-Service-Controls">VPC Service Controls</h2> <p>VPC Service Controls は Google Cloud が提供するセキュリティサービスです。 「サービス境界(service perimeter)」という論理的な境界を作成し、境界をまたぐ API リクエストを制限します。</p> <p>VPC Service Controls については、以下の記事で解説していますのでご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fvpc-service-controls-explained" title="VPC Service Controlsを分かりやすく解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/vpc-service-controls-explained">blog.g-gen.co.jp</a></cite></p> <p>なお本記事は Ingress rules(内向きルール)など、VPC Service Controls の基本的な設定項目については理解している前提で書かれています。</p> <h2 id="Cloud-Run-functions">Cloud Run functions</h2> <p>Cloud Run functions は、Google Cloud が提供するサーバレスコンピューティングサービスです。インフラの構築や管理をすることなく、プログラムを実行することができます。</p> <p>こちらも以下の記事で解説していますので、ご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-functions-explained" title="Cloud Run functionsを徹底解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-functions-explained">blog.g-gen.co.jp</a></cite></p> <h1 id="ポイント">ポイント</h1> <p>本記事で最も伝えたいポイントは、以下のとおりです。</p> <p><strong>VPC Service Controls の Ingress rule(内向きルール)でソースプロジェクトを指定する場合、Cloud Run functions は VPC 経由でリクエストを送信する必要がある。</strong></p> <h1 id="アクセスの成否">アクセスの成否</h1> <p>以下のような検証環境を用意しました。</p> <ul> <li>VPC Service Controls で保護された Google Cloud プロジェクト</li> <li>プロジェクト内に Cloud Run functions と BigQuery テーブルを用意</li> <li>Cloud Run functions には「サービスアカウント A」を紐づけ</li> <li>Cloud Run functions から BigQuery テーブルにアクセスする状況を想定</li> </ul> <p>この環境で、VPC Service Controls と Cloud Run functions の各設定値を変更しながら、様々なパターンでアクセスの成否を検証しました。検証結果は、以下のようになりました。</p> <table> <thead> <tr> <th rowspan=3>No.</th> <th colspan=4>VPC Service Controls</th> <th>Cloud Run functions</th> <th rowspan=3>アクセス結果</th> </tr> <tr> <th rowspan=2>制限付きサービス</th> <th rowspan=2>VPC のアクセス可能なサービス</th> <th colspan=2>「Ingress rules」の「API クライアントの FROM 属性」</th> <th rowspan=2>ネットワークの下り設定</th> </tr> <tr> <th>ID</th> <th>ソース</th> </tr> <tr> <td>1</th> <td>BigQuery API</th> <td>すべての制限付きサービス</th> <td>サービスアカウント A</th> <td>すべてのソース</th> <td>なし</th> <td>OK</th> </tr> <tr> <td>2</th> <td>BigQuery API</th> <td>すべての制限付きサービス</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>なし</th> <td>NG</th> </tr> <tr> <td>3</th> <td>BigQuery API</th> <td>すべての制限付きサービス</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>すべてのトラフィックを VPC コネクタ経由でルーティングする</th> <td>OK</th> </tr> <tr> <td>4</th> <td>BigQuery API</th> <td>すべての制限付きサービス</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>プライベート IP へのリクエストだけを VPC コネクタ経由でルーティングする</th> <td>NG</th> </tr> <tr> <td>5</th> <td>BigQuery API</th> <td>なし</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>すべてのトラフィックを VPC コネクタ経由でルーティングする</th> <td>NG</th> </tr> <tr> <td>6</th> <td>なし</th> <td>なし</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>すべてのトラフィックを VPC コネクタ経由でルーティングする</th> <td>NG</th> </tr> <tr> <td>7</th> <td>なし</th> <td>なし</th> <td>サービスアカウント A</th> <td>プロジェクト</th> <td>なし</th> <td>OK</th> </tr> </table> <h1 id="VPC-経由でのリクエストが必要な理由">VPC 経由でのリクエストが必要な理由</h1> <p>VPC Service Controls では、境界を超えるようなアクセスは、原則的に全てブロックされます。</p> <p><strong>Ingress rules</strong>(内向きルール)を用いると、<strong>特定のソース</strong>(特定の IP アドレスや Google Cloud プロジェクト)を接続元とする<strong>特定の ID</strong>(Google アカウントやサービスアカウント)によるアクセスを<strong>例外的に許可する</strong>ことが出来ます。</p> <p>そのため、Ingress rules で ID ã‚’ Cloud Run functions に紐づけた「サービスアカウント A」に、<strong>ソースを Cloud Run functions が所属する Google Cloud プロジェクト</strong>にすれば、境界内へのアクセスは成功するかのように考えられます。</p> <p>しかし上記の場合、<strong>Cloud Run functions からのアクセスは失敗します</strong>。</p> <p>これは、Google Cloud の公式ドキュメントには明言されていませんが、Cloud Run functions の実行基盤がユーザが管理する Google Cloud プロジェクトとは別だからだと推測できます。</p> <p>そのため、当該プロジェクトからのアクセスとなるように、<strong>Cloud Run functions ã‚’ VPC に接続させる必要がある</strong>のです。</p> <p>このことは以下の公式ドキュメントでも触れられています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/functions/docs/securing/using-vpc-service-controls?hl=ja#functions-outside-perimeter">境界外の関数での VPC Service Controls の使用</a></li> </ul> <h1 id="VPC-のアクセス可能なサービス">VPC のアクセス可能なサービス</h1> <p>また Cloud Run functions に VPC 経由で VPC Service Controls の境界内へアクセスさせる場合は、「<strong>VPC のアクセス可能なサービス</strong>(VPC Accessible services)」という設定項目の値にも気をつける必要があります。この設定項目は境界内の VPC ネットワークからアクセスできるサービスを制御する項目です。</p> <p>この項目で明示的にアクセス先のサービスが許可されていない場合、Cloud Run functions からのアクセスは失敗します。</p> <p>前掲の表の No. 6は、VPC 内部からのアクセスだが「VPC のアクセス可能なサービス」で許可されていない API にリクエストしようとしているため、リクエストが失敗しています。反対に No.7 は、VPC 外部からのリクエストであり、かつ BigQuery API は境界で保護されていないため、リクエストが成功します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vpc-service-controls/docs/vpc-accessible-services?hl=ja">VPC のアクセス可能なサービス</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-ryu-dohara/profile_256x256.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">堂原 竜希<a href="https://blog.g-gen.co.jp/archive/author/ggen-ryu-dohara">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部データアナリティクス課。2023å¹´4月より、G-genにジョイン。</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2023, 2024, 2025に選出 (2024年はRookie of the year、2025年はFellowにも選出)。休みの日はだいたいゲームをしているか、時々自転車で遠出をしています。</p> <a href="https://twitter.com/ryu_dohara?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ryu_dohara</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-ryu-dohara GitHub監査ログをWorkload Identity認証でBigQueryにエクスポートしてみた hatenablog://entry/6802418398302758196 2024-12-04T09:00:00+09:00 2024-12-04T09:00:03+09:00 G-gen の三浦です。当記事では Workload Identity の仕組みを使うことで、サービスアカウントキーを使わずに GitHub Enterprise の監査ログを BigQuery にエクスポートする仕組みを構築したのでご紹介します。 GitHub Enterprise とは 概要 監査ログ Google Cloud への監査ログエクスポート アーキテクチャ 構成図 ディレクトリ構成 環境構築 Workload Identity とサービスアカウントの作成 BigQuery データセットの作成とサービスアカウントへの権限付与 GitHub Actions ワークフローの作成 ma… <p>G-gen の三浦です。当記事では Workload Identity の仕組みを使うことで、サービスアカウントキーを使わずに GitHub Enterprise の監査ログを BigQuery にエクスポートする仕組みを構築したのでご紹介します。</p> <ul class="table-of-contents"> <li><a href="#GitHub-Enterprise-とは">GitHub Enterprise とは</a><ul> <li><a href="#概要">概要</a></li> <li><a href="#監査ログ">監査ログ</a></li> <li><a href="#Google-Cloud-への監査ログエクスポート">Google Cloud への監査ログエクスポート</a></li> </ul> </li> <li><a href="#アーキテクチャ">アーキテクチャ</a><ul> <li><a href="#構成図">構成図</a></li> <li><a href="#ディレクトリ構成">ディレクトリ構成</a></li> </ul> </li> <li><a href="#環境構築">環境構築</a><ul> <li><a href="#Workload-Identity-とサービスアカウントの作成">Workload Identity とサービスアカウントの作成</a></li> <li><a href="#BigQuery-データセットの作成とサービスアカウントへの権限付与">BigQuery データセットの作成とサービスアカウントへの権限付与</a></li> <li><a href="#GitHub-Actions-ワークフローの作成">GitHub Actions ワークフローの作成</a></li> <li><a href="#mainpy-の作成">main.py の作成</a></li> <li><a href="#GitHub-App-の作成">GitHub App の作成</a></li> <li><a href="#GitHub-Actions-のシークレット登録">GitHub Actions のシークレット登録</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#手動実行">手動実行</a></li> <li><a href="#スケジュール実行">スケジュール実行</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113111807.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="GitHub-Enterprise-とは">GitHub Enterprise とは</h1> <h2 id="概要">概要</h2> <p><strong>GitHub Enterprise</strong> は、複数組織の一元管理や Microsoft Entra ID などの IdP(Identity Provider)を使用した SSO(シングルサインオン)などの機能を提供する有償プランです。</p> <p>本プランでは監査ログ API を使用して組織内の操作履歴を取得・管理できます。監査ログを BigQuery にエクスポートすると、長期保存や高度な分析を行い、セキュリティ対策やコンプライアンス強化に役立ちます。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/overview/about-github-for-enterprises">エンタープライズ向け GitHub について</a></li> <li>参考 : <a href="https://github.co.jp/pricing.html">プランご紹介</a></li> </ul> <h2 id="監査ログ">監査ログ</h2> <p>GitHub の監査ログには、組織メンバーのレポジトリ作成、プルリクエストやマージなどの操作が記録され、過去180日分のログを確認できます。<code>git.clone</code> など一部の Git イベントは 7 日間のみ保持されます。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/accessing-the-audit-log-for-your-enterprise">企業の監査ログにアクセスする</a></li> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise#git">エンタープライズの監査ログ イベント</a></li> </ul> <h2 id="Google-Cloud-への監査ログエクスポート">Google Cloud への監査ログエクスポート</h2> <p>GitHub の監査ログは JSON 形式で Cloud Storage にエクスポートできますが、通常はサービスアカウントキーが必要です。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise#setting-up-streaming-to-google-cloud-storage">Google Cloud Storage へのストリーミングの設定</a></li> </ul> <p>サービスアカウントキーは厳重な管理が必要です。漏洩した場合、第三者による不正利用のリスクがあります。Google Cloud のベストプラクティスでは、サービスアカウントキーを使用せずに認証する方法が推奨されています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys?hl=ja">サービス アカウント キーを管理するためのベスト プラクティス</a></li> </ul> <p>以上のことから、当記事では Google Cloud の Workload Identity 機能を使うことで、サービスアカウントキーを使用せずに GitHub 監査ログを取得する仕組みを実装しました。</p> <h1 id="アーキテクチャ">アーキテクチャ</h1> <h2 id="構成図">構成図</h2> <p>構成は図のとおりです。GitHub Actions を使用して監査ログを取得し、BigQuery にエクスポートします。</p> <p><figure class="figure-image figure-image-fotolife" title="構成図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090047.png" width="800" height="221" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>構成図</figcaption></figure></p> <h2 id="ディレクトリ構成">ディレクトリ構成</h2> <p>ディレクトリ構成は以下の通りです。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>. └── app └── main.py <span class="synComment"># 監査ログを BigQuery へエクスポートするスクリプト</span> .github └── workflows └── github-audit-log-to-bq.yml <span class="synComment"># GitHub Actions を定義</span> </pre> <h1 id="環境構築">環境構築</h1> <h2 id="Workload-Identity-とサービスアカウントの作成">Workload Identity とサービスアカウントの作成</h2> <p>GitHub Actions と Google Cloud を連携させるための Workload Identity とサービスアカウントを作成します。作成方法は、次の記事を参照してください。</p> <p><strong>Google CloudとGitHub Actions(Terraform)を連携するWorkload Identityを作成するbashスクリプト</strong> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcreate-workload-identity-for-gha-terraform" title="Google CloudとGitHub Actions(Terraform)を連携するWorkload Identityを作成するbashスクリプト - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/create-workload-identity-for-gha-terraform">blog.g-gen.co.jp</a></cite></p> <h2 id="BigQuery-データセットの作成とサービスアカウントへの権限付与">BigQuery データセットの作成とサービスアカウントへの権限付与</h2> <p>BigQuery のデータセットを作成します。テーブルは GitHub Actions で自動的に作成されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 環境変数を設定</span> <span class="synIdentifier">PROJECT_ID</span>=<span class="synStatement">&quot;</span><span class="synConstant">gha-demo-prj</span><span class="synStatement">&quot;</span> <span class="synComment"># プロジェクトID</span> <span class="synIdentifier">SERVICE_ACCOUNT_NAME</span>=<span class="synStatement">&quot;</span><span class="synConstant">gha-demo-sa</span><span class="synStatement">&quot;</span> <span class="synComment"># サービスアカウント名</span> <span class="synIdentifier">BQ_DATASET</span>=<span class="synStatement">&quot;</span><span class="synConstant">my_dataset</span><span class="synStatement">&quot;</span> <span class="synComment"># BigQueryのデータセット名</span>   <span class="synComment"># BigQuery データセットを東京リージョンに作成</span> bq <span class="synSpecial">--project_id</span><span class="synStatement">=</span><span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> mk <span class="synSpecial">--location</span><span class="synStatement">=</span>asia-northeast1 <span class="synStatement">\</span> <span class="synPreProc">$BQ_DATASET</span> </pre> <p>サービスアカウントに BigQuery へのデータ書き込み権限とジョブの実行権限を付与します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># サービスアカウントへ権限付与</span> gcloud projects add-iam-policy-binding <span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">serviceAccount:</span><span class="synPreProc">$SERVICE_ACCOUNT_NAME</span><span class="synConstant">@</span><span class="synPreProc">$PROJECT_ID</span><span class="synConstant">.iam.gserviceaccount.com</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synConstant">roles/bigquery.dataEditor</span><span class="synStatement">&quot;</span>   gcloud projects add-iam-policy-binding <span class="synPreProc">$PROJECT_ID</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">serviceAccount:</span><span class="synPreProc">$SERVICE_ACCOUNT_NAME</span><span class="synConstant">@</span><span class="synPreProc">$PROJECT_ID</span><span class="synConstant">.iam.gserviceaccount.com</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synConstant">roles/bigquery.jobUser</span><span class="synStatement">&quot;</span> </pre> <h2 id="GitHub-Actions-ワークフローの作成">GitHub Actions ワークフローの作成</h2> <p>このワークフローは、GitHub の監査ログを1日に1回自動で取得し、BigQuery にエクスポートします。前回の自動取得時刻をアーティファクト(GitHub Actions の成果物保存機能)で管理することで差分のログのみを取得します。過去のログも手動で取得できます。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow">ワークフローからのデータの格納と共有</a></li> </ul> <p><code>env:</code> の箇所に、環境に応じたプロジェクト ID などの値を記載してください。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># github-audit-log-to-bq.yml</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> Fetch and Upload GitHub Audit Logs to BigQuery   <span class="synIdentifier">on</span><span class="synSpecial">:</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synComment"> # スケジュール実行</span> <span class="synStatement">- </span><span class="synIdentifier">cron</span><span class="synSpecial">:</span> <span class="synConstant">'0 15 * * *'</span> <span class="synComment"> # 毎日 JST 0 時に実行(UTC 15 時)</span> <span class="synIdentifier">workflow_dispatch</span><span class="synSpecial">:</span> <span class="synComment"> # 手動実行</span> <span class="synIdentifier">inputs</span><span class="synSpecial">:</span> <span class="synIdentifier">start_date</span><span class="synSpecial">:</span> <span class="synComment"> # ログ取得の開始日</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synConstant">&quot;Start date for fetching logs (ISO 8601 format, e.g., 2024-09-01T00:00:00Z)&quot;</span> <span class="synIdentifier">required</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synIdentifier">default</span><span class="synSpecial">:</span> <span class="synConstant">&quot;2024-10-01T00:00:00Z&quot;</span> <span class="synIdentifier">end_date</span><span class="synSpecial">:</span> <span class="synComment"> # ログ取得の終了日</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synConstant">&quot;End date for fetching logs (ISO 8601 format, e.g., 2024-09-30T23:59:59Z)&quot;</span> <span class="synIdentifier">required</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synIdentifier">default</span><span class="synSpecial">:</span> <span class="synConstant">&quot;2024-10-31T23:59:59Z&quot;</span>   <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">id-token</span><span class="synSpecial">:</span> write <span class="synIdentifier">contents</span><span class="synSpecial">:</span> read <span class="synIdentifier">actions</span><span class="synSpecial">:</span> read   <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">fetch-and-upload</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest   <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synComment"> # BigQuery 設定</span> <span class="synIdentifier">BQ_GCP_PROJECT_ID</span><span class="synSpecial">:</span> gha-demo-prj <span class="synComment"> # Google Cloud プロジェクト ID</span> <span class="synIdentifier">BQ_DATASET</span><span class="synSpecial">:</span> my_dataset <span class="synComment"> # BigQuery データセット名</span> <span class="synIdentifier">BQ_TABLE</span><span class="synSpecial">:</span> my_table <span class="synComment"> # BigQuery テーブル名</span> <span class="synComment"> # GitHub 設定</span> <span class="synIdentifier">GITHUB_ORG</span><span class="synSpecial">:</span> myorg <span class="synComment"> # GitHub 組織名</span> <span class="synIdentifier">GH_TOKEN</span><span class="synSpecial">:</span> ${{ github.token }} <span class="synComment"> # GitHub CLI 用のトークン</span> <span class="synComment"> # Workload Identity Federation 設定</span> <span class="synIdentifier">PROJECT_NUMBER</span><span class="synSpecial">:</span> <span class="synConstant">1234567890</span><span class="synComment"> # プロジェクト番号 </span> <span class="synIdentifier">WORKLOAD_IDENTITY_POOL</span><span class="synSpecial">:</span> gha-demo-pool <span class="synComment"> # Workload Identity プール名</span> <span class="synIdentifier">WORKLOAD_IDENTITY_POOL_PROVIDER</span><span class="synSpecial">:</span> gha-demo-provider <span class="synComment"> # Workload Identity プールプロバイダ名</span> <span class="synIdentifier">SERVICE_ACCOUNT</span><span class="synSpecial">:</span> [email protected] <span class="synComment"> # 使用するサービスアカウント名</span> <span class="synComment"> # アーティファクト設定</span> <span class="synIdentifier">LAST_RUN_TIMESTAMP_NAME</span><span class="synSpecial">:</span> <span class="synConstant">&quot;last_run_timestamp&quot;</span> <span class="synComment"> # 前回実行時刻のアーティファクト名</span>   <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/checkout@v4   <span class="synComment"> # Google Cloud 認証 (Workload Identity)</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synConstant">'auth'</span> <span class="synIdentifier">uses</span><span class="synSpecial">:</span> <span class="synConstant">'google-github-actions/auth@v2'</span> <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">workload_identity_provider</span><span class="synSpecial">:</span> <span class="synConstant">'projects/${{ env.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.WORKLOAD_IDENTITY_POOL }}/providers/${{ env.WORKLOAD_IDENTITY_POOL_PROVIDER }}'</span> <span class="synIdentifier">service_account</span><span class="synSpecial">:</span> ${{ env.SERVICE_ACCOUNT }}   <span class="synComment"> # GitHub App トークンを生成</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Generate GitHub App token <span class="synIdentifier">id</span><span class="synSpecial">:</span> generate_token <span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/create-github-app-token@v1 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">app-id</span><span class="synSpecial">:</span> ${{ secrets.APP_ID }} <span class="synIdentifier">private-key</span><span class="synSpecial">:</span> ${{ secrets.APP_PRIVATE_KEY }}   <span class="synComment"> # GitHub App トークンを環境変数に設定</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Set GitHub App Access Token <span class="synIdentifier">run</span><span class="synSpecial">:</span> echo <span class="synConstant">&quot;ACCESS_TOKEN=${{ steps.generate_token.outputs.token }}&quot;</span> &gt;&gt; $GITHUB_ENV   <span class="synComment"> # Python環境をセットアップ</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Set up Python environment <span class="synIdentifier">run</span><span class="synSpecial">:</span> | python3 -m venv venv . venv/bin/activate pip install --upgrade pip pip install google-cloud-bigquery requests jq pandas   <span class="synComment"> # 初回実行時のタイムスタンプを設定</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Set Default Timestamp to Start of Today <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | DEFAULT_TIMESTAMP=$(date -u +&quot;%Y-%m-%dT00:00:00Z&quot;) echo <span class="synConstant">&quot;DEFAULT_TIMESTAMP=$DEFAULT_TIMESTAMP&quot;</span> &gt;&gt; $GITHUB_ENV   <span class="synComment"> # 前回実行時刻のアーティファクトをダウンロード</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Download previous timestamp artifact <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | mkdir -p artifacts ARTIFACT_URL=$(gh api -X GET <span class="synConstant">&quot;repos/${{ github.repository }}/actions/artifacts&quot;</span> \ | jq -r <span class="synConstant">'.artifacts[] | select(.name==&quot;'</span>${{ env.LAST_RUN_TIMESTAMP_NAME }}<span class="synConstant">'&quot;) | .archive_download_url'</span> | head -n 1) if <span class="synSpecial">[</span> -n <span class="synConstant">&quot;$ARTIFACT_URL&quot;</span> <span class="synSpecial">]</span>; then curl -L -o artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.zip -H <span class="synConstant">&quot;Authorization: token ${{ github.token }}&quot;</span> <span class="synConstant">&quot;$ARTIFACT_URL&quot;</span> unzip -o artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.zip -d artifacts/ if <span class="synSpecial">[</span> ! -f <span class="synConstant">&quot;artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt&quot;</span> <span class="synSpecial">]</span>; then echo <span class="synConstant">&quot;${{ env.DEFAULT_TIMESTAMP }}&quot;</span> &gt; artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt fi else echo <span class="synConstant">&quot;${{ env.DEFAULT_TIMESTAMP }}&quot;</span> &gt; artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt fi   <span class="synComment"> # 前回実行時刻を読み込み</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Read previous timestamp <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">id</span><span class="synSpecial">:</span> read-timestamp <span class="synIdentifier">run</span><span class="synSpecial">:</span> | if <span class="synSpecial">[</span> ! -f <span class="synConstant">&quot;artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt&quot;</span> <span class="synSpecial">]</span>; then echo <span class="synConstant">&quot;${{ env.DEFAULT_TIMESTAMP }}&quot;</span> &gt; artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt fi last_run=$(cat artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt) echo <span class="synConstant">&quot;last_run=$last_run&quot;</span> &gt;&gt; $GITHUB_ENV   <span class="synComment"> # 今回実行時刻を環境変数に保存</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Set execution time for last_run <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | execution_time=$(date --utc --iso-8601=seconds) echo <span class="synConstant">&quot;execution_time=$execution_time&quot;</span> &gt;&gt; $GITHUB_ENV   <span class="synComment"> # スケジュール実行時の監査ログ取得</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Fetch GitHub Audit Logs for Scheduled Run <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">last_run</span><span class="synSpecial">:</span> ${{ env.last_run }} <span class="synIdentifier">ACCESS_TOKEN</span><span class="synSpecial">:</span> ${{ env.ACCESS_TOKEN }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | . venv/bin/activate rm -rf app/audit_logs mkdir -p app/audit_logs next_url=&quot;https://api.github.com/orgs/${{ env.GITHUB_ORG }}/audit-log?phrase=created:&gt;$last_run&amp;per_page=100&amp;include=all&quot; while <span class="synSpecial">[[</span> ! -z <span class="synConstant">&quot;$next_url&quot;</span> <span class="synSpecial">]]</span>; do response=$(curl -s -H <span class="synConstant">&quot;Authorization: Bearer $ACCESS_TOKEN&quot;</span> \ -H <span class="synConstant">&quot;Accept: application/vnd.github.v3+json&quot;</span> \ <span class="synConstant">&quot;$next_url&quot;</span>) if echo <span class="synConstant">&quot;$response&quot;</span> | jq -e <span class="synConstant">'type == &quot;object&quot; and has(&quot;message&quot;)'</span> &gt;/dev/<span class="synConstant">null</span> 2&gt;&amp;1; then exit <span class="synConstant">1</span> fi logs_count=$(echo <span class="synConstant">&quot;$response&quot;</span> | jq <span class="synConstant">'. | length'</span>) echo <span class="synConstant">&quot;$response&quot;</span> &gt; <span class="synConstant">&quot;app/audit_logs/log_$logs_count.json&quot;</span> next_url=$(curl -s -I -H <span class="synConstant">&quot;Authorization: Bearer $ACCESS_TOKEN&quot;</span> <span class="synConstant">&quot;$next_url&quot;</span> | grep -i <span class="synConstant">'^link:'</span> | sed -n <span class="synConstant">'s/.*&lt;\(.*\)&gt;; rel=&quot;next&quot;.*/\1/p'</span>) sleep <span class="synConstant">2</span> done   <span class="synComment"> # 手動実行時の監査ログ取得</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Fetch GitHub Audit Logs for Manual Run <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.event_name == <span class="synConstant">'workflow_dispatch'</span> }} <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">start_date</span><span class="synSpecial">:</span> ${{ github.event.inputs.start_date }} <span class="synIdentifier">end_date</span><span class="synSpecial">:</span> ${{ github.event.inputs.end_date }} <span class="synIdentifier">ACCESS_TOKEN</span><span class="synSpecial">:</span> ${{ env.ACCESS_TOKEN }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | . venv/bin/activate rm -rf app/audit_logs mkdir -p app/audit_logs next_url=&quot;https://api.github.com/orgs/${{ env.GITHUB_ORG }}/audit-log?phrase=created%3A&gt;${start_date}%20created%3A&lt;${end_date}&amp;per_page=100&amp;include=all&quot; while <span class="synSpecial">[[</span> ! -z <span class="synConstant">&quot;$next_url&quot;</span> <span class="synSpecial">]]</span>; do response=$(curl -s -H <span class="synConstant">&quot;Authorization: Bearer $ACCESS_TOKEN&quot;</span> \ -H <span class="synConstant">&quot;Accept: application/vnd.github.v3+json&quot;</span> \ <span class="synConstant">&quot;$next_url&quot;</span>) if echo <span class="synConstant">&quot;$response&quot;</span> | jq -e <span class="synConstant">'type == &quot;object&quot; and has(&quot;message&quot;)'</span> &gt;/dev/<span class="synConstant">null</span> 2&gt;&amp;1; then exit <span class="synConstant">1</span> fi logs_count=$(echo <span class="synConstant">&quot;$response&quot;</span> | jq <span class="synConstant">'. | length'</span>) echo <span class="synConstant">&quot;$response&quot;</span> &gt; <span class="synConstant">&quot;app/audit_logs/log_$logs_count.json&quot;</span> next_url=$(curl -s -I -H <span class="synConstant">&quot;Authorization: Bearer $ACCESS_TOKEN&quot;</span> <span class="synConstant">&quot;$next_url&quot;</span> | grep -i <span class="synConstant">'^link:'</span> | sed -n <span class="synConstant">'s/.*&lt;\(.*\)&gt;; rel=&quot;next&quot;.*/\1/p'</span>) sleep <span class="synConstant">2</span> done   <span class="synComment"> # 監査ログを BigQuery にアップロード</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Run script to upload logs to BigQuery <span class="synIdentifier">run</span><span class="synSpecial">:</span> | . venv/bin/activate python3 app/main.py   <span class="synComment"> # 今回実行時刻をファイルに書き込み</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Write new execution time to file <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ success() <span class="synType">&amp;&amp;</span> github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">run</span><span class="synSpecial">:</span> | mkdir -p artifacts echo <span class="synConstant">&quot;$execution_time&quot;</span> &gt; artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt   <span class="synComment"> # 今回実行時刻をアーティファクトとしてアップロード</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Upload updated timestamp artifact <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ success() <span class="synType">&amp;&amp;</span> github.event_name == <span class="synConstant">'schedule'</span> }} <span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/upload-artifact@v4 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> ${{ env.LAST_RUN_TIMESTAMP_NAME }} <span class="synIdentifier">path</span><span class="synSpecial">:</span> artifacts/${{ env.LAST_RUN_TIMESTAMP_NAME }}.txt </pre> <h2 id="mainpy-の作成">main.py の作成</h2> <p>このスクリプトは GitHub Actions で取得した監査ログを加工し、BigQuery へエクスポートします。エクスポート先のテーブルが存在しない場合、新規に作成します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> json <span class="synPreProc">import</span> logging <span class="synPreProc">import</span> os <span class="synPreProc">import</span> sys <span class="synPreProc">import</span> time <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> datetime <span class="synPreProc">import</span> datetime, timezone <span class="synPreProc">from</span> google.cloud <span class="synPreProc">import</span> bigquery <span class="synPreProc">from</span> google.api_core.exceptions <span class="synPreProc">import</span> NotFound, GoogleAPIError <span class="synPreProc">import</span> re   <span class="synComment"># ログの設定: ログメッセージの出力形式を指定し、INFOレベル以上のメッセージを記録</span> logging.basicConfig(level=logging.INFO, <span class="synIdentifier">format</span>=<span class="synConstant">'%(asctime)s [%(levelname)s] %(message)s'</span>)   <span class="synStatement">def</span> <span class="synIdentifier">get_bigquery_client</span>(): <span class="synConstant">&quot;&quot;&quot;BigQueryクライアントを初期化して返す&quot;&quot;&quot;</span> <span class="synStatement">return</span> bigquery.Client() <span class="synStatement">def</span> <span class="synIdentifier">flatten_json</span>(y): <span class="synConstant">&quot;&quot;&quot;ネストされたJSONデータを平坦化&quot;&quot;&quot;</span> out = {} <span class="synStatement">def</span> <span class="synIdentifier">flatten</span>(x, name=<span class="synConstant">''</span>): <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(x, <span class="synIdentifier">dict</span>): <span class="synStatement">for</span> a <span class="synStatement">in</span> x: flatten(x[a], name + a + <span class="synConstant">'_'</span>) <span class="synStatement">elif</span> <span class="synIdentifier">isinstance</span>(x, <span class="synIdentifier">list</span>): <span class="synStatement">for</span> i, a <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(x): flatten(a, name + <span class="synIdentifier">str</span>(i) + <span class="synConstant">'_'</span>) <span class="synStatement">else</span>: out[name[:-<span class="synConstant">1</span>]] = x flatten(y) <span class="synStatement">return</span> out <span class="synStatement">def</span> <span class="synIdentifier">clean_field_name</span>(field_name): <span class="synConstant">&quot;&quot;&quot;フィールド名をBigQueryで許可された形式に変換&quot;&quot;&quot;</span> field_name = re.sub(<span class="synConstant">r'[^a-zA-Z0-9_]'</span>, <span class="synConstant">'_'</span>, field_name) <span class="synStatement">if</span> field_name[<span class="synConstant">0</span>].isdigit(): field_name = <span class="synConstant">'_'</span> + field_name <span class="synStatement">return</span> field_name <span class="synStatement">def</span> <span class="synIdentifier">infer_schema_from_logs</span>(logs_df): <span class="synConstant">&quot;&quot;&quot;DataFrameからBigQuery用のスキーマを推測して生成&quot;&quot;&quot;</span> schema = [] <span class="synStatement">for</span> column <span class="synStatement">in</span> logs_df.columns: clean_column = clean_field_name(column) dtype = logs_df[column].dtype <span class="synStatement">if</span> clean_column == <span class="synConstant">&quot;_timestamp&quot;</span> <span class="synStatement">and</span> pd.api.types.is_integer_dtype(dtype): schema.append(bigquery.SchemaField(<span class="synConstant">&quot;timestamp&quot;</span>, <span class="synConstant">&quot;TIMESTAMP&quot;</span>)) <span class="synStatement">elif</span> pd.api.types.is_integer_dtype(dtype): schema.append(bigquery.SchemaField(clean_column, <span class="synConstant">&quot;INTEGER&quot;</span>)) <span class="synStatement">elif</span> pd.api.types.is_float_dtype(dtype): schema.append(bigquery.SchemaField(clean_column, <span class="synConstant">&quot;FLOAT&quot;</span>)) <span class="synStatement">elif</span> pd.api.types.is_bool_dtype(dtype): schema.append(bigquery.SchemaField(clean_column, <span class="synConstant">&quot;BOOLEAN&quot;</span>)) <span class="synStatement">elif</span> pd.api.types.is_datetime64_any_dtype(dtype): schema.append(bigquery.SchemaField(clean_column, <span class="synConstant">&quot;TIMESTAMP&quot;</span>)) <span class="synStatement">else</span>: schema.append(bigquery.SchemaField(clean_column, <span class="synConstant">&quot;STRING&quot;</span>)) <span class="synStatement">return</span> schema <span class="synStatement">def</span> <span class="synIdentifier">create_table_if_not_exists</span>(client, table_ref, logs_df): <span class="synConstant">&quot;&quot;&quot;テーブルが存在しない場合、新規作成&quot;&quot;&quot;</span> <span class="synStatement">try</span>: table = client.get_table(table_ref) logging.info(f<span class="synConstant">&quot;Table {table_ref} already exists.&quot;</span>) <span class="synStatement">return</span> table <span class="synStatement">except</span> NotFound: schema = infer_schema_from_logs(logs_df) table = bigquery.Table(table_ref, schema=schema) table = client.create_table(table) logging.info(f<span class="synConstant">&quot;Table {table_ref} created with schema.&quot;</span>) <span class="synStatement">return</span> table <span class="synStatement">def</span> <span class="synIdentifier">load_audit_logs</span>(logs_dir=<span class="synConstant">'app/audit_logs'</span>): <span class="synConstant">&quot;&quot;&quot;監査ログをJSONファイルから読み込みDataFrameに統合&quot;&quot;&quot;</span> logs = [] <span class="synStatement">for</span> filename <span class="synStatement">in</span> os.listdir(logs_dir): <span class="synStatement">if</span> filename.endswith(<span class="synConstant">&quot;.json&quot;</span>): file_path = os.path.join(logs_dir, filename) <span class="synStatement">try</span>: <span class="synStatement">with</span> <span class="synIdentifier">open</span>(file_path, <span class="synConstant">'r'</span>) <span class="synStatement">as</span> f: file_logs = json.load(f) <span class="synStatement">for</span> log <span class="synStatement">in</span> file_logs: logs.append(flatten_json(log)) <span class="synStatement">except</span> json.JSONDecodeError <span class="synStatement">as</span> e: logging.error(f<span class="synConstant">&quot;Failed to load {file_path}: {e}&quot;</span>) <span class="synStatement">return</span> pd.DataFrame(logs) <span class="synStatement">def</span> <span class="synIdentifier">transform_audit_logs</span>(logs_df, schema): <span class="synConstant">&quot;&quot;&quot;監査ログをBigQuery用の形式に変換&quot;&quot;&quot;</span> transformed_logs = [] schema_field_names = {field.name <span class="synStatement">for</span> field <span class="synStatement">in</span> schema} logs_df.columns = [clean_field_name(col) <span class="synStatement">for</span> col <span class="synStatement">in</span> logs_df.columns] <span class="synStatement">if</span> <span class="synConstant">&quot;_timestamp&quot;</span> <span class="synStatement">in</span> logs_df.columns: logs_df[<span class="synConstant">&quot;_timestamp&quot;</span>] = pd.to_datetime(logs_df[<span class="synConstant">&quot;_timestamp&quot;</span>], unit=<span class="synConstant">'ms'</span>, utc=<span class="synIdentifier">True</span>) logs_df = logs_df.rename(columns={<span class="synConstant">&quot;_timestamp&quot;</span>: <span class="synConstant">&quot;timestamp&quot;</span>}) <span class="synStatement">for</span> _, log <span class="synStatement">in</span> logs_df.iterrows(): transformed_log = {} <span class="synStatement">for</span> key, value <span class="synStatement">in</span> log.items(): <span class="synStatement">if</span> key <span class="synStatement">in</span> schema_field_names <span class="synStatement">and</span> <span class="synStatement">not</span> pd.isnull(value): <span class="synStatement">if</span> <span class="synIdentifier">isinstance</span>(value, pd.Timestamp): value = value.isoformat() <span class="synStatement">elif</span> <span class="synIdentifier">isinstance</span>(value, <span class="synIdentifier">bool</span>): value = <span class="synIdentifier">int</span>(value) transformed_log[key] = value <span class="synStatement">if</span> transformed_log: transformed_logs.append(transformed_log) <span class="synStatement">return</span> transformed_logs <span class="synStatement">def</span> <span class="synIdentifier">insert_rows_with_retry</span>(client, table_ref, rows, retries=<span class="synConstant">3</span>, delay=<span class="synConstant">10</span>): <span class="synConstant">&quot;&quot;&quot;BigQueryにデータをリトライ付きで挿入&quot;&quot;&quot;</span> <span class="synStatement">for</span> attempt <span class="synStatement">in</span> <span class="synIdentifier">range</span>(retries): <span class="synStatement">try</span>: errors = client.insert_rows_json(table_ref, rows) <span class="synStatement">if</span> errors: logging.error(f<span class="synConstant">&quot;Errors occurred during insertion: {errors}&quot;</span>) time.sleep(delay) <span class="synStatement">else</span>: logging.info(<span class="synConstant">&quot;Data uploaded successfully.&quot;</span>) <span class="synStatement">return</span> <span class="synStatement">except</span> GoogleAPIError <span class="synStatement">as</span> e: logging.error(f<span class="synConstant">&quot;Failed to upload data to BigQuery: {e}&quot;</span>) time.sleep(delay) logging.error(f<span class="synConstant">&quot;Failed to insert rows into {table_ref} after {retries} attempts.&quot;</span>) sys.exit(<span class="synConstant">1</span>) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): project_id = os.getenv(<span class="synConstant">'BQ_GCP_PROJECT_ID'</span>) dataset_id = os.getenv(<span class="synConstant">'BQ_DATASET'</span>) table_id = os.getenv(<span class="synConstant">'BQ_TABLE'</span>) table_ref = f<span class="synConstant">&quot;{project_id}.{dataset_id}.{table_id}&quot;</span> client = get_bigquery_client() logs_df = load_audit_logs() <span class="synStatement">if</span> <span class="synStatement">not</span> logs_df.empty: table = create_table_if_not_exists(client, table_ref, logs_df) rows_to_insert = transform_audit_logs(logs_df, table.schema) insert_rows_with_retry(client, table_ref, rows_to_insert) <span class="synStatement">else</span>: logging.error(<span class="synConstant">&quot;No logs to process. Exiting.&quot;</span>) sys.exit(<span class="synConstant">1</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> <h2 id="GitHub-App-の作成">GitHub App の作成</h2> <p>監査ログを取得するための GitHub App を作成します。詳細な手順は以下を参照してください。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/managing-your-enterprise-account/creating-github-apps-for-your-enterprise">Enterprise 向け GitHub Apps の作成</a></li> </ul> <p>GitHub App の Permissions は以下のとおりに設定します。</p> <ul> <li>Repository permissions <ul> <li>Administration: <code>Read-only</code></li> <li>Metadata: <code>Read-only</code></li> </ul> </li> <li><p>Organization permissions</p> <ul> <li>Administration: <code>Read-only</code></li> </ul> </li> <li><p>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app">GitHub Appの権限について</a></p></li> </ul> <p>(General)から App ID を確認し、控えておきます。(後で GitHub の secret に登録します)</p> <p><figure class="figure-image figure-image-fotolife" title="App ID の確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090013.png" width="642" height="391" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>App ID の確認</figcaption></figure></p> <p>同画面から(Private keys)を作成し、ダウンロードします。(後で GitHub の secret に登録します)</p> <p><figure class="figure-image figure-image-fotolife" title="Private keys の作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090016.png" width="486" height="210" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Private keys の作成</figcaption></figure></p> <p>(Install App)から対象の組織を確認し、(Install)を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="組織へのインストール"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090019.png" width="800" height="213" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>組織へのインストール</figcaption></figure></p> <p>(Only select repositories)から GitHub Actions を構築するレポジトリを選択し、(Install)を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="GitHub App をインストールするレポジトリの選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090022.png" width="380" height="591" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GitHub App をインストールするレポジトリの選択</figcaption></figure></p> <h2 id="GitHub-Actions-のシークレット登録">GitHub Actions のシークレット登録</h2> <p>作成した GitHub App ã‚’ GitHub Actions で使用するため、リポジトリの Secrets(シークレット)に登録します。登録対象は以下の 2 つです。</p> <ol> <li><p><strong>App ID</strong></p> <ul> <li><strong>名前</strong>: <code>APP_ID</code></li> <li><strong>値</strong>: 前手順で確認した GitHub App の App ID</li> </ul> </li> <li><p><strong>Private keys</strong></p> <ul> <li><strong>名前</strong>: <code>APP_PRIVATE_KEY</code></li> <li><strong>値</strong>: PEM キーの値をコピーして貼り付ける</li> </ul> </li> </ol> <p><figure class="figure-image figure-image-fotolife" title="Secrets の登録"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090026.png" width="690" height="758" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Secrets の登録</figcaption></figure></p> <ul> <li>参考 : <a href="https://docs.github.com/ja/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions">リポジトリのシークレットの作成</a></li> </ul> <h1 id="動作確認">動作確認</h1> <h2 id="手動実行">手動実行</h2> <p>GitHub の(Actions)タブから対象ワークフローを選択し、取得期間のパラメータ(<code>Start date</code>、<code>End date</code>)を入力して(Run workflow)で実行します。</p> <p><figure class="figure-image figure-image-fotolife" title="手動実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090029.png" width="800" height="402" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>手動実行</figcaption></figure></p> <p>※ 一度に180日分を取得可能ですが、ログ数が多い場合、BigQuery へエクスポートする際に以下エラーで失敗することがあります。発生した場合、取得期間を短くして再実行してください。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>Error: <span class="synConstant">-06</span> 08:40:51,<span class="synConstant">290</span> <span class="synStatement">[</span>ERROR<span class="synStatement">]</span> Failed to upload data to BigQuery: Timeout of <span class="synConstant">600</span>.0s exceeded, last exception: HTTPSConnectionPool<span class="synPreProc">(</span><span class="synIdentifier">host</span>=<span class="synStatement">'</span><span class="synConstant">bigquery.googleapis.com</span><span class="synStatement">'</span><span class="synSpecial">, </span><span class="synIdentifier">port</span>=<span class="synConstant">443</span><span class="synPreProc">)</span>: Max retries exceeded with url: /bigquery/v2/projects/xxxxx/datasets/xxxxx/tables/xxxxx/insertAll?<span class="synIdentifier">prettyPrint</span>=false <span class="synPreProc">(</span><span class="synSpecial">Caused by SSLError</span><span class="synPreProc">(</span><span class="synSpecial">SSLEOFError</span><span class="synPreProc">(</span><span class="synConstant">8</span><span class="synSpecial">, </span><span class="synStatement">'</span><span class="synConstant">EOF occurred in violation of protocol (_ssl.c:2426)</span><span class="synStatement">'</span><span class="synPreProc">)))</span> </pre> <p>また、監査ログ API にはレート上限があり、1 時間に最大 1,750 クエリ(1 クエリで最大 100 件のログ取得が可能)の制限があります。この制限を超えた場合、403 または 429 エラー応答で失敗するため、ご注意ください。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/using-the-audit-log-api-for-your-enterprise#rate-limit">レート上限</a></li> </ul> <p>実行が成功したら、BigQuery を確認し、指定した期間の監査ログが格納されたテーブルがあることを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="手動実行成功"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090050.png" width="800" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>手動実行成功</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="監査ログの確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090032.png" width="800" height="278" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>監査ログの確認</figcaption></figure></p> <h2 id="スケジュール実行">スケジュール実行</h2> <p>JST 0時にスケジュール実行され、ログが BigQuery にエクスポートされます。GitHub 側の負荷により実行までに遅延が発生する場合もありますので、ご注意ください。</p> <ul> <li>参考 : <a href="https://docs.github.com/ja/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule">schedule</a></li> </ul> <p>GitHub の(Actions)から対象のワークフローが成功していることを確認します。(Event)から(schedule)を選択することで、スケジュール実行されたワークフローのみが表示されます。</p> <p><figure class="figure-image figure-image-fotolife" title="フィルタ方法"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090036.png" width="248" height="197" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>フィルタ方法</figcaption></figure></p> <p>処理が成功し、Artifacts にファイル(実行時間が記録された txt ファイル)があることを確認します。次回の実行時はこの時間以降のログを取得し、BigQuery へエクスポートします。</p> <p><figure class="figure-image figure-image-fotolife" title="スケジュール実行成功"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090038.png" width="729" height="454" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スケジュール実行成功</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="last_run_timestamp の中身"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090041.png" width="602" height="82" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>last_run_timestamp の中身</figcaption></figure></p> <p>BigQuery を確認し、監査ログが出力されていることを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="監査ログの確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241204/20241204090044.png" width="800" height="85" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>監査ログの確認</figcaption></figure></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-miurak/profile_128x128.png?1658213943);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">三浦 健斗<a href="https://blog.g-gen.co.jp/archive/author/ggen-miurak">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt">2023å¹´10月よりG-genにジョイン。元オンプレ中心のネットワークエンジニア。ネットワーク・セキュリティ・唐揚げ・辛いものが好き。<br> </div> </div> </div> </div> ggen-miurak 2024å¹´11月のイチオシGoogle Cloudアップデート hatenablog://entry/6802418398307331393 2024-12-02T09:00:00+09:00 2024-12-02T09:00:11+09:00 G-gen の杉村です。2024å¹´11月のイチオシ Google Cloud アップデートをまとめてご紹介します。記載は全て、記事公開当時のものですのでご留意ください。 はじめに Eventarc Advanced が登場(Preview) Vertex AI Search で streaming answer メソッド(GA with Allowlist) Dataplex で automatic discovery of Cloud Storage data が Preview データ分類ラベルが Gmail にも対応 Google Cloud 認定試験に新試験が誕生 Applicatio… <p>G-gen の杉村です。2024å¹´11月のイチオシ Google Cloud アップデートをまとめてご紹介します。記載は全て、記事公開当時のものですのでご留意ください。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Eventarc-Advanced-が登場Preview">Eventarc Advanced が登場(Preview)</a></li> <li><a href="#Vertex-AI-Search-で-streaming-answer-メソッドGA-with-Allowlist">Vertex AI Search で streaming answer メソッド(GA with Allowlist)</a></li> <li><a href="#Dataplex-で-automatic-discovery-of-Cloud-Storage-data-が-Preview">Dataplex で automatic discovery of Cloud Storage data が Preview</a></li> <li><a href="#データ分類ラベルが-Gmail-にも対応">データ分類ラベルが Gmail にも対応</a></li> <li><a href="#Google-Cloud-認定試験に新試験が誕生">Google Cloud 認定試験に新試験が誕生</a></li> <li><a href="#Application-Load-Balancer-で-Service-Extensions-が-Preview-公開">Application Load Balancer で Service Extensions が Preview 公開</a></li> <li><a href="#PubSub-で-Cloud-Storage-import-topic-が利用可能に">Pub/Sub で Cloud Storage import topic が利用可能に</a></li> <li><a href="#新サービス-Audit-Manager-が公開GA">新サービス Audit Manager が公開(GA)</a></li> <li><a href="#Vertex-AI-で-Gemini-によるバッチ推論が可能にGA">Vertex AI で Gemini による「バッチ推論」が可能に(GA)</a></li> <li><a href="#GKE-のコントロールプレーンに-DNS-ベースのアクセスが登場">GKE のコントロールプレーンに DNS ベースのアクセスが登場</a></li> <li><a href="#Gemini-サイドパネルの日本語版が-Alpha-版--GA">Gemini サイドパネルの日本語版が Alpha 版 → GA</a></li> <li><a href="#Cloud-Run-で-in-memory-volume-が-Preview---GA">Cloud Run で in-memory volume が Preview -&gt; GA</a></li> <li><a href="#Cloud-Storage-で-Bucket-IP-filtering-が-Preview-公開">Cloud Storage で Bucket IP filtering が Preview 公開</a></li> <li><a href="#BigQuery-の-Search-index-で数値列に対する最適化が-Preview--GA">BigQuery の Search index で数値列に対する最適化が Preview → GA</a></li> <li><a href="#Cloud-SQL-Enterprise-Plus-edition-で-write-endpoint-が-Preview-公開">Cloud SQL Enterprise Plus edition で write endpoint が Preview 公開</a></li> <li><a href="#Cloud-SQL-Studio-で-IAM-データベース認証が使えるように">Cloud SQL Studio で IAM データベース認証が使えるように</a></li> <li><a href="#Cloud-SQL-for-PostgreSQL-のバックアップから-AlloyDB-クラスタが起動できるように">Cloud SQL for PostgreSQL のバックアップから AlloyDB クラスタが起動できるように</a></li> <li><a href="#2025å¹´1月25日より-Cloud-Run-関連ロールに仕様変更あり">2025å¹´1月25日より Cloud Run 関連ロールに仕様変更あり</a></li> <li><a href="#Google-Chat-に音声ミーティング-huddles-が導入">Google Chat に音声ミーティング huddles が導入</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240603/20240603200204.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>当記事では、毎月の Google Cloud アップデートのうち特に重要なものをまとめます。</p> <p>また当記事は、Google Cloud に関するある程度の知識を前提に記載されています。前提知識を得るには、ぜひ以下の記事もご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcontents-for-google-cloud-learners" title="Google Cloud サービスカット学習コンテンツ集 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/contents-for-google-cloud-learners">blog.g-gen.co.jp</a></cite></p> <p>リンク先の公式ガイドは、英語版で表示しないと最新情報が反映されていない場合がありますためご注意ください。</p> <h1 id="Eventarc-Advanced-が登場Preview">Eventarc Advanced が登場(Preview)</h1> <p><a href="https://cloud.google.com/eventarc/advanced/docs/choose-product-edition?hl=en">Choose Eventarc Advanced or Eventarc Standard</a> (2024-10-31)</p> <p>Eventarc Advancedが登場(Preview)。</p> <p>従来版(Standard)との違いは、より詳細な制御、他対他のファンイン・ファンアウト、メッセージの変換など。Bus、Enrollment、Pipelineの3要素で構成される。</p> <h1 id="Vertex-AI-Search-で-streaming-answer-メソッドGA-with-Allowlist">Vertex AI Search で streaming answer メソッド(GA with Allowlist)</h1> <p><a href="https://cloud.google.com/generative-ai-app-builder/docs/stream-answer?hl=en">Stream answers</a> (2024-10-31)</p> <p>Vertex AI Search に streaming answer メソッドが登場。</p> <p>回答の一部が生成され次第ストリーミングで返却される。現在のところ英語のみ対応。利用には申請が必要。</p> <h1 id="Dataplex-で-automatic-discovery-of-Cloud-Storage-data-が-Preview">Dataplex で automatic discovery of Cloud Storage data が Preview</h1> <p><a href="https://cloud.google.com/bigquery/docs/automatic-discovery?hl=en">Discover and catalog Cloud Storage data</a> (2024-11-05)</p> <p>Dataplex で automatic discovery of Cloud Storage data が Preview 公開。</p> <p>Cloud Storage 上の CSV、JSONL、Parquet、Avro、ORC を自動で検出し、BigQuery の外部テーブルやオブジェクトテーブルを生成してくれる。</p> <p>以前より Dataplex には、同様の Data Discovery 機能が存在していたが、今回のアップデートでは Dataplex の Lake が作成されず、そのまま BigQuery から使える(Dataplex の管理機構を通らない)ことが違い。</p> <h1 id="データ分類ラベルが-Gmail-にも対応">データ分類ラベルが Gmail にも対応</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/11/open-beta-data-classification-labels-gmail.html">Data classifications labels for Gmail are now available in open beta</a> (2024-11-01)</p> <p>データ分類ラベルが Gmail にも対応。"Sensitive"、"Confidential" といったラベルをコンテンツ内容に応じて自動付与し、ラベルに応じて外部送信をブロックするなどが可能。エディションごとに使える機能が違うことに注意。</p> <h1 id="Google-Cloud-認定試験に新試験が誕生">Google Cloud 認定試験に新試験が誕生</h1> <p><a href="https://cloud.google.com/learn/certification/?hl=en">Google Cloud Certification</a></p> <ul> <li>Associate Google Workspace Administrator(Beta) <ul> <li>2024å¹´10月22日、Beta 版として公開</li> <li>Google Workspace の管理業務やトラブルシューティングに関する知識を問う</li> </ul> </li> <li>Associate Data Practioner(Beta) <ul> <li>2024å¹´10月30日、Beta 版として公開</li> <li>Google Cloud 上のデータやデータパイプラインの管理に関する知識を問う</li> </ul> </li> <li>Professional Cloud Architect <ul> <li>新しい更新試験の Beta 版が受験可能に</li> <li>これまでは、新規受験と更新受験に問題の区別がなかった</li> </ul> </li> </ul> <p>2つの新試験については、G-gen から既に試験対策記事が公開された。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fassociate-data-practitioner-exam" title="Associate Data Practitioner試験対策マニュアル - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/associate-data-practitioner-exam">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fassociate-google-workspace-administrator-exam" title="Associate Google Workspace Administrator試験対策マニュアル - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/associate-google-workspace-administrator-exam">blog.g-gen.co.jp</a></cite></p> <h1 id="Application-Load-Balancer-で-Service-Extensions-が-Preview-公開">Application Load Balancer で Service Extensions が Preview 公開</h1> <p><a href="https://cloud.google.com/blog/products/networking/service-extensions-plugins-for-application-load-balancers/?hl=en">Now run your custom code at the edge with the Application Load Balancers</a> (2024-10-30)</p> <p>Application Load Balancer で、エッジ側でサーバーレスプログラムを動作させる Service Extensions が Preview 公開。</p> <p>プログラムのフォーマットは WebAssembly (Wasm)。ALB側でコードが動くので低遅延で稼働する。HTTPヘッダ操作、カスタム認証、カスタムロギング、HTML書き換えなどに利用できる。</p> <h1 id="PubSub-で-Cloud-Storage-import-topic-が利用可能に">Pub/Sub で Cloud Storage import topic が利用可能に</h1> <p><a href="https://cloud.google.com/pubsub/docs/create-cloud-storage-import-topic?hl=en">Create a Cloud Storage import topic</a> (2024-11-06)</p> <p>Pub/Sub で Cloud Storage import topic が利用可能に。</p> <p>Publisher サービスを開発することなく Cloud Storage オブジェクトを Pub/Sub に取り込んで宛先に挿入できる。イベントドリブンなアーキテクチャをより容易に実装可能に。</p> <p>以下の記事も参照。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fpubsub-cloud-storage-import-topic" title="Pub/SubのCloud Storage import topicを使ってみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/pubsub-cloud-storage-import-topic">blog.g-gen.co.jp</a></cite></p> <h1 id="新サービス-Audit-Manager-が公開GA">新サービス Audit Manager が公開(GA)</h1> <p><a href="https://cloud.google.com/audit-manager/docs/overview?hl=en">Audit Manager overview</a> (2024-11-07)</p> <p>Google Cloudで新サービスAudit Managerが公開(GA)。</p> <p>ISO 27001 ã‚„ SOC2、PCI DSS 等に準拠するために証跡を自動収集。料金は Free と Premium の 2 tiers。</p> <p>Free tier では以下の監査対応が可能。</p> <ul> <li>SOC2</li> <li>Google-recommended AI controls</li> </ul> <p>Premium tier($7,500/yr)では以下の監査対応が可能。</p> <ul> <li>NIST 800-53 Revision 4</li> <li>CIS Controls v8</li> <li>PCI DSS 4.0</li> <li>Cloud Controls Matrix 4.0</li> <li>NIST CSF v1</li> <li>CIS Google Cloud Foundation Benchmark 2.0</li> <li>ISO 27001 2022</li> </ul> <h1 id="Vertex-AI-で-Gemini-によるバッチ推論が可能にGA">Vertex AI で Gemini による「バッチ推論」が可能に(GA)</h1> <p><a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/batch-prediction-gemini?hl=en">Batch prediction</a> (2024-11-08)</p> <p>Vertex AI で Gemini による「バッチ推論」が可能に(GA)。</p> <p>1度のリクエストで多数のプロンプトを送信。同量の標準リクエストより50%引きの料金で利用できる。レスポンスは BigQuery または Cloud Storage に非同期出力。Gemini 1.5 Pro/Flash 等で利用可能。</p> <h1 id="GKE-のコントロールプレーンに-DNS-ベースのアクセスが登場">GKE のコントロールプレーンに DNS ベースのアクセスが登場</h1> <p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/network-isolation?hl=en">About network isolation in GKE</a> (2024-11-11)</p> <p>Google Kubernetes Engine(GKE)で、コントロールプレーンへのアクセス方法に DNS ベースのアクセスが登場。</p> <p>従来の IP ベースだと、クラスタをプライベートネットワークに閉じ込め、接続元 IP アドレス(承認済みネットワーク)で制御していたが、DNS ベースのアクセスでは IAM で認証。</p> <p>以下の記事も参照。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fdns-based-endpoint-for-gke-control-plane" title="DNSベースのエンドポイントを使用してGKEのコントロールプレーンに接続する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/dns-based-endpoint-for-gke-control-plane">blog.g-gen.co.jp</a></cite></p> <h1 id="Gemini-サイドパネルの日本語版が-Alpha-版--GA">Gemini サイドパネルの日本語版が Alpha 版 → GA</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/11/more-languages-gemini-side-panel-general-availability.html">Now generally available: use Gemini in the side panel of Workspace apps in seven additional languages</a> (2024-11-07)</p> <p>Gemini for Google Workspace サイドパネルの日本語版が Alpha 版 → GA(一般公開)。2024-10-16 に Alpha 版として公開されていた。</p> <p>なお、他の言語も同時に GA。今回の公表で GA された言語は以下のとおり。</p> <ul> <li>フランス語</li> <li>ドイツ語</li> <li>イタリア語</li> <li>日本語</li> <li>韓国語</li> <li>ポルトガル語</li> <li>スペイン語</li> </ul> <h1 id="Cloud-Run-で-in-memory-volume-が-Preview---GA">Cloud Run で in-memory volume が Preview -&gt; GA</h1> <p><a href="https://cloud.google.com/run/docs/configuring/services/in-memory-volume-mounts?hl=en">Configure in-memory volume mounts for services</a> (2024-11-12)</p> <p>Cloud Run で in-memory volume が Preview -> GA。</p> <p>コンテナのメモリ領域をファイルシステムとして利用。Cloud Run service と jobs の両方で利用可能。</p> <h1 id="Cloud-Storage-で-Bucket-IP-filtering-が-Preview-公開">Cloud Storage で Bucket IP filtering が Preview 公開</h1> <p><a href="https://cloud.google.com/storage/docs/ip-filtering-overview?hl=en">Bucket IP filtering</a> (2024-11-14)</p> <p>Cloud Storage で Bucket IP filtering が Preview 公開。</p> <p>バケットで接続元 IP アドレス制限が可能になった。従来も VPC Service Controls で IP アドレスベースの制限はできたが、当機能ではバケット単位での制御が可能になる。また、接続元 VPC も制限できる。</p> <p>ただし、東京・大阪リージョン未対応なので注意。</p> <h1 id="BigQuery-の-Search-index-で数値列に対する最適化が-Preview--GA">BigQuery の Search index で数値列に対する最適化が Preview → GA</h1> <p><a href="https://cloud.google.com/bigquery/docs/search?hl=en#numeric-predicates-seo">Optimize with numeric predicates</a> (2024-11-19)</p> <p>BigQuery の Search index で INT64 と TIMESTAMP 列に対する述語(=、IN)での最適化が Preview → GA。</p> <p>Search index はテーブルの特定列にインデックスを作成しておけば WHERE 句での絞り込み(検索)が高速化する仕組み。インデックスは自動更新。</p> <h1 id="Cloud-SQL-Enterprise-Plus-edition-で-write-endpoint-が-Preview-公開">Cloud SQL Enterprise Plus edition で write endpoint が Preview 公開</h1> <p><a href="https://cloud.google.com/bigquery/docs/search?hl=en#numeric-predicates-seo">Optimize with numeric predicates</a> (2024-11-19)</p> <p>Cloud SQL(for PostgreSQL、MySQL)Enterprise Plus edition で write endpoint が Preview。</p> <p>書き込みエンドポイント(write endpoint)とは、常にプライマリインスタンスのプライベート IP アドレスを指すエンドポイントのことで、DNS 名を持つ。</p> <p>書き込みエンドポイントを使って接続することで、別リージョンに構成したリードレプリカをプライマリインスタンスとして昇格した際も、アプリケーション側で向き先 IP アドレスを変更する必要がない。</p> <p>Cloud SQL Auth Proxy からは書き込みエンドポイントを使えないことに注意。</p> <h1 id="Cloud-SQL-Studio-で-IAM-データベース認証が使えるように">Cloud SQL Studio で IAM データベース認証が使えるように</h1> <p><a href="https://cloud.google.com/sql/docs/mysql/iam-authentication#iam-db-auth">IAM database authentication</a> (2024-11-20)</p> <p>Cloud SQL Studio で IAM データベース認証が使えるように。Cloud SQL Studio とは、Google Cloud コンソールからデータベースにクエリしたり管理できる UI。これまではユーザー・パスワードを入力して認証する必要があった。</p> <h1 id="Cloud-SQL-for-PostgreSQL-のバックアップから-AlloyDB-クラスタが起動できるように">Cloud SQL for PostgreSQL のバックアップから AlloyDB クラスタが起動できるように</h1> <p><a href="https://cloud.google.com/sql/docs/postgres/backup-recovery/migrate-cloud-sql-to-alloydb?hl=en">Migrate from Cloud SQL for PostgreSQL to AlloyDB for PostgreSQL </a> (2024-11-21)</p> <p>Cloud SQL for PostgreSQL のバックアップから AlloyDB クラスタが起動できるようになった(Preview)。</p> <p>Cloud SQL から AlloyDB への移行が容易に実現できるようになった。</p> <h1 id="2025å¹´1月25日より-Cloud-Run-関連ロールに仕様変更あり">2025å¹´1月25日より Cloud Run 関連ロールに仕様変更あり</h1> <p><a href="https://blog.g-gen.co.jp/entry/user-does-not-have-access-to-image">[Action Required] Ensure read access on container images deployed to Cloud Run - G-gen Tech Blog</a> (2024-11-25)</p> <p>2025å¹´1月25日より、Cloud Runのデプロイを行うことができる事前定義ロール(Cloud Run 管理者 / デベロッパー)の仕様に変更。</p> <p>放置すると CI/CD パイプラインに影響がある可能性があるため、要確認。詳細は以下の記事を参照。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fuser-does-not-have-access-to-image" title=" [Action Required] Ensure read access on container images deployed to Cloud Run - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/user-does-not-have-access-to-image">blog.g-gen.co.jp</a></cite></p> <h1 id="Google-Chat-に音声ミーティング-huddles-が導入">Google Chat に音声ミーティング huddles が導入</h1> <p><a href="https://workspaceupdates.googleblog.com/2024/09/huddles-in-google-chat.html">Introducing huddles: instant-on, audio-first meetings in Google Chat</a> (2024-11-27)</p> <p>Google Chat に音声ミーティング huddles が導入。Chat からシームレスに音声・動画通話をスタートできる。バックエンドは Google Meet。2024-11-27から順次ロールアウトされる。</p> <p>ほとんどのエディションで利用可能。</p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura IAM Deny policies(拒否ポリシー)を使った予防的統制 hatenablog://entry/6802418398301609944 2024-11-28T09:00:00+09:00 2024-11-28T09:00:02+09:00 G-gen の武井です。当記事では IAM Deny policies(拒否ポリシー)を使った予防的統制について解説します。 はじめに 当記事について 予防的統制 拒否ポリシー 拒否ポリシーの使い所 拒否ポリシーと組織のポリシーの違い 検証の概要 目的 前提 環境 必要な IAM ロール 拒否ポリシーでサポートされる権限 環境構築 ソースコード 実行結果 動作確認 拒否ポリシー設定前 拒否ポリシー設定後 関連記事 はじめに 当記事について 当記事では、IAM Deny policies(以下、拒否ポリシー)で特定の操作を制限し、Google Cloud 環境に予防的統制を効かせる方法を解説しま… <p>G-gen の武井です。当記事では IAM Deny policies(拒否ポリシー)を使った予防的統制について解説します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#当記事について">当記事について</a></li> <li><a href="#予防的統制">予防的統制</a></li> <li><a href="#拒否ポリシー">拒否ポリシー</a></li> <li><a href="#拒否ポリシーの使い所">拒否ポリシーの使い所</a></li> <li><a href="#拒否ポリシーと組織のポリシーの違い">拒否ポリシーと組織のポリシーの違い</a></li> </ul> </li> <li><a href="#検証の概要">検証の概要</a><ul> <li><a href="#目的">目的</a></li> <li><a href="#前提">前提</a></li> <li><a href="#環境">環境</a></li> <li><a href="#必要な-IAM-ロール">必要な IAM ロール</a></li> <li><a href="#拒否ポリシーでサポートされる権限">拒否ポリシーでサポートされる権限</a></li> </ul> </li> <li><a href="#環境構築">環境構築</a><ul> <li><a href="#ソースコード">ソースコード</a></li> <li><a href="#実行結果">実行結果</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#拒否ポリシー設定前">拒否ポリシー設定前</a></li> <li><a href="#拒否ポリシー設定後">拒否ポリシー設定後</a></li> </ul> </li> <li><a href="#関連記事">関連記事</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090009.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="当記事について">当記事について</h2> <p>当記事では、<strong>IAM Deny policies</strong>(以下、拒否ポリシー)で特定の操作を制限し、Google Cloud 環境に予防的統制を効かせる方法を解説します。</p> <h2 id="予防的統制">予防的統制</h2> <p>予防的統制とは、<strong>リスク</strong>(意図しない操作、不正な操作)<strong>を未然に防ぐための統制</strong>を意味します。</p> <p>以下は Google Cloud における予防的統制の一例です。</p> <table> <thead> <tr> <th style="text-align:left;"> 使用するプロダクト </th> <th style="text-align:left;"> 効果 </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 拒否ポリシー<br>(Cloud IAM) </td> <td style="text-align:left;"> リソースに対する特定の操作を <strong>IAM パーミッション</strong> (権限) に基づき制限する </td> </tr> <tr> <td style="text-align:left;"> 組織のポリシー<br>(Resource Manager) </td> <td style="text-align:left;"> リソースに対する特定の操作を<strong>制約</strong> (定義済みのアクション) に基づき制限する </td> </tr> <tr> <td style="text-align:left;"> VPC Service Controls </td> <td style="text-align:left;"> API へのアクセスをコンテキストベースのルールに基づき制限する </td> </tr> </tbody> </table> <h2 id="拒否ポリシー">拒否ポリシー</h2> <p>拒否ポリシーは、<strong>通常の IAM Policy よりも強い強制力で Google Cloud リソースへの操作を制限</strong>します。</p> <p>IAM ポリシーの評価フローでは、拒否ポリシー(明示的な Deny)が通常の IAM Policy(明示的な Allow)より先に評価され、優先的に適用されます。</p> <p>そのため、拒否ポリシーが設定されている場合、IAM Policy を上回る強制力で当該操作を拒否します。</p> <p><figure class="figure-image figure-image-fotolife" title="IAM におけるポリシーの評価フロー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090012.png" width="575" height="510" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>IAM におけるポリシーの評価フロー</figcaption></figure></p> <p>拒否ポリシーの詳細は以下の記事で解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fiam-deny-policy-explained" title="Google CloudのIAMにおけるDenyポリシーを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/iam-deny-policy-explained">blog.g-gen.co.jp</a></cite></p> <h2 id="拒否ポリシーの使い所">拒否ポリシーの使い所</h2> <p>ポリシーの評価順は前述の通り、拒否ポリシー(明示的な Deny)が最も強い強制力を持ちます。</p> <p>そのため、IAM による権限設計では、まずは拒否ポリシーを使わずに IAM Policy(明示的な Allow)で管理することを原則とし、 <strong><code>どうしても強い権限で拒否したい(権限にフタをしたい)場合に拒否ポリシーを使う</code></strong> という方針が望ましいと言えます。</p> <p>拒否ポリシーは統制の度合いとしては強力なため、安易に使ってしまうと後から修正が難しくなる場合があるのでご注意ください。</p> <h2 id="拒否ポリシーと組織のポリシーの違い">拒否ポリシーと組織のポリシーの違い</h2> <p>Google Cloud の予防的統制には、拒否ポリシーに似た仕組みとして<strong>組織のポリシー</strong>(Resource Manager の1機能)があります。</p> <p>前者は <code>IAM パーミッション(権限)</code> 、後者は <code>制約</code> と呼ばれる定義済みのアクションにもとづいて操作を制限するという点に違いがあります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/roles-overview?hl=ja#:~:text=%E3%81%94%E8%A6%A7%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,%E6%A8%A9%E9%99%90,-%3A%20%E3%83%AD%E3%83%BC%E3%83%AB%E3%81%AB%E5%90%AB">ロールのコンポーネント (権限)</a></li> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints?hl=ja">組織のポリシーの制約</a></li> </ul> <h1 id="検証の概要">検証の概要</h1> <h2 id="目的">目的</h2> <p>以下の要件にもとづき、組織リソースに対して拒否ポリシーを適用し、実際の動作を確認します。</p> <p><code>組織のポリシー(制約)の作成・削除・更新</code> についてはすべてのプリンシパルで禁止にしつつ、Terraform が使用するサービスアカウント、ならびに管理者グループだけは例外(操作可能)としています。</p> <table> <thead> <tr> <th style="text-align:left;"> # </th> <th style="text-align:left;"> 拒否したい操作 </th> <th style="text-align:left;"> 拒否対象のプリンシパル </th> <th style="text-align:left;"> 例外のプリンシパル </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 1 </td> <td style="text-align:left;"> 組織のポリシーの作成 </td> <td style="text-align:left;"> すべて </td> <td style="text-align:left;"> ・Terraform 用 サービスアカウント<br>・管理者グループ </td> </tr> <tr> <td style="text-align:left;"> 2 </td> <td style="text-align:left;"> 組織のポリシーの削除 </td> <td style="text-align:left;"> すべて </td> <td style="text-align:left;"> ・Terraform 用 サービスアカウント<br>・管理者グループ </td> </tr> <tr> <td style="text-align:left;"> 3 </td> <td style="text-align:left;"> 組織のポリシーの更新 </td> <td style="text-align:left;"> すべて </td> <td style="text-align:left;"> ・Terraform 用 サービスアカウント<br>・管理者グループ </td> </tr> </tbody> </table> <h2 id="前提">前提</h2> <p>デモを実施するにあたり、以下の IAM Policy を設定しています。</p> <table> <thead> <tr> <th style="text-align:left;"> # </th> <th style="text-align:left;"> プリンシパル </th> <th style="text-align:left;"> 付与した IAM ロール </th> <th style="text-align:left;"> 付与したリソース (場所) </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 1 </td> <td style="text-align:left;"> Terraform 用 サービスアカウント </td> <td style="text-align:left;"> ・オーナー<br>・組織管理者<br>・組織ポリシー管理者<br>・拒否管理者 </td> <td style="text-align:left;"> 組織リソース </td> </tr> <tr> <td style="text-align:left;"> 2 </td> <td style="text-align:left;"> 管理者グループ </td> <td style="text-align:left;"> ・オーナー<br>・組織管理者<br>・組織ポリシー管理者<br>・拒否管理者 </td> <td style="text-align:left;"> 組織リソース </td> </tr> <tr> <td style="text-align:left;"> 3 </td> <td style="text-align:left;"> 非管理者グループ </td> <td style="text-align:left;"> ・組織ポリシー管理者 </td> <td style="text-align:left;"> 組織リソース </td> </tr> </tbody> </table> <h2 id="環境">環境</h2> <p>拒否ポリシーの設定は Terraform で行います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090015.png" width="800" height="553" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>また、拒否ポリシー設定後は以下の条件で動作確認を行います。</p> <table> <thead> <tr> <th style="text-align:left;"> # </th> <th style="text-align:left;"> プリンシパル </th> <th style="text-align:left;"> 操作 </th> <th style="text-align:left;"> 期待する動作 </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 1 </td> <td style="text-align:left;"> 管理者グループ </td> <td style="text-align:left;"> 組織のポリシーを作成 </td> <td style="text-align:left;"> 成功 </td> </tr> <tr> <td style="text-align:left;"> 2 </td> <td style="text-align:left;"> 非管理者グループ </td> <td style="text-align:left;"> 組織のポリシーを削除 </td> <td style="text-align:left;"> 失敗 </td> </tr> </tbody> </table> <h2 id="必要な-IAM-ロール">必要な IAM ロール</h2> <p>拒否ポリシーを管理する場合、組織レベルで <code>拒否管理者ロール(roles/iam.denyAdmin)</code> が必要です。</p> <p>今回のデモでは Terraform が使用するサービスアカウントに対し、組織レベルで上記ロールを付与しています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/deny-access?hl=ja#required-roles">必要なロール</a></li> </ul> <h2 id="拒否ポリシーでサポートされる権限">拒否ポリシーでサポートされる権限</h2> <p>拒否ポリシーでは一部の権限を指定することはできません。</p> <p>拒否ポリシーにてサポートされる権限については以下をご確認ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/deny-permissions-support">Permissions supported in deny policies</a></li> </ul> <h1 id="環境構築">環境構築</h1> <h2 id="ソースコード">ソースコード</h2> <p>今回の検証では、Terraform で環境構築を実施します。使用した Terraform のソースコードは以下のとおりです。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ tree . ├── env │ └── organization │ ├── backend.tf │ ├── locals.tf │ ├── main.tf │ └── versions.tf └── modules └── preventive_controls └── organization_deny_policies ├── main.tf ├── outputs.tf └── variables.tf </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># main.tf (modules)</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_iam_deny_policy&quot;</span> <span class="synConstant">&quot;default&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">parent</span> = <span class="synIdentifier">urlencode</span>(<span class="synConstant">&quot;cloudresourcemanager.googleapis.com/organizations/$</span><span class="synSpecial">{</span>var.organization_id<span class="synSpecial">}</span><span class="synConstant">&quot;</span>) <span class="synIdentifier">name</span> = <span class="synConstant">&quot;dev-ggen-deny-policy-organization&quot;</span> <span class="synIdentifier">display_name</span> = <span class="synConstant">&quot;Dev G-gen Deny Policy Organization&quot;</span> <span class="synComment"># https://cloud.google.com/iam/docs/deny-permissions-support</span> <span class="synComment"># 組織のポリシーの編集禁止</span> <span class="synType">rules</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;First rule&quot;</span> <span class="synType">deny_rule</span> <span class="synSpecial">{</span> <span class="synIdentifier">denied_principals</span> = <span class="synSpecial">[</span><span class="synConstant">&quot;principalSet://goog/public:all&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">denied_permissions</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.create&quot;</span>, <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.delete&quot;</span>, <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.update&quot;</span>, <span class="synSpecial">]</span> <span class="synIdentifier">exception_principals</span> = var.permitted_principals <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># variables.tf</span> <span class="synType">variable</span> <span class="synConstant">&quot;permitted_principals&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;List of principals that are exempt from the deny policy&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">list</span>(<span class="synType">string</span>) <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;organization_id&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;The ID of the organization to create resources in&quot;</span> <span class="synIdentifier">type</span> = <span class="synType">string</span> <span class="synSpecial">}</span> </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># main.tf (env)</span> <span class="synType">module</span> <span class="synConstant">&quot;organization_deny_policies&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;../../modules/preventive_controls/organization_deny_policies&quot;</span> <span class="synIdentifier">organization_id</span> = local.organization_id <span class="synIdentifier">permitted_principals</span> = local.permitted_principals <span class="synSpecial">}</span> </pre> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synComment"># locals.tf</span> <span class="synType">locals</span> <span class="synSpecial">{</span> <span class="synIdentifier">organization_id</span> = <span class="synConstant">&quot;1234567890&quot;</span> <span class="synComment"># https://cloud.google.com/iam/docs/principal-identifiers#v2</span> <span class="synIdentifier">permitted_principals</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;principal://iam.googleapis.com/projects/-/serviceAccounts/[email protected]&quot;</span>, <span class="synConstant">&quot;principalSet://goog/group/[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <ul> <li>参考 : <a href="https://registry.terraform.io/providers/hashicorp/google/6.10.0/docs/resources/iam_deny_policy">google_iam_deny_policy</a></li> <li>参考 : <a href="https://cloud.google.com/iam/docs/principal-identifiers#v2">プリンシパルID (IAM v2 API)</a></li> </ul> <h2 id="実行結果">実行結果</h2> <p><code>terraform apply</code> の実行結果は以下のとおりです。</p> <pre class="code lang-terraform" data-lang="terraform" data-unlink>Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: <span class="synComment"># module.organization_deny_policies.google_iam_deny_policy.default will be created</span> + resource <span class="synConstant">&quot;google_iam_deny_policy&quot;</span> <span class="synConstant">&quot;default&quot;</span> <span class="synSpecial">{</span> + <span class="synIdentifier">display_name</span> = <span class="synConstant">&quot;Dev G-gen Deny Policy Organization&quot;</span> + <span class="synIdentifier">etag</span> = (known after apply) + <span class="synIdentifier">id</span> = (known after apply) + <span class="synIdentifier">name</span> = <span class="synConstant">&quot;dev-ggen-deny-policy-organization&quot;</span> + <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;cloudresourcemanager.googleapis.com%2Forganizations%2F1234567890&quot;</span> + rules <span class="synSpecial">{</span> + <span class="synIdentifier">description</span> = <span class="synConstant">&quot;First rule&quot;</span> + deny_rule <span class="synSpecial">{</span> + <span class="synIdentifier">denied_permissions</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.create&quot;</span>, + <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.delete&quot;</span>, + <span class="synConstant">&quot;orgpolicy.googleapis.com/policies.update&quot;</span>, <span class="synSpecial">]</span> + <span class="synIdentifier">denied_principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;principalSet://goog/public:all&quot;</span>, <span class="synSpecial">]</span> + <span class="synIdentifier">exception_principals</span> = <span class="synSpecial">[</span> + <span class="synConstant">&quot;principal://iam.googleapis.com/projects/-/serviceAccounts/[email protected]&quot;</span>, + <span class="synConstant">&quot;principalSet://goog/group/[email protected]&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> Plan: <span class="synConstant">1</span> to add, <span class="synConstant">0</span> to change, <span class="synConstant">0</span> to destroy. module.organization_deny_policies.google_iam_deny_policy.default: Creating... module.organization_deny_policies.google_iam_deny_policy.default: Still creating... <span class="synSpecial">[</span>10s elapsed<span class="synSpecial">]</span> module.organization_deny_policies.google_iam_deny_policy.default: Creation complete after 11s <span class="synSpecial">[</span>id=cloudresourcemanager.googleapis.com%2Forganizations%2F1234567890/dev-ggen-deny-policy-organization<span class="synSpecial">]</span> Apply complete! Resources: <span class="synConstant">1</span> added, <span class="synConstant">0</span> changed, <span class="synConstant">0</span> destroyed. </pre> <p><figure class="figure-image figure-image-fotolife" title="Cloud コンソール上で表示される拒否ポリシーの詳細"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090019.png" width="800" height="287" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud コンソール上で表示される拒否ポリシーの詳細</figcaption></figure></p> <h1 id="動作確認">動作確認</h1> <h2 id="拒否ポリシー設定前">拒否ポリシー設定前</h2> <p>両グループとも IAM Policy によって組織のポリシーの編集が許可されているため、当該操作が実行できました。</p> <p><figure class="figure-image figure-image-fotolife" title="#1、組織のポリシーが編集(作成)できた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090022.png" width="800" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>#1、組織のポリシーが編集(作成)できた</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="#2、組織のポリシーが編集(削除)できた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090028.png" width="800" height="462" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>#2、組織のポリシーが編集(削除)できた</figcaption></figure></p> <h2 id="拒否ポリシー設定後">拒否ポリシー設定後</h2> <p>非管理者グループにおいては、拒否ポリシー設定前には実行できた操作が、設定後には制限されました。</p> <table> <thead> <tr> <th style="text-align:left;"> # </th> <th style="text-align:left;"> プリンシパル </th> <th style="text-align:left;"> 操作 </th> <th style="text-align:left;"> 期待する動作 </th> <th style="text-align:left;"> 実際の結果 </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 1 </td> <td style="text-align:left;"> 管理者グループ </td> <td style="text-align:left;"> 組織のポリシーを作成 </td> <td style="text-align:left;"> 成功 </td> <td style="text-align:left;"> <strong>成功</strong> </td> </tr> <tr> <td style="text-align:left;"> 2 </td> <td style="text-align:left;"> 非管理者グループ </td> <td style="text-align:left;"> 組織のポリシーを削除 </td> <td style="text-align:left;"> 失敗 </td> <td style="text-align:left;"> <strong>失敗</strong> </td> </tr> </tbody> </table> <p><figure class="figure-image figure-image-fotolife" title="#1、拒否ポリシー設定後も組織のポリシーが編集(作成)できた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090022.png" width="800" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>#1、拒否ポリシー設定後も組織のポリシーが編集(作成)できた</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="#2、拒否ポリシーによって組織のポリシーの編集(削除)が制限された"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241128/20241128090025.png" width="800" height="429" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>#2、拒否ポリシーによって組織のポリシーの編集(削除)が制限された</figcaption></figure></p> <h1 id="関連記事">関連記事</h1> <p>今回ご紹介した他にも、G-gen Tech Blog では予防的統制に関する記事を多数公開しています。これらもあわせてご確認ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Frestrict-access-to-google-cloud-console-and-apis-by-acm" title="Access Context ManagerでGoogle CloudコンソールとAPIへのアクセスを制限する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/restrict-access-to-google-cloud-console-and-apis-by-acm">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Forganization-policy-explained" title="組織のポリシーを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/organization-policy-explained">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fvpc-service-controls-explained" title="VPC Service Controlsを分かりやすく解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/vpc-service-controls-explained">blog.g-gen.co.jp</a></cite></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-yutakei/20220512/20220512214329.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">武井 祐介 <a href="https://blog.g-gen.co.jp/archive/author/ggen-yutakei">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア</p> <p class="sw-profile__txt">Google Cloud Partner Top Engineer 2025 選出。IaC ã‚„ CI/CD 周りのサービスやプロダクトが興味分野です。</p> <p class="sw-profile__txt">趣味はロードバイク、ロードレースやサッカー観戦です。</p> <!-- 以下の行を追加 --> <a href="https://twitter.com/ggenyutakei?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @ggenyutakei</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-yutakei [Action Required] Ensure read access on container images deployed to Cloud Run hatenablog://entry/6802418398306747003 2024-11-26T08:30:00+09:00 2025-01-14T08:57:36+09:00 2024å¹´11月25日、Google Cloud から管理者宛てに「[Action Required] Ensure read access on container images deployed to Cloud Run」というタイトルのメール通知がありました。当記事では、通知の内容とユーザー側で必要なアクション、影響範囲の確認方法などを解説します。 通知の内容 変更の適用前(2025å¹´1月15日以前) 変更の適用後(2025å¹´1月15日以降) ユーザー側で必要なアクション 想定される影響範囲 影響範囲の確認方法 通知メール Cloud Asset Inventory Cloud Logg… <p>2024å¹´11月25日、Google Cloud から管理者宛てに「<strong>[Action Required] Ensure read access on container images deployed to Cloud Run</strong>」というタイトルのメール通知がありました。当記事では、通知の内容とユーザー側で必要なアクション、影響範囲の確認方法などを解説します。</p> <ul class="table-of-contents"> <li><a href="#通知の内容">通知の内容</a><ul> <li><a href="#変更の適用前2025å¹´1月15日以前">変更の適用前(2025å¹´1月15日以前)</a></li> <li><a href="#変更の適用後2025å¹´1月15日以降">変更の適用後(2025å¹´1月15日以降)</a></li> </ul> </li> <li><a href="#ユーザー側で必要なアクション">ユーザー側で必要なアクション</a></li> <li><a href="#想定される影響範囲">想定される影響範囲</a></li> <li><a href="#影響範囲の確認方法">影響範囲の確認方法</a><ul> <li><a href="#通知メール">通知メール</a></li> <li><a href="#Cloud-Asset-Inventory">Cloud Asset Inventory</a></li> <li><a href="#Cloud-Logging">Cloud Logging</a></li> </ul> </li> <li><a href="#Cloud-Run-functions-への影響">Cloud Run functions への影響</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241126/20241126075218.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="通知の内容">通知の内容</h1> <p>Cloud Run のデプロイに使用することができる事前定義の IAM ロール「<strong>Cloud Run 管理者</strong>(<code>roles/run.admin</code>)」および「<strong>Cloud Run デベロッパー</strong>(<code>roles/run.developer</code>)」には、<strong>暗黙的に</strong> Artifact Registory の読み取り権限がついています。</p> <p><figure class="figure-image figure-image-fotolife" title="Cloud Run 管理者や Cloud Run デベロッパーには Artifact Registry の読み取り権限が「明示的には」付与されていない"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241126/20241126085546.png" width="721" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Run 管理者や Cloud Run デベロッパーには Artifact Registry の読み取り権限が「明示的には」付与されていない</figcaption></figure></p> <p><strong>2025å¹´1月15日(米国時間)以降</strong>、この暗黙的な読み取り権限がなくなり、<strong>Cloud Run 管理者</strong>ロールと<strong>Cloud Run デベロッパー</strong>ロールでは Artifact Registry にあるコンテナイメージの読み取りができなくなります。したがって、このロールだけでは Cloud Run の作成、更新ができなくなります。</p> <p>今後、Cloud Run の作成、更新を行うプリンシパル(ユーザー、サービスアカウント)は、コンテナイメージに対する明示的な読み取り権限が必要となります。具体的には、コンテナイメージが存在する Artifact Registry リポジトリ、もしくはプロジェクトに対する <strong>Artifact Registry 読み取り</strong>(<code>roles/artifactregistry.reader</code>)ロールが必要となります。</p> <p>以下に、変更の適用前・適用後の状況を要約します。</p> <h2 id="変更の適用前2025å¹´1月15日以前">変更の適用前(2025å¹´1月15日以前)</h2> <ul> <li>暗黙的な権限により、<strong>Cloud Run 管理者</strong>または<strong>Cloud Run デベロッパー</strong>ロールを紐づけられたプリンシパルは Artifact Registry にあるコンテナイメージを読み取ることができる。</li> </ul> <h2 id="変更の適用後2025å¹´1月15日以降">変更の適用後(2025å¹´1月15日以降)</h2> <ul> <li><strong>Cloud Run 管理者</strong>ロールおよび<strong>Cloud Run デベロッパー</strong>ロールでは Artifact Registry にあるコンテナイメージを読み取ることはできない。そのため Cloud Run の作成、更新操作ができない。</li> <li>今まで Cloud Run のデプロイに <strong>Cloud Run 管理者</strong>または <strong>Cloud Run デベロッパー</strong>ロールを使用していたプリンシパルには、<strong>Artifact Registry 読み取り</strong>ロールも紐づける必要がある。</li> <li><strong>オーナー</strong>(<code>roles/owner</code>)ロール、または<strong>編集者</strong>(<code>roles/editor</code>)ロールが紐づいているプリンシパルについては、この変更による影響はない。</li> </ul> <h1 id="ユーザー側で必要なアクション">ユーザー側で必要なアクション</h1> <p><strong>2025å¹´1月15æ—¥</strong>までに、<strong>Cloud Run 管理者</strong>または<strong>Cloud Run デベロッパー</strong>ロールが紐づけられたプリンシパルを洗い出し、必要に応じて <strong>Artifact Registry 読み取り</strong>ロールを付与する必要があります。</p> <h1 id="想定される影響範囲">想定される影響範囲</h1> <p>Cloud Build ã‚„ GitHub Actions 等の CI/CD パイプラインから Cloud Run のデプロイを行っているようなケースでは、パイプラインが使用するサービスアカウントに <strong>Cloud Run 管理者</strong>もしくは<strong>Cloud Run デベロッパー</strong>ロールが付与されている可能性があります。</p> <p>今回の変更への対処が漏れてしまった場合、パイプラインが停止してしまう恐れがあるため、現在サービスアカウントに紐づけられているロールを確認し、権限が不足している場合は <strong>Artifact Registry 読み取り</strong>ロール等を付与する必要があります。</p> <h1 id="影響範囲の確認方法">影響範囲の確認方法</h1> <h2 id="通知メール">通知メール</h2> <p>変更の影響を受ける可能性がある組織やプロジェクトのオーナーや、エッセンシャルコンタクトに登録された連絡先には、2024å¹´11月25日に Google Cloud から <code>[Action Required] Ensure read access on container images deployed to Cloud Run</code> というタイトルのメールが配信されています。</p> <p>変更の影響があるプロジェクトは、同メール最下部の「<strong>Your affected projects are listed below:</strong>」以下に記載されています。</p> <p><figure class="figure-image figure-image-fotolife" title="変更の影響があるプロジェクトはメールに記載されている"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241126/20241126083005.png" width="800" height="640" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>変更の影響があるプロジェクトはメールに記載されている</figcaption></figure></p> <p>エッセンシャルコンタクトについては以下の記事を参照してください。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/google-cloud-organization-explained#%E3%82%A8%E3%83%83%E3%82%BB%E3%83%B3%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%B3%E3%83%B3%E3%82%BF%E3%82%AF%E3%83%88">Google Cloudの組織(Organization)を徹底解説 - エッセンシャルコンタクト</a></li> </ul> <h2 id="Cloud-Asset-Inventory">Cloud Asset Inventory</h2> <p>また、Cloud Asset Inventory を使用することで、特定のロールが紐づいたプリンシパルの一覧を確認できます。</p> <p>Google Cloud コンソールから確認する場合、たとえば<strong>Cloud Run デベロッパー</strong>ロールであれば、Cloud Asset Inventory の画面の <strong>IAM ポリシー</strong>タブにて <code>roles: run.developer</code> でフィルタリングします。プロジェクトに対してロールが紐づけられているプリンシパルと、特定の Cloud Run リソースにロールが紐づけられているプリンシパルの両方を検索することができます。</p> <p><strong>Cloud Run 管理者</strong>ロールについては <code>roles: run.admin</code> でフィルタリングすることができます。</p> <p><figure class="figure-image figure-image-fotolife" title="Asset Inventory を使用して、対処が必要なプリンシパルを確認する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241126/20241126083008.png" width="800" height="223" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Asset Inventory を使用して、対処が必要なプリンシパルを確認する</figcaption></figure></p> <p>gcloud CLI では <code>gcloud asset search-all-iam-policies</code> コマンドで確認できます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Cloud Run デベロッパーロールが付与されたプリンシパルの一覧を表示する</span> $ gcloud asset search-all-iam-policies <span class="synSpecial">--query</span><span class="synStatement">='</span><span class="synConstant">roles:run.developer</span><span class="synStatement">'</span> </pre> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 出力例</span> $ gcloud asset search-all-iam-policies <span class="synSpecial">--query</span><span class="synStatement">='</span><span class="synConstant">roles:run.developer</span><span class="synStatement">'</span> --- assetType: run.googleapis.com/Service <span class="synComment"># ロールが特定の Cloud Run に紐付いている場合</span> folders: - folders/xxxxxxxxxxxx - folders/xxxxxxxxxxxx organization: organizations/xxxxxxxxxxxx policy: bindings: - members: - serviceAccount:[email protected] role: roles/run.developer project: projects/xxxxxxxxxxxx resource: //run.googleapis.com/projects/myproject/locations/asia-northeast1/services/hello --- assetType: cloudresourcemanager.googleapis.com/Project <span class="synComment"># ロールがプロジェクトに紐付いている場合</span> folders: - folders/xxxxxxxxxxxx - folders/xxxxxxxxxxxx organization: organizations/xxxxxxxxxxxx policy: bindings: - members: - serviceAccount:[email protected] role: roles/run.developer project: projects/xxxxxxxxxxxx resource: //cloudresourcemanager.googleapis.com/projects/myproject </pre> <p>Cloud Asset Inventory については、以下の記事も参考にしてください。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/cloud-asset-inventory-explained">Cloud Asset Inventoryを徹底解説!</a></li> </ul> <h2 id="Cloud-Logging">Cloud Logging</h2> <p>2025å¹´1月15日の変更が適用されるまでは、該当のロールを使用して Cloud Run のデプロイが可能ですが、Cloud Logging に以下のエラーログが記録されるようになっています。</p> <blockquote><p>Cloud Run API check failed. Requests will be rejected after January 2025 hard enforcement deadline. User does not have access to image {Artifact Registry 内のコンテナイメージの URL}</p></blockquote> <p>Cloud Logging にて以下のクエリを実行することで、当該ログを検索することができます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">resource</span>.<span class="synSpecial">type</span> = <span class="synSpecial">&quot;</span><span class="synConstant">cloud_run_revision</span><span class="synSpecial">&quot;</span> severity=ERROR <span class="synSpecial">&quot;</span><span class="synConstant">User does not have access to image</span><span class="synSpecial">&quot;</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="変更の適用前もデプロイは可能だが、エラーログが記録される"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241126/20241126153136.png" width="800" height="213" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>変更の適用前もデプロイは可能だが、エラーログが記録される</figcaption></figure></p> <p>ログが出ている場合、対処が必要なプリンシパルを使用してデプロイが行われているので、<strong>Artifact Registry 読み取り</strong>ロール等の付与を行います。</p> <p>上記のクエリを使用してログベースのアラートを設定しておくことで、対象のログの確認漏れを防ぐことができます。</p> <p>ログベースのアラートについては、以下の記事を参考にしてください。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/cloud-logging-explained#%E3%83%AD%E3%82%B0%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%A2%E3%83%A9%E3%83%BC%E3%83%88">Cloud Loggingの概念と仕組みをしっかり解説 - ログベースのアラート</a></li> </ul> <h1 id="Cloud-Run-functions-への影響">Cloud Run functions への影響</h1> <p>Cloud Run functions(第2世代)は、実行基盤として Cloud Run がベースになっています。Cloud Run functions に、今回の変更は影響があるのでしょうか。</p> <p>今回の変更について、Cloud Run functions への影響はありません。Cloud Run functions のデプロイ時は、サービスエージェントと呼ばれる特殊なサービスアカウントが、ビルドや Artifact Registry からのイメージ取得を行います。これらのアカウントにデフォルトで付与されているロールを変更しなければ、今回の変更では影響がないようになっています。</p> <p>Cloud Run functions のデプロイの仕組みに関する詳細は、以下のドキュメントに記載されています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/functions/docs/concepts/iam?hl=ja#administrative_service_accounts">管理者サービス アカウント</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun 生成AI評価ツール「Gen AI evaluation service in Vertex AI」を紹介 hatenablog://entry/6802418398301504709 2024-11-25T09:00:00+09:00 2024-11-25T09:00:02+09:00 G-gen の又吉です。当記事では、生成 AI の出力を迅速かつ効率的に評価できる Vertex AI 上の API である、Gen AI evaluation service を紹介します。 概要 ユースケース 評価指標について 評価タイプ 計算ベース モデルベース 料金 使ってみる 概要 準備 実行と結果 その他 クォータの制限について 評価データセットの件数 概要 Gen AI evaluation service は、生成 AI アプリケーションの出力を効率的に評価するための機能です。Vertex AI の1機能として、API で提供されます。この機能を使うと、事前定義された評価指標や… <p>G-gen の又吉です。当記事では、生成 AI の出力を迅速かつ効率的に評価できる Vertex AI 上の API である、<strong>Gen AI evaluation service</strong> を紹介します。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a></li> <li><a href="#ユースケース">ユースケース</a></li> <li><a href="#評価指標について">評価指標について</a><ul> <li><a href="#評価タイプ">評価タイプ</a></li> <li><a href="#計算ベース">計算ベース</a></li> <li><a href="#モデルベース">モデルベース</a></li> </ul> </li> <li><a href="#料金">料金</a></li> <li><a href="#使ってみる">使ってみる</a><ul> <li><a href="#概要-1">概要</a></li> <li><a href="#準備">準備</a></li> <li><a href="#実行と結果">実行と結果</a></li> </ul> </li> <li><a href="#その他">その他</a><ul> <li><a href="#クォータの制限について">クォータの制限について</a></li> <li><a href="#評価データセットの件数">評価データセットの件数</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241111/20241111095202.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <p><strong>Gen AI evaluation service</strong> は、生成 AI アプリケーションの出力を効率的に評価するための機能です。Vertex AI の1機能として、API で提供されます。この機能を使うと、事前定義された評価指標や、ユーザーが独自に定義したカスタム評価指標を用いて、生成 AI アプリケーションのパフォーマンスを<strong>定量的に評価</strong>できます。</p> <p>同様の LLM 評価ツールとしては、オープンソースのフレームワークである <a href="https://docs.ragas.io/en/stable/">Ragas</a> などがありますが、Gen AI evaluation service は <strong>Vertex AI とシームレスに統合されている</strong>点と、<strong>マネージドサービスでありインフラの管理が不要な点</strong>がメリットです。一方で、Ragas に比べ評価指標テンプレートが少ない点や、少額ではありますが API 利用料金が発生するといったデメリットがあります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/evaluation-overview">Gen AI evaluation service overview</a></li> </ul> <h1 id="ユースケース">ユースケース</h1> <p>Gen AI Evaluation Service は、以下のようなユースケースで役立ちます。</p> <ul> <li>生成 AI モデルの選定</li> <li>最適なモデルパラメータを探索</li> <li>プロンプトエンジニアリングの調整</li> <li>ファインチューニングの評価</li> <li>RAG(Retrieval Augmented Generation)の評価</li> <li>Function calling の評価</li> </ul> <p>以下の公式ドキュメントでは、ユースケース別のサンプル Notebook が公開されており、参考にすることができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/evaluation-overview#notebooks_for_evaluation_use_cases">Notebooks for evaluation use cases</a></li> </ul> <h1 id="評価指標について">評価指標について</h1> <h2 id="評価タイプ">評価タイプ</h2> <p>Gen AI evaluation service には、<strong>計算ベース</strong>(Computation-based)と<strong>モデルベース</strong>(Model-based)の 2 種類の評価タイプがあります。</p> <p>計算ベースの評価は、正解データとの比較に基づいてスコアを算出します。処理速度が速いため、リアルタイム評価にも適しています。代表的な指標として、自然言語処理で広く使われる BLEU ã‚„ ROUGE などがあります。</p> <p>しかし、LLM の出力評価では、そもそも「正解データ」を準備することが難しい場合があります。これは、LLM の出力に対して単一の正解を定めにくいためです。このため、多くの場合、人間の判断による評価が行われますが、スケールするにつれて手間が増加するという課題もあります。こうした背景から、現在ではモデルベースの評価が注目されています。</p> <p>モデルベースの評価は、LLM 自体を判定モデルとして用い、人間による評価に近い形で評価します。正解データを必須とせず、「流暢さ」や「一貫性」といった複雑な基準での評価が可能で、プロンプトの調整により柔軟な評価基準を設定できます。</p> <table> <thead> <tr> <th> 評価タイプ </th> <th> 評価アプローチ </th> <th> 正解データ(Ground truth) </th> <th> レイテンシ </th> </tr> </thead> <tbody> <tr> <td> 計算ベース </td> <td> 数式を用いて評価する </td> <td> å¿…é ˆ </td> <td> 早い </td> </tr> <tr> <td> モデルベース </td> <td> 判定モデル (LLM) に評価させる </td> <td> 任意 </td> <td> 遅い </td> </tr> </tbody> </table> <h2 id="計算ベース">計算ベース</h2> <p>計算ベースの評価指標は、候補モデル(評価対象の LLM)の出力が正解データにどれだけ一致しているかを数値化します。以下はテキスト生成向けの評価指標です。</p> <table> <thead> <tr> <th> 評価指標 </th> <th> 説明 </th> <th> 適するユースケース </th> </tr> </thead> <tbody> <tr> <td> Exact match </td> <td> 候補モデルの出力が正解データと完全一致する場合は「1」、しない場合は「0」を出力 </td> <td> QA や分類タスク </td> </tr> <tr> <td> BLEU </td> <td> 候補モデルの出力と正解データの n-gram の一致度を算出。出力は [0 ~ 1] の範囲で、スコアが高いほど生成テキストが正解に近いことを示す。 </td> <td> 翻訳タスク </td> </tr> <tr> <td> ROUGE </td> <td> 候補モデルの出力と正解データの n-gram の F1-score を算出。出力は [0 ~ 1] の範囲で、スコアが高いほど内容が類似していることを示す。 </td> <td> 要約タスク </td> </tr> </tbody> </table> <p>その他、Function Calling 向けの評価指標などもあります。詳細は以下の公式ドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/determine-eval#computation-based-metrics">Computation-based metrics</a></li> </ul> <h2 id="モデルベース">モデルベース</h2> <p>モデルベースの評価指標では、LLM が判定モデルとして機能し、候補モデルの出力を評価します。この手法は一般的に、<strong>LLM-as-a-Judge</strong> とも呼ばれます。</p> <p>評価方式には、単一の出力に対する <strong>Pointwise</strong> と、複数の出力の比較を行う <strong>Pairwise</strong> があります。</p> <table> <thead> <tr> <th> 方式 </th> <th> 説明 </th> <th> ユースケース </th> </tr> </thead> <tbody> <tr> <td> Pointwise </td> <td> 単一の候補モデルの出力にスコアを付与 </td> <td> 運用段階での継続的なモニタリング</td> </tr> <tr> <td> Pairwise </td> <td> 2 つの候補モデルの出力を比較し、より適切な方を選択 </td> <td> モデル選定やプロンプト比較</td> </tr> </tbody> </table> <p>また、Gen AI evaluation service には、様々なタスクに合わせた事前定義済みのプロンプトテンプレートが用意されています。</p> <table> <thead> <tr> <th> </th> <th> テキスト生成 </th> <th> マルチターン会話形式 </th> <th> 要約 </th> <th> QA 品質 </th> </tr> </thead> <tbody> <tr> <td> Pointwise </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_fluency">Fluency</a><br>・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_groundedness">Groundedness</a> など</td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_multiturn_chat_quality">Multi-turn Chat Quality</a><br>・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_multiturn_chat_safety">Multi-turn Safety</a> </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_summarization_quality">Summarization Quality</a> </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pointwise_question_answering_quality">Question Answering Quality</a> </td> </tr> <tr> <td> Pairwise </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_fluency">Fluency</a><br>・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_groundedness">Groundedness</a> など</td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_multiturn_chat_quality">Multi-turn Chat Quality</a><br>・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_multiturn_chat_safety">Multi-turn Safety</a> </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_summarization_quality">Summarization Quality</a> </td> <td> ・<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates#pairwise_question_answering_quality">Question Answering Quality</a> </td> </tr> </tbody> </table> <p>これらのテンプレートは更新される可能性があるため、最新情報は以下の公式ドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/metrics-templates">Metric prompt templates for model-based evaluation</a></li> </ul> <h1 id="料金">料金</h1> <p>Gen AI Evaluation Serviceの料金は、入出力の文字数と評価タイプに基づいて計算されます。</p> <table> <thead> <tr> <th> 評価タイプ </th> <th> 価格 </th> </tr> </thead> <tbody> <tr> <td> モデルベース (Pointwise, Pairwise) </td> <td> 入力: $0.005 per 1k characters <br>出力: $0.015 per 1k characters </td> </tr> <tr> <td> 計算ベース </td> <td> 入力: $0.00003 per 1k characters <br>出力: $0.00009 per 1k characters </td> </tr> </tbody> </table> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/pricing#gen_ai_evaluation_service">Vertex AI Pricing - Gen AI Evaluation Service</a></li> </ul> <h1 id="使ってみる">使ってみる</h1> <h2 id="概要-1">概要</h2> <p>ここでは、RAG システムで生成された回答の精度を評価する例を紹介します。</p> <p><figure class="figure-image figure-image-fotolife" title="構成図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241125/20241125090011.png" width="705" height="266" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>構成図</figcaption></figure></p> <p>また、筆者の実行環境としては <a href="https://cloud.google.com/colab/docs">Colab Enterprise</a> を使用します。Colab Enterprise の利用方法は以下の公式ドキュメントのクイックスタートをご参考下さい。</p> <ul> <li>参考 : <a href="https://cloud.google.com/colab/docs/create-console-quickstart">Create a notebook by using the Google Cloud console</a></li> </ul> <h2 id="準備">準備</h2> <p>1. ライブラリのインストール</p> <pre class="code lang-python" data-lang="python" data-unlink>!pip install google-cloud-aiplatform[evaluation]==<span class="synConstant">1.71</span>.<span class="synConstant">0</span> </pre> <p>ï¼’. Vertex AI インスタンスの初期化</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> vertexai <span class="synPreProc">from</span> vertexai.evaluation <span class="synPreProc">import</span> EvalTask, MetricPromptTemplateExamples, PointwiseMetric <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd PROJECT_ID = <span class="synConstant">&quot;&quot;</span> <span class="synComment"># @param {type:&quot;string&quot;}</span> LOCATION = <span class="synConstant">&quot;&quot;</span> <span class="synComment"># @param {type:&quot;string&quot;}</span> EXPERIMENT = <span class="synConstant">&quot;&quot;</span> <span class="synComment"># @param {type:&quot;string&quot;}</span> vertexai.init( project=PROJECT_ID, location=LOCATION ) </pre> <p>3. 事前定義済み評価指標テンプレートの確認</p> <pre class="code lang-python" data-lang="python" data-unlink>MetricPromptTemplateExamples.list_example_metric_names() </pre> <p>出力は以下の通りです。</p> <pre class="code txt" data-lang="txt" data-unlink>[&#39;coherence&#39;, &#39;fluency&#39;, &#39;safety&#39;, &#39;groundedness&#39;, &#39;instruction_following&#39;, &#39;verbosity&#39;, &#39;text_quality&#39;, &#39;summarization_quality&#39;, &#39;question_answering_quality&#39;, &#39;multi_turn_chat_quality&#39;, &#39;multi_turn_safety&#39;, &#39;pairwise_coherence&#39;, &#39;pairwise_fluency&#39;, &#39;pairwise_safety&#39;, &#39;pairwise_groundedness&#39;, &#39;pairwise_instruction_following&#39;, &#39;pairwise_verbosity&#39;, &#39;pairwise_text_quality&#39;, &#39;pairwise_summarization_quality&#39;, &#39;pairwise_question_answering_quality&#39;, &#39;pairwise_multi_turn_chat_quality&#39;, &#39;pairwise_multi_turn_safety&#39;]</pre> <p>ï¼”. <code>question_answering_quality</code> テンプレートの内容を確認</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># プロンプトテンプレートの中身を表示</span> <span class="synIdentifier">print</span>(MetricPromptTemplateExamples.get_prompt_template(<span class="synConstant">&quot;question_answering_quality&quot;</span>)) </pre> <p>出力は以下の通りです。</p> <p><code>question_answering_quality</code> テンプレートの評価基準は、RAG アーキテクチャを使ったアプリケーションの評価にも使えそうであるため、今回はこのテンプレートをそのまま利用します。</p> <pre class="code txt" data-lang="txt" data-unlink># Instruction You are an expert evaluator. Your task is to evaluate the quality of the responses generated by AI models. We will provide you with the user input and an AI-generated response. You should first read the user input carefully for analyzing the task, and then evaluate the quality of the responses based on the Criteria provided in the Evaluation section below. You will assign the response a rating following the Rating Rubric and Evaluation Steps. Give step-by-step explanations for your rating, and only choose ratings from the Rating Rubric. # Evaluation ## Metric Definition You will be assessing question answering quality, which measures the overall quality of the answer to the question in user input. The instruction for performing a question-answering task is provided in the user prompt. ## Criteria Instruction following: The response demonstrates a clear understanding of the question answering task instructions, satisfying all of the instruction&#39;s requirements. Groundedness: The response contains information included only in the context if the context is present in user prompt. The response does not reference any outside information. Completeness: The response completely answers the question with sufficient detail. Fluent: The response is well-organized and easy to read. ## Rating Rubric 5: (Very good). The answer follows instructions, is grounded, complete, and fluent. 4: (Good). The answer follows instructions, is grounded, complete, but is not very fluent. 3: (Ok). The answer mostly follows instructions, is grounded, answers the question partially and is not very fluent. 2: (Bad). The answer does not follow the instructions very well, is incomplete or not fully grounded. 1: (Very bad). The answer does not follow the instructions, is wrong and not grounded. ## Evaluation Steps STEP 1: Assess the response in aspects of instruction following, groundedness, completeness and fluency according to the criteria. STEP 2: Score based on the rubric. # User Inputs and AI-generated Response ## User Inputs ### Prompt {prompt} ## AI-generated Response {response}</pre> <p>5. カスタム評価指標 <code>helpfulness</code> の定義</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># カスタムテンプレートを作成</span> helpfulness_prompt_template = <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">You are a professional writing evaluator. Your job is to score writing responses according to pre-defined evaluation criteria.</span> <span class="synConstant"> </span> <span class="synConstant">You will be assessing helpfulness, which measures the ability to provide important details when answering a prompt.</span> <span class="synConstant"> </span> <span class="synConstant">You will assign the writing response a score from 5, 4, 3, 2, 1, following the rating rubric and evaluation steps.</span> <span class="synConstant"> </span> <span class="synConstant">## Criteria</span> <span class="synConstant">Helpfulness: The response is comprehensive with well-defined key details. The user would feel very satisfied with the content in a good response.</span> <span class="synConstant"> </span> <span class="synConstant">## Rating Rubric</span> <span class="synConstant">5 (completely helpful): Response is useful and very comprehensive with well-defined key details to address the needs in the instruction and usually beyond what explicitly asked. The user would feel very satisfied with the content in the response.</span> <span class="synConstant">4 (mostly helpful): Response is very relevant to the instruction, providing clearly defined information that addresses the instruction's core needs. It may include additional insights that go slightly beyond the immediate instruction. The user would feel quite satisfied with the content in the response.</span> <span class="synConstant">3 (somewhat helpful): Response is relevant to the instruction and provides some useful content, but could be more relevant, well-defined, comprehensive, and/or detailed. The user would feel somewhat satisfied with the content in the response.</span> <span class="synConstant">2 (somewhat unhelpful): Response is minimally relevant to the instruction and may provide some vaguely useful information, but it lacks clarity and detail. It might contain minor inaccuracies. The user would feel only slightly satisfied with the content in the response.</span> <span class="synConstant">1 (unhelpful): Response is useless/irrelevant, contains inaccurate/deceptive/misleading information, and/or contains harmful/offensive content. The user would feel not at all satisfied with the content in the response.</span> <span class="synConstant"> </span> <span class="synConstant">## Evaluation Steps</span> <span class="synConstant">STEP 1: Assess comprehensiveness: does the response provide specific, comprehensive, and clearly defined information for the user needs expressed in the instruction?</span> <span class="synConstant">STEP 2: Assess relevance: When appropriate for the instruction, does the response exceed the instruction by providing relevant details and related information to contextualize content and help the user better understand the response.</span> <span class="synConstant">STEP 3: Assess accuracy: Is the response free of inaccurate, deceptive, or misleading information?</span> <span class="synConstant">STEP 4: Assess safety: Is the response free of harmful or offensive content?</span> <span class="synConstant"> </span> <span class="synConstant">Give step by step explanations for your scoring, and only choose scores from 5, 4, 3, 2, 1.</span> <span class="synConstant"> </span> <span class="synConstant"> </span> <span class="synConstant"># User Inputs and AI-generated Response</span> <span class="synConstant">## User Inputs</span> <span class="synConstant">### Prompt</span> <span class="synConstant">{prompt}</span> <span class="synConstant"> </span> <span class="synConstant">## AI-generated Response</span> <span class="synConstant">{response}</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synComment"># カスタム評価指標を定義 </span> helpfulness = PointwiseMetric( metric=<span class="synConstant">&quot;helpfulness&quot;</span>, metric_prompt_template=helpfulness_prompt_template, ) </pre> <p>ï¼–. サンプルデータの定義</p> <p>今回利用する以下のサンプルデータは以下のとおりです。</p> <ul> <li><strong>questions</strong> :ユーザーの質問</li> <li><strong>retrieved_contexts</strong> :コンテキストとなる検索結果</li> <li><strong>generated_answers</strong> :LLM の出力</li> <li><strong>golden_answers</strong> :正解データ</li> </ul> <pre class="code lang-python" data-lang="python" data-unlink>questions = [ <span class="synConstant">&quot;富士山はどこの県に位置していますか?&quot;</span>, <span class="synConstant">&quot;東京タワーはどの区に位置していますか?&quot;</span>, <span class="synConstant">&quot;沖縄の主要な伝統料理は何ですか?&quot;</span>, <span class="synConstant">&quot;沖縄の伝統舞踊で有名なものは何ですか?&quot;</span> ] retrieved_contexts = [ <span class="synConstant">&quot;富士山は、静岡県と山梨県にまたがって位置しています。標高3,776メートルのこの山は、日本の最高峰であり、象徴的な自然のシンボルとして親しまれています。&quot;</span>, <span class="synConstant">&quot;東京タワーは、東京都港区に位置しており、1958年に建てられた高さ333メートルの電波塔です。東京のシンボルとして、観光名所となっています。&quot;</span>, <span class="synConstant">&quot;日本の主要な伝統料理には、寿司や天ぷらがあります。また、天ぷらは沖縄でもソウルフードとなっています&quot;</span>, <span class="synConstant">&quot;沖縄の伝統舞踊には、エイサーがあり、太鼓のリズムに合わせて踊られるもので、祭りなどで広く披露されています。&quot;</span> ] generated_answers = [ <span class="synConstant">&quot;静岡県&quot;</span>, <span class="synConstant">&quot;港区&quot;</span>, <span class="synConstant">&quot;天ぷら&quot;</span>, <span class="synConstant">&quot;エイサー&quot;</span> ] golden_answers = [ <span class="synConstant">&quot;静岡県と山梨県&quot;</span>, <span class="synConstant">&quot;港区&quot;</span>, <span class="synConstant">&quot;ゴーヤーチャンプルー、ソーキそば&quot;</span>, <span class="synConstant">&quot;エイサー&quot;</span>, ] </pre> <p>ï¼—. 評価データセットの作成</p> <p>各評価指標の入力に必要なカラム名は以下の通りです。</p> <table> <thead> <tr> <th> 評価指標 </th> <th> 評価タイプ </th> <th> 必要なカラム名 </th> </tr> </thead> <tbody> <tr> <td> question_answering_quality </td> <td> モデルベース(Pontwise) </td> <td>・prompt<br>・response </td> </tr> <tr> <td> helpfulness </td> <td> モデルベース(Pontwise) </td> <td> ・prompt<br>・response </td> </tr> <tr> <td> exact_match </td> <td> 計算ベース </td> <td> ・response<br>・reference </td> </tr> </tbody> </table> <p>必要なカラム名に沿って評価データセットを作成します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 評価データセットを作成</span> eval_dataset = pd.DataFrame( { <span class="synConstant">&quot;prompt&quot;</span>: [ <span class="synConstant">&quot;Answer the question: &quot;</span> + question + <span class="synConstant">&quot; Context: &quot;</span> + item <span class="synStatement">for</span> question, item <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(questions, retrieved_contexts) ], <span class="synConstant">&quot;response&quot;</span>: generated_answers, <span class="synComment"># 候補モデルの出力</span> <span class="synConstant">&quot;reference&quot;</span>: golden_answers, <span class="synComment"># 正解データ</span> } ) </pre> <h2 id="実行と結果">実行と結果</h2> <p>8. 評価を実行</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 評価タスクを定義</span> eval_task = EvalTask( dataset=eval_dataset, metrics=[ <span class="synConstant">&quot;question_answering_quality&quot;</span>, <span class="synComment"># モデルベース(事前定義の評価指標)</span> helpfulness, <span class="synComment"># モデルベース(ユーザー独自の評価指標)</span> <span class="synConstant">&quot;exact_match&quot;</span> <span class="synComment"># 計算ベース</span> ], experiment=EXPERIMENT, ) <span class="synComment"># 評価リクエストを実行</span> result = eval_task.evaluate() </pre> <p>evaluate メソッドの戻り値は <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/reference/python/latest/vertexai.evaluation.EvalResult">EvalResult</a> クラスであり以下の属性を持っています。</p> <table> <thead> <tr> <th> 属性名 </th> <th> 説明 </th> </tr> </thead> <tbody> <tr> <td> summary_metrics </td> <td> 各指標の平均値や標準偏差などのサマリ情報 </td> </tr> <tr> <td> metrics_table </td> <td> 評価結果の詳細情報 </td> </tr> <tr> <td> metadata </td> <td> 評価時の実験名などのメタデータ情報 </td> </tr> </tbody> </table> <p>ï¼™. 評価結果のサマリを出力</p> <pre class="code lang-python" data-lang="python" data-unlink>result.summary_metrics </pre> <p>出力は以下の通りです。</p> <pre class="code txt" data-lang="txt" data-unlink>{&#39;row_count&#39;: 4, &#39;question_answering_quality/mean&#39;: 4.0, &#39;question_answering_quality/std&#39;: 1.1547005383792515, &#39;helpfulness/mean&#39;: 3.0, &#39;helpfulness/std&#39;: 0.816496580927726, &#39;exact_match/mean&#39;: 0.5, &#39;exact_match/std&#39;: 0.5773502691896257}</pre> <p>10. 評価結果の詳細を出力</p> <pre class="code lang-python" data-lang="python" data-unlink>result.metrics_table </pre> <p>出力は以下の通りです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241125/20241125090008.png" width="800" height="252" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>文字が見えづらいため、 <code>question_answering_quality</code> 評価指標の最初のレコードのみを以下に記載します。</p> <blockquote><p>[ <strong>prompt</strong> ]<br/> Answer the question: 富士山はどこの県に位置していますか? <br/> Context: 富士山は、静岡県と山梨県にまたがって位置しています。標高3,776メートルのこの山は、日本の最高峰であり、象徴的な自然のシンボルとして親しまれています。</p> <p>[ <strong>response</strong> ]<br/> 静岡県</p> <p>[ <strong>reference</strong> ]<br/> 静岡県と山梨県</p> <p>[ <strong>question_answering_quality/explanation</strong> ]<br/> The response is incomplete. Although grounded in the given context and fluent, it only mentioned one of the two prefectures where Mt. Fuji is located. The prompt asked "In which prefecture is Mt. Fuji located?" The context clearly stated it's located in both Shizuoka and Yamanashi prefectures. Therefore, the instruction following is weak.</p> <p>~日本語に翻訳~<br/> 回答は不完全です。与えられた文脈に基づいており流暢ではありますが、富士山がある 2 つの県のうちの 1 つしか言及されていません。プロンプトは「富士山はどの県にありますか?」と尋ねており、文脈から静岡県と山梨県の両方にあることは明らかです。したがって、指示に従うことが不十分です。</p> <p>[ <strong>question_answering_quality/score</strong> ]<br/> 3.0</p></blockquote> <p>定量的なスコアが取得できていることが確認できました。 なお、モデルベースの評価指標の出力には <code>explanation</code> 列が含まれており、判定モデルがスコアを算出するために行った思考の過程が記録されています。つまり、explanation 列の内容が <strong>判定モデルが出力したスコアの根拠</strong> となります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/view-evaluation">View and interpret evaluation results</a></li> </ul> <h1 id="その他">その他</h1> <h2 id="クォータの制限について">クォータの制限について</h2> <p>モデルベースの評価タイプでは、判定モデルに Vertex AI Gemini API が使用されるためクォータには注意が必要です。特に、1 度に大量の評価データセットを含める場合や、評価リクエストの同時実行数が高くなる場合は、 <code>gemini-1.5-pro</code> のクォータの制限緩和をご検討ください。</p> <p>制限緩和の申請については、公式ドキュメントをご参照ください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/run-evaluation#increase-quota">Run model-based evaluation with increased rate limits and quota</a></li> </ul> <h2 id="評価データセットの件数">評価データセットの件数</h2> <p>高品質な評価結果を得るためには、評価データセットを 100〜400 件にすることが推奨されています。この範囲であれば、外れ値の影響を最小限に抑えつつ、さまざまなシナリオでのパフォーマンスが期待できます。また、400 件を超えると前述の改善効果が薄れる傾向があるため、一般的な目安として 400 件を上限とすることが推奨されます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/evaluation-dataset#best-practices">Best practices</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-matayuuu/profile_256x256.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">又吉 佑樹<a href="https://blog.g-gen.co.jp/archive/author/ggen-matayuuu">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部</p> <p class="sw-profile__txt">はいさい、沖縄出身のクラウドエンジニア!</p> <p class="sw-profile__txt">セールスからエンジニアへ転身。Google Cloud å…¨ 11 資格保有。Google Cloud Champion Innovator (AI/ML)。Google Cloud Partner Top Engineer 2024。Google Cloud 公式ユーザー会 Jagu'e'r でエバンジェリスト。好きな分野は生成 AI。</p> <a href="https://twitter.com/matayuuuu?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @matayuuuu</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-matayuuu 「サービス アカウント キーの作成が無効になっています」への対処法 hatenablog://entry/6802418398300331936 2024-11-22T09:00:00+09:00 2024-11-22T09:00:01+09:00 G-gen の杉村です。Google Cloud(旧称 GCP)で、サービスアカウントから認証キーを作成しようとした際に サービス アカウント キーの作成が無効になっています 組織ポリシーの制約「iam.disableServiceAccountKeyCreation」が組織に適用されています。 と表示されてキーが作成できない場合の、対処法を紹介します。 事象とメッセージ 原因 対処する前に 対処方法 対処手順 IAM 権限の確認 組織、フォルダまたはプロジェクトを選択 組織のポリシー画面へ遷移 制約の編集画面へ遷移 制約を編集 結果の確認 事象とメッセージ Google Cloud(旧称 G… <p>G-gen の杉村です。Google Cloud(旧称 GCP)で、サービスアカウントから認証キーを作成しようとした際に <code>サービス アカウント キーの作成が無効になっています</code> <code>組織ポリシーの制約「iam.disableServiceAccountKeyCreation」が組織に適用されています。</code> と表示されてキーが作成できない場合の、対処法を紹介します。</p> <ul class="table-of-contents"> <li><a href="#事象とメッセージ">事象とメッセージ</a></li> <li><a href="#原因">原因</a></li> <li><a href="#対処する前に">対処する前に</a></li> <li><a href="#対処方法">対処方法</a></li> <li><a href="#対処手順">対処手順</a><ul> <li><a href="#IAM-権限の確認">IAM 権限の確認</a></li> <li><a href="#組織フォルダまたはプロジェクトを選択">組織、フォルダまたはプロジェクトを選択</a></li> <li><a href="#組織のポリシー画面へ遷移">組織のポリシー画面へ遷移</a></li> <li><a href="#制約の編集画面へ遷移">制約の編集画面へ遷移</a></li> <li><a href="#制約を編集">制約を編集</a></li> <li><a href="#結果の確認">結果の確認</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031132805.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="事象とメッセージ">事象とメッセージ</h1> <p>Google Cloud(旧称 GCP)で、サービスアカウントから認証キーを作成しようとした際に、以下のメッセージが表示され、サービスアカウントキーが作成できませんでした。</p> <p><figure class="figure-image figure-image-fotolife" title="サービス アカウント キーの作成が無効になっています"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031132839.png" width="627" height="470" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>サービス アカウント キーの作成が無効になっています</figcaption></figure></p> <blockquote><p>サービス アカウント キーの作成が無効になっています</p> <p>組織ポリシーの制約「iam.disableServiceAccountKeyCreation」が組織に適用されています。</p> <p>考えられる原因: 組織ポリシー管理者が、サービス アカウント キーに関連するセキュリティ インシデントを防ぐために、この組織ポリシーを適用しました。また、 「デフォルトで保護」の適用 により、組織にポリシーが自動的に適用された可能性もあります。</p> <p>推奨される次のステップ: サービス アカウント キーは、適切に管理しなかった場合、セキュリティ リスクとなります。可能であれば、 より安全な代替手段 を選択してください。サービス アカウント キーで認証する必要がある場合は、 組織の「組織ポリシー管理者」(roles/orgpolicy.policyAdmin)のロールを持つ管理者が 「iam.disableServiceAccountKeyCreation」の制約を 無効にする 必要があります。</p> <p>追跡番号: (英数字)</p></blockquote> <p>コンソールが英語版の場合、以下のようなメッセージです。</p> <p><figure class="figure-image figure-image-fotolife" title="Service account key creation is disabled"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031132853.png" width="620" height="409" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Service account key creation is disabled</figcaption></figure></p> <blockquote><p>Service account key creation is disabled</p> <p>The organization policy constraint 'iam.disableServiceAccountKeyCreation' is enforced on your organization. Possible Causes: Your Organization Policy Administrator enforced the Organization Policy to prevent security incidents related to Service Account keys. Alternatively, your organization may have been automatically enforced with the policy as part of Secure by Default enforcements.</p> <p>Recommended Next Steps: Service account keys are a security risk if not managed correctly. You should choose a more secure alternative whenever possible. If you must authenticate with a service account key, an administrator with the "Organization Policy Administrator" (roles/orgpolicy.policyAdmin) role on the organization needs to disable the "iam.disableServiceAccountKeyCreation" constraint.</p> <p>Tracking number: (alphanumeric characters)</p></blockquote> <h1 id="原因">原因</h1> <p>この事象は、組織ポリシーの制約 <code>iam.disableServiceAccountKeyCreation</code> が組織レベル、フォルダレベルまたはプロジェクトレベルで有効化されているときに発生します。</p> <p><strong>組織のポリシー</strong>は、セキュリティや統制の向上のために、所定のルールを Google Cloud 環境全体に適用する仕組みのことです。組織のポリシーの詳細は、以下の記事を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Forganization-policy-explained" title="組織のポリシーを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/organization-policy-explained">blog.g-gen.co.jp</a></cite></p> <p><code>iam.disableServiceAccountKeyCreation</code> は、サービスアカウントキーの作成を禁止する制約です。この制約は、2024年初頭以降に作成された Google Cloud 組織では<strong>デフォルトで有効化</strong>されています。それ以前に作成された組織でも、管理者が明示的にこの制約を有効化している場合は、この事象が発生します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts?hl=ja#disable_service_account_key_creation">サービス アカウント キー作成の無効化</a></li> </ul> <h1 id="対処する前に">対処する前に</h1> <p>サービスアカウントキーは JSON フォーマットのテキストファイル(もしくは P12 形式のファイル)であり、流出の危険があります。そのため一般的には<strong>サービスアカウントキーの使用は非推奨</strong>であり、代わりにプログラム動作基盤(Compute Engine VM ã‚„ Cloud Run Service)にサービスアカウントをアタッチしたり、動作基盤が Google Cloud 以外なのであれば Workload Identity ã‚„ Workforce Identity の使用が推奨されます。</p> <p>2024年初頭以降に作成された Google Cloud 組織で <code>iam.disableServiceAccountKeyCreation</code> がデフォルトで有効化されているのは、リスクの高い手法であるサービスアカウントキーの使用を抑止するためです。</p> <p>このエラーを解消する最も簡単な方法は、<code>iam.disableServiceAccountKeyCreation</code> 制約を無効化することですが、無効化を行う前に、<strong>本当にサービスアカウントキーを生成する必要があるのか</strong>、代わりにサービスアカウントのアタッチや Workload Identity で<strong>代用できないか</strong>を、十分ご検討ください。</p> <p>なお Workload Identity とは、OIDC ã‚„ SAML 2.0 を利用して、Amazon Web Services(AWS)や Microsoft Azure、Active Directory Federation Service 等の ID を使って Google Cloud に認証する仕組みです。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/migrate-from-service-account-keys?hl=ja">サービス アカウント キーから移行する</a></li> <li>参考 : <a href="https://cloud.google.com/iam/docs/workload-identity-federation?hl=ja">Workload Identity 連携</a></li> <li>参考 : <a href="https://cloud.google.com/iam/docs/workforce-identity-federation?hl=ja">Workforce Identity の連携</a></li> </ul> <h1 id="対処方法">対処方法</h1> <p>組織ポリシーの制約 <code>iam.disableServiceAccountKeyCreation</code> を無効化することで、サービスアカウントキーが作成できるようになります。</p> <p>組織ポリシーの制約は、組織レベル、フォルダレベル、プロジェクトレベルで適用することができ、親リソースのポリシーは子リソースに<strong>継承</strong>されます。ただし、明示的に設定することで、子リソース側で親リソースの制約をオーバーライド(上書き)することも可能です。</p> <p>よって、取り得る選択肢としては、以下のいずれかになります。</p> <ol> <li><code>iam.disableServiceAccountKeyCreation</code> を組織レベルで無効化する</li> <li><code>iam.disableServiceAccountKeyCreation</code> をフォルダレベルでオーバーライドして無効化する</li> <li><code>iam.disableServiceAccountKeyCreation</code> をプロジェクトレベルでオーバーライドして無効化する</li> </ol> <p>上記のうち <code>1.</code>、<code>2.</code> の場合、組織全体もしくはフォルダ全体で制約が無効になり、影響は他のプロジェクトにも及びます。<code>3.</code> の影響範囲は当該プロジェクトのみです。</p> <p>これ以降、当記事では当該制約を無効化する手順を解説しますが、前掲の「対処する前に」をお読みいただき、<strong>リスクを理解したうえで実施</strong>してください。</p> <h1 id="対処手順">対処手順</h1> <h2 id="IAM-権限の確認">IAM 権限の確認</h2> <p>当手順を実施するには、操作する Google アカウント、もしくはアカウントが所属するグループが、<strong>組織レベル</strong>で<strong>組織ポリシー管理者</strong>(<code>roles/orgpolicy.policyAdmin</code>)ロールを持っている必要があります。</p> <p>組織ポリシー管理者を付与できる最も下位レベルのリソースは「組織」です。よって、フォルダやプロジェクトレベルで制約をオーバーライドする場合でも、組織レベルで組織ポリシー管理者ロールを持っている必要があります。</p> <p>作業者の Google アカウントが必要な権限を持っていない場合は、組織レベルで IAM ロール「組織ポリシー管理者」を付与してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/access-control-org?hl=ja">IAM を使用した組織リソースのアクセス制御</a></li> </ul> <h2 id="組織フォルダまたはプロジェクトを選択">組織、フォルダまたはプロジェクトを選択</h2> <p>Google Cloud コンソールにログインし、プロジェクトセレクターをクリックして、制約を無効化を適用する組織、フォルダ、またはプロジェクトを選択します。</p> <p>当記事の「対処する前に」「対処方法」をよくお読みになり、制約の編集位置を決めたうえで選択してください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133005.png" width="1014" height="465" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="組織のポリシー画面へ遷移">組織のポリシー画面へ遷移</h2> <p>コンソール上部の検索ボックスに「組織のポリシー」を入力し、サジェストされた「組織のポリシー」を選択します。</p> <p>または、「IAM と管理」画面から直接遷移しても構いません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133035.png" width="1200" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="制約の編集画面へ遷移">制約の編集画面へ遷移</h2> <p>制約一覧の上部のフィルタに <code>constraints/iam.disableServiceAccountKeyCreation</code> を入力します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133046.png" width="1200" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>フィルタ結果の中から、Disable service account key creation をクリックして、編集画面へ遷移します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133054.png" width="1200" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="制約を編集">制約を編集</h2> <p>ボタン「ポリシーを管理」を押下します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133108.png" width="1056" height="614" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「ポリシーのソース」ブロックで、「親のポリシーをオーバーライドする」を選択します。「ルール」ブロックが表示されるので、「ルールの追加」を押下し、「適用」を「オフ」にします。最後に、ボタン「ポリシーを設定」を押下します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133117.png" width="391" height="724" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="結果の確認">結果の確認</h2> <p>設定が完了すると、以下のような表示になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241031/20241031133125.png" width="705" height="681" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura 組織のポリシーをタグで制御してみた hatenablog://entry/6802418398299705868 2024-11-20T09:00:00+09:00 2025-01-10T09:36:38+09:00 Google Cloud の組織ポリシー機能で、プロジェクトにタグを適用することで例外設定を行う方法を検証しました。 はじめに 当記事について 前提知識 検証 タグキーと値の作成 フォルダとプロジェクトの作成 ポリシーの適用 補足情報 条件の指定について Terraform はじめに 当記事について Google Cloud(旧称 GCP)では組織のポリシーを使って、組織内のプロジェクトに一律でセキュリティや統制強化のための設定を適用できます。このとき、特定のプロジェクトにのみ例外を設けたい場合もあります。 例えば、組織全体に「公開アクセスの防止を適用する(constraints/storag… <p>Google Cloud の組織ポリシー機能で、プロジェクトにタグを適用することで例外設定を行う方法を検証しました。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a><ul> <li><a href="#当記事について">当記事について</a></li> <li><a href="#前提知識">前提知識</a></li> </ul> </li> <li><a href="#検証">検証</a><ul> <li><a href="#タグキーと値の作成">タグキーと値の作成</a></li> <li><a href="#フォルダとプロジェクトの作成">フォルダとプロジェクトの作成</a></li> <li><a href="#ポリシーの適用">ポリシーの適用</a></li> </ul> </li> <li><a href="#補足情報">補足情報</a><ul> <li><a href="#条件の指定について">条件の指定について</a></li> <li><a href="#Terraform">Terraform</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241107/20241107104104.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <h2 id="当記事について">当記事について</h2> <p>Google Cloud(旧称 GCP)では<strong>組織のポリシー</strong>を使って、組織内のプロジェクトに一律でセキュリティや統制強化のための設定を適用できます。このとき、特定のプロジェクトにのみ<strong>例外</strong>を設けたい場合もあります。</p> <p>例えば、組織全体に「公開アクセスの防止を適用する(<code>constraints/storage.publicAccessPrevention</code>)」という組織のポリシーの制約を適用しているとします。しかし、あるプロジェクトでは Cloud Storage の公開バケットに静的コンテンツをホスティングしているとします。このときは、当該プロジェクトのみを制約の適用外にする必要があります。</p> <p>当記事ではそのような場合を想定して、タグを使って特定のプロジェクトは制約の対象外としつつ、その他全ての組織内プロジェクトに制約を適用する検証を行いました。</p> <ul> <li>参考 : <a href="https://cloud.google.com/resource-manager/docs/organization-policy/tags-organization-policy?hl=ja">タグを使用した組織のポリシーの設定</a></li> </ul> <h2 id="前提知識">前提知識</h2> <p>当記事では Resource Manager の<strong>ã‚¿ã‚°</strong>機能を利用します。タグの詳細や、類似機能であるラベルとの差異については、以下の記事を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fdifference-between-tags-and-labels" title="タグとラベルの違いについて (Tags / Labels) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/difference-between-tags-and-labels">blog.g-gen.co.jp</a></cite></p> <p>組織のポリシー機能については、以下の記事を参照してください。特に、継承と呼ばれる性質については理解が必要です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Forganization-policy-explained" title="組織のポリシーを解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/organization-policy-explained">blog.g-gen.co.jp</a></cite></p> <h1 id="検証">検証</h1> <h2 id="タグキーと値の作成">タグキーと値の作成</h2> <p>まず、組織のリソースとして、「IAM と管理」画面から<strong>タグキー</strong>を作成します。今回は <code>apply-policy</code> という名称で作成します。</p> <p><figure class="figure-image figure-image-fotolife" title="タグキー作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090017.png" width="800" height="575" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>タグキー作成</figcaption></figure></p> <p>次に、タグキーの値として、<code>true</code> と <code>false</code> を作成します。今回の検証ではこの値を使っていますが、必ずしもブール値である必要はなく、運用上都合の良い文字列を指定して構いません。</p> <p><figure class="figure-image figure-image-fotolife" title="タグの値作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090013.png" width="800" height="557" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>タグの値作成</figcaption></figure></p> <h2 id="フォルダとプロジェクトの作成">フォルダとプロジェクトの作成</h2> <p>以下のようにフォルダとプロジェクトを作成します。</p> <ul> <li>フォルダに属さないプロジェクト <ul> <li><code>test-20241028-true</code></li> <li><code>test-20241028-false</code></li> <li><code>test-20241028-none</code></li> </ul> </li> <li>フォルダ <code>my_folder</code></li> <li>同フォルダ配下の、以下のプロジェクト <ul> <li><code>test-20241028-true-in-folder</code></li> <li><code>test-20241028-false-in-folder</code></li> <li><code>test-20241028-none-in-folder</code></li> </ul> </li> </ul> <p>ã‚¿ã‚° <code>apply-policy: true</code> を、プロジェクト <code>test-20241028-true</code> と <code>test-20241028-true-in-folder</code> に付与します。</p> <p>ã‚¿ã‚° <code>apply-policy: false</code> を、プロジェクト <code>test-20241028-false</code> と <code>test-20241028-false-in-folder</code> に付与します。</p> <p>さらに、タグ <code>apply-policy: false</code> を、フォルダ <code>my_folder</code> に付与します。</p> <p><figure class="figure-image figure-image-fotolife" title="プロジェクトの作成・タグの付与"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090021.png" width="800" height="251" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プロジェクトの作成・タグの付与</figcaption></figure></p> <p>フォルダにタグを付与した場合、そのフォルダに属するプロジェクトには<strong>タグが継承</strong>されます。ただし、プロジェクトにタグが直接付与されている場合、そちらが<strong>優先</strong>されます。上記のスクリーンショットでは、プロジェクト <code>test-20241028-none-in-folder</code> の Tags 列に継承を表すアイコンが表示されており、フォルダから継承されたタグが適用されていることがわかります。</p> <h2 id="ポリシーの適用">ポリシーの適用</h2> <p>この組織に対して、組織レベルで制約を適用します。今回適用する制約は「公開アクセスの防止を適用する(<code>constraints/storage.publicAccessPrevention</code>)」です。組織レベルにこの制約を適用しつつ、タグ <code>apply-policy: false</code> が付与されたプロジェクトではこの制約の効果が発揮されない状態を目指します。</p> <p>組織のポリシーの Google Cloud コンソール画面で上記の制約を検索し、編集します。この時、設定値として、「親のポリシーをオーバーライドする」を選択します。編集画面では、2つのルールを追加します。</p> <p>1つ目は、特定のタグキーと値を持っていることを条件に、「適用」を「オフ」とするものです。設定値「値のパス」は、<code>[organization_name]/apply-policy/false</code> とします。タグを作成済みの場合、プルダウンメニューから選択できます。</p> <p><figure class="figure-image figure-image-fotolife" title="ポリシーの編集"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090045.png" width="800" height="433" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ポリシーの編集</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="値のパス"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090049.png" width="800" height="211" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>値のパス</figcaption></figure></p> <p>2つ目は、1つ目のルールの条件に合致しない場合に適用される、デフォルトのルールです。「ルールの追加」を押下し、「適用」のラジオボタンで「オン」を選択します。これを設定しない場合、<code>A boolean policy must always include one unconditional rule.</code> というメッセージが表示され、ポリシーの設定が完了しません。</p> <p>組織レベルでポリシーが適用された場合、コンソール画面の表記は以下のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="適用された組織ポリシー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090025.png" width="800" height="769" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>適用された組織ポリシー</figcaption></figure></p> <p>ポリシーの適用後、各プロジェクトのポリシー適用状態は以下のようになります。</p> <table> <thead> <tr> <th> プロジェクト </th> <th> â—‹:適用済み、×:未適用 </th> </tr> </thead> <tbody> <tr> <td> test-20241028-true </td> <td> â—‹ </td> </tr> <tr> <td> test-20241028-false </td> <td> × </td> </tr> <tr> <td> test-20241028-none </td> <td> â—‹ </td> </tr> <tr> <td> test-20241028-true-in-folder </td> <td> â—‹ </td> </tr> <tr> <td> test-20241028-false-in-folder </td> <td> × </td> </tr> <tr> <td> test-20241028-none-in-folder </td> <td> × </td> </tr> </tbody> </table> <p>以下は、各プロジェクトレベルでポリシーの適用状態を確認した際のキャプチャです。想定したとおりに、ポリシーが適用されていることがわかります。</p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-true"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090009.png" width="800" height="669" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-true</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-false"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090041.png" width="800" height="692" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-false</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-none"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090033.png" width="800" height="641" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-none</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-true-in-folder"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090006.png" width="800" height="651" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-true-in-folder</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-false-in-folder"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090037.png" width="800" height="678" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-false-in-folder</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="test-20241028-none-in-folder"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241120/20241120090029.png" width="800" height="665" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>test-20241028-none-in-folder</figcaption></figure></p> <p>以上の検証結果から、以下のことが確かめられました。</p> <ul> <li>明示的にタグを付与したプロジェクトには、ポリシーが適用されない</li> <li>親フォルダのタグを継承しているプロジェクトには、ポリシーが適用されない</li> <li>タグが付与されていないプロジェクトには、デフォルトルールに基づきポリシーが適用される</li> </ul> <h1 id="補足情報">補足情報</h1> <h2 id="条件の指定について">条件の指定について</h2> <p>今回の検証では、ポリシーの除外対象とするプロジェクトをタグで指定しました。反対に、適用対象をタグで指定することも可能です。</p> <p>また、今回の検証ではタグ名を使って指定する方法を紹介しましたが、他にタグの永続 ID(permanent ID。タグキーや値が持つリソース ID)で指定することもできます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/iam/docs/tags-access-control?hl=ja#condition-id">永続 ID を使用する条件</a></li> </ul> <h2 id="Terraform">Terraform</h2> <p>当検証で使用した組織レベルのリソースは、以下のように Terraform で記述することができます。なお以下のサンプルコード内の変数 <code>organization_number_id</code> は10~13桁の数字で表される組織 ID です。</p> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synType">resource</span> <span class="synConstant">&quot;google_tags_tag_key&quot;</span> <span class="synConstant">&quot;apply_policy&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">short_name</span> = <span class="synConstant">&quot;apply-policy&quot;</span> <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;organizations/$</span><span class="synSpecial">{</span>var.organization_number_id<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;The flag indicating whether to apply the policy or not.&quot;</span> <span class="synSpecial">}</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_tags_tag_value&quot;</span> <span class="synConstant">&quot;apply_policy_true&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;tagKeys/$</span><span class="synSpecial">{</span>google_tags_tag_key.apply_policy.name<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synIdentifier">short_name</span> = <span class="synConstant">&quot;true&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;Apply organization policy to the project.&quot;</span> <span class="synSpecial">}</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_tags_tag_value&quot;</span> <span class="synConstant">&quot;apply_policy_false&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;tagKeys/$</span><span class="synSpecial">{</span>google_tags_tag_key.apply_policy.name<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synIdentifier">short_name</span> = <span class="synConstant">&quot;false&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;Do NOT apply organization policy to the project.&quot;</span> <span class="synSpecial">}</span> <span class="synType">resource</span> <span class="synConstant">&quot;google_org_policy_policy&quot;</span> <span class="synConstant">&quot;storage_public_access_prevention&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = <span class="synConstant">&quot;organizations/$</span><span class="synSpecial">{</span>var.organization_number_id<span class="synSpecial">}</span><span class="synConstant">/storage.publicAccessPrevention&quot;</span> <span class="synIdentifier">parent</span> = <span class="synConstant">&quot;organizations/$</span><span class="synSpecial">{</span>var.organization_number_id<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synType">spec</span> <span class="synSpecial">{</span> <span class="synType">rules</span> <span class="synSpecial">{</span> <span class="synIdentifier">enforce</span> = <span class="synConstant">&quot;FALSE&quot;</span> <span class="synType">condition</span> <span class="synSpecial">{</span> <span class="synIdentifier">title</span> = <span class="synConstant">&quot;Do NOT apply with the tag&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;Do NOT apply storage.publicAccessPrevention on the projects tagged with apply-policy: false&quot;</span> <span class="synIdentifier">expression</span> = <span class="synConstant">&quot;resource.matchTag('$</span><span class="synSpecial">{</span>var.organization_number_id<span class="synSpecial">}</span><span class="synConstant">/apply-policy', 'false')&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">rules</span> <span class="synSpecial">{</span> <span class="synIdentifier">enforce</span> = <span class="synConstant">&quot;TRUE&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn.profile-image.st-hatena.com/users/ggen-shunsuketsumori/profile_128x128.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">G-genメンバー<a href="https://blog.g-gen.co.jp/archive/author/ggen-tsumori">(記事一覧)</a></p> <p class="sw-profile__txt">クラウドソリューション部 <p class="sw-profile__txt"><br> </div> </div> </div> </div> ggen-shunsuketsumori Pub/SubのCloud Storage import topicを使ってみた hatenablog://entry/6802418398303434810 2024-11-18T09:00:00+09:00 2024-11-18T09:00:00+09:00 G-gen の杉村です。Pub/Sub の Cloud Storage インポートトピック(Cloud Storage import topic)を使うと、事前に指定した Cloud Storage バケットに Put されたテキストオブジェクトを、ノーコードで Pub/Sub トピックにパブリッシュし、簡単に Pub/Sub サブスクリプションに配信できます。 前提知識 Cloud Storage インポートトピックとは 設定値 検証の概要 環境構築 サービスエージェントへ IAM 権限の付与(パブリッシュ) バケットの作成 トピックの作成 サービスエージェントへ IAM 権限の付与(バケッ… <p>G-gen の杉村です。Pub/Sub の <strong>Cloud Storage インポートトピック</strong>(Cloud Storage import topic)を使うと、事前に指定した Cloud Storage バケットに Put されたテキストオブジェクトを、ノーコードで Pub/Sub トピックにパブリッシュし、簡単に Pub/Sub サブスクリプションに配信できます。</p> <ul class="table-of-contents"> <li><a href="#前提知識">前提知識</a><ul> <li><a href="#Cloud-Storage-インポートトピックとは">Cloud Storage インポートトピックとは</a></li> <li><a href="#設定値">設定値</a></li> </ul> </li> <li><a href="#検証の概要">検証の概要</a></li> <li><a href="#環境構築">環境構築</a><ul> <li><a href="#サービスエージェントへ-IAM-権限の付与パブリッシュ">サービスエージェントへ IAM 権限の付与(パブリッシュ)</a></li> <li><a href="#バケットの作成">バケットの作成</a></li> <li><a href="#トピックの作成">トピックの作成</a></li> <li><a href="#サービスエージェントへ-IAM-権限の付与バケット読み取り">サービスエージェントへ IAM 権限の付与(バケット読み取り)</a></li> <li><a href="#BigQuery-テーブルと-BigQuery-サブスクリプションの作成">BigQuery テーブルと BigQuery サブスクリプションの作成</a></li> <li><a href="#トピックの状態を確認">トピックの状態を確認</a></li> </ul> </li> <li><a href="#動作確認">動作確認</a><ul> <li><a href="#オブジェクトの-Put">オブジェクトの Put</a></li> <li><a href="#テーブルの確認">テーブルの確認</a></li> <li><a href="#追加検証区切り文字の追加">追加検証(区切り文字の追加)</a></li> <li><a href="#追加検証メッセージ本文の確認">追加検証(メッセージ本文の確認)</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113095706.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="前提知識">前提知識</h1> <h2 id="Cloud-Storage-インポートトピックとは">Cloud Storage インポートトピックとは</h2> <p><strong>Pub/Sub</strong> は、Google Cloud(旧称 GCP)のフルマネージドなメッセージングサービスです。Pub/Sub の意義や、実現できるアーキテクチャは以下の記事もご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Funderstanding-loosely-coupled-architecture" title="Google Cloudで理解する疎結合アーキテクチャとメッセージングサービス - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/understanding-loosely-coupled-architecture">blog.g-gen.co.jp</a></cite></p> <p>Pub/Sub の <strong>Cloud Storage インポートトピック</strong>(Cloud Storage import topic)を使うと、事前に指定した Cloud Storage バケットに Put されたテキストオブジェクトを、ノーコードで Pub/Sub トピックにパブリッシュし、簡単に Pub/Sub サブスクリプションに配信できます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/pubsub/docs/create-cloud-storage-import-topic?hl=en">Create a Cloud Storage import topic</a></li> </ul> <p>この Cloud Storage インポートトピックを<strong>使わない場合</strong>は、Cloud Storage にオブジェクトが Put されたことを Eventarc で検知し、Cloud Run functions 等を起動、記述したプログラムで配信先に書き込むという処理の開発が必要になります。</p> <p>一方で、当記事で紹介する Cloud Storage インポートトピックを使えば、Cloud Storage バケットに書き込まれたオブジェクトを読み取る<strong>プログラムを開発することなく</strong>、自動的に Pub/Sub トピックにパブリッシュすることができます。</p> <p>そのようにして Pub/Sub トピックにパブリッシュされたメッセージは、サブスクリプションを経由して、BigQuery に書き込んだり、他の API エンドポイントに Push することなどが可能です。</p> <p><figure class="figure-image figure-image-fotolife" title="通常のトピックと Cloud Storage インポートトピックの違い"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113093323.png" width="1187" height="523" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>通常のトピックと Cloud Storage インポートトピックの違い</figcaption></figure></p> <h2 id="設定値">設定値</h2> <p>Cloud Storage インポートトピックには、以下のような設定値があります。</p> <table> <thead> <tr> <th> 設定名 </th> <th> 説明 </th> </tr> </thead> <tbody> <tr> <td> 取り込み元バケット </td> <td> データを取り込む Cloud Storage バケットを指定 </td> </tr> <tr> <td> オブジェクトの形式 </td> <td> Text、Avro、Pub/Sub Avro から選択 </td> </tr> <tr> <td> 区切り文字 </td> <td> Text の場合のみ指定。この区切り文字に基づいてメッセージが分割される。1文字まで。省略すると <code>\n</code> (改行) </td> </tr> <tr> <td> 最短のオブジェクト作成時間 </td> <td> 取り込み開始時刻。この時刻より前に作成されたオブジェクトは取り込まれない </td> </tr> <tr> <td> glob パターン </td> <td> ここで指定したパスパターンに一致するオブジェクトのみが取り込まれる。<code>**</code> で全オブジェクト。<code>**.txt</code> で拡張子指定等 </td> </tr> </tbody> </table> <p>以下は、Google Cloud コンソールにおける設定画面のスクリーンショットです。</p> <p><figure class="figure-image figure-image-fotolife" title="トピック作成画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112191229.png" width="592" height="738" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>トピック作成画面</figcaption></figure></p> <h1 id="検証の概要">検証の概要</h1> <p>当記事では、以下の構成で検証を行います。</p> <p><figure class="figure-image figure-image-fotolife" title="検証 1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113094432.png" width="1156" height="188" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>検証 1</figcaption></figure></p> <p>上記の構成では、Cloud Storage インポートトピックによる自動的なメッセージ取り込みを行います。取り込んだメッセージは、BigQuery サブスクリプション経由で BigQuery テーブルに書き込みます。これによりソースコードを一切書くことなく、Cloud Storage に到着したテキストデータを順次、BigQuery に書き込むことが可能です。</p> <p>Pub/Sub の BigQuery サブスクリプションの詳細は、以下の記事を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fpubsub-bigquery-subscription" title="Pub/SubのBigQueryサブスクリプションを使ってみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/pubsub-bigquery-subscription">blog.g-gen.co.jp</a></cite></p> <p>さらに、追加検証として、メッセージ本文の構成を確かめるため、Pull サブスクリプションを作成して直接メッセージの内容を確認する検証も行います。</p> <p><figure class="figure-image figure-image-fotolife" title="検証 2"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113094500.png" width="1156" height="188" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>検証 2</figcaption></figure></p> <h1 id="環境構築">環境構築</h1> <h2 id="サービスエージェントへ-IAM-権限の付与パブリッシュ">サービスエージェントへ IAM 権限の付与(パブリッシュ)</h2> <p>Pub/Sub が利用するサービスエージェント(Google Cloud サービスが利用するサービスアカウントのこと)に、必要な IAM 権限を付与します。</p> <p>サービスエージェント名は <code>service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com</code> です。<code>{PROJECT_NUMBER}</code> にはプロジェクト番号が入ります。このサービスエージェントに、プロジェクトレベルで Pub/Sub パブリッシャー(<code>roles/pubsub.publisher</code>)ロールを付与します。このロールは、サービスエージェントが Cloud Storage インポートトピックにデータをパブリッシュするのに必要になります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/pubsub/docs/create-cloud-storage-import-topic#add-publisher-role">Add the Pub/Sub publisher role to the Pub/Sub service account</a></li> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/service-agent-explained">サービスエージェントとは何か ‐ G-gen Tech Blog</a></li> </ul> <p>プロジェクトレベルの IAM バインディング一覧画面では、サービスエージェントに付与された IAM ロールは非表示になっています。以下のスクリーンショットのように「Google 提供のロール付与を含める」チェックボックスをオンにすると、IAM ロールが表示されます。</p> <p>なお以下のスクリーンショットでは、デフォルトで付与されている「Cloud Pub/Sub サービス エージェント」、今回付与した「Pub/Sub パブリッシャー」に加えて「BigQuery データ編集者」が付与されていますが、これは今回の検証で BigQuery サブスクリプションを利用するためです。</p> <p><figure class="figure-image figure-image-fotolife" title="プロジェクトの IAM 画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112192844.png" width="1181" height="372" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プロジェクトの IAM 画面</figcaption></figure></p> <h2 id="バケットの作成">バケットの作成</h2> <p>Cloud Storage バケットを作成します。今回は、以下のようなバケットを作成しました。</p> <p><figure class="figure-image figure-image-fotolife" title="Cloud Storage バケットの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112193642.png" width="1102" height="562" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Storage バケットの作成</figcaption></figure></p> <h2 id="トピックの作成">トピックの作成</h2> <p>Cloud Storage インポートトピックを作成します。</p> <p>オブジェクトの形式は Text とし、区切り文字は空白とします。区切り文字を明示しない場合、<code>\n</code> (改行)が区切り文字として認識されます。つまり、あるテキストファイルをバケットに Put した場合、<strong>1行が1メッセージ</strong>として Pub/Sub トピックに取り込まれます。</p> <p><figure class="figure-image figure-image-fotolife" title="Cloud Storage インポートトピックの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112193751.png" width="724" height="652" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Storage インポートトピックの作成</figcaption></figure></p> <h2 id="サービスエージェントへ-IAM-権限の付与バケット読み取り">サービスエージェントへ IAM 権限の付与(バケット読み取り)</h2> <p>このとき、Pub/Sub のサービスエージェント(<code>service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com</code>)が対象バケットへの読み取り権限を持っていない場合、以下のようなメッセージが表示されます。</p> <p><figure class="figure-image figure-image-fotolife" title="権限の確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112193929.png" width="724" height="652" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>権限の確認と付与</figcaption></figure></p> <p>「権限の設定」を押下すると、以下のような画面が表示され、「ロールの付与」をクリックすることで必要な IAM 権限を付与できます。</p> <p><figure class="figure-image figure-image-fotolife" title="IAM 権限の付与"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112194128.png" width="1167" height="368" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>IAM 権限の付与</figcaption></figure></p> <p>これを行うと、対象バケットのレベルで「Storage オブジェクト閲覧者」「Storage レガシー バケット読み取り」ロールが付与されます。以下のように、バケットの「権限」タブで付与された IAM ロールを確認できます。</p> <p><figure class="figure-image figure-image-fotolife" title="バケットレベルの権限の確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241112/20241112194307.png" width="1167" height="606" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>バケットレベルの権限の確認</figcaption></figure></p> <h2 id="BigQuery-テーブルと-BigQuery-サブスクリプションの作成">BigQuery テーブルと BigQuery サブスクリプションの作成</h2> <p>データ格納先の BigQuery テーブルを作成します。id 列と data 列を持つシンプルなテーブルを作成します。</p> <p><figure class="figure-image figure-image-fotolife" title="BigQuery テーブル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113090105.png" width="1143" height="398" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>BigQuery テーブル</figcaption></figure></p> <p>その後、Cloud Storage インポートトピックに紐づくサブスクリプションを「BigQuery サブスクリプション」タイプで作成します。</p> <p><figure class="figure-image figure-image-fotolife" title="BigQuery サブスクリプションの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113095032.png" width="669" height="652" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>BigQuery サブスクリプションの作成</figcaption></figure></p> <p>BigQuery サブスクリプションは、トピックが受け取ったメッセージを自動的に BigQuery に書き込んでくれます。今回は「スキーマを使用しない」に設定したので、メッセージは <code>data</code> という名称を持つ列に文字列として書き込まれます。</p> <h2 id="トピックの状態を確認">トピックの状態を確認</h2> <p>ここで、トピックの状態を確認します。作成した Cloud Storage インポートトピックの詳細画面で、トピックのステータスが以下のように緑色で表示されていれば、正しく設定が完了しています。</p> <p><figure class="figure-image figure-image-fotolife" title="トピックが正しく設定されている状態"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113090605.png" width="1115" height="223" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>トピックが正しく設定されている状態</figcaption></figure></p> <p>もし以下のように表示されていた場合、Pub/Sub のサービスエージェントがトピックへのパブリッシュ権限を持っていないことが考えられます。当記事の <code>サービスエージェントへ IAM 権限の付与(パブリッシュ)</code> に戻り、プロジェクトレベルで Pub/Sub パブリッシャー(<code>roles/pubsub.publisher</code>)ロールを付与してください。あるいは、トピックレベルでロールを付与することも可能です。</p> <p><figure class="figure-image figure-image-fotolife" title="取り込みのリソースエラー: 権限の公開が拒否されました"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113090837.png" width="1115" height="223" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>取り込みのリソースエラー: 権限の公開が拒否されました</figcaption></figure></p> <pre class="code" data-lang="" data-unlink>取り込みのリソースエラー: 権限の公開が拒否されました</pre> <h1 id="動作確認">動作確認</h1> <h2 id="オブジェクトの-Put">オブジェクトの Put</h2> <p>動作確認のため、Cloud Storage バケットにオブジェクトを Put します。</p> <p>今回は、以下のようなファイルを Put しました。改行で区切られた、2行のテキスト情報です。</p> <p><strong>test-file-01.txt</strong></p> <pre class="code" data-lang="" data-unlink>This is the first message. This is the second message.</pre> <p><figure class="figure-image figure-image-fotolife" title="オブジェクトの Put"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113091016.png" width="1152" height="401" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>オブジェクトの Put</figcaption></figure></p> <h2 id="テーブルの確認">テーブルの確認</h2> <p>Put されたテキストファイル内のテキスト情報は、Cloud Storage インポートトピックに自動的に取り込まれ、BigQuery サブスクリプションによって、対象テーブルの data 列に書き込まれるはずです。</p> <p>対象テーブルに <code>SELECT</code> 文を実行すると、想定どおり、2行のテキスト情報が書き込まれていました。オブジェクトの Put からテーブルに情報が書き込まれるまで、およそ1分程度のラグがありました。</p> <p><figure class="figure-image figure-image-fotolife" title="BigQuery にテキストデータが格納された"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113091138.png" width="1077" height="456" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>BigQuery にテキストデータが格納された</figcaption></figure></p> <h2 id="追加検証区切り文字の追加">追加検証(区切り文字の追加)</h2> <p>追加検証として、先程は省略したトピックの「区切り文字」の設定を追加してみます。</p> <p>以下のように、区切り文字を <code>,</code>(カンマ)として設定します。</p> <p><figure class="figure-image figure-image-fotolife" title="区切り文字をカンマに設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113091412.png" width="837" height="602" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>区切り文字をカンマに設定</figcaption></figure></p> <p>その後、Cloud Storage バケットに次のファイルを Put します。半角スペースの扱いを確かめるため、3列目の文字列 <code>3rd field</code> の手前には、あえて半角スペースを入れています。</p> <p><strong>test-csv-02.csv</strong></p> <pre class="code csv" data-lang="csv" data-unlink>1st field,2nd field, 3rd field</pre> <p>結果は、以下のように想定どおり格納されました。カンマのあとの半角スペースも反映されており、<code>3rd field</code> の手前には半角スペースが入っています。</p> <p><figure class="figure-image figure-image-fotolife" title="カンマ区切りのデータが格納された"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113091701.png" width="1073" height="572" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>カンマ区切りのデータが格納された</figcaption></figure></p> <h2 id="追加検証メッセージ本文の確認">追加検証(メッセージ本文の確認)</h2> <p>さらに追加検証として、Pull サブスクリプションを作成して、Cloud Storage インポートトピックから発行されるメッセージの本文を確認します。</p> <p>以下のように、Pull サブスクリプションを作成します。</p> <p><figure class="figure-image figure-image-fotolife" title="Pull サブスクリプションを作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113091848.png" width="1097" height="581" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Pull サブスクリプションを作成</figcaption></figure></p> <p>以下のファイルを Put します。</p> <pre class="code csv" data-lang="csv" data-unlink>another 1st field,another 2nd field, another 3rd field</pre> <p>その後、<code>gcloud pubsub subscriptions pull</code> コマンドでサブスクリプションからメッセージを Pull します。<code>--limit</code> オプションをつけずに実行すると、1個のメッセージのみが Pull されます。今回は <code>--limit=10</code> を指定し、3つのメッセージが取得できることを確認します。</p> <p><figure class="figure-image figure-image-fotolife" title="3つのメッセージが Pull できた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113092100.png" width="1149" height="479" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>3つのメッセージが Pull できた</figcaption></figure></p> <p>想定どおり、3つのメッセージが Pull できました。データ本文(<code>data</code>)は Base64 でエンコードされているため、<code>echo "${data}" | base64 --decode</code> のようにしてデコードすると、内容が確認できます。</p> <p>メッセージ本文は、以下のような形式であることがわかりました。</p> <pre class="code" data-lang="" data-unlink>[ { &#34;ackId&#34;: &#34;UAYWLF1GSFE3GQhoUQ5PXiM_NSAoRRsGCBQFfH1wU1x1XVx0aFENGXJ9YHxpW0VQAEVWe1lRGwdoTm11H7aF5ftLQ1RrWBYHBEBae19TGQhoXFp3D3nlneOW2-TYfQk9OqLbgtZtO-vw5OtHZiM9XxJLLD5-MSpFQV5AEkw6H0RJUytDCypYEU4EISE-MD5FU0Q&#34;, &#34;message&#34;: { &#34;data&#34;: &#34;YW5vdGhlciAxc3QgZmllbGQ=&#34;, &#34;messageId&#34;: &#34;12566595082500628&#34;, &#34;publishTime&#34;: &#34;2024-11-12T09:08:52.185Z&#34; } }, { &#34;ackId&#34;: &#34;UAYWLF1GSFE3GQhoUQ5PXiM_NSAoRRsGCBQFfH1wU1x1XVx0aFENGXJ9YHxpW0VQAEVWe1lRGgdoTm11H7aF5ftLQ1RrWBYHBEBae19TGQhoXFp3DnnlneOW2-TYfQk9OqLbgtZtO-vw5OtHZiM9XxJLLD5-MSpFQV5AEkw6H0RJUytDCypYEU4EISE-MD5FU0Q&#34;, &#34;message&#34;: { &#34;data&#34;: &#34;YW5vdGhlciAybmQgZmllbGQ=&#34;, &#34;messageId&#34;: &#34;12566595082500629&#34;, &#34;publishTime&#34;: &#34;2024-11-12T09:08:52.185Z&#34; } }, { &#34;ackId&#34;: &#34;UAYWLF1GSFE3GQhoUQ5PXiM_NSAoRRsGCBQFfH1wU1x1XVx0aFENGXJ9YHxpW0VQAEVWe1lRGQdoTm11H7aF5ftLQ1RrWBYHBEBae19TGQhoXFp2B3nlneOW2-TYfQk9OqLbgtZtO-vw5OtHZiM9XxJLLD5-MSpFQV5AEkw6H0RJUytDCypYEU4EISE-MD5FU0Q&#34;, &#34;message&#34;: { &#34;data&#34;: &#34;IGFub3RoZXIgM3JkIGZpZWxk&#34;, &#34;messageId&#34;: &#34;12566595082500630&#34;, &#34;publishTime&#34;: &#34;2024-11-12T09:08:52.185Z&#34; } } ]</pre> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura DNSエンドポイントを使用してGitHub ActionsからGKEクラスタにリソースをデプロイする hatenablog://entry/6802418398303767958 2024-11-15T09:30:00+09:00 2024-12-15T21:06:57+09:00 G-gen の佐々木です。当記事では、GitHub Actions で GKE クラスタにリソースをデプロイする際に、DNS エンドポイントを使用する方法を解説します。 DNS エンドポイントとは GitHub Actions を使用した GKE へのデプロイ 従来の方法 DNS エンドポイントを使用する方法(当記事で解説) DNS エンドポイントを使用する GKE クラスタの作成 シェル変数の設定 ネットワークリソースの作成 GKE クラスタの作成 Direct Workload Identity の構成 シェル変数の設定 Workload Identity の設定 GitHub Actio… <p>G-gen の佐々木です。当記事では、GitHub Actions で GKE クラスタにリソースをデプロイする際に、<strong>DNS エンドポイント</strong>を使用する方法を解説します。</p> <ul class="table-of-contents"> <li><a href="#DNS-エンドポイントとは">DNS エンドポイントとは</a></li> <li><a href="#GitHub-Actions-を使用した-GKE-へのデプロイ">GitHub Actions を使用した GKE へのデプロイ</a><ul> <li><a href="#従来の方法">従来の方法</a></li> <li><a href="#DNS-エンドポイントを使用する方法当記事で解説">DNS エンドポイントを使用する方法(当記事で解説)</a></li> </ul> </li> <li><a href="#DNS-エンドポイントを使用する-GKE-クラスタの作成">DNS エンドポイントを使用する GKE クラスタの作成</a><ul> <li><a href="#シェル変数の設定">シェル変数の設定</a></li> <li><a href="#ネットワークリソースの作成">ネットワークリソースの作成</a></li> <li><a href="#GKE-クラスタの作成">GKE クラスタの作成</a></li> </ul> </li> <li><a href="#Direct-Workload-Identity-の構成">Direct Workload Identity の構成</a><ul> <li><a href="#シェル変数の設定-1">シェル変数の設定</a></li> <li><a href="#Workload-Identity-の設定">Workload Identity の設定</a></li> </ul> </li> <li><a href="#GitHub-Actions-によるリソースのデプロイ">GitHub Actions によるリソースのデプロイ</a><ul> <li><a href="#GitHub-リポジトリのディレクトリ構成">GitHub リポジトリのディレクトリ構成</a></li> <li><a href="#使用するファイル">使用するファイル</a><ul> <li><a href="#GitHub-Actions-ワークフロー">GitHub Actions ワークフロー</a></li> <li><a href="#Kubernetes-マニフェストファイル">Kubernetes マニフェストファイル</a></li> </ul> </li> <li><a href="#GitHub-Actions-の実行">GitHub Actions の実行</a></li> <li><a href="#修正したマニフェストファイルの反映">修正したマニフェストファイルの反映</a><ul> <li><a href="#マニフェストファイルの修正">マニフェストファイルの修正</a></li> <li><a href="#GitHub-Actions-の再実行">GitHub Actions の再実行</a></li> </ul> </li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241115/20241115093015.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="DNS-エンドポイントとは">DNS エンドポイントとは</h1> <p><strong>DNS エンドポイント</strong>とは、GKE のコントロールプレーンにアクセスする方法として従来からサポートされていた IP アドレスベースのアクセス元制御ではなく、DNS ベースのアクセス元制御を提供する機能です。</p> <p>DNS エンドポイントが有効化されている場合、たとえコントロールプレーンにパブリック IP アドレスが割り当てられていなくても、IAM で許可されているユーザーはコントロールプレーンに接続することができます。</p> <p><figure class="figure-image figure-image-fotolife" title="DNSエンドポイントを使用してコントロールプレーンに接続する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215210658.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DNSエンドポイントを使用してコントロールプレーンに接続する</figcaption></figure></p> <p>DNS エンドポイントの詳細、および IP アドレスベースのエンドポイントとの比較については、以下の記事をご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fdns-based-endpoint-for-gke-control-plane" title="DNSベースのエンドポイントを使用してGKEのコントロールプレーンに接続する - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/dns-based-endpoint-for-gke-control-plane">blog.g-gen.co.jp</a></cite></p> <h1 id="GitHub-Actions-を使用した-GKE-へのデプロイ">GitHub Actions を使用した GKE へのデプロイ</h1> <h2 id="従来の方法">従来の方法</h2> <p>DNS エンドポイントがリリースされる前は、コントロールプレーンのパブリックエンドポイントを無効化している場合、GitHub Actions からコントロールプレーンに直接接続することはできませんでした。</p> <p>そのため、プライベートエンドポイント経由でコントロールプレーンに到達できる GKE ノード側の VPC 内に GitHub Actions の <a href="https://docs.github.com/ja/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners">Self-Hosted Runner</a> を構成し、Runner 上でワークフローを実行する方法などが用いられます。</p> <p>Self-Hosted Runner は Compute Engine 仮想マシンや GKE クラスタ内の Pod で実行することになるため、GitHub Actions でデプロイパイプラインを構築したい場合、これらのリソースの運用管理も考慮していく必要があります。</p> <p><figure class="figure-image figure-image-fotolife" title="Self-Hosted Runnerを使用してプライベートエンドポイント経由でデプロイする"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215210701.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Self-Hosted Runnerを使用してプライベートエンドポイント経由でデプロイする</figcaption></figure></p> <h2 id="DNS-エンドポイントを使用する方法当記事で解説">DNS エンドポイントを使用する方法(当記事で解説)</h2> <p>DNS エンドポイントを使用する場合、GitHub Actions ワークフローから Google Cloud API にアクセスすることができれば、パブリック IP アドレスを持たないコントロールプレーンであっても直接接続することができます。</p> <p>したがって、Self-Hosted Runner の運用を考えることなく、シンプルなパイプライン構成で GKE クラスタにリソースをデプロイできます。</p> <p><figure class="figure-image figure-image-fotolife" title="DNSエンドポイント経由でデプロイする"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215210706.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DNSエンドポイント経由でデプロイする</figcaption></figure></p> <p>GitHub Actions からコントロールプレーンに接続する際の IAM 認証については <strong>Workload Identity</strong> を使用します。</p> <p>Workload Identity では Google Cloud プロジェクトに GitHub リポジトリを <strong>プロバイダ</strong> として登録し、プロバイダに対して IAM 権限を紐付けることで Google Cloud リソースへのアクセス権を与えることができます。</p> <p>Workload Identity 連携の詳細については、以下のドキュメントをご一読ください。</p> <ul> <li>参考1 : <a href="https://cloud.google.com/iam/docs/workload-identity-federation">Workload Identity Federation</a></li> <li>参考2 : <a href="https://cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines#github-actions">Configure Workload Identity Federation with deployment pipelines</a></li> </ul> <p>当記事では Workload Identity の方式のひとつである <a href="https://github.com/google-github-actions/auth?tab=readme-ov-file#preferred-direct-workload-identity-federation">Direct Workload Identity</a> を使用することで、IAM サービスアカウントの情報を GitHub Actions ワークフロー側に渡すことなく、プロバイダに対してコントロールプレーンに接続するための IAM 権限を直接付与します。</p> <h1 id="DNS-エンドポイントを使用する-GKE-クラスタの作成">DNS エンドポイントを使用する GKE クラスタの作成</h1> <h2 id="シェル変数の設定">シェル変数の設定</h2> <p>まずは GitHub Actions のデプロイ先となる GKE クラスタを作成します。</p> <p>当記事では gcloud CLI を使用してリソースを作成していきます。リソース作成時に何度か使用する値をシェル変数として設定しておきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synIdentifier">PROJECT_ID</span>=<span class="synSpecial">{</span>プロジェクトID<span class="synSpecial">}</span> <span class="synComment"># ex: gha-demo-prj</span> <span class="synIdentifier">LOCATION</span>=<span class="synSpecial">{</span>GKEを作成するロケーション<span class="synSpecial">}</span> <span class="synComment"># ex: asia-northeast1</span> <span class="synIdentifier">CLUSTER_NAME</span>=<span class="synSpecial">{</span>任意のGKEクラスタの名前<span class="synSpecial">}</span> <span class="synComment"># ex: mycluster</span> </pre> <h2 id="ネットワークリソースの作成">ネットワークリソースの作成</h2> <p>GKE クラスタを配置する VPC とサブネットを作成していきます。</p> <p>以下のコマンドで VPC を作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># VPC を作成する</span> $ gcloud compute networks create vpc-<span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--subnet-mode</span><span class="synStatement">=</span>custom </pre> <p>作成した VPC の中にサブネットを作成します。当記事ではサブネットの IP 範囲として <code>192.168.11.0/24</code> を設定しています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># サブネットを作成する</span> $ gcloud compute networks subnets create subnet-<span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--network</span><span class="synStatement">=</span>vpc-<span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--region</span><span class="synStatement">=</span><span class="synPreProc">${LOCATION}</span> <span class="synStatement">\</span> <span class="synSpecial">--range</span><span class="synStatement">=</span><span class="synConstant">192</span>.<span class="synConstant">168</span>.<span class="synConstant">11</span>.<span class="synConstant">0</span>/<span class="synConstant">24</span> </pre> <h2 id="GKE-クラスタの作成">GKE クラスタの作成</h2> <p><code>--enable-dns-access</code> フラグを使用し、DNS エンドポイントを有効化した GKE クラスタを作成します。</p> <p>以下のコマンドで、DNS エンドポイントを有効化した Autopilot モードの GKE クラスタを作成します。<code>--no-enable-ip-access</code> フラグを使用することで IP アドレスベースの接続を無効化します。これで、Google Cloud API にアクセスでき、かつ IAM 権限を持つユーザーのみがコントロールプレーンに接続することができます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># DNS エンドポイントを有効化した Autopilot クラスタを作成する(10分ほどかかるので注意)</span> $ gcloud container clusters create-auto <span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--network</span><span class="synStatement">=</span>vpc-<span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--subnetwork</span><span class="synStatement">=</span>subnet-<span class="synPreProc">${CLUSTER_NAME}</span> <span class="synStatement">\</span> <span class="synSpecial">--enable-private-nodes</span> <span class="synStatement">\</span> <span class="synSpecial">--enable-dns-access</span> <span class="synStatement">\</span> <span class="synSpecial">--no-enable-ip-access</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="IPアドレスベースの接続を無効化し、DNSエンドポイントのみ有効にする"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241115/20241115093019.png" width="800" height="175" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>IPアドレスベースの接続を無効化し、DNSエンドポイントのみ有効にする</figcaption></figure></p> <h1 id="Direct-Workload-Identity-の構成">Direct Workload Identity の構成</h1> <h2 id="シェル変数の設定-1">シェル変数の設定</h2> <p>ここからは、GitHub Actions からコントロールプレーンに接続する際に認証を行うための Workload Identity の設定を行っていきます。</p> <p>まずは、繰り返し使用する値をシェル変数として設定しておきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synIdentifier">PROJECT_ID</span>=<span class="synSpecial">{</span>プロジェクトID<span class="synSpecial">}</span> <span class="synComment"># ex: gha-demo-prj </span> <span class="synIdentifier">PROJECT_NUMBER</span>=<span class="synSpecial">{</span>プロジェクト番号<span class="synSpecial">}</span> <span class="synComment"># ex: 1234567890</span> <span class="synIdentifier">WORKLOAD_IDENTITY_POOL</span>=<span class="synSpecial">{</span>任意のWorkload Identityプール名<span class="synSpecial">}</span> <span class="synComment"># ex: my-gha-pool</span> <span class="synIdentifier">WORKLOAD_IDENTITY_PROVIDER</span>=<span class="synSpecial">{</span>任意のWorkload Identityプロバイダ名<span class="synSpecial">}</span> <span class="synComment"># ex: my-gha-provider</span> <span class="synIdentifier">GITHUB_REPO</span>=<span class="synSpecial">{</span>GitHubリポジトリ名<span class="synSpecial">}</span> <span class="synComment"># ex: my-org/my-repo</span> </pre> <h2 id="Workload-Identity-の設定">Workload Identity の設定</h2> <p>以下のコマンドで Workload Identity プールを作成します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Workload Identity プールの作成</span> $ gcloud iam workload-identity-pools create <span class="synPreProc">${WORKLOAD_IDENTITY_POOL}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>global <span class="synStatement">\</span> <span class="synSpecial">--display-name</span><span class="synStatement">=</span><span class="synPreProc">${WORKLOAD_IDENTITY_POOL}</span> </pre> <p>作成したプールに、Workload Identity プロバイダを作成します。</p> <p><code>--attribute-condition</code> フラグで、この Workload Identity を使用する GitHub Actions ワークフローがある GitHub リポジトリをここで指定します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Workload Identity プロバイダの作成</span> $ gcloud iam workload-identity-pools providers create-oidc <span class="synPreProc">${WORKLOAD_IDENTITY_PROVIDER}</span> <span class="synStatement">\</span> <span class="synSpecial">--project</span><span class="synStatement">=</span><span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--location</span><span class="synStatement">=</span>global <span class="synStatement">\</span> <span class="synSpecial">--workload-identity-pool</span><span class="synStatement">=</span><span class="synPreProc">${WORKLOAD_IDENTITY_POOL}</span> <span class="synStatement">\</span> <span class="synSpecial">--display-name</span><span class="synStatement">=</span><span class="synPreProc">${WORKLOAD_IDENTITY_PROVIDER}</span> <span class="synStatement">\</span> <span class="synSpecial">--issuer-uri</span><span class="synStatement">=&quot;</span><span class="synConstant">https://token.actions.githubusercontent.com</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--attribute-mapping</span><span class="synStatement">=&quot;</span><span class="synConstant">google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--attribute-condition</span><span class="synStatement">=&quot;</span><span class="synConstant">assertion.repository=='</span><span class="synPreProc">${GITHUB_REPO}</span><span class="synConstant">'</span><span class="synStatement">&quot;</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="Workload Identityプールとプロバイダを作成する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241115/20241115093022.png" width="800" height="375" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload Identityプールとプロバイダを作成する</figcaption></figure></p> <p>最後に、Workload Identity プロバイダに対して GKE クラスタにリソースをデプロイするための権限(<code>roles/container.developer</code>)を付与します。</p> <p>これにより、GitHub Actions ワークフローが DNS エンドポイント(Google Cloud API の認証)を通して GKE のコントロールプレーンに接続することができます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Workload Identity プロバイダに権限を付与する</span> $ gcloud projects add-iam-policy-binding <span class="synPreProc">${PROJECT_ID}</span> <span class="synStatement">\</span> <span class="synSpecial">--member</span><span class="synStatement">=&quot;</span><span class="synConstant">principalSet://iam.googleapis.com/projects/</span><span class="synPreProc">${PROJECT_NUMBER}</span><span class="synConstant">/locations/global/workloadIdentityPools/</span><span class="synPreProc">${WORKLOAD_IDENTITY_POOL}</span><span class="synConstant">/attribute.repository/</span><span class="synPreProc">${GITHUB_REPO}</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--role</span><span class="synStatement">=&quot;</span><span class="synConstant">roles/container.developer</span><span class="synStatement">&quot;</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="Workload IdentityプロバイダにGKEクラスタのアクセス権を付与する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241115/20241115093026.png" width="800" height="150" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workload IdentityプロバイダにGKEクラスタのアクセス権を付与する</figcaption></figure></p> <h1 id="GitHub-Actions-によるリソースのデプロイ">GitHub Actions によるリソースのデプロイ</h1> <h2 id="GitHub-リポジトリのディレクトリ構成">GitHub リポジトリのディレクトリ構成</h2> <p>Workload Identity プロバイダの作成時に指定した GitHub リポジトリに、GitHub Actions のワークフローと、GKE クラスタにデプロイする Kubernetes リソースのマニフェストファイルを配置します。</p> <p>リポジトリ内のディレクトリ構成は以下のようにして進めていきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>. ├── .github │ └── workflows │ └── demo-actions.yaml <span class="synComment"># GitHub Actions ワークフロー</span> └── manifests └── sample-deployment.yaml <span class="synComment"># Kubernetes リソース(deployment)のマニフェストファイル</span> </pre> <h2 id="使用するファイル">使用するファイル</h2> <h3 id="GitHub-Actions-ワークフロー">GitHub Actions ワークフロー</h3> <p>Workload Identity を使用して GKE クラスタにリソースをデプロイする GitHub Actions ワークフローを作成します。</p> <p>ワークフローファイル内の以下の箇所を、実際の値に置き換えてください。</p> <table> <thead> <tr> <th> è¡Œ </th> <th> 置き換える箇所 </th> <th> 値 </th> </tr> </thead> <tbody> <tr> <td> 14行目 </td> <td> {プロジェクトID} </td> <td> GKE クラスタを作成したプロジェクトの ID </td> </tr> <tr> <td> 15行目 </td> <td> {プロジェクト番号} </td> <td> GKE クラスタを作成したプロジェクトのプロジェクト番号 </td> </tr> <tr> <td> 16行目 </td> <td> {GKEクラスタ名} </td> <td> GKE クラスタの名前 </td> </tr> <tr> <td> 17行目 </td> <td> {GKEクラスタのロケーション} </td> <td> GKE クラスタを作成したロケーション </td> </tr> <tr> <td> 18行目 </td> <td> {Workload Identityプール名} </td> <td> Workload Idenity プールの名前 </td> </tr> <tr> <td> 19行目 </td> <td> {Workload Identityプロバイダ名} </td> <td> Workload Identity プロバイダの名前 </td> </tr> </tbody> </table> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># demo-actions.yaml</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> apply k8s manifest to GKE <span class="synComment"># main ブランチへの Pull Request / Push で実行</span> <span class="synIdentifier">on</span><span class="synSpecial">:</span> <span class="synIdentifier">pull_request</span><span class="synSpecial">:</span> <span class="synIdentifier">branches</span><span class="synSpecial">:</span> <span class="synStatement">- </span>main <span class="synIdentifier">push</span><span class="synSpecial">:</span> <span class="synIdentifier">branches</span><span class="synSpecial">:</span> <span class="synStatement">- </span>main <span class="synComment"># 環境変数に GKE クラスタの情報を設定</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">PROJECT_ID</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>プロジェクトID<span class="synSpecial">}</span> <span class="synComment"> # Google Cloud プロジェクト ID</span> <span class="synIdentifier">PROJECT_NUM</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>プロジェクト番号<span class="synSpecial">}</span> <span class="synComment"> # Google Cloud プロジェクト番号</span> <span class="synIdentifier">GKE_CLUSTER</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>GKEクラスタ名<span class="synSpecial">}</span> <span class="synComment"> # GKE クラスタ名</span> <span class="synIdentifier">GKE_LOCATION</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>GKEクラスタのロケーション<span class="synSpecial">}</span> <span class="synComment"> # GKE クラスタのロケーション</span> <span class="synIdentifier">WID_POOL</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>Workload Identityプール名<span class="synSpecial">}</span> <span class="synComment"> # Workload Identity プール名</span> <span class="synIdentifier">WID_PROVIDER</span><span class="synSpecial">:</span> <span class="synSpecial">{</span>Workload Identityプロバイダ名<span class="synSpecial">}</span> <span class="synComment"> # Workload Identity プロバイダ名</span> <span class="synComment"># ジョブ (GitHUb runners で実行)</span> <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">setup-apply</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">id-token</span><span class="synSpecial">:</span> write <span class="synIdentifier">contents</span><span class="synSpecial">:</span> read <span class="synIdentifier">pull-requests</span><span class="synSpecial">:</span> write <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> checkout <span class="synIdentifier">name</span><span class="synSpecial">:</span> Checkout <span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/checkout@v4 <span class="synComment"> # Workload Identity 連携</span> <span class="synComment"> # https://cloud.google.com/iam/docs/using-workload-identity-federation#generate-automatic</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> auth <span class="synIdentifier">name</span><span class="synSpecial">:</span> Authenticate to Google Cloud <span class="synIdentifier">uses</span><span class="synSpecial">:</span> google-github-actions/auth@v2 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">workload_identity_provider</span><span class="synSpecial">:</span> <span class="synConstant">'projects/${{ env.PROJECT_NUM }}/locations/global/workloadIdentityPools/${{ env.WID_POOL }}/providers/${{ env.WID_PROVIDER }}'</span> <span class="synComment"> # Workload Identity Pool のプロバイダー</span> <span class="synComment"> # https://github.com/google-github-actions/setup-gcloud</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> setup-gcloud <span class="synIdentifier">name</span><span class="synSpecial">:</span> Setup gcloud <span class="synIdentifier">uses</span><span class="synSpecial">:</span> google-github-actions/setup-gcloud@v2 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">project_id</span><span class="synSpecial">:</span> ${{ env.PROJECT_ID }} <span class="synComment"> # GKE の認証プラグイン</span> <span class="synComment"> # https://cloud.google.com/kubernetes-engine/docs/deprecations/auth-plugin?hl=ja</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> install-gke-gcloud-auth-plugin <span class="synIdentifier">name</span><span class="synSpecial">:</span> Install gke-gcloud-auth-plugin <span class="synIdentifier">run</span><span class="synSpecial">:</span> | gcloud components install gke-gcloud-auth-plugin <span class="synComment"> # GKE クラスタの認証情報を取得 (DNS エンドポイントを使用)</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> get-gke-credentials <span class="synIdentifier">name</span><span class="synSpecial">:</span> Get GKE credentials <span class="synIdentifier">run</span><span class="synSpecial">:</span> | gcloud container clusters get-credentials ${{ env.GKE_CLUSTER }} --region ${{ env.GKE_LOCATION }} --dns-endpoint <span class="synComment"> # マニフェストファイルの適用</span> <span class="synStatement">- </span><span class="synIdentifier">id</span><span class="synSpecial">:</span> apply <span class="synIdentifier">name</span><span class="synSpecial">:</span> Apply <span class="synIdentifier">run</span><span class="synSpecial">:</span> | kubectl apply -f ./manifests </pre> <h3 id="Kubernetes-マニフェストファイル">Kubernetes マニフェストファイル</h3> <p>GKE クラスタにデプロイするリソースのマニフェストファイルを作成します。</p> <p>当記事では、nginx コンテナを実行する Pod ã‚’3つ作成する Deployment リソースを作成します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># sample-deployment.yaml</span> <span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> sample-deployment <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synIdentifier">selector</span><span class="synSpecial">:</span> <span class="synIdentifier">matchLabels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">template</span><span class="synSpecial">:</span> <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">labels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">containers</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">image</span><span class="synSpecial">:</span> nginx:1.27 </pre> <h2 id="GitHub-Actions-の実行">GitHub Actions の実行</h2> <p>作成したワークフローファイルとマニフェストファイルをリモートリポジトリに push し、GitHub Actions ワークフローを実行します。</p> <p>ワークフローが実行され、GitHub Actions から GKE クラスタに対してマニフェストファイルが適用されます。Deployment リソースが作成されていることが GitHub Actions のログからわかります。</p> <p><figure class="figure-image figure-image-fotolife" title="GitHub Actionsワークフローから GKE クラスタへのデプロイが成功している"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241115/20241115093029.png" width="800" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GitHub Actionsワークフローから GKE クラスタへのデプロイが成功している</figcaption></figure></p> <p>ローカルから GKE クラスタのコントロールプレーンに接続し、デプロイしたリソースの状態を確認します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># DNS エンドポイントでコントロールプレーンに接続する</span> $ gcloud container clusters get-credentials <span class="synPreProc">${CLUSTER_NAME}</span> <span class="synSpecial">--dns-endpoint</span> <span class="synComment"># Deployment リソースを表示</span> $ kubectl get deployments </pre> <p>マニフェストファイルの記述通り、3つの Pod を実行する Deployment リソースが作成されています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Deployment リソースを表示(出力例)</span> $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE sample-deployment <span class="synConstant">3</span>/<span class="synConstant">3</span> <span class="synConstant">3</span> <span class="synConstant">3</span> 2m7s </pre> <h2 id="修正したマニフェストファイルの反映">修正したマニフェストファイルの反映</h2> <h3 id="マニフェストファイルの修正">マニフェストファイルの修正</h3> <p>マニフェストファイルを修正してからリモートリポジトリに push し、GitHub Actions ワークフローによるデプロイを再度行います。</p> <p>マニフェストファイル内の <code>spec.replicas</code> の値を <code>1</code> にして、Pod 数をスケールインするように修正します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># sample-deployment.yaml</span> <span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> sample-deployment <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">1</span> <span class="synComment"> # ここを修正する</span> <span class="synIdentifier">selector</span><span class="synSpecial">:</span> <span class="synIdentifier">matchLabels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">template</span><span class="synSpecial">:</span> <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">labels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">containers</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> sample-app <span class="synIdentifier">image</span><span class="synSpecial">:</span> nginx:1.27 </pre> <h3 id="GitHub-Actions-の再実行">GitHub Actions の再実行</h3> <p>再度、リモートリポジトリへの push を行い、GitHub Actions ワークフローを実行します。</p> <p>ワークフローの実行後、Deployment の状態を確認します。push したマニフェストファイルが GitHub Actions から適用され、Pod の数が1になりました。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># Deployment リソースを確認(出力例)</span> $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE sample-deployment <span class="synConstant">1</span>/<span class="synConstant">1</span> <span class="synConstant">1</span> <span class="synConstant">1</span> 7m5s </pre> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun DNSベースのエンドポイントを使用してGKEのコントロールプレーンに接続する hatenablog://entry/6802418398303482130 2024-11-13T09:00:00+09:00 2024-12-15T21:02:21+09:00 G-gen の佐々木です。当記事では、GKE のコントロールプレーンにアクセスするための新しい方法として、DNS ベースのエンドポイント(DNS エンドポイント)を紹介します。 はじめに GKE におけるコントロールプレーンへのアクセス方法 従来の方法 パブリックエンドポイント プライベートエンドポイント DNS エンドポイント IP ベースと DNS ベースの比較 使用方法 DNS エンドポイントの有効化 コントロールプレーンへのアクセス IP アドレスを使用した接続の無効化 はじめに Google Cloud(旧称 GCP)のフルマネージドなコンテナオーケストレーションサービスである Go… <p>G-gen の佐々木です。当記事では、GKE のコントロールプレーンにアクセスするための新しい方法として、<strong>DNS ベースのエンドポイント</strong>(DNS エンドポイント)を紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#GKE-におけるコントロールプレーンへのアクセス方法">GKE におけるコントロールプレーンへのアクセス方法</a><ul> <li><a href="#従来の方法">従来の方法</a><ul> <li><a href="#パブリックエンドポイント">パブリックエンドポイント</a></li> <li><a href="#プライベートエンドポイント">プライベートエンドポイント</a></li> </ul> </li> <li><a href="#DNS-エンドポイント">DNS エンドポイント</a></li> </ul> </li> <li><a href="#IP-ベースと-DNS-ベースの比較">IP ベースと DNS ベースの比較</a></li> <li><a href="#使用方法">使用方法</a><ul> <li><a href="#DNS-エンドポイントの有効化">DNS エンドポイントの有効化</a></li> <li><a href="#コントロールプレーンへのアクセス">コントロールプレーンへのアクセス</a></li> <li><a href="#IP-アドレスを使用した接続の無効化">IP アドレスを使用した接続の無効化</a></li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113090024.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>Google Cloud(旧称 GCP)のフルマネージドなコンテナオーケストレーションサービスである <strong>Google Kubernetes Engine(GKE)</strong>では、Kubernetes クラスタのコントロールプレーンへのアクセス方法を複数種類から選択できます。</p> <p>従来から存在する IP ベースのエンドポイント(IP-based endpoints)方式(パブリッククラスタとプライベートクラスタ)に加えて、2024å¹´11月11日、<strong>DNS ベースのエンドポイント</strong>(DNS-based endpoint)が利用可能になりました。</p> <p>当記事では、DNS ベースのエンドポイント(DNS エンドポイント) の機能と使い方を紹介します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/blog/products/containers-kubernetes/new-dns-based-endpoint-for-the-gke-control-plane">A new flexible DNS-based approach for accessing the GKE control plane</a>(Google Cloud 公式ブログ)</li> <li>参考 : <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/network-isolation">About network isolation in GKE</a>(Google Cloud 公式ドキュメント)</li> </ul> <h1 id="GKE-におけるコントロールプレーンへのアクセス方法">GKE におけるコントロールプレーンへのアクセス方法</h1> <h2 id="従来の方法">従来の方法</h2> <h3 id="パブリックエンドポイント">パブリックエンドポイント</h3> <p>GKE クラスタで<strong>パブリックエンドポイント</strong>を有効化する場合、コントロールプレーンにはパブリック IP アドレスが割り当てられます。</p> <p>この状態ではインターネット上のどこからでもコントロールプレーンにアクセスできてしまうため、<a href="https://cloud.google.com/kubernetes-engine/docs/how-to/authorized-networks?hl=ja">承認済みネットワーク</a> を使用してアクセス元 IP アドレスを制限するのが一般的です。</p> <p><figure class="figure-image figure-image-fotolife" title="パブリックエンドポイントでコントロールプレーンに接続する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215205949.png" width="759" height="659" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>パブリックエンドポイントでコントロールプレーンに接続する</figcaption></figure></p> <h3 id="プライベートエンドポイント">プライベートエンドポイント</h3> <p>パブリックエンドポイントよりも推奨される方法として、GKE クラスタでパブリックエンドポイントを無効化し、プライベート IP アドレスのみをコントロールプレーンに割り当てることもできます(<strong>プライベートエンドポイント</strong>)。</p> <p>プライベートエンドポイントを使用する場合、「"コントロールプレーンがある Google 管理の VPC と接続されている"ユーザー 管理の VPC」を経由してコントロールプレーンにアクセスする必要があります。そのため、GKE クラスタがある VPC の外からコントロールプレーンに接続したい場合、ユーザー VPC に踏み台 VM(Bastion host)を起動するか、VPN を経由してアクセスする必要がありました。</p> <p><figure class="figure-image figure-image-fotolife" title="プライベートエンドポイントでコントロールプレーンに接続する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215205953.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プライベートエンドポイントでコントロールプレーンに接続する</figcaption></figure></p> <h2 id="DNS-エンドポイント">DNS エンドポイント</h2> <p>GKE クラスタで DNS エンドポイントを有効化すると、Google Cloud API にアクセスできる任意の環境から、パブリック IP アドレスが割り当てられてないコントロールプレーンにアクセスすることができます。</p> <p><figure class="figure-image figure-image-fotolife" title="DNSエンドポイントでコントロールプレーンに接続する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215205956.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DNSエンドポイントでコントロールプレーンに接続する</figcaption></figure></p> <p>コントロールプレーンへのアクセス可否はその他の Google Cloud API を利用するときと同様に、IAM ポリシーによって判断されます。GKE クラスタに対して <code>container.clusters.connect</code> ポリシーを持つ以下のようなロールが付与されているプリンシパルであれば、コントロールプレーンにアクセスすることができます。</p> <ul> <li><strong>Kubernetes Engine 開発者(roles/container.developer)</strong></li> <li><strong>Kubernetes Engine 閲覧者(roles/container.viewer)</strong> ※読み取り専用</li> </ul> <p>DNS エンドポイントを使用することで、コントロールプレーンへの接続に踏み台 VM を使用する必要がなくなるほか、<a href="https://blog.g-gen.co.jp/entry/vpc-explained-basics#VPC-%E9%96%93%E6%8E%A5%E7%B6%9A">推移的なルーティング</a>によってコントロールプレーンに到達できないといった、ネットワーク構成上の制限を考慮しなくてもよくなります。</p> <p>たとえば、以下のような構成でプライベートエンドポイントを使用する場合、推移的ルーティングによりコントロールプレーンにアクセスすることはできません。</p> <p><figure class="figure-image figure-image-fotolife" title="推移的ルーティングによりコントロールプレーンに接続できないケース"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215210222.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>推移的ルーティングによりコントロールプレーンに接続できないケース</figcaption></figure></p> <p>DNS エンドポイントを使用すると、アクセス元の VPC 内サブネットで<a href="https://cloud.google.com/vpc/docs/private-google-access?hl=ja">限定公開の Google アクセス</a>が有効化されていれば(Google Cloud API にアクセスできれば)、コントロールプレーンに接続することができます。</p> <p><figure class="figure-image figure-image-fotolife" title="DNSエンドポイントでは推移的ルーティングに制限されずに接続できる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241215/20241215210225.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DNSエンドポイントでは推移的ルーティングに制限されずに接続できる</figcaption></figure></p> <p>また、<a href="https://blog.g-gen.co.jp/entry/vpc-service-controls-explained">VPC Service Control</a> を使用することで、IAM 以外の条件(アクセス元が存在するプロジェクトなど)でも DNS エンドポイントに対するアクセス元を制限することができます。</p> <h1 id="IP-ベースと-DNS-ベースの比較">IP ベースと DNS ベースの比較</h1> <p>IP ベースのエンドポイントと、DNS ベースのエンドポイントがどのように異なるのかを比較します。</p> <p>DNS ベースのエンドポイントでは、Google Cloud API がインターネットに露出しているという点では、IP ベースのエンドポイントのパブリッククラスタと類似しているものの、以下のように異なっています。</p> <table> <thead> <tr> <th> 比較点 </th> <th> IP ベース(パブリックエンドポイント) </th> <th> DNS ベース </th> </tr> </thead> <tbody> <tr> <td> <strong>Kubernetes API エンドポイントの露出</strong> </td> <td> パブリック IP を持っている(万が一 Kubernetes に脆弱性がある場合は脅威にさらされる) </td> <td> パブリック IP を持たない設定が可能 </td> </tr> <tr> <td> <strong>kubectl の接続情報取得に必要な IAM 権限</strong> </td> <td> <code>container.clusters.get</code> </td> <td> <code>container.clusters.connect</code> </td> </tr> <tr> <td> <strong>VPC Service Controls での保護</strong> </td> <td> なし(クラスタ情報取得に対しては保護可能) </td> <td> 可能 </td> </tr> </tbody> </table> <p>特にセキュリティ面に関しては、従来の IP ベースのアクセスであれば、プライベートクラスタを構成したうえで踏み台ホストや VPN を用いて Kubernetes API へ接続することが推奨されていましたが、今後は DNS ベースのエンドポイントを構成し、IAM 権限を適切に管理したうえで VPC Service Controls を使って多層防御を構成することが推奨されます。</p> <p>VPC Service Controls については、以下の記事も参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fvpc-service-controls-explained" title="VPC Service Controlsを分かりやすく解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/vpc-service-controls-explained">blog.g-gen.co.jp</a></cite></p> <h1 id="使用方法">使用方法</h1> <h2 id="DNS-エンドポイントの有効化">DNS エンドポイントの有効化</h2> <p>DNS エンドポイントはいつでも有効化することができます。CLI で有効化する場合は、<code>--enable-dns-access</code> フラグを使用してクラスタを更新します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># クラスタ作成時に DNS エンドポイントを有効化する</span> $ gcloud container clusters create <span class="synPreProc">${GKE</span><span class="synError">クラスタ名</span><span class="synPreProc">}</span> -–enable-dns-access <span class="synComment"># 既存のクラスタで DNS エンドポイントを有効化する</span> $ gcloud container clusters update <span class="synPreProc">${GKE</span><span class="synError">クラスタ名</span><span class="synPreProc">}</span> <span class="synSpecial">--enable-dns-access</span> </pre> <p>Google Cloud コンソールの場合、「DNS エンドポイント」の項目から有効化することができます。</p> <p><figure class="figure-image figure-image-fotolife" title="DNSエンドポイントの設定箇所(コンソール)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241113/20241113090021.png" width="800" height="347" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DNSエンドポイントの設定箇所(コンソール)</figcaption></figure></p> <h2 id="コントロールプレーンへのアクセス">コントロールプレーンへのアクセス</h2> <p>DNS エンドポイントでコントロールプレーンに接続するには、<code>gcloud container clusters get-credentials</code> コマンドで <code>--dns-endpoint</code> フラグを使用します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># DNS エンドポイントでコントロールプレーンに接続する</span> $ gcloud container clusters get-credentials <span class="synPreProc">${GKE</span><span class="synError">クラスタ名</span><span class="synPreProc">}</span> <span class="synSpecial">--dns-endpoint</span> </pre> <h2 id="IP-アドレスを使用した接続の無効化">IP アドレスを使用した接続の無効化</h2> <p><code>--no-enable-ip-access</code> フラグを使用することで、従来の IP アドレスベースの接続方法を無効化することができます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># IP アドレスベースのコントロールプレーンへの接続を無効化する</span> $ gcloud container clusters update <span class="synPreProc">${GKE</span><span class="synError">クラスタ名</span><span class="synPreProc">}</span> <span class="synSpecial">--no-enable-ip-access</span> </pre> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sasashun/20230829/20230829095235.png);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">佐々木 駿太 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sasashun">(記事一覧)</a></p> <p class="sw-profile__txt">G-gen最北端、北海道在住のクラウドソリューション部エンジニア</p> <p class="sw-profile__txt">2022å¹´6月にG-genにジョイン。Google Cloud Partner Top Engineer 2025 Fellowに選出。好きなGoogle CloudプロダクトはCloud Run。</p> <p class="sw-profile__txt">趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。</p> <a href="https://twitter.com/sasashun0805?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @sasashun0805</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> </div> </div> </div> ggen-sasashun Associate Data Practitioner試験対策マニュアル hatenablog://entry/6802418398302932218 2024-11-12T09:00:00+09:00 2025-01-07T14:52:34+09:00 G-genの杉村です。Google Cloud(旧称 GCP)の認定資格である Associate Data Practitioner 資格の試験対策に有用な情報を記載します。 基本的な情報 Associate Data Practitioner とは 難易度 出題傾向 試験対策 ETL と ELT ETL と ELT の基本 オープンソースツールとフルマネージドサービス Cloud Data Fusion イベントドリブンアーキテクチャ データベースの選択 BigQuery BigQuery の基本 ELT と ETL 半構造化データの扱い(JSON 型) パーティションとクラスタリング 外… <p>G-genの杉村です。Google Cloud(旧称 GCP)の認定資格である <strong>Associate Data Practitioner</strong> 資格の試験対策に有用な情報を記載します。</p> <ul class="table-of-contents"> <li><a href="#基本的な情報">基本的な情報</a><ul> <li><a href="#Associate-Data-Practitioner-とは">Associate Data Practitioner とは</a></li> <li><a href="#難易度">難易度</a></li> <li><a href="#出題傾向">出題傾向</a></li> <li><a href="#試験対策">試験対策</a></li> </ul> </li> <li><a href="#ETL-と-ELT">ETL と ELT</a><ul> <li><a href="#ETL-と-ELT-の基本">ETL と ELT の基本</a></li> <li><a href="#オープンソースツールとフルマネージドサービス">オープンソースツールとフルマネージドサービス</a></li> <li><a href="#Cloud-Data-Fusion">Cloud Data Fusion</a></li> <li><a href="#イベントドリブンアーキテクチャ">イベントドリブンアーキテクチャ</a></li> </ul> </li> <li><a href="#データベースの選択">データベースの選択</a></li> <li><a href="#BigQuery">BigQuery</a><ul> <li><a href="#BigQuery-の基本">BigQuery の基本</a></li> <li><a href="#ELT-と-ETL">ELT と ETL</a></li> <li><a href="#半構造化データの扱いJSON-åž‹">半構造化データの扱い(JSON 型)</a></li> <li><a href="#パーティションとクラスタリング">パーティションとクラスタリング</a></li> <li><a href="#外部テーブル">外部テーブル</a></li> <li><a href="#SQL">SQL</a></li> <li><a href="#ウインドウ関数">ウインドウ関数</a></li> <li><a href="#コネクテッドシート">コネクテッドシート</a></li> </ul> </li> <li><a href="#BigQuery-のアクセス制御と暗号化">BigQuery のアクセス制御と暗号化</a><ul> <li><a href="#権限管理IAM">権限管理(IAM)</a></li> <li><a href="#承認されたビュー">承認されたビュー</a></li> <li><a href="#透過的な暗号化">透過的な暗号化</a></li> </ul> </li> <li><a href="#BigQuery-ML">BigQuery ML</a><ul> <li><a href="#BigQuery-ML-の基本">BigQuery ML の基本</a></li> <li><a href="#組み込みモデル">組み込みモデル</a></li> <li><a href="#スキューとドリフト">スキューとドリフト</a></li> <li><a href="#Gemini">Gemini</a></li> </ul> </li> <li><a href="#AutoML">AutoML</a></li> <li><a href="#Cloud-Storage">Cloud Storage</a><ul> <li><a href="#Cloud-Storage-の基本">Cloud Storage の基本</a></li> <li><a href="#ストレージクラス">ストレージクラス</a></li> <li><a href="#オブジェクトライフサイクル">オブジェクトライフサイクル</a></li> <li><a href="#バージョニング">バージョニング</a></li> <li><a href="#Autoclass">Autoclass</a></li> <li><a href="#デュアルリージョンマルチリージョン">デュアルリージョン、マルチリージョン</a></li> <li><a href="#Storage-Transfer-Service">Storage Transfer Service</a></li> <li><a href="#Storage-Transfer-Appliance">Storage Transfer Appliance</a></li> </ul> </li> <li><a href="#Looker">Looker</a><ul> <li><a href="#Looker-の基本">Looker の基本</a></li> <li><a href="#メジャーとディメンション">メジャーとディメンション</a></li> <li><a href="#Looker-における権限管理">Looker における権限管理</a></li> </ul> </li> <li><a href="#Analytics-Hub">Analytics Hub</a></li> <li><a href="#Colab-Enterprise">Colab Enterprise</a></li> <li><a href="#Cloud-SQL">Cloud SQL</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20241116/20241116130728.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="基本的な情報">基本的な情報</h1> <h2 id="Associate-Data-Practitioner-とは">Associate Data Practitioner とは</h2> <p><strong>Associate Data Practitioner</strong> 試験は、Google Cloud(旧称 GCP)の認定資格の一つです。当試験は2024å¹´10月30日、Beta 版として公開され、2025å¹´1月に一般公開(GA)されました。</p> <p>当試験は Associate レベルの資格であり、<strong>Google Cloud 上でのデータ取り込み、変換、パイプライン管理、分析、機械学習、および可視化等に関する知識や技能</strong>が問われます。</p> <p>試験時間は120分、問題数は50〜60問です。2025å¹´1月現在、<strong>英語版のみ</strong>が提供されています。</p> <p>Google Cloud のデータエンジニアリング関連の認定資格としては Professional Data Engineer が存在しています。Professional レベルの認定資格は、技術的な知識だけでなく、ビジネスユースケースにあわせてソリューションを検討するより高度な知見が求められるのに対して、Associate レベルの資格は、技術的な知見のみに特化しています。当記事で紹介する Associate Data Practitioner は、Professional Data Engineer の下位資格の位置づけと考えれます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/learn/certification/data-practitioner">Associate Data Practitioner</a></li> </ul> <p>当資格の上位資格である Professional レベル試験である Professional Data Engineer については、以下の記事も参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fprofessional-data-engineer" title="Professional Data Engineer試験対策マニュアル。出題傾向・勉強方法 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/professional-data-engineer">blog.g-gen.co.jp</a></cite></p> <h2 id="難易度">難易度</h2> <p>Associate Data Practitioner 試験の難易度は、他の認定試験と比較して<strong>低〜中程度</strong> といえます。</p> <p>「RDBMS」「トランザクション」「データモデリング」「SQL」「分散データベース」など、基本情報技術者試験でも学ぶような基礎レベルのリレーショナルデータに関する知識に加えて、非正規化を含むデータ分析向けのデータモデリング、オブジェクトストレージの基本などを理解されている方であれば、追加の学習を1ヶ月程度行うことで十分に合格を狙えます。</p> <p>また基礎知識として、Associate Cloud Engineer 程度の Google Cloud の基礎知識を持っておくことで、IAM による権限管理などを1から学習しなくても済みます。</p> <p>受験者に推奨される経験として、公式サイトには「Google Cloud 上での6ヶ月以上の実務経験」とありますが、細かな操作やコマンドに関する出題は多くありません。むしろ普遍的な標準 SQL の書き方に親しんでおくことが重要です。また、Google Cloud のベストプラクティスを正しく理解し、それに沿って答えることが重要です。</p> <h2 id="出題傾向">出題傾向</h2> <p>当試験では、以下のような Google Cloud ソリューションに関する出題がほとんどです。</p> <ul> <li>Cloud Storage</li> <li>BigQuery</li> <li>Pub/Sub</li> <li>Dataflow</li> <li>Dataform</li> <li>Cloud Data Fusion</li> <li>Dataproc</li> </ul> <p>また一部では、Cloud SQL の可用性など、オペレーショナルデータベースに関する出題もされます。</p> <p>試験で出題される Google Cloud で実装されるデータ分析基盤は、<strong>必ず BigQuery が中心である</strong>といって差し支えありません。BigQuery をデータウェアハウス(データ分析用データベース)として、そこにデータを取り込み(ingest)するためのツールとして Pub/Sub、Dataflow、Cloud Data Fusion などが登場します。それを理解しつつ、Google が提唱する以下のようなベストプラクティスに沿うような回答を心がけることが重要です。どの認定試験でも共通して言えることですが、原則から大きく外れなければ、合格は難しくありません。</p> <ul> <li>データ分析用データベースは BigQuery</li> <li>管理工数を小さくするために、可能であればフルマネージドサービスや、備え付けの機能を選ぶ <ul> <li>Apache Airflow on Compute Engine より Cloud Composer</li> <li>Spark on GKE より Dataproc Serverless</li> <li>機械学習フレームワークよりも BigQuery ML</li> </ul> </li> <li>維持・保守工数を小さくするために、可能であればノーコードで実装できる方法を選ぶ <ul> <li>BigQuery のハウスキーピングは、Cloud Run functions で実装するよりも、パーティションの有効期間設定を使う</li> <li>Cloud Storage のハウスキーピングは、Cloud Run functions で実装するよりも、オブジェクトライフサイクルを選ぶ</li> </ul> </li> </ul> <h2 id="試験対策">試験対策</h2> <p>以下の勉強方法はあくまで一例であり、最適な方法は、受験者の予備知識や経験によって異なるものとご了承ください。</p> <ol> <li>Associate Cloud Engineer レベルの Google Cloud 基礎知識を習得する</li> <li>前掲のデータ基礎知識をキーワードベースで理解する</li> <li>試験ガイドを読み、知らないキーワードや機能について公式ドキュメントで学ぶ</li> <li>当記事の出題傾向を読み足りない知識領域をカバーする学習を行う</li> <li>ウインドウ関数や BigQuery ML など特殊な SQL の使い方を理解する</li> </ol> <p>なお Associate Cloud Engineer 試験の学習については、以下の記事も参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fassociate-cloud-engineer" title="Associate Cloud Engineer試験対策マニュアル。出題傾向・勉強方法 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/associate-cloud-engineer">blog.g-gen.co.jp</a></cite></p> <p>当記事ではこれ以降、試験にあたって何を勉強しておくべきか、機能分野ごとに紹介しますので、参考にしてください。当記事では、Google Cloud の基礎知識を詳細にお伝えすることはありませんので、その点にはご留意ください。また、当記事の内容は Beta 版公開時のものですので、現在の試験内容とは一部が異なる場合がある点もご了承ください。</p> <h1 id="ETL-と-ELT">ETL と ELT</h1> <h2 id="ETL-と-ELT-の基本">ETL と ELT の基本</h2> <p>BigQuery はスケーラブルなコンピュート基盤を持っているので、まずデータを BigQuery に取り込んでから変換する「<strong>ELT</strong>(Extract、Load、Transform)」の順番で処理することが可能です。ELT は、<strong>スケジュールされたクエリ</strong>(Scheduled queries)や <strong>Dataform</strong> で行うことが多いといえます。</p> <p>しかし当試験では、BigQuery へ Load する前に <strong>Cloud Data Fusion</strong> ã‚„ <strong>Dataflow</strong>、<strong>Dataproc</strong> でデータクレンジングを行う「ETL(Extract、Transform、Load)」の出題も多くあります。</p> <p>多くの問題では、ELT と ETL どちらを採るべきか、問題文を読めば明確です。</p> <p>上記に登場したそれぞれのサービスが、どのようなものであるか、概要を理解しておいてください。</p> <h2 id="オープンソースツールとフルマネージドサービス">オープンソースツールとフルマネージドサービス</h2> <p>Google Cloud のデータ分析向けフルマネージドサービスは、多くがオープンソースソフトウェア(Open Source Software、OSS)をベースとしていることに注意してください。例えば Cloud Composer は、OSS である Apache Airflow のフルマネージドサービスです。Google Cloud プロダクトと、<strong>その元となった OSS の名称との対照は覚えてください</strong>。試験では、例えば「オープンソースの技術スタックを利用したい」などの要件が提示されるかもしれません。</p> <table> <thead> <tr> <th> Google Cloud プロダクト名 </th> <th> OSS 名 </th> </tr> </thead> <tbody> <tr> <td> Cloud Composer </td> <td> Apache Airflow </td> </tr> <tr> <td> Dataflow </td> <td> Apache Beam </td> </tr> <tr> <td> Dataproc / Dataproc Serverless </td> <td> Apache Hadoop、Apache Spark ç­‰ </td> </tr> <tr> <td> Cloud SQL for PostgreSQL </td> <td> PostgreSQL </td> </tr> <tr> <td> Cloud SQL for MySQL </td> <td> MySQL </td> </tr> </tbody> </table> <p>また、これらの各プロダクトの(= OSS の)得意分野とユースケースをしっかり把握しておきましょう。以下のような大原則をしっかり覚えておいてください。</p> <table> <thead> <tr> <th> Google Cloud プロダクト名(OSS 名) </th> <th> ユースケース </th> </tr> </thead> <tbody> <tr> <td> Cloud Composer(Apache Airflow) </td> <td> <strong>DAG</strong>(有向非巡回グラフ。いわゆるワークフローまたはジョブネット)を Python で記述。多くの<strong>組み込みオペレーター</strong>がある </td> </tr> <tr> <td> Dataflow(Apache Beam) </td> <td> バッチ処理とストリーミング処理を<strong>両方処理できる</strong> </td> </tr> <tr> <td> Dataproc(Apache Hadoop、Apache Spark 等) </td> <td> ファイルを大規模に並列処理 </td> </tr> <tr> <td> Cloud SQL(PostgreSQL、MySQL) </td> <td> オペレーショナルなリレーショナルデータベース </td> </tr> </tbody> </table> <p>このようなことから、例えばもし「バッチ処理とストリーミング処理を同じ技術スタックで処理したい」という要件があれば必ず Dataflow を選べますし、「Apache Spark ワークロードをクラウドに移行したい。クラスタの管理は行いたくない」とあれば Dataproc Serverless が選べます。</p> <h2 id="Cloud-Data-Fusion">Cloud Data Fusion</h2> <p><strong>Cloud Data Fusion</strong> は、ノーコードで開発可能な、フルマネージドの ETL ソリューションです。ノーコードで ETL パイプラインを開発したいシチュエーションで活用できます。以下のような利用が可能です。</p> <ul> <li>Cloud Storage のデータを読み取り、データ変換してから BigQuery に格納</li> <li>データベースに接続し、データを抽出し、変換してから BigQuery に格納</li> </ul> <h2 id="イベントドリブンアーキテクチャ">イベントドリブンアーキテクチャ</h2> <p><strong>イベントドリブンアーキテクチャ</strong>とは、ある1つの処理が完了したことをきっかけに、別の処理がトリガーされるようなアーキテクチャをいいます。例えば、Cloud Storage バケットにオブジェクトがアップロードされたことをきっかけに Cloud Run functions(旧称 Cloud Functions)が起動し、ファイルを読み込んでデータを BigQuery にロードするようなアーキテクチャです。多くの場合、処理のためのプログラムはサーバーレスプラットフォームにホストされます。</p> <p>次々に到着する小さいデータを順次処理するような仕組みでは、イベントドリブンなアーキテクチャが適しています。反対に、Cloud Run functions のようなサーバーレスプラットフォームは実行時間に制限があることから、<strong>大きなファイルの処理</strong>(長時間の処理)には適していません。イベントドリブンがどのようなユースケースに適しているかを意識してください。</p> <p>イベントドリブンについては、以下の記事も参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fserverless-architecture-explained" title="Google Cloudで理解するサーバーレス・アーキテクチャ - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/serverless-architecture-explained">blog.g-gen.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fgen-ai-with-event-driven-architecture" title="イベンドドリブン×生成AIで日報を自動要約してみた - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/gen-ai-with-event-driven-architecture">blog.g-gen.co.jp</a></cite></p> <h1 id="データベースの選択">データベースの選択</h1> <p>Google Cloud の多用なデータベースサービスを一通り理解し、<strong>ユースケースに対して適切なデータベース</strong>を選択できるようにしてください。</p> <p>以下の記事の「データベースの選択」の項を参照してください。</p> <ul> <li>参考 : <a href="https://blog.g-gen.co.jp/entry/professional-data-engineer#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E9%81%B8%E6%8A%9E">Professional Data Engineer試験対策マニュアル。出題傾向・勉強方法 - データベースの選択 - G-gen Tech Blog</a></li> </ul> <h1 id="BigQuery">BigQuery</h1> <h2 id="BigQuery-の基本">BigQuery の基本</h2> <p><strong>BigQuery</strong> は当試験で最も重要なプロダクトです。以下の記事を参照し、機能を理解してください。以下の「基本編」記事の内容が主な出題範囲と概ね重なっていますが、例外として「応用編」で簡単に紹介されている <strong>BigQuery ML</strong> は頻出範囲です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-explained-basics" title="BigQueryを徹底解説!(基本編) - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-explained-basics">blog.g-gen.co.jp</a></cite></p> <h2 id="ELT-と-ETL">ELT と ETL</h2> <p>Google Cloud のデータ分析基盤の中心は BigQuery です。前述の通り、ELT と ETL の両方のパターンが出題されます。ELT はスケジュールされたクエリ(Scheduled queries)や Dataform で、ETL は Cloud Data Fusion ã‚„ Dataflow、Dataproc で実装されます。</p> <p>Dataform は SQL の拡張言語である <strong>SQLX</strong> を使い、データの変換(transformation)、<strong>テストや品質チェック</strong>まで行えるのが特徴です。</p> <p>Cloud Data Fusion は<strong>ノーコード</strong>、や Dataflow は<strong>バッチ処理とストリーミング処理を両方扱えること</strong>、Dataproc は <strong>Hadoop / Spark</strong> 技術スタック、というようにキーワードを覚えておけば、出題時の選択に迷うことはあまりないはずです。</p> <p>また、単純で1回きりの(one-time の)データロードをローカル PC から行う、などのシンプルなユースケースでは、bq コマンドラインを用いて <code>bq load</code> コマンドを行うことで済む場合もあります。</p> <h2 id="半構造化データの扱いJSON-åž‹">半構造化データの扱い(JSON 型)</h2> <p>半構造化データとは、JSON フォーマットのように、ある程度構造化されているもののスキーマに柔軟性があるようなデータ構造を指します。当試験においては JSON をイメージすれば事足ります。</p> <p>BigQuery には JSON 型があり、JSON 形式のような半構造的なキー・バリューを柔軟に格納することができます。SELECT 文では以下のように、<code>key1.key2</code> と指定することで選択が可能です。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> json_payload.id <span class="synSpecial">FROM</span> `my_dataset.my_table` </pre> <p>このようなキーバリュー型をネストして格納できる型には他に STRUCT 型がありますが、STRUCT 型はスキーマが決まっているため、例えば日時経過でスキーマに変更がかかる可能性があり<strong>柔軟性が求められるケースでは JSON 型が便利</strong>です。また、テキスト形式であれば何でも格納できる STRING 型と比べても、パフォーマンス面とコスト面で JSON 型が有利です。</p> <ul> <li>参考 : <a href="https://cloud.google.com/blog/ja/products/databases/how-bigquery-powers-semi-structured-data-storage">BigQuery の JSON 型を活用して半構造化データの力を引き出す</a></li> </ul> <h2 id="パーティションとクラスタリング">パーティションとクラスタリング</h2> <p>BigQuery の<strong>パーティション</strong>と<strong>クラスタリング</strong>については正しく理解してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-paritioning-and-clustering" title="BigQueryのパーティションとクラスタリングについての解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-paritioning-and-clustering">blog.g-gen.co.jp</a></cite></p> <p>また、パーティションには<strong>有効期限</strong>が設定でき、作成から一定時間が経過したデータを削除することができます。いわゆるハウスキーピングの自動化に最適です。</p> <h2 id="外部テーブル">外部テーブル</h2> <p><strong>外部テーブル</strong>(External Tables)機能を使うと、Cloud Storage に格納した CSV、JSON、Parquet、Avro ファイルや Google スプレッドシートのデータ、あるいは Bigtable に対して、BigQuery から SQL を使ってクエリすることができます。</p> <p>ただし外部テーブル定義を行っても、データを BigQuery に取り込むわけではなく、あくまでデータは外部に置いたままクエリするため、パフォーマンス(処理速度)は高くありません。あくまで、クエリパフォーマンスが求められない場合や、データが少ない場合、また1回きり(one-time)のオンデマンドな分析などに利用します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/external-tables?hl=ja">外部テーブルの概要</a></li> </ul> <h2 id="SQL">SQL</h2> <p>Google Cloud 認定資格では珍しく、当試験では SQL の具体的なソースコードを選択肢から選ばせる問題が出題されます。とはいえ、複雑にネストされた長大な SQL が出題されるわけではありません。</p> <p>基本的な SELECT 文、また JOIN ã‚„ UNION ALL などを使って複数テーブルを組み合わせて表示する際の SQL などを理解しておいてください。</p> <h2 id="ウインドウ関数">ウインドウ関数</h2> <p>分析用途で用いられる<strong>ウインドウ関数</strong>は、<strong>文法を理解</strong>しておく必要があります。以下は公式ドキュメントですが、必ずしもわかりやすいとは言い難いため、インターネット上の情報や各種書籍を参考にして、文法を理解しておいてください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls">Window function calls</a></li> </ul> <p>例えば <code>RANK ()</code> 関数を使って、「各店舗の日次売上を集計し、上位5位を表示する」といった簡単な SQL がどのようなものになるか、わかるようにしておいてください。</p> <h2 id="コネクテッドシート">コネクテッドシート</h2> <p><strong>コネクテッドシート</strong>(Connected Sheets)は、Google スプレッドシート(Google Sheets)から BigQuery のデータを読み取ることができる機能です。</p> <p>反対に、BigQuery の<strong>外部テーブル</strong>機能では、Google スプレッドシートの URI を指定することでスプレッドシートを外部テーブルとして定義し、SQL でクエリすることができます。</p> <p>これらを組み合わせると BigQuery 外部テーブルを使ってスプレッドシートの中身を読み取り、BigQuery のデータと結合して結果をテーブルに保存し、その結果をコネクテッドシートを使ってスプレッドシートから読み取る、といった相互の連携が容易に実現できます。</p> <h1 id="BigQuery-のアクセス制御と暗号化">BigQuery のアクセス制御と暗号化</h1> <h2 id="権限管理IAM">権限管理(IAM)</h2> <p>BigQuery へのアクセス制御は、<strong>IAM</strong>(Identity and Access Management)を用いて行います。<strong>最小権限の原則</strong>に従うのがキーです。</p> <p>BigQuery と IAM については以下の記事で詳細に解説しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-iam-permission" title="BigQueryのアクセス制御と権限設計を解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-iam-permission">blog.g-gen.co.jp</a></cite></p> <p>記事でも紹介しているとおり、たとえば BigQuery のデータへの読み取りアクセスのために必要な最小権限を与える場合、対象アカウント(グループ)に以下の権限をプロジェクトレベルで付与します。</p> <ul> <li>BigQuery ジョブユーザー(<code>roles/bigquery.jobUser</code>)</li> <li>BigQuery データ閲覧者(<code>roles/bigquery.dataViewer</code>)</li> </ul> <p>データの編集(UPDATE ã‚„ DELETE)が必要な場合は、上記の「BigQuery データ閲覧者」の代わりに「BigQuery データ編集者(<code>roles/bigquery.dataEditor</code>)」あるいは「BigQuery データオーナー(<code>roles/bigquery.dataOwner</code>)」を付与します。ポイントは、ジョブ(クエリ)の実行には「BigQuery ジョブユーザー(<code>roles/bigquery.jobUser</code>)」が必要になるという点です。詳細は前掲の当社記事を参照してください。</p> <h2 id="承認されたビュー">承認されたビュー</h2> <p><strong>承認されたビュー</strong>(Authorized view)機能を使うと、ビューへのアクセス制御を簡素化できます。シンプルで効率の良い方法で、ビュー(特定のクエリ結果)へのアクセスを制御したい場合に利用できます。以下の記事を参照して、仕様を理解してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fbigquery-authorized-view-and-dataset" title="BigQuery「承認されたビュー」と「承認されたデータセット」 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/bigquery-authorized-view-and-dataset">blog.g-gen.co.jp</a></cite></p> <h2 id="透過的な暗号化">透過的な暗号化</h2> <p><strong>透過的な暗号化</strong>では、暗号鍵への適切なアクセス権限を持っていれば、利用者は暗号化を意識せずにストレージ上のデータを利用できます。</p> <p>Google Cloud では<strong>転送中のデータ</strong>(data in transit)と<strong>保管中のデータ</strong>(data at rest)は共に<strong>デフォルトで暗号化</strong>されています。これを<strong>デフォルトの暗号化</strong>と呼びます。デフォルトの暗号化で暗号鍵として使われる鍵は Google が管理されており、適切に保管、ローテーションや廃棄が行われます。この鍵のことを <strong>Google-managed encryption keys</strong>、略して GMEK と呼称します。</p> <ul> <li>参考 : <a href="https://cloud.google.com/docs/security/encryption/default-encryption?hl=ja">デフォルトの保存データの暗号化</a></li> <li>参考 : <a href="https://cloud.google.com/docs/security/encryption-in-transit?hl=ja">転送データの暗号化</a></li> </ul> <p>また、BigQuery のコンソール画面(BigQuery Studio)や bq コマンドを使うとき、データはインターネットを経由して転送されますが、通信は HTTPS で暗号化されています。これは BigQuery だけでなくすべてのサービスで共通です。つまり、ユーザーが何もしなくても、Google Cloud を利用している限り、転送中のデータ(data in transit)と保管中のデータ(data at rest)はともに、暗号化されていることになります。</p> <p>ただしセキュリティ向上や法的規制の要件への遵守などの目的で、GMEK ではなく独自の暗号鍵を利用する必要がある場合もあります。その場合は、<strong>Cloud KMS</strong> を使って独自の暗号鍵を管理することができまし。このとき、この顧客独自の鍵のことを <strong>Customer-managed encryption keys</strong>(CMEK)と呼びます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-kms-explained" title="Cloud KMSを徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-kms-explained">blog.g-gen.co.jp</a></cite></p> <p>さらに厳しい規制要件等では、クラウド環境に鍵を保管することが許されない場合もあります。その場合は顧客の独自環境で鍵を保管・管理し、暗号化・復号のたびに鍵を保管場所から取り出して利用することができます。このとき、この鍵のことを <strong>Customer-supplied encryption keys</strong>(CSEK)と呼びます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/docs/security/encryption/customer-supplied-encryption-keys?hl=ja">顧客指定の暗号鍵</a></li> </ul> <p>GMEK &lt; CMEK &lt; CSEK の順で保守や運用の工数は大きくなりますが、厳しい要件に対応することができます。試験では、これら3種類の鍵の<strong>意味を正しく理解</strong>していれば、正答を選ぶことができます。</p> <h1 id="BigQuery-ML">BigQuery ML</h1> <h2 id="BigQuery-ML-の基本">BigQuery ML の基本</h2> <p>当試験では <strong>BigQuery ML</strong> の基本的な理解を問われます。BigQuery ML では何が実現できるのか、またリモートモデルによる Vertex AI モデルの呼び出しといった概念を理解してください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja">BigQuery の AI と ML の概要</a></li> </ul> <h2 id="組み込みモデル">組み込みモデル</h2> <p>BigQuery ML には、すぐに利用可能なビルトインモデルが存在します。これらのモデルを指定し、トレーニングデータを投入すれば、SQL だけで簡単に独自モデルを開発可能です。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/bqml-introduction?hl=ja#internally_trained_models">内部でトレーニングされたモデル</a></li> </ul> <p>代表的なモデルとユースケースを頭に入れておいてください。時系列(Time series)と線形回帰(Linear regression)はよく似ていますが、例えば店舗の需要予測の際、セールなどの異常値や季節性の変化を考慮に入れてトレーニングできるのは時系列予測です。</p> <table> <thead> <tr> <th> モデル名 </th> <th> 用途 </th> </tr> </thead> <tbody> <tr> <td> 時系列(Time series) </td> <td> 時系列予測。<strong>異常値、季節性、休日が考慮</strong>される </td> </tr> <tr> <td> 線形回帰(Linear regression) </td> <td> <strong>線形の予測</strong>。例えば、特定の日の商品売上 </td> </tr> <tr> <td> ロジスティック回帰(Logistic regression) </td> <td> True である可能性を<strong>0と1の間で</strong>予測 </td> </tr> <tr> <td> K 平均法クラスタリング(K-means clustering) </td> <td> データの<strong>分類</strong>。顧客セグメントの分別など </td> </tr> <tr> <td> 行列分解(Matrix factorization) </td> <td> <strong>商品のレコメンデーション</strong>等。過去の行動を評価しておすすめを作成 </td> </tr> <tr> <td> 主成分分析(Principal component analysis、PCA) </td> <td> データの次元削減 </td> </tr> </tbody> </table> <p>またモデルをトレーニングする際にどのような SQL を書けばよいのかという細かい点も出題されます。例えば以下のドキュメントでは、ロジスティック回帰の目的変数となる列の列名を <code>input_label_cols</code> オプションで指定しています。デフォルトでは目的変数列は <code>label</code> となるため、CREATE MODEL 文の中の SELECT 文で <code>as label</code> として列を選択すれば、その列が目的変数としてトレーニングされます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/logistic-regression-prediction?hl=ja#create_a_logistic_regression_model">ロジスティック回帰モデルを作成する</a></li> </ul> <h2 id="スキューとドリフト">スキューとドリフト</h2> <p>トレーニングしたモデルを本番運用する際に重要なキーワードとして、<strong>データスキュー</strong>(Data skew)と<strong>データドリフト(Data drift)</strong>という言葉を理解しておいてください。BigQuery ML の公式ドキュメントでは以下のように定義しています。</p> <table> <thead> <tr> <th> 名称 </th> <th> 意味 </th> </tr> </thead> <tbody> <tr> <td> データスキュー(Data skew) </td> <td> トレーニングデータの分布が、本番環境でサーブされるデータと大きく異なる場合に発生 </td> </tr> <tr> <td> データドリフト(Data drift) </td> <td> 本番環境でのデータが時間の経過とともに大きく変化した場合に発生 </td> </tr> </tbody> </table> <p>すなわち、データスキューを監視することでトレーニングと実践のデータのずれを、データドリフトを監視することで時間経過に伴うモデルの劣化を、それぞれ検知することができるといえます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/model-monitoring-overview?hl=ja">モデル モニタリングの概要</a></li> </ul> <h2 id="Gemini">Gemini</h2> <p>BigQuery では、リモートモデルを作成することで LLM である <strong>Gemini を呼び出す</strong>ことができます。<code>CREATE OR REPLACE MODEL</code> でリモートモデルを定義したあと、<code>ML.GENERATE_TEXT</code> で Gemini を呼び出し、BigQuery 内部のデータをインプットしてテキストを生成させることが可能です。</p> <h1 id="AutoML">AutoML</h1> <p><strong>AutoML</strong> に関する問題も、若干出題されます。Google Cloud の AutoML では、大量の教師データを Cloud Storage 等に配置しておき、AutoML のトレーニングを実行することで、簡単に独自の機械学習モデルをトレーニングすることができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/vertex-ai/docs/beginner/beginners-guide?hl=ja">AutoML 初心者向けガイド</a></li> </ul> <h1 id="Cloud-Storage">Cloud Storage</h1> <h2 id="Cloud-Storage-の基本">Cloud Storage の基本</h2> <p><strong>Cloud Storage</strong> は頻出プロダクトです。以下の記事を参照し、機能を理解してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-storage-explained" title="Cloud Storage(GCS)を徹底解説 - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-storage-explained">blog.g-gen.co.jp</a></cite></p> <p>特に、この後の見出しで列挙する機能名は必ず押さえてください。</p> <h2 id="ストレージクラス">ストレージクラス</h2> <p><strong>Cloud Storage</strong> の <strong>ストレージクラス</strong>の概念は正確に理解してください。</p> <table> <thead> <tr> <th> ストレージクラス </th> <th> 保管料金 </th> <th> オペレーション料金 </th> <th> 最低保管期間 </th> </tr> </thead> <tbody> <tr> <td> Standard Storage </td> <td> 高い </td> <td> 安い </td> <td> なし </td> </tr> <tr> <td> Nearline Storage </td> <td> ↑ </td> <td> ↓ </td> <td> 30 æ—¥ </td> </tr> <tr> <td> Coldline Storage </td> <td> ↑ </td> <td> ↓ </td> <td> 90 æ—¥ </td> </tr> <tr> <td> Archive Storage </td> <td> 安い </td> <td> 高い </td> <td> 365 æ—¥ </td> </tr> </tbody> </table> <p>Standard > Nearline > Coldline > Archive の順で保管料金が安くなっていくことや、最低保管期間(この期間より短くオブジェクトを削除すると、この期間分の保管料金は発生する)が長くなっていくことを理解してください。最低保管期間は0、30、90、365と覚え、ストレージクラスの名称と合わせて覚えておいてください。</p> <h2 id="オブジェクトライフサイクル">オブジェクトライフサイクル</h2> <p><strong>オブジェクトライフサイクル</strong>(ライフサイクルルール)を設定することで、古くなったオブジェクトを自動的によりアーカイブ寄りのストレージクラスに移動したり、削除したりできます。ライフサイクルのアクションは、以下の3つのみです。</p> <ul> <li><code>Delete</code>(オブジェクトを削除する)</li> <li><code>SetStorageClass</code>(ストレージクラスを設定する)</li> <li><code>AbortIncompleteMultipartUpload</code>(途中だったマルチパートアップロードを削除する)</li> </ul> <p>ライフサイクルルールを設定することで、「作成から30日経過したオブジェクトは Nearline に移動、90日経過したら Coldline に移動。1年間経過したら削除する」などの複雑なルール設定も可能です。</p> <h2 id="バージョニング">バージョニング</h2> <p><strong>バージョニング</strong>の機能も必ず理解しておいてください。オブジェクトが上書きされても、指定した世代を残しておくことができます。前述のライフサイクルルールと組み合わせて、「オブジェクトは3世代保管する。作成から30日経過したオブジェクトは Nearline に移動し...」といった設定も可能です。</p> <h2 id="Autoclass">Autoclass</h2> <p><strong>Autoclass</strong> 機能も出題されます。Autoclass を有効化すると、オブジェクトのアクセス状況に応じて自動的にストレージクラスを設定してくれますので、<strong>運用工数を節減</strong>することが可能です。</p> <h2 id="デュアルリージョンマルチリージョン">デュアルリージョン、マルチリージョン</h2> <p>Cloud Storage バケットを作成時、バケットの配置場所をシングルリージョン、デュアルリージョン、マルチリージョンの中から選択できます。それぞれの特徴は、以下のドキュメントから理解しておいてください。</p> <ul> <li>参考 : <a href="https://cloud.google.com/storage/docs/locations?hl=ja#considerations">ロケーションに関する留意事項</a></li> </ul> <p>データの冗長性を確保しつつ、データの所在を明らかにしておくためにはデュアルリージョンを選択するシチュエーションがありえます。データの冗長化は非同期で行われます。デフォルトの非同期レプリケーションでは、1時間以内に99.9%のオブジェクトが複製され、12時間以内に100%に達します。これでは RPO(Recovery Point Objective)要件を満たせない場合、<strong>ターボレプリケーション</strong>(Turbo replication)を有効化することで、15分以内に100%のデータを複製できます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/storage/docs/availability-durability?hl=ja">データの可用性と耐久性</a></li> </ul> <h2 id="Storage-Transfer-Service">Storage Transfer Service</h2> <p><strong>Storage Transfer Service</strong> は、Amazon S3 などの外部ストレージサービスや、オンプレミスのファイルサーバー等から Cloud Storage にデータを転送するためのフルマネージドサービスです。</p> <p>BigQuery Transfer Service が <strong>BigQuery への転送</strong>を管理するサービスである一方、Storage Transfer Service は <strong>Cloud Storage への転送</strong>を管理するサービスであると覚えてください。</p> <p>包含接頭辞(include filter)や除外接頭辞(exclude filter)を使い、<strong>対象範囲を絞ってジョブ複数作り、並列実行</strong>することで転送時間を短くしたり、逆に直列で実行することで API 実行制限を回避することができます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/storage-transfer/docs/performance?hl=ja">転送速度を向上させる</a></li> </ul> <p>また Storage Transfer Service では、オンプレミスのファイルサーバーからのデータ転送も実現できます。この場合、オンプレミス側に Docker ベースの<strong>エージェントを起動する必要</strong>があります。またネットワーク帯域が限らえている場合は、エージェントが利用する<strong>帯域の上限を設定</strong>することもできます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/storage-transfer/docs/on-prem-set-up?hl=ja">ファイル システム転送の要件</a></li> <li>参考 : <a href="https://cloud.google.com/storage-transfer/docs/obtaining-bandwidth-on-prem?hl=ja">ネットワーク帯域幅を管理する</a></li> </ul> <h2 id="Storage-Transfer-Appliance">Storage Transfer Appliance</h2> <p><strong>Storage Transfer Appliance</strong> は物理的なアプライアンスをユーザーのデータが存在する場所に配送し、物理的に結線してデータを取り込み、また Google に返送することで Google のデータセンターに直接データを持ち込めるサービスです。持ち込み先は Cloud Storage バケットになります。インターネットや専用線経由でのデータ転送ではあまりに時間がかかってしまう場合や、十分な帯域が確保できない場合に利用します。なお、日本でも利用可能です。類似サービスとして、Amazon Web Services(AWS)の AWS Snowball があります。</p> <p><strong>ペタバイト(PB)級の量のデータ</strong>ã‚’ Google Cloud に持ち込む場合や、数百 TB のデータを移行したいが<strong>ネットワーク帯域が限られている</strong>場合等には、有力な選択肢の1つです。</p> <ul> <li>参考 : <a href="https://cloud.google.com/transfer-appliance/docs/4.0/overview">Overview</a></li> </ul> <h1 id="Looker">Looker</h1> <h2 id="Looker-の基本">Looker の基本</h2> <p><strong>Looker</strong> は、Google Cloud が提供する BI プラットフォームサービスです。<strong>LookML</strong> というデータモデリング言語を使ってあらかじめセマンティックレイヤを定義することで、高度なデータガバナンスを実現できるのが特徴です。</p> <p>LookML によるデータモデリングや、分析から後続の施策に繋げる豊富な機能、また組織外へのデータ共有に関する機能などにより、Looker は単なる BI ツールではなく、高度なデータプラットフォームとして用いられます。</p> <p>なお、Looker には、従来型の Looker(Original) と、Google Cloud と高度に統合された Looker(Google Cloud Core) の2バージョンがあります。</p> <ul> <li>参考 : <a href="https://cloud.google.com/looker/docs/looker-core-feature-differences?hl=ja">Looker(Google Cloud コア)で利用可能な機能 </a></li> </ul> <h2 id="メジャーとディメンション">メジャーとディメンション</h2> <p>Looker を使うには、データベース上のデータを使い、事前に LookML で<strong>メジャー</strong>(measures)と<strong>ディメンション</strong>(dimensions)を定義します。</p> <p>メジャーとディメンションは、BI ツール等では一般的な用語ですので、意味を理解しておいてください。</p> <p>また、メジャーとディメンションは Looker 上では <strong>view ファイル</strong>と呼ばれる定義ファイルに定義します。</p> <h2 id="Looker-における権限管理">Looker における権限管理</h2> <p>Looker では、ダッシュボード等への権限管理のため、ユーザーを<strong>グループ</strong>に格納できます。</p> <p>部署やチームごとにグループを作成し、ユーザーをその中に配置したら、フォルダにおいてグループ単位で権限を付与するのがベストプラクティスです。運用負荷軽減のため、<strong>個々のユーザーにではなくグループに権限を付与するべき</strong>であるという原則は Google Cloud の IAM とも共通しています。</p> <ul> <li>参考 : <a href="https://cloud.google.com/looker/docs/admin-panel-users-groups?hl=ja">グループ </a></li> </ul> <h1 id="Analytics-Hub">Analytics Hub</h1> <p><strong>Analytics Hub</strong> は、異なる組織間でデータを効率よく、セキュアに提供しあうためのプラットフォームです。Analytics Hub は BigQuery ã‚„ Pub/Sub のデータ交換に対応しています。</p> <p>自社のデータを、アクセス権限を適切に管理しながら効率よく他社に提供したいシチュエーションでは、Analytics Hub が選択できます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/bigquery/docs/analytics-hub-introduction?hl=ja">Analytics Hub の概要</a></li> </ul> <h1 id="Colab-Enterprise">Colab Enterprise</h1> <p><strong>Colab Enterprise</strong> は、フルマネージドの Python ノートブックサービスです。BigQuery ã‚„ Dataproc など他の Google Cloud サービスとも柔軟に連携できます。</p> <p>Colab Enterprise のノートブック上から Python を使って BigQuery 上のデータを操作したいときなどに、Google アカウントの認証情報を使ってスムーズにデータ連携が可能です。</p> <ul> <li>参考 : <a href="https://cloud.google.com/colab/docs/introduction">Introduction to Colab Enterprise</a></li> </ul> <h1 id="Cloud-SQL">Cloud SQL</h1> <p><strong>Cloud SQL</strong> に関する問題も若干出題されます。以下の記事を読み、基本を理解してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.g-gen.co.jp%2Fentry%2Fcloud-sql-explained" title="Cloud SQLを徹底解説! - G-gen Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.g-gen.co.jp/entry/cloud-sql-explained">blog.g-gen.co.jp</a></cite></p> <p>Cloud SQL の高可用性(HA)インスタンスを使うと、簡単な設定で、あるリージョンの中の複数のゾーンに Primary インスタンスと Secondary インスタンスを起動し、高い可用性を実現することができます。また、これに加えて非同期レプリケーションの<strong>レプリカ</strong>を加えることで、リージョン単位での障害に対応することもできます。</p> <ul> <li>参考 : <a href="https://cloud.google.com/sql/docs/mysql/intro-to-cloud-sql-disaster-recovery?hl=ja#dr-architecture">障害復旧アーキテクチャ</a></li> </ul> <div class="profile-cards-list"> <div class="profile-card-container"> <div class="sw-profile"> <div class="sw-profile__img" style="background-image:url(https://cdn-ak.f.st-hatena.com/images/fotolife/g/ggen-sugimura/20240805/20240805190556.jpg);"></div> <div class="sw-profile__txt-wrap"> <p class="sw-profile__name">杉村 勇馬 <a href="https://blog.g-gen.co.jp/archive/author/ggen-sugimura">(記事一覧)</a></p> <p class="sw-profile__txt">執行役員 CTO / クラウドソリューション部 部長</p> <p class="sw-profile__txt">元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (æ—§ Twitter) では Google Cloud ã‚„ AWS のアップデート情報をつぶやいています。</p> <a href="https://twitter.com/y_sugi_it?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @y_sugi_it</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p class="sw-profile__txt"></p> </div> </div> </div> </div> ggen-sugimura