⛓️

Cloud Service Mesh + Cloud Run でサービスメッシュ構築

2024/09/11に公開
3

2024 年 8 月 30 日に行われたJagu'e'r クラウドネイティブ分科会 Meetup#15 に登壇させていただきました。

「夏休みの自由研究」がテーマだったので、Cloud Service Mesh を改めて勉強したいと思い、当初 GKE のサービスメッシュ構築を検討していましたが、登壇の数日前に Cloud Run のサポートが Preview となった発表があったので急遽検証して簡単に登壇に盛り込みました。

こちらの記事では、Cloud Service Mesh で Cloud Run のサービスメッシュ構築を実際にやってみた部分にフォーカスして登壇資料より詳細にまとめています。触る前は「単一のクライアントとなる Cloud Run サービスとサービスメッシュに紐づく Cloud Run サービスによってサービスメッシュが構築されて、どの Cloud Run サービスでもサービスメッシュの機能を利用できる」と思っていました。

実際に触ってみると 「サービスメッシュの機能を適切に利用するには、どの Cloud Run サービスがクライアント or 宛先になるかを意識しなければならない」 ことがわかりました。サービスメッシュの機能を利用するには Cloud Service Mesh からサービスルーティングの構成情報を受け取る必要があり、受け取りたい Cloud Run サービスはクライアントとしてデプロイする必要がありました!

のんびり執筆していたら同様の内容で下記の素晴らしい記事がリリースされました。大変参考にさせていただきました。本記事では少し違った観点で学びがあるように、公式ドキュメントの引用などでの解説を多めに盛り込むことを意識して執筆してみました。

https://zenn.dev/google_cloud_jp/articles/14956782beaf97

この記事のターゲット

  • Cloud Run でサービスメッシュを構築したい方
  • Cloud Service Mesh に興味がある方

Cloud Service Mesh とは?

そもそも Cloud Service Mesh を知らないという方は、以前私が執筆した記事をご覧ください。Google Cloud Next '24 での発表を受けた際に執筆したもので、概要を掴めるかと思います。

https://zenn.dev/t_hayashi/articles/ad115c602203da

Cloud Service Mesh + Cloud Run

さっそく Cloud Service Mesh によって Cloud Run でサービスメッシュを構築したいと思います。ここではこちらの公式ドキュメントの内容を参考に独自のアプリケーションでやってみます。

すぐにというわけではないですが、以降の内容を試せるリポジトリも用意したので、そちらを扱いつつまとめていきたいと思います。リポジトリには Terraform のコードを中心に格納しています。

https://github.com/hayashit6239/cloudrun-cloudservicemesh-sample

検証① カスタム URL 呼び出し & 自動認証 - 成功 -

まずは、チュートリアル的な公式ドキュメントに記載されている内容を検証したいと思います。

Cloud Run uses the Cloud Service Mesh service routing APIs. These APIs let a Cloud Run service call other Cloud Run services using a customized URL rather than the provided run.app URL. In addition, Cloud Run automatically authenticates to the destination Cloud Run service so that you don't have to configure your own authentication to attach request credentials to other Cloud Run services.
公式ドキュメント - Configure Cloud Service Mesh for Cloud Run

  • Point①:Cloud Runサービスは、提供されているrun.app URLではなく、カスタマイズされたURLを使用して他のCloud Runサービスを呼び出すことが可能
  • Point②:Cloud Runは、他のCloud Runサービスにリクエスト認証情報を添付するための独自の認証を構成する必要がないように、宛先Cloud Runサービスへの認証を自動的に行う

用意した環境

シンプルに 2 種類の Cloud Run サービスで検証します。クライアントの役割を果たす Service-Client-for-Backendバックエンドの役割を果たす Service-Backend-a です。

Service-Client-for-Backend ではリクエストを受け付けて、叩かれたエンドポイントによって適切なバックエンドを叩きにいく動きをします。Service-Backend-a は単純なレスポンスとして、小説の著者を返す動きをします。

上記で挙げた Cloud Service Mesh によって構築したサービスメッシュの特性を検証内容に落とし込みます。

  • Point①:Cloud Runサービスは、提供されているrun.app URLではなく、カスタマイズされたURLを使用して他のCloud Runサービスを呼び出すことが可能

検証として、Service-Client-for-Backend から Service-Backend-a を叩きにいく時のエンドポイント URL を http://service-backend-a.csm.dev としてレスポンスを得られるかをやってみます。

  • Point②:Cloud Runは、他のCloud Runサービスにリクエスト認証情報を添付するための独自の認証を構成する必要がないように、宛先Cloud Runサービスへの認証を自動的に行う

検証として、Service-Backend-a の Cloud Run サービスの設定としてセキュリティの認証について「認証が必要」に設定して、Service-Client-for-Backend のソースコード上のリクエストヘッダーを何もいじらずに Service-Backend-a でリクエストを受け付けられるかをやってみます。

手順① Mesh リソースの作成

Cloud Service Mesh を実際に触ってみるまで Mesh リソースについて知らなかったので、公式ドキュメントの概要を引用します。

Mesh リソースは、サービス メッシュのインスタンスを表します。このプロジェクトを使用して、プロジェクトに論理サービス メッシュを作成します。各 Mesh リソースには、プロジェクト内で一意の名前を付ける必要があります。Mesh リソースを作成した後に、その名前を変更することはできません。
公式ドキュメント - Cloud Service Mesh サービス ルーティング API の概要:Mesh リソース

下記でデプロイする Mesh リソースで定義された名前のサービスメッシュに参加すると、Cloud Service Mesh から構成情報を受け取れるようです。

terraform/main.tf
terraform/main.tf
...
resource "google_network_services_mesh" "test" {
  provider    = google-beta
  name        = "mesh-test"
}

手順② DNS レコードの作成

Point① に関わるところなのですが、メッシュクライアントが宛先サービスを呼び出す場合、リクエストで使用されるホスト名はDNSを通じて解決可能である必要があるため DNS 周りをセットします。

When a mesh client calls the destination service, the hostname used in the request must be resolvable through DNS. Any valid RFC 1918 IP address is acceptable because the sidecar captures all IP traffic to these IP address ranges and redirects it appropriately.
公式ドキュメント - Configure Cloud Service Mesh for Cloud Run:Set up Cloud DNS

下記で Cloud DNS のマネージドゾーンの作成および A レコードをセットします。

terraform/main.tf
terraform/main.tf
...
resource "google_dns_managed_zone" "mesh-zone" {
  name        = "mesh-zone"
  dns_name    = "cms.dev."

  visibility = "private"

  private_visibility_config {
    networks {
      network_url = "projects/${var.project_id}/global/networks/${var.vpc}"
    }
  }
}

resource "google_dns_record_set" "mesh-domain-record" {
  name = "*.cms.dev."
  type = "A"
  ttl  = 3600

  managed_zone = google_dns_managed_zone.mesh-zone.name

  rrdatas = ["10.0.0.1"]
}

手順③ 宛先となる Cloud Run サービス周りの作成

サービスメッシュを構築する上でのリソースの対応図を公式ドキュメントから引用します。

公式ドキュメント - Cloud Service Mesh サービス ルーティング API の概要:Route リソース

この後作成する Route リソースに紐づけるために Cloud Run サービスにトラフィックを転送するバックエンドサービスを作成します。何気なく使っているバックエンドサービスについても意外と実態を知らなかったので、公式ドキュメントから概要を引用します。

バックエンド サービスは、Cloud Load Balancing によるトラフィックの分散方法を定義します。バックエンド サービスの構成には、バックエンドへの接続に使用されるプロトコル、さまざまな配信とセッションの設定、ヘルスチェック、タイムアウトなどの値が含まれます。
...
ロードバランサ、Envoy プロキシ、プロキシレス gRPC クライアントは、バックエンド サービス リソースの構成情報を使用して次のことを行います。

  • トラフィックを正しいバックエンド、すなわちインスタンス グループまたはネットワーク エンドポイント グループ(NEG)に転送する。

公式ドキュメント - バックエンド サービスの概要

下記の Terraform のコードでは、バックエンドサービス・Network Endpoint Group・Cloud Run サービスを 1:1:1 で紐づける形としています。

また、Point② に関連する点ですが、宛先となる Cloud Run サービスに認証周りの設定を入れないとデフォルトで「認証が必要」の設定となります。(※ Terraform での話)

terraform/modules/service-backend/main.tf
terraform/modules/service-backend/main.tf
resource "google_cloud_run_v2_service" "backend" {
  name     = "service-${var.backend_name}"
  location = var.region
  ingress = "INGRESS_TRAFFIC_ALL"

  template {
    containers {
      image = "${var.artifact_registry_path}/cloudrun/${var.backend_name}:latest"
      ports {
        container_port = var.port
      }
      env {
        name = "SERVICE_BACKEND_B_URL"
        value = ""
      }
      env {
        name = "OTEL_EXPORTER_OTLP_ENDPOINT"
        value = ""
      }
    }
    vpc_access{
      network_interfaces {
        network = var.vpc
        subnetwork = var.subnet
      }
      egress = "ALL_TRAFFIC"
    }
    service_account = var.service_account
  }
}

resource "google_compute_region_network_endpoint_group" "backend-neg" {
  name                  = "service-${var.backend_name}-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast1"
  cloud_run {
    service = google_cloud_run_v2_service.backend.name
  }
}

resource "google_compute_backend_service" "backend-service" {
  provider              = google-beta
  name                  = "service-${var.backend_name}-backend-service"
  load_balancing_scheme = "INTERNAL_SELF_MANAGED"

  backend {
    group = google_compute_region_network_endpoint_group.backend-neg.id
  }
}

なので、こちらでデプロイした Service-Backend-a から正常にレスポンスが得られると Point②の特性の検証結果が得られそうです。

コンソール画面からの確認

作成後にコンソールの「Cloud Service Mesh(Traffic Director)」という項目ではバックエンドサービスの一覧および詳細を確認することができます。一覧についてはアップデートが間に合っていないのか一部表示がうまくいっていないような画面となっています。

手順④ HTTP Route リソースの作成

HTTP Route リソースは Route リソースの中の一種別です。こちらについてもなんとなくでしかやっていることがわからなかったので、公式ドキュメントから概要を引用します。

Route リソースは、サービスにルーティングを設定するために使用します。Route API リソースには 4 つの種類があります。トラフィックをバックエンドサービスに転送するためのプロトコルを定義します。

  • HTTPRoute
  • GRPCRoute
  • TCPRoute
  • TLSRoute

公式ドキュメント - Cloud Service Mesh サービス ルーティング API の概要:Route リソース

改めて、知ってみると役割は至ってシンプルですね。サービスメッシュを構築する際には、プロトコルにあった Route リソースを選択して、meshes プロパティに Mesh リソースの ID を渡す必要があります。

こうすることで Route リソースは Mesh リソースを参照できるようになり、Route リソースに紐づく宛先となる Cloud Run サービスがサービスメッシュに参加することができるようです。

terraform/modules/service-backend/main.tf
terraform/modules/service-backend/main.tf
...
resource "google_network_services_http_route" "backend_http_route" {
  provider               = google-beta
  name                   = "service-${var.backend_name}-http-route"

  hostnames              = ["service-${var.backend_name}.${var.domain_name}"]
  meshes = [
    var.mesh_id
  ]
  rules {
    action {
      destinations {
        service_name = google_compute_backend_service.backend-service.id
      }
    }
  }
}

ここで Point① に関連するところで、Route リソース単位でカスタム URL を設定することができます。上記のコードの hostnames = ["service-${var.backend_name}.${var.domain_name}"] で設定しています。service-backend-a.cms.dev となる想定です。

Route リソースによるトラフィック分割

今回は設定していませんが、Route リソースでトラフィック分割を設定できます。公式ドキュメントから図を引用しますが、Route リソースに複数のバックエンドサービスを紐づけてトラフィックの比率を設定することで制御が可能です。

公式ドキュメント - Cloud Service Mesh サービス ルーティング API の概要:Route リソース

ここまでで宛先となる Cloud Run サービスのセットアップは完了です。
最後にクライアントとなる Cloud Run サービスをデプロイします。

手順⑤ クライアントとなる Cloud Run サービスの作成

クライアントとなる Cloud Run サービスの Terraform での記述はまだサポートされていないように見えた(※)ので、gcloud コマンドで作成します。

クライアントとなる Cloud Run サービスの gcloud コマンドの特徴として、参照する(=参加する)サービスメッシュである Mesh リソースの ID を渡している点があります。また、Point① に関わる点として VPC への Network Egress アクセスができることが必要となります。

ここでは少し前に GA となった Direct VPC Egress を利用しています。

Terminal
gcloud beta run deploy service-client-for-backend \
--no-allow-unauthenticated \
--region={region} \
--image={service-client-for-backend image} \
--network={vpc name} \
--subnet={subnet name} \
--mesh={mesh id} \
--env-vars-file=/path/to/containers/service-backend-for-frontend/env_val.yaml

環境変数として下記のファイルを渡しています。アプリケーションで必要となるエンドポイント URL を環境変数で渡せるようにしています。今回は検証内容に落とし込んだ際の http://service-backend-a.cms.dev と記述して渡しています。

containers/service-backend-for-frontend/env_val.yaml
SERVICE_BACKEND_A_URL: "http://service-backend-a.{domain_name}"
SERVICE_BACKEND_B_URL: "http://service-backend-b.{domain_name}"
OTEL_EXPORTER_OTLP_ENDPOINT: ""
terraform/main.tf
terraform/main.tf
...
resource "google_cloud_run_v2_service" "clinent" {
  provider    = google-beta
  name        = "service-client-for-backend"
  location    = var.region
  ingress     = "INGRESS_TRAFFIC_ALL"
  launch_stage = "BETA"

  template {
    containers {
      image   = "${var.artifact_registry_path}/cloudrun/client-for-backend:latest"
      ports {
        container_port = 8080
      }
      env {
        name  = "SERVICE_BACKEND_A_URL"
        value = "http://service-backend-a.${var.domain_name}"
        # value = "http://service-backend-c.${var.domain_name}"
      }
      env {
        name  = "SERVICE_BACKEND_B_URL"
        value = "http://service-backend-b.${var.domain_name}"
      }
      env {
        name  = "OTEL_EXPORTER_OTLP_ENDPOINT"
        value = ""
      }
    }
    service_mesh {
      mesh = google_network_services_mesh.test.id
    }
    vpc_access{
      network_interfaces {
        network     = google_compute_network.mesh-vpc.name
        subnetwork  = google_compute_subnetwork.mesh-subnet.name
      }
      egress        = "ALL_TRAFFIC"
    }
    service_account = var.service_account
  }
}

ここまでで、検証のための準備が整いました。
リソース群を並べるとこんな感じです。

サービスメッシュの情報の受け渡し

mesh オプションを指定すると、Cloud Run サービスのログに下記のような情報が出力されます。cloud-run-mesh-proxy という名の Envoy プロキシコンテナが隣で立ち上がっているように見えます。

この Envoy プロキシコンテナは Cloud Service Mesh からサービスルーティング情報を受け取り、Mesh リソース名はどのサービスメッシュの情報を受け取るかに利用されているようです。情報受け取った Envoy プロキシコンテナが受けたリクエストをその情報を基に適切にルーティングしているとのことでした。

Envoy proxies running as sidecars receive their service routing configuration from Cloud Service Mesh. The Mesh name is the key that the sidecar proxy uses to request the configuration associated with the Mesh resource. Cloud Service Mesh provides the routing configuration to the proxy. The sidecar proxy then directs traffic to the correct backend service, relying on request parameters such as the hostname, headers, and others that are configured in the Route resources.
公式ドキュメント - Set up Envoy proxies with HTTP services

For Your Info

公式ドキュメントにはこんな記載もあるので、引用しておきます。

Creating a client Cloud Run service creates an Envoy sidecar whose resources relate to your QPS and total configuration size. Most of the time, CPU usage is less than 1 percent of a vCPU and memory usage is less than 50 MB.
公式ドキュメント - Create the client Cloud Run service

クライアントの役割を果たす Cloud Run サービスには、Envoyサイドカーが作成されるのですが、だいたい CPU使用率はvCPUの1%未満、メモリ使用率は50MB未満ですよと。

検証結果①

検証自体は単純で、curl コマンドを実行するだけです。Service-Client-for-Backend も「認証が必要」な設定としているので下記のコマンドで実行します。

Terminal
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/authors

このように「認証が必要」だとリクエストヘッダーにトークンを仕込む必要がありますが、今回は Service-Client-for-BackendService-Backend-a を叩きにいくときにはヘッダーには何も仕込んでいません。

service-backend-for-frontend/src/gateway.py
service-backend-for-frontend/src/gateway.py
...
SERVICE_BACKEND_A_URL = os.getenv("SERVICE_BACKEND_A_URL")
...
async def get_authors_service_backend_a():
    logger.info("REQUEST TO SERVICE BACKEND A")
    func_name = f"{__name__}.get_authors_service_backend_a"
    with tracer.start_as_current_span(func_name) as span:
        # スパンに属性を追加する
        span.set_attribute("function.name", func_name)

        headers = {}
        inject(headers)
        url = f"{SERVICE_BACKEND_A_URL}/authors"
        response = requests.get(
            url,
            headers=headers
        )
        return response.json()

結果として、無事にレスポンスを得ることができました。コードの変更なく、Cloud Run サービスに認証設定をすることが可能となる点は、セキュリティ面で非常に嬉しいなと感じました。

Terminal
$curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/authors
[{"id":1,"name":"夏目漱石"},{"id":2,"name":"泉鏡花"}]

検証② サービスメッシュ内の自動認証 - 失敗 -

シンプルなクライアントと宛先の Cloud Run サービスの構成でのカスタム URL と自動認証の検証は成功しました。次にサービスメッシュ内の自動認証について検証したいと思います。

  • Point①:Cloud Runサービスは、提供されているrun.app URLではなく、カスタマイズされたURLを使用して他のCloud Runサービスを呼び出すことが可能

検証として、Service-Client-for-Backend から Service-Backend-a を叩きにいく時のエンドポイント URL を http://service-backend-a.csm.dev とするのに加えて、 Service-Backend-a から Service-Backend-b を叩きにいく時のエンドポイント URL を http://service-backend-b.csm.dev としてレスポンスを得られるかをやってみます。

  • Point②:Cloud Runは、他のCloud Runサービスにリクエスト認証情報を添付するための独自の認証を構成する必要がないように、宛先Cloud Runサービスへの認証を自動的に行う

検証として、Service-Backend-a に加えて Service-Backend-b の Cloud Run サービスの設定としてセキュリティの認証について「認証が必要」に設定してクライアントのリクエストを何もいじらずにリクエストを受け付けられるかをやってみます。

手順⑥ 宛先となる Cloud Run サービス周り & HTTP Route リソースの作成

Service-Backend-b について作成します。下記で Service-Backend-a と同様のリソースを作成することができます。

terraform/main.tf
terraform/main.tf
...
module "service-backend-b" {
  source = "./modules/service-backend"

  region = var.region
  artifact_registry_path = var.artifact_registry_path
  vpc = var.vpc
  subnet = var.subnet
  service_account = var.service_account
  backend_name = "backend-b"
  port = 8082
  mesh_id = google_network_services_mesh.test.id
  domain_name = var.domain_name
}

Point②に関連する点として、HTTP Route リソースは前回同様に下記のようになっています。hostnames には var.backend_name=backend-b, var.domain_name=cms.dev となっているため上記で述べたように Service-backend-bhttp://service-backend-b.cms.dev でアクセス可能となる想定です。

terraform/modules/service-backend/main.tf
terraform/modules/service-backend/main.tf
...
resource "google_network_services_http_route" "backend_http_route" {
  provider               = google-beta
  name                   = "service-${var.backend_name}-http-route"

  hostnames              = ["service-${var.backend_name}.${var.domain_name}"]
  meshes = [
    var.mesh_id
  ]
  rules {
    action {
      destinations {
        service_name = google_compute_backend_service.backend-service.id
      }
    }
  }
}

リソース群を並べるとこんな感じです。

検証結果②

同様に Service-Client-for-Backend に下記のコマンドでリクエストします。

Terminal
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/a/b

Service-Backend-a では下記のように Service-Backend-b を叩きにいくようになっています。SERVICE_BACKEND_B_URL には、http://service-backend-b.cms.dev が渡されます。

service-backend-a/src/gateway.py
service-backend-a/src/gateway.py
...
SERVICE_BACKEND_B_URL = os.getenv("SERVICE_BACKEND_B_URL")

async def get_service_backend_to_b():
    logger.info("REQUEST TO SERVICE BACKEND B")
    func_name = f"{__name__}.get_service_backend_b"
    with tracer.start_as_current_span(__name__) as span:
        span.set_attribute("function.name", func_name)

        url = f"{SERVICE_BACKEND_B_URL}/micro/b"
        headers = {}
        inject(headers)
        response = requests.post(
            url,
            headers=headers
        )
        return response.json()

結果は、Internal Server Error となります。バックエンドを雑に作ったため、エラーハンドリングがテキトーな部分はお許しください。

Terminal
$curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/a/b
Internal Server Error

エラーログとしては、下記のように宛先が見つからないための Connection Timeout となっていました。

エラーログ
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='service-backend-b.cms.dev', port=80): Max retries exceeded with url: /micro/b (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x3e7df116bc50>, 'Connection to service-backend-b.cms.dev timed out. (connect timeout=None)'))

下記の変更により期待する結果を得られました。このことから Service-Client-for-Backend から Service-Backend-a を経由して Service-Backend-b への連携は確認できました

  • Service-Backend-aSERVICE_BACKEND_B_URLService-Backend-b の従来の払い出された URL に変更
  • Service-Backend-b のセキュリティを「未認証の呼び出しを許可」に変更

さらに Service-Client-for-Backend から Service-Backend-b への直通はどうでしょうか?なんとこちらは期待通りの結果を得ることができました

Terminal
$curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/books
[{"id":1,"name":"吾輩は猫である","author_id":1},{"id":2,"name":"高野聖","author_id":2},{"id":3,"name":"micro連携3","author_id":1}]

よって、クライアントとなる Cloud Run サービスを経由した宛先となる各 Cloud Run サービスへのリクエストは正常にレスポンスを得ることができました。(下図、Point③)

検証③ サービスメッシュ内の自動認証 - 成功 -

では最後にもう一度、複数の Cloud Run サービスを経由するサービスメッシュ内の連携の検証です。改めて、公式ドキュメントの Cloud Service Mesh を用いた Cloud Run サービス間の呼び出しに関する記述を引用します。

Cloud Run uses the Cloud Service Mesh service routing APIs. These APIs let a Cloud Run service call other Cloud Run services using a customized URL rather than the provided run.app URL. In addition, Cloud Run automatically authenticates to the destination Cloud Run service so that you don't have to configure your own authentication to attach request credentials to other Cloud Run services.
公式ドキュメント - Configure Cloud Service Mesh for Cloud Run

やはりカスタム URL や自動認証といったサービスメッシュの機能を利用するためには、リクエストを投げる側はクライアントとなる必要がありそうですService-Backend-c として、クライアントオプションをつけた Cloud Run サービスをデプロイします。こちらで改めて、Point①② を検証します。

手順⑦ クライアントと宛先となる Cloud Run サービス周り & HTTP Route リソースの作成

例によって、クライアントは gcloud コマンドでデプロイします。イメージは Service-Backend-a と同様のものを使います。つまり、コードは変更せずに Cloud Run サービスのデプロイオプションの変更と Terraform を一部変更によって実現します。

Terminal
gcloud beta run deploy service-backend-c \
--no-allow-unauthenticated \
--region={region} \
--image={service-backend-a image} \
--network={vpc name} \
--subnet={subnet name} \
--mesh={mesh id} \
--port=8081
--env-vars-file=/path/to/containers/service-backend-c/env_val.yaml
containers/service-backend-c/env_val.yaml
SERVICE_BACKEND_B_URL: "http://service-backend-b.{domain_name}"
OTEL_EXPORTER_OTLP_ENDPOINT: ""

こちらで Service-Backend-c の Cloud Run サービスのデプロイが完了です。次に Route リソースからバックエンドサービスまでを作成します。Service-Backend-c 用の module を利用します。

terraform/main.tf
terraform/main.tf
...
module "service-backend-c" {
  source = "./modules/service-backend-c"

  project_id = var.project_id
  backend_name = "backend-c"
  mesh_id = google_network_services_mesh.test.id
  domain_name = var.domain_name
}

これで準備完了です。
リソースを並べるとこんな感じです。

検証結果③

Service-Backend-c に対して Service-Client-for-Backend から http://service-backend-c.cms.dev でアクセスできる想定なので、Service-Client-for-Backend の環境変数 SERVICE_BACKEND_A_URLhttp://service-backend-c.cms.dev に変更します。

最終的に、先ほど Internal Server Error を起こした https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/a/b に対するリクエストは正常にレスポンスを受け取ることがきました。

Terminal
$curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://service-client-for-backend-{project id}.asia-northeast1.run.app/micro/a/b
[{"id":1,"name":"吾輩は猫である","author_id":1},{"id":2,"name":"高野聖","author_id":2},{"id":3,"name":"micro連携3","author_id":1}]

クライアントと宛先の役割を果たす Service-Backend-c を経由することで Service-Client-for-Backend から Service-Backend-b への連携を確認できたとともに、Point①② についても実現できることを確認できました!

以上で検証終了です!!

ハマったところ:Terraform によるデプロイ失敗

Terraform の Provider である Google Beta Version 6.2.0 には Cloud Run サービスのパラメーターに service_mesh の項目が追加されていたため、試してみたところ Cloud Run サービス自体は作成されるのですが上物のアプリケーションで落ちるという事象が発生しました。

Cloud Logging を見てみると下図のように Envoy not ready が表示されており、最後には Default STARTUP HTTP probe failed 200 times consecutively for container "cloud-run-mesh-proxy" on path "/healthz". The instance was not started. と表示されました。

Terraform で作成した際には、この Envoy がうまく立ち上がらず、結果として本体のアプリケーションも立ち上がらない形となったように見えます。

一方で、同様の設定値で gcloud コマンドから Cloud Run サービスを作成すると、下図のようにある程度時間が経つと Envoy が立ち上がっているように見えます。最後には Default STARTUP HTTP probe succeeded after 20 attempts for container "cloud-run-mesh-proxy" on path "/healthz". となり、正常な挙動となりました。

Terraform での作成が起因しているのかは、うまく原因の切り分けはできませんでしたが、備忘録的に残しておきます。

さいごに

Cloud Service Mesh の基本的な機能であるカスタム URL によるアクセスと自動認証について、Cloud Run サービスに適用する検証を実施しました。

最初は「クライアント」と「宛先」の関係性をあまり意識していませんでしたが、想定している構成を実現するためには、特にリクエストを投げる側には「クライアント」の設定を入れることが必須であることがわかりました。

やはりアプリケーションコードの変更なく、メリットを享受できる点がいいなと思いました。

https://twitter.com/pHaya72

Discussion

whatasodawhatasoda

Terraform で作成した際には、この Envoy がうまく立ち上がらず、結果として本体のアプリケーションも立ち上がらない形となったように見えます。

こちらと同じ事象が自分の手元でも再現したのですが、 gcloud コマンドによるデプロイでは解消しませんでした。代わりに、サービスを削除してまっさらな状態にしてから作成したところ自分の手元では terraform でも gcloud コマンドでも成功することが確認できました!明確に何が原因だったのかまでは自分でも判っていませんが、参考まで…!

Tomonori HayashiTomonori Hayashi

貴重な情報共有ありがとうございます。Terraform 起因ではないのかもですね。。(たしかに裏で API 叩いているだけだと思うと、、)

こちらの情報も本編に追記させていただきます!

abekohabekoh

こちら、自分の場合はサービスアカウントを個別ではなくデフォルトのもので設定し直すことで成功しました。
権限に関連するところが原因かもしれません。具体的にどの権限が効いているのかまでは調査しておらずですが…

追記:
https://zenn.dev/google_cloud_jp/articles/14956782beaf97
こちらの記事にあるように、 roles/trafficdirector.clientロールを付与することで解決しました!