gRPC の使用

このページでは、デベロッパーが gRPC を使用し、Cloud Run サービスを他のサービスと接続して、たとえば、内部のマイクロサービス間のシンプルでパフォーマンスの高い通信を行う方法について詳しく説明します。Cloud Run では、すべての gRPC タイプ(ストリーミングまたは単項)を使用できます。

考えられるユースケースには、次のようなものがあります。

  • 内部マイクロサービス間の通信。
  • 高負荷のデータ(gRPC では、プロトコル バッファを使用します。これは REST 呼び出しの最大 7 倍の速度です)。
  • 単純なサービス定義だけが必要で、完全なクライアント ライブラリは作成しないケース。
  • gRPC サーバーで gRPC ストリーミングを使用して、より応答性の高いアプリケーションと API を構築する。

サービスを gRPC と統合するには:

  • ストリーミング gRPC を使用している場合に HTTP/2 を使用するようにサービスを構成します。HTTP/2 は、gRPC ストリーミングの転送メソッドです。
  • リクエスト メッセージとレスポンスを proto ファイルに定義し、コンパイルします。
  • リクエストを処理してレスポンスを返す gRPC サーバーを作成します。このサーバーは PORT 環境変数をリッスンする必要があります。
  • リクエストを送信し、gRPC サーバーからのレスポンスを処理するクライアントを作成します。
  • 必要に応じて、認証を追加します。
  • サービスをビルドしてデプロイします。

HTTP/2 を使用するようにサービスを構成する

Cloud Run で gRPC を使用する場合は、HTTP/2 を使用するようにサービスを構成することをおすすめします。シンプルな一部の gRPC 機能は HTTP/2 を使用することなく動作しますが、ストリーミングやメタデータなど、gRPC 機能の多くには HTTP/2 が必要です。

proto ファイルでメッセージを定義してコンパイルする

proto 定義に追加するその他のものや Cloud Run 固有のものはありません。gRPC の他の場合での使用と同様に、サービス定義とデータのシリアル化には gRPC プロトコル バッファを使用します。

gRPC クライアントを作成する

gRPC を使用するクライアントに追加するその他のものや Cloud Run 固有のものはありません。クライアント コードのサービス定義と、言語別の gRPC チュートリアルで提示されるサンプル クライアントの使用については、gRPC ドキュメントを参考にしてください。

自動スケーリングとロード バランシング

Cloud Run は、Google が管理するロードバランサを使用して、クライアントと Cloud Run インスタンス間の接続を分離します。gRPC では、自動スケーリングは次のように動作します。

  • クライアントからの gRPC 接続はエッジ ロードバランサで終了します。KeepAlive 設定を調整しても、Cloud Run インスタンスではなく、ロードバランサへの接続にのみ影響します。クライアントは、インスタンスの削除を認識しません。
  • スケールイン中、ロードバランサは、シャットダウン時にバックエンド インスタンスに GOAWAY メッセージを送信して接続を閉じます。
  • スケールアウト中に、ロードバランサはバックエンド インスタンスへの新しい接続を作成します。これらのオペレーションはすべてクライアントに対して透過的です。
  • 自動スケーリング中に、多くのインスタンスが起動し、クライアントとプロキシ ロードバランサ間の単一の接続に多重化される場合があります。
  • 同時実行は、メッセージのインスタンスあたりの最大同時リクエスト数によって決定されます。ストリーミングでは、各ストリームが最大同時リクエスト数に対して 1 回カウントされます。

Cloud Run サービスで gRPC リクエストをリッスンする

Cloud Run で実行される gRPC サーバーの唯一の特別な要件は、次のコードに示されているように PORT 環境変数で指定されたポートをリッスンすることです。

Go

