Cloud Run サービスのセキュリティ保護のチュートリアル

このチュートリアルでは、Cloud Run で実行されるセキュアな 2 つのサービス アプリケーションを作成する方法について説明します。このアプリケーションは、誰でもマークダウン テキストを作成できる一般公開の「フロントエンド」サービス、およびマークダウン テキストを HTML にレンダリングする限定公開の「バックエンド」サービスを含む、Markdown エディタです。

フロントエンドの「Editor」からバックエンドの「Renderer」へのリクエスト フローを示す図。
「Renderer」バックエンドは限定公開のサービスです。これにより、複数言語のライブラリ間の変更をトラッキングすることなく、組織全体に対してテキスト変換の標準を保証できます。

このバックエンド サービスは、Cloud Run 組み込みの IAM ベースのサービス間認証機能を使用した限定公開です。これにより、サービスを呼び出すことができるユーザーが限定されます。どちらのサービスも、最小権限の原則に基づいて構築されており、必要な場合を除いて Google Cloud の他の部分にはアクセスできません。

このチュートリアルの制限事項または目標でないこと

  • このチュートリアルでは、Identity Platform または Firebase Authentication を使用してユーザー ID トークンを生成し、手動でユーザー認証を行うエンドユーザー認証については説明しません。エンドユーザー認証の詳細については、Cloud Run チュートリアルのエンドユーザー認証をご覧ください。

  • IAM ベースの認証と ID トークンのメソッドを組み合わせることについては、サポートされていないためこのチュートリアルでは説明しません。

gcloud のデフォルトを設定する

Cloud Run サービスを gcloud のデフォルトに構成するには:

  1. デフォルト プロジェクトを設定します。

    gcloud config set project PROJECT_ID

    PROJECT_ID は、このチュートリアルで作成したプロジェクトの名前に置き換えます。

  2. 選択したリージョン向けに gcloud を構成します。

    gcloud config set run/region REGION

    REGION は、任意のサポートされている Cloud Run のリージョンに置き換えます。

Cloud Run のロケーション

Cloud Run はリージョナルです。つまり、Cloud Run サービスを実行するインフラストラクチャは特定のリージョンに配置され、そのリージョン内のすべてのゾーンで冗長的に利用できるように Google によって管理されます。

レイテンシ、可用性、耐久性の要件を満たしていることが、Cloud Run サービスを実行するリージョンを選択する際の主な判断材料になります。一般的には、ユーザーに最も近いリージョンを選択できますが、Cloud Run サービスで使用されている他の Google Cloudプロダクトのロケーションも考慮する必要があります。 Google Cloud プロダクトを複数のロケーションで使用すると、サービスのレイテンシだけでなく、コストにも影響を及ぼす可能性があります。

Cloud Run は、次のリージョンで利用できます。

ティア 1 料金を適用

  • asia-east1(台湾)
  • asia-northeast1(東京)
  • asia-northeast2(大阪)
  • asia-south1(ムンバイ、インド)
  • europe-north1(フィンランド) リーフアイコン 低 CO2
  • europe-north2(ストックホルム) リーフアイコン 低 CO2
  • europe-southwest1(マドリッド) リーフアイコン 低 CO2
  • europe-west1(ベルギー) リーフアイコン 低 CO2
  • europe-west4(オランダ) リーフアイコン 低 CO2
  • europe-west8(ミラノ)
  • europe-west9(パリ) リーフアイコン 低 CO2
  • me-west1(テルアビブ)
  • northamerica-south1(メキシコ)
  • us-central1(アイオワ) リーフアイコン 低 CO2
  • us-east1(サウスカロライナ)
  • us-east4(北バージニア)
  • us-east5(コロンバス)
  • us-south1(ダラス) リーフアイコン 低 CO2
  • us-west1(オレゴン) リーフアイコン 低 CO2

ティア 2 料金を適用

  • africa-south1(ヨハネスブルグ)
  • asia-east2(香港)
  • asia-northeast3(ソウル、韓国)
  • asia-southeast1(シンガポール)
  • asia-southeast2 (ジャカルタ)
  • asia-south2(デリー、インド)
  • australia-southeast1(シドニー)
  • australia-southeast2(メルボルン)
  • europe-central2(ワルシャワ、ポーランド)
  • europe-west10(ベルリン)
  • europe-west12(トリノ)
  • europe-west2(ロンドン、イギリス) リーフアイコン 低 CO2
  • europe-west3(フランクフルト、ドイツ)
  • europe-west6(チューリッヒ、スイス) リーフアイコン 低 CO2
  • me-central1(ドーハ)
  • me-central2(ダンマーム)
  • northamerica-northeast1(モントリオール) リーフアイコン 低 CO2
  • northamerica-northeast2(トロント) リーフアイコン 低 CO2
  • southamerica-east1(サンパウロ、ブラジル) リーフアイコン 低 CO2
  • southamerica-west1(サンティアゴ、チリ) リーフアイコン 低 CO2
  • us-west2(ロサンゼルス)
  • us-west3(ソルトレイクシティ)
  • us-west4(ラスベガス)

Cloud Run サービスをすでに作成している場合は、Google Cloud コンソールの Cloud Run ダッシュボードにリージョンが表示されます。

サンプルコードを取得する

使用するコードサンプルを取得するには:

  1. Cloud Shell またはローカルマシンにサンプルアプリ リポジトリのクローンを作成します。

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    または、zip 形式のサンプルをダウンロードし、ファイルを抽出してもかまいません。

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    または、zip 形式のサンプルをダウンロードし、ファイルを抽出してもかまいません。

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    または、zip 形式のサンプルをダウンロードし、ファイルを抽出してもかまいません。

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    または、zip 形式のサンプルをダウンロードしてファイルを抽出してもかまいません。

    C#

    git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git

    または、zip 形式のサンプルをダウンロードし、ファイルを抽出してもかまいません。

  2. Cloud Run のサンプルコードが含まれているディレクトリに移動します。

    Node.js

    cd nodejs-docs-samples/run/markdown-preview/

    Python

    cd python-docs-samples/run/markdown-preview/

    Go

    cd golang-samples/run/markdown-preview/

    Java

    cd java-docs-samples/run/markdown-preview/

    C#

    cd dotnet-docs-samples/run/markdown-preview/

限定公開の Markdown レンダリング サービスを確認する

フロントエンドの観点から、Markdown サービスの簡単な API 仕様があります。

  • / に 1 つのエンドポイントがある
  • POST リクエストの受信を想定する
  • POST リクエストの本文が、Markdown テキストである

セキュリティ上の問題については、すべてのコードを確認するか、または ./renderer/ ディレクトリで詳細を確認してください。このチュートリアルでは、Markdown 変換コードについては説明しません。

限定公開の Markdown レンダリング サービスを配布する

コードを配布するには、Cloud Build でビルドし、Artifact Registry にアップロードしてから、Cloud Run にデプロイします。

  1. renderer ディレクトリに移動します。

    Node.js

    cd renderer/

    Python

    cd renderer/

    Go

    cd renderer/

    Java

    cd renderer/

    C#

    cd Samples.Run.MarkdownPreview.Renderer/

  2. Artifact Registry を作成します。

    gcloud artifacts repositories create REPOSITORY \
        --repository-format docker \
        --location REGION

    次のように置き換えます。

    • REPOSITORY は、リポジトリの一意の名前に置き換えます。プロジェクト内のリポジトリのロケーションごとに、リポジトリ名は一意でなければなりません。
    • REGION は、Artifact Registry リポジトリに使用する Google Cloud リージョンに置き換えます。
  3. 次のコマンドを実行してコンテナをビルドし、Artifact Registry に公開します。

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、renderer はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、renderer はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    Go

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、renderer はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    Java

    このサンプルでは、Jib を使用して一般的な Java ツールにより Docker イメージをビルドします。Jib は、Dockerfile や Docker をインストールせずにコンテナのビルドを最適化します。Jib を使用して Java コンテナを構築する方法の詳細を確認します。

    1. Docker を承認して Artifact Registry に push するには、gcloud 認証ヘルパーを使用します。

      gcloud auth configure-docker

    2. Jib Maven プラグインを使用して、コンテナをビルドし Artifact Registry に push します。

      mvn compile jib:build -Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、renderer はサービスに付ける名前です。

    ビルドが成功すると、BUILD SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    C#

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、renderer はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

  4. アクセスが制限された限定公開のサービスとしてデプロイします。

    Cloud Run では、すぐに使用できる機能としてアクセス制御サービス ID があります。アクセス制御では、ユーザーやその他のサービスによるサービスの呼び出しを制限する認証レイヤが提供されます。サービス ID によって、権限が制限された専用のサービス アカウントを作成することにより、サービスによるその他のGoogle Cloud リソースへのアクセスを制限できます。

    1. レンダリング サービスの「コンピューティング ID」として機能する、サービス アカウントを作成します。このアカウントには、デフォルトでは、プロジェクト メンバーシップ以外の権限は付与されません。

      コマンドライン

      gcloud iam service-accounts create renderer-identity

      Terraform

      Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

      resource "google_service_account" "renderer" {
        account_id   = "renderer-identity"
        display_name = "Service identity of the Renderer (Backend) service."
      }

      Markdown レンダリング サービスは、 Google Cloud内の他のサービスとは直接には統合されていません。それ以上の権限は必要ありません。

    2. renderer-identity サービス アカウントを使用してデプロイします。未認証のアクセスは拒否します。

      コマンドライン

      gcloud run deploy renderer \
      --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/renderer \
      --service-account renderer-identity \
      --no-allow-unauthenticated

      このサービス アカウントが同じプロジェクトに含まれている場合は、Cloud Run において、短い形式のサービス アカウント名を完全なメールアドレスの代わりに使用できます。

      Terraform

      Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

      resource "google_cloud_run_v2_service" "renderer" {
        name     = "renderer"
        location = "us-central1"
      
        deletion_protection = false # set to "true" in production
      
        template {
          containers {
            # Replace with the URL of your Secure Services > Renderer image.
            #   gcr.io/<PROJECT_ID>/renderer
            image = "us-docker.pkg.dev/cloudrun/container/hello"
          }
          service_account = google_service_account.renderer.email
        }
      }

限定公開の Markdown レンダリング サービスを試用する

限定公開のサービスは、ウェブブラウザから直接読み込むことはできません。その代わりに、curl を使用するか、または Authorization ヘッダーを挿入できる同様の HTTP リクエスト CLI ツールを使用します。

このサービスに太字のテキストを送信し、それによりマークダウン アスタリスクが HTML の <strong> タグに変換されるのを確認するには、次の手順を行います。

  1. デプロイ出力から URL を取得します。

  2. gcloud を使用して、特別な開発専用の ID トークンを取得します。

    TOKEN=$(gcloud auth print-identity-token)
  3. 未加工の Markdown テキストを、URL の生成から除外されたクエリ文字列のパラメータとして渡す、curl リクエストを作成します。

    curl -H "Authorization: Bearer $TOKEN" \
       -H 'Content-Type: text/plain' \
       -d '**Hello Bold Text**' \
       SERVICE_URL

    SERVICE_URL は、Markdown レンダリング サービスのデプロイ後に提供された URL に置き換えます。

  4. レスポンスは HTML スニペットである必要があります。

     <strong>Hello Bold Text</strong>
    

エディタ サービスとレンダリング サービスの統合を確認する

エディタ サービスは、単純なテキスト入力 UI と HTML プレビューを表示する空間を提供します。続行する前に、./editor/ ディレクトリを開いて、先ほど取得したコードを確認してください。

次に、以下のセクションで 2 つのサービスをセキュアに統合するコードを確認します。

Node.js

render.js モジュールは、限定公開の Renderer サービスへの認証済みリクエストを作成します。Cloud Run 環境の Google Cloud メタデータ サーバーを使用して、ID トークンを作成し、作成した ID トークンを Authorization ヘッダーの一部として HTTP リクエストに追加します。

他の環境では、render.jsアプリケーションのデフォルト認証情報を使用して Google のサーバーからトークンをリクエストします。

const {GoogleAuth} = require('google-auth-library');
const got = require('got');
const auth = new GoogleAuth();

let client, serviceUrl;

// renderRequest creates a new HTTP request with IAM ID Token credential.
// This token is automatically handled by private Cloud Run (fully managed) and Cloud Functions.
const renderRequest = async markdown => {
  if (!process.env.EDITOR_UPSTREAM_RENDER_URL)
    throw Error('EDITOR_UPSTREAM_RENDER_URL needs to be set.');
  serviceUrl = process.env.EDITOR_UPSTREAM_RENDER_URL;

  // Build the request to the Renderer receiving service.
  const serviceRequestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'text/plain',
    },
    body: markdown,
    timeout: 3000,
  };

  try {
      // Create a Google Auth client with the Renderer service url as the target audience.
      if (!client) client = await auth.getIdTokenClient(serviceUrl);
      // Fetch the client request headers and add them to the service request headers.
      // The client request headers include an ID token that authenticates the request.
      const clientHeaders = await client.getRequestHeaders();
      serviceRequestOptions.headers['Authorization'] =
        clientHeaders['Authorization'];
  } catch (err) {
    throw Error('could not create an identity token: ' + err.message);
  }

  try {
    // serviceResponse converts the Markdown plaintext to HTML.
    const serviceResponse = await got(serviceUrl, serviceRequestOptions);
    return serviceResponse.body;
  } catch (err) {
    throw Error('request to rendering service failed: ' + err.message);
  }
};

JSON のマークダウンを解析し、HTML に変換する Renderer サービスに送信します。

app.post('/render', async (req, res) => {
  try {
    const markdown = req.body.data;
    const response = await renderRequest(markdown);
    res.status(200).send(response);
  } catch (err) {
    console.error('Error rendering markdown:', err);
    res.status(500).send(err);
  }
});

Python

new_request メソッドは、限定公開サービスに対する認証済みリクエストを作成します。Cloud Run 環境の Google Cloud メタデータ サーバーを使用して、ID トークンを作成し、作成した ID トークンを Authorization ヘッダーの一部として HTTP リクエストに追加します。

他の環境では、new_request は、アプリケーションのデフォルト認証情報で認証することで Google のサーバーから ID トークンをリクエストします。

import os
import urllib

import google.auth.transport.requests
import google.oauth2.id_token


def new_request(data):
    """Creates a new HTTP request with IAM ID Token credential.

    This token is automatically handled by private Cloud Run and Cloud Functions.

    Args:
        data: data for the authenticated request

    Returns:
        The response from the HTTP request
    """
    url = os.environ.get("EDITOR_UPSTREAM_RENDER_URL")
    if not url:
        raise Exception("EDITOR_UPSTREAM_RENDER_URL missing")

    req = urllib.request.Request(url, data=data.encode())
    auth_req = google.auth.transport.requests.Request()
    target_audience = url

    id_token = google.oauth2.id_token.fetch_id_token(auth_req, target_audience)
    req.add_header("Authorization", f"Bearer {id_token}")

    response = urllib.request.urlopen(req)
    return response.read()

JSON のマークダウンを解析し、HTML に変換する Renderer サービスに送信します。

@app.route("/render", methods=["POST"])
def render_handler():
    """Parse the markdown from JSON and send it to the Renderer service to be
    transformed into HTML.
    """
    body = request.get_json(silent=True)
    if not body:
        return "Error rendering markdown: Invalid JSON", 400

    data = body["data"]
    try:
        parsed_markdown = render.new_request(data)
        return parsed_markdown, 200
    except Exception as err:
        return f"Error rendering markdown: {err}", 500

Go

RenderService は、限定公開サービスに対する認証済みリクエストを作成します。Cloud Run 環境の Google Cloud メタデータ サーバーを使用して、ID トークンを作成し、作成した ID トークンを Authorization ヘッダーの一部として HTTP リクエストに追加します。

他の環境では、RenderService は、アプリケーションのデフォルト認証情報で認証することで Google のサーバーから ID トークンをリクエストします。

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"time"

	"golang.org/x/oauth2"
	"google.golang.org/api/idtoken"
)

// RenderService represents our upstream render service.
type RenderService struct {
	// URL is the render service address.
	URL string
	// tokenSource provides an identity token for requests to the Render Service.
	tokenSource oauth2.TokenSource
}

// NewRequest creates a new HTTP request to the Render service.
// If authentication is enabled, an Identity Token is created and added.
func (s *RenderService) NewRequest(method string) (*http.Request, error) {
	req, err := http.NewRequest(method, s.URL, nil)
	if err != nil {
		return nil, fmt.Errorf("http.NewRequest: %w", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Create a TokenSource if none exists.
	if s.tokenSource == nil {
		s.tokenSource, err = idtoken.NewTokenSource(ctx, s.URL)
		if err != nil {
			return nil, fmt.Errorf("idtoken.NewTokenSource: %w", err)
		}
	}

	// Retrieve an identity token. Will reuse tokens until refresh needed.
	token, err := s.tokenSource.Token()
	if err != nil {
		return nil, fmt.Errorf("TokenSource.Token: %w", err)
	}
	token.SetAuthHeader(req)

	return req, nil
}

変換するマークダウン テキストを HTML に追加した後、リクエストが Renderer サービスに送信されます。レンダリング機能と通信の問題を切り分けるため、レスポンス エラーが処理されます。


var renderClient = &http.Client{Timeout: 30 * time.Second}

// Render converts the Markdown plaintext to HTML.
func (s *RenderService) Render(in []byte) ([]byte, error) {
	req, err := s.NewRequest(http.MethodPost)
	if err != nil {
		return nil, fmt.Errorf("RenderService.NewRequest: %w", err)
	}

	req.Body = io.NopCloser(bytes.NewReader(in))
	defer req.Body.Close()

	resp, err := renderClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http.Client.Do: %w", err)
	}

	out, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("ioutil.ReadAll: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return out, fmt.Errorf("http.Client.Do: %s (%d): request not OK", http.StatusText(resp.StatusCode), resp.StatusCode)
	}

	return out, nil
}

Java

makeAuthenticatedRequest は、限定公開サービスに対する認証済みリクエストを作成します。Cloud Run 環境の Google Cloud メタデータ サーバーを使用して、ID トークンを作成し、作成した ID トークンを Authorization ヘッダーの一部として HTTP リクエストに追加します。

他の環境では、makeAuthenticatedRequest は、アプリケーションのデフォルト認証情報で認証することで Google のサーバーから ID トークンをリクエストします。

// makeAuthenticatedRequest creates a new HTTP request authenticated by a JSON Web Tokens (JWT)
// retrievd from Application Default Credentials.
public String makeAuthenticatedRequest(String url, String markdown) {
  String html = "";
  try {
    // Retrieve Application Default Credentials
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    IdTokenCredentials tokenCredentials =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider((IdTokenProvider) credentials)
            .setTargetAudience(url)
            .build();

    // Create an ID token
    String token = tokenCredentials.refreshAccessToken().getTokenValue();
    // Instantiate HTTP request
    MediaType contentType = MediaType.get("text/plain; charset=utf-8");
    okhttp3.RequestBody body = okhttp3.RequestBody.create(markdown, contentType);
    Request request =
        new Request.Builder()
            .url(url)
            .addHeader("Authorization", "Bearer " + token)
            .post(body)
            .build();

    Response response = ok.newCall(request).execute();
    html = response.body().string();
  } catch (IOException e) {
    logger.error("Unable to get rendered data", e);
  }
  return html;
}

JSON のマークダウンを解析し、HTML に変換する Renderer サービスに送信します。

// '/render' expects a JSON body payload with a 'data' property holding plain text
// for rendering.
@PostMapping(value = "/render", consumes = "application/json")
public String render(@RequestBody Data data) {
  String markdown = data.getData();

  String url = System.getenv("EDITOR_UPSTREAM_RENDER_URL");
  if (url == null) {
    String msg =
        "No configuration for upstream render service: "
            + "add EDITOR_UPSTREAM_RENDER_URL environment variable";
    logger.error(msg);
    throw new IllegalStateException(msg);
  }

  String html = makeAuthenticatedRequest(url, markdown);
  return html;
}

C#

GetAuthenticatedPostResponse は、限定公開サービスに対する認証済みリクエストを作成します。Cloud Run 環境の Google Cloud メタデータ サーバーを使用して、ID トークンを作成し、作成した ID トークンを Authorization ヘッダーの一部として HTTP リクエストに追加します。

他の環境では、GetAuthenticatedPostResponse は、アプリケーションのデフォルト認証情報で認証することで Google のサーバーから ID トークンをリクエストします。

private async Task<string> GetAuthenticatedPostResponse(string url, string postBody)
{
    // Get the OIDC access token from the service account via Application Default Credentials
    GoogleCredential credential = await GoogleCredential.GetApplicationDefaultAsync();  
    OidcToken token = await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(url));
    string accessToken = await token.GetAccessTokenAsync();

    // Create request to the upstream service with the generated OAuth access token in the Authorization header
    var upstreamRequest = new HttpRequestMessage(HttpMethod.Post, url);
    upstreamRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    upstreamRequest.Content = new StringContent(postBody);

    var upstreamResponse = await _httpClient.SendAsync(upstreamRequest);
    upstreamResponse.EnsureSuccessStatusCode();

    return await upstreamResponse.Content.ReadAsStringAsync();
}

JSON のマークダウンを解析し、HTML に変換する Renderer サービスに送信します。

public async Task<IActionResult> Index([FromBody] RenderModel model)
{
    var markdown = model.Data ?? string.Empty;
    var renderedHtml = await GetAuthenticatedPostResponse(_editorUpstreamRenderUrl, markdown);
    return Content(renderedHtml);
}

一般公開のエディタ サービスを配布する

コードをビルドしてデプロイするには、次の手順を行います。

  1. editor ディレクトリに移動します。

    Node.js

    cd ../editor

    Python

    cd ../editor

    Go

    cd ../editor

    Java

    cd ../editor

    C#

    cd ../Samples.Run.MarkdownPreview.Editor/

  2. 次のコマンドを実行してコンテナをビルドし、Artifact Registry に公開します。

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、editor はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、editor はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    Go

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、editor はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    Java

    このサンプルでは、Jib を使用して一般的な Java ツールにより Docker イメージをビルドします。Jib は、Dockerfile や Docker をインストールせずにコンテナのビルドを最適化します。Jib を使用して Java コンテナを構築する方法の詳細を確認します。

    mvn compile jib:build -Dimage=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、editor はサービスに付ける名前です。

    ビルドが成功すると、BUILD SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

    C#

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor

    ここで、PROJECT_ID は Google Cloud プロジェクト ID、editor はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Artifact Registry に保存されます。このイメージは必要に応じて再利用できます。

  3. レンダリング サービスへの特別なアクセス権を持つ、限定公開のサービスとしてデプロイします。

    1. 限定公開サービスの「コンピューティング ID」として機能する、サービス アカウントを作成します。デフォルトでは、このアカウントにプロジェクト メンバーシップ以外の権限は付与されません。

      コマンドライン

      gcloud iam service-accounts create editor-identity

      Terraform

      Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

      resource "google_service_account" "editor" {
        account_id   = "editor-identity"
        display_name = "Service identity of the Editor (Frontend) service."
      }

      エディタ サービスは、Markdown レンダリング サービス以外の Google Cloud 内の要素とやり取りする必要はありません。

    2. editor-identity コンピューティング ID へのアクセス権を付与して、Markdown レンダリング サービスを呼び出します。このコンピューティング ID を使用するすべてのサービスに、この権限が付与されています。

      コマンドライン

      gcloud run services add-iam-policy-binding renderer \
      --member serviceAccount:editor-identity@PROJECT_ID. \
      --role roles/run.invoker

      Terraform

      Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

      resource "google_cloud_run_service_iam_member" "editor_invokes_renderer" {
        location = google_cloud_run_v2_service.renderer.location
        service  = google_cloud_run_v2_service.renderer.name
        role     = "roles/run.invoker"
        member   = "serviceAccount:${google_service_account.editor.email}"
      }

      レンダリング サービスのコンテキスト内で起動元ロールが付与されるため、このレンダリング サービスは、エディタから呼び出せる唯一の限定公開 Cloud Run サービスになります。

    3. editor-identity サービス アカウントでデプロイし、一般公開の未認証アクセスを許可します。

      コマンドライン

      gcloud run deploy editor --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/editor \
      --service-account editor-identity \
      --set-env-vars EDITOR_UPSTREAM_RENDER_URL=SERVICE_URL \
      --allow-unauthenticated

      次のように置き換えます。

      • PROJECT_ID はプロジェクト ID に置き換えます。
      • SERVICE_URL は、Markdown レンダリング サービスのデプロイ後に提供された URL に置き換えます。

      Terraform

      Terraform 構成を適用または削除する方法については、基本的な Terraform コマンドをご覧ください。

      エディタ サービスをデプロイします。

      resource "google_cloud_run_v2_service" "editor" {
        name     = "editor"
        location = "us-central1"
      
        deletion_protection = false # set to "true" in production
      
        template {
          containers {
            # Replace with the URL of your Secure Services > Editor image.
            #   gcr.io/<PROJECT_ID>/editor
            image = "us-docker.pkg.dev/cloudrun/container/hello"
            env {
              name  = "EDITOR_UPSTREAM_RENDER_URL"
              value = google_cloud_run_v2_service.renderer.uri
            }
          }
          service_account = google_service_account.editor.email
      
        }
      }

      サービスを呼び出すための allUsers 権限を付与します。

      data "google_iam_policy" "noauth" {
        binding {
          role = "roles/run.invoker"
          members = [
            "allUsers",
          ]
        }
      }
      
      resource "google_cloud_run_service_iam_policy" "noauth" {
        location = google_cloud_run_v2_service.editor.location
        project  = google_cloud_run_v2_service.editor.project
        service  = google_cloud_run_v2_service.editor.name
      
        policy_data = data.google_iam_policy.noauth.policy_data
      }

HTTPS トラフィックについて

これらのサービスを使用したマークダウンのレンダリングに関する HTTP リクエストは 3 つあります。

ユーザーからエディタへのリクエスト フロー、メタデータ サーバーからトークンを取得するエディタ、レンダリング サービスへのリクエストを行うエディタ、HTML をエディタに返すレンダリング サービスを示す図。
editor-identity を含むフロントエンド サービスがレンダリング サービスを呼び出します。editor-identityrenderer-identity の両方に権限の制限が適用されているため、セキュリティ悪用やコード挿入を目的とするその他の Google Cloud リソースへのアクセスは制限されます。

試してみる

完全な 2 つのサービス アプリケーションを試すには、次の手順を行います。

  1. ブラウザで、前述のデプロイの手順により提供された URL に移動します。

  2. 左側のマークダウン テキストを編集してからボタンをクリックすると、右側にプレビューが表示されます。

    次のようになります。

    マークダウン エディタのユーザー インターフェースのスクリーン ショット

これらのサービスの開発を継続する場合は、 Google Cloud の他のサービスへの Identity and Access Management(IAM)アクセスが制限されます。他の多くのサービスにアクセスするには、追加の IAM ロールをこれらのサービスに付与する必要があることにご注意ください。