Cloud Run 安全服務教學課程


本教學課程將逐步說明如何建立在 Cloud Run 上執行的安全雙服務應用程式。這個應用程式是 Markdown 編輯器,包含公開的「前端」服務 (任何人都能用來撰寫 Markdown 文字),以及私人的「後端」服務 (可將 Markdown 文字轉譯為 HTML)。

顯示從前端「編輯器」到後端「算繪器」的要求流程圖。
「Renderer」後端是私人服務。這樣一來,您就能確保整個機構的文字轉換標準一致,不必追蹤多種語言的程式庫變更。

後端服務會使用 Cloud Run 內建的 IAM 服務對服務驗證功能,限制可呼叫服務的使用者,這兩項服務都採用最低權限原則,除非必要,否則無法存取 Google Cloud 的其餘部分。

本教學課程的限制或非目標

  • 本教學課程不會說明使用者驗證,這類驗證會使用 Identity PlatformFirebase 驗證產生使用者 ID 權杖,並手動驗證使用者身分。如要進一步瞭解使用者驗證,請參閱使用者驗證的 Cloud Run 教學課程。

  • 本教學課程不會說明如何合併使用 IAM 驗證和 ID 權杖方法,因為系統不支援這類做法。

目標

  • 建立專屬服務帳戶,並授予最低權限,用於服務對服務驗證,以及服務存取 Google Cloud的其餘部分。
  • 撰寫、建構及部署兩個會互動的 Cloud Run 服務。
  • 在公開和私人的 Cloud Run 服務之間提出要求。

費用

在本文件中,您會使用 Google Cloud的下列計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator 初次使用 Google Cloud 的使用者可能符合免費試用資格。

事前準備

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

  6. Enable the Cloud Run API.

    Enable the API

  7. 安裝並初始化 gcloud CLI
  8. 安裝 curl 以便試用服務
  9. 必要的角色

    如要取得完成本教學課程所需的權限,請要求管理員為您授予專案的下列 IAM 角色:

    如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和機構的存取權」。

    您或許還可透過自訂角色或其他預先定義的角色取得必要權限。

設定 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 定價

採用級別 2 定價

如果您已建立 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 規格很簡單:

  • 一個端點,位於 /
  • 預期會收到 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. 使用 gcloud 憑證輔助程式授權 Docker 推送至 Artifact Registry。

      gcloud auth configure-docker

    2. 使用 Jib Maven 外掛程式建構容器,並推送至 Artifact Registry。

      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 提供開箱即用的存取權控管服務身分功能。存取權控管機制提供驗證層,可限制使用者和其他服務叫用服務。服務身分可建立權限受限的專屬服務帳戶,限制服務存取其他Google Cloud 資源。

    1. 建立服務帳戶,做為算繪服務的「運算身分」。根據預設,除了專案成員資格外,這項角色沒有其他權限。

      指令列

      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 或類似的 HTTP 要求 CLI 工具,以便插入 Authorization 標頭。

如要將一些粗體文字傳送至服務,並查看服務如何將 Markdown 星號轉換為 HTML <strong> 標記,請按照下列步驟操作:

  1. 從部署輸出內容取得網址。

  2. 使用 gcloud 衍生專屬的開發專用身分權杖,以進行驗證:

    TOKEN=$(gcloud auth print-identity-token)
  3. 建立 curl 要求,將原始 Markdown 文字做為經過網址逸出的查詢字串參數傳遞:

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

    SERVICE_URL 替換為部署 Markdown 轉譯服務後提供的網址。

  4. 回應應為 HTML 程式碼片段:

     <strong>Hello Bold Text</strong>
    

查看編輯器與算繪服務之間的整合

編輯器服務提供簡單的文字輸入 UI,以及可查看 HTML 預覽的空間。請先開啟 ./editor/ 目錄,檢查先前擷取的程式碼,再繼續操作。

接著,請探索下列幾節程式碼,瞭解如何安全地整合這兩項服務。

Node.js

render.js模組會向私有轉譯器服務建立已驗證的要求。這項服務會使用 Cloud Run 環境中的 Google Cloud 中繼資料伺服器建立身分識別權杖,並將權杖新增至 HTTP 要求,做為 Authorization 標頭的一部分。