func main() {
	log.Printf("grpc-ping: starting server...")

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}

	listener, err := net.Listen("tcp", ":"+port)
	if err != nil {
		log.Fatalf("net.Listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterPingServiceServer(grpcServer, &pingService{})
	if err = grpcServer.Serve(listener); err != nil {
		log.Fatal(err)
	}
}

サービスへの gRPC 接続を開く

gRPC メッセージを送信できるようにサービスへの gRPC 接続を開くには、ホストドメイン(Cloud Run サービスの URL、またはサービスにマッピングされたカスタム ドメイン)を、ポート 443(gRPC で使用されると予想されるポート)とともに指定する必要があります。

Go


import (
	"crypto/tls"
	"crypto/x509"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// NewConn creates a new gRPC connection.
// host should be of the form domain:port, e.g., example.com:443
func NewConn(host string, insecure bool) (*grpc.ClientConn, error) {
	var opts []grpc.DialOption
	if host != "" {
		opts = append(opts, grpc.WithAuthority(host))
	}

	if insecure {
		opts = append(opts, grpc.WithInsecure())
	} else {
		// Note: On the Windows platform, use of x509.SystemCertPool() requires
		// go version 1.18 or higher.
		systemRoots, err := x509.SystemCertPool()
		if err != nil {
			return nil, err
		}
		cred := credentials.NewTLS(&tls.Config{
			RootCAs: systemRoots,
		})
		opts = append(opts, grpc.WithTransportCredentials(cred))
	}

	return grpc.Dial(host, opts...)
}

認証なしで gRPC リクエストを送信する

次のサンプルは、前述のように構成された gRPC 接続を使用して、認証なしでリクエストを送信する方法を示しています。

Go


import (
	"context"
	"time"

	pb "github.com/GoogleCloudPlatform/golang-samples/run/grpc-ping/pkg/api/v1"
	"google.golang.org/grpc"
)

// pingRequest sends a new gRPC ping request to the server configured in the connection.
func pingRequest(conn *grpc.ClientConn, p *pb.Request) (*pb.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	client := pb.NewPingServiceClient(conn)
	return client.Send(ctx, p)
}

認証付き gRPC リクエストの送信

次のサンプルは、呼び出し側のサービスが受信側のサービスに対する呼び出し元権限を持っている場合に、サービス間の認証を使用する方法を示しています。このコードは、適切な ID トークンを持つ承認ヘッダーを作成します(必須)。必要な権限と承認ヘッダーの詳細については、サービス間認証をご覧ください。

Go


import (
	"context"
	"fmt"
	"time"

	"google.golang.org/api/idtoken"
	"google.golang.org/grpc"
	grpcMetadata "google.golang.org/grpc/metadata"

	pb "github.com/GoogleCloudPlatform/golang-samples/run/grpc-ping/pkg/api/v1"
)

// pingRequestWithAuth mints a new Identity Token for each request.
// This token has a 1 hour expiry and should be reused.
// audience must be the auto-assigned URL of a Cloud Run service or HTTP Cloud Function without port number.
func pingRequestWithAuth(conn *grpc.ClientConn, p *pb.Request, audience string) (*pb.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Create an identity token.
	// With a global TokenSource tokens would be reused and auto-refreshed at need.
	// A given TokenSource is specific to the audience.
	tokenSource, err := idtoken.NewTokenSource(ctx, audience)
	if err != nil {
		return nil, fmt.Errorf("idtoken.NewTokenSource: %w", err)
	}
	token, err := tokenSource.Token()
	if err != nil {
		return nil, fmt.Errorf("TokenSource.Token: %w", err)
	}

	// Add token to gRPC Request.
	ctx = grpcMetadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token.AccessToken)

	// Send the request.
	client := pb.NewPingServiceClient(conn)
	return client.Send(ctx, p)
}

gRPC ストリーミングのサンプルコード

サンプルコードについては、お使いの言語の gRPC 基本チュートリアルの RouteGuide の実装をご覧ください。たとえば、Go を使用する場合は、RouteGuide の実装をご覧ください。