在其他環境中,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 剖析 Markdown,並傳送至 Renderer 服務,轉換為 HTML。

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 中繼資料伺服器建立身分符記,並將該符記新增至 HTTP 要求,做為 Authorization 標頭的一部分。

在其他環境中,new_request 會透過應用程式預設憑證進行驗證,並向 Google 伺服器要求身分識別權杖。

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 剖析 Markdown,並傳送至 Renderer 服務,轉換為 HTML。

@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 中繼資料伺服器建立身分識別權杖,並將權杖新增至 HTTP 要求,做為 Authorization 標頭的一部分。

在其他環境中,RenderService 會透過應用程式預設憑證進行驗證,並向 Google 伺服器要求身分識別權杖。

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 的 Markdown 文字加入要求後,要求會傳送至 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 中繼資料伺服器建立身分識別權杖,並將其新增至 HTTP 要求,做為 Authorization 標頭的一部分。

在其他環境中,makeAuthenticatedRequest 會透過應用程式預設憑證進行驗證,並向 Google 伺服器要求身分識別權杖。

// 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 剖析 Markdown,並傳送至 Renderer 服務,轉換為 HTML。

// '/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 中繼資料伺服器建立身分識別權杖,並將其新增至 HTTP 要求,做為 Authorization 標頭的一部分。

在其他環境中,GetAuthenticatedPostResponse 會透過應用程式預設憑證進行驗證,並向 Google 伺服器要求身分識別權杖。

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 剖析 Markdown,並傳送至 Renderer 服務,轉換為 HTML。

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. 建立服務帳戶,做為私有服務的「運算身分」。根據預設,除了專案成員資格外,這項角色沒有其他權限。

      指令列

      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."
      }

      Editor 服務不需要與 Markdown 算繪服務以外的任何項目互動。 Google Cloud

    2. 授予 editor-identity 計算身分存取權,以便叫用 Markdown 算繪服務。凡是將此做為運算身分的服務,都會擁有這項權限。

      指令列

      gcloud run services add-iam-policy-binding renderer \
      --member serviceAccount:editor-identity@PROJECT_ID.iam.gserviceaccount.com \
      --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 轉譯服務後提供的網址。

      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 流量

使用這些服務算繪 Markdown 時,會涉及三項 HTTP 要求。

這張圖表顯示要求流程:從使用者到編輯器,編輯器從中繼資料伺服器取得權杖,編輯器向轉譯服務提出要求,轉譯服務將 HTML 傳回編輯器。
前端服務會透過 editor-identity 叫用算繪服務。「editor-identity」和「renderer-identity」的權限有限,因此任何安全性漏洞或程式碼注入行為,都只能存取有限的 Google Cloud 資源。

立即體驗

如要試用完整的雙服務應用程式,請按照下列步驟操作:

  1. 在瀏覽器中前往上述部署步驟提供的網址。

  2. 嘗試編輯左側的 Markdown 文字,然後按一下按鈕,即可在右側預覽。

    內容應該會類似這樣:

    Markdown 編輯器使用者介面螢幕截圖

如果您選擇繼續開發這些服務,請注意,這些服務對其餘服務的存取權受到身分與存取權管理 (IAM) 限制,且需要額外指派 IAM 角色,才能存取許多其他服務。 Google Cloud

清除所用資源

如果您是為了這個教學課程建立新專案,請刪除專案。如果您使用現有專案,並想保留專案,但不要本教學課程新增的變更,請刪除為本教學課程建立的資源

刪除專案

如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。

如要刪除專案:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

刪除教學課程資源

  1. 刪除您在本教學課程中部署的 Cloud Run 服務:

    gcloud

    gcloud run services delete editor
    gcloud run services delete renderer

    您也可以從Google Cloud 控制台刪除 Cloud Run 服務。

  2. 移除您在教學課程設定期間新增的 gcloud 預設設定。

     gcloud config unset run/region
    
  3. 移除專案設定:

     gcloud config unset project
    
  4. 刪除在本教學課程中建立的其他 Google Cloud 資源:

後續步驟