Application Load Balancer における gRPC バランシングを試してみた

2022-12-12
デベロッパーのためのクラウド活用方法

Author : 邵 正

この記事は AWS for Games Advent Calendar 2022 の 12 月 12 日の記事になります。

皆さんこんにちは、Game Solutions Architect の邵 (@axot) です。この記事では、昨今ゲーム業界でスタンダードになってきているマルチプレイヤーゲーム、そしてその裏側として注目を集めている gRPC について Application Load Balancer(ALB) を利用するときのバランシング状況を検証しました。

記事の中ではマルチプレイヤーゲーム以外でも役に立つ検証の内容に合わせて、実際使った golang のテストコードや、楽にテストを実施するためのオススメ構成も紹介します、最後まで是非ご覧ください !


はじめに

gRPC のロードバランシング方法 は、クライアントサイトや Proxy 方式で組めることができます。今回は ALB (Proxy 方式) を利用して、実際バランシングの動きを検証してみました。


テストケース

1. gRPC Channel ごとに送信するケース (*golang の ClientConn は gRPC Channel と同じ意味です)

package main

import (
    "context"
    "flag"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/insecure"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    defaultContent = "test-1"
)

var (
    addr = flag.String("addr", "localhost:50051", "The address to connect to")
    cont = flag.String("conntent", defaultContent, "Content to greet")
    tls  = flag.Bool("tls", true, "Enable tls")
)

func main() {
    var creds credentials.TransportCredentials

    flag.Parse()

    if *tls {
        creds = credentials.NewClientTLSFromCert(nil, "")

    } else {
        creds = insecure.NewCredentials()
    }

    for j := 0; j < 10000; j++ {
        // リクエストごと gRPC Channel を作成
        conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds))

        if err != nil {
            log.Fatalf("did not connect: %v", err)
        }

        c := pb.NewGreeterClient(conn)

        // Response は今回のテストで表示する必要ないため、省略しています
        _, err = c.SayHello(context.TODO(), &pb.HelloRequest{Name: *cont})

        if err != nil {
            log.Fatalf("could not greet: %v", err)
        }

        conn.Close()
    }
    log.Printf("ok")
}

2. 一つの gRPC Channel を共有して利用するケース

package main

import (
    "context"
    "flag"
    "log"
    "sync"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/insecure"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    defaultContent = "test-2"
    goroutines     = 100
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    cont = flag.String("conntent", defaultContent, "Content to greet")
    tls  = flag.Bool("tls", true, "Enable tls")
)

func main() {
    var creds credentials.TransportCredentials

    flag.Parse()

    if *tls {
        creds = credentials.NewClientTLSFromCert(nil, "")

    } else {
        creds = insecure.NewCredentials()
    }

    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    wg := &sync.WaitGroup{}

    // 100 goroutines で同時にリクエスト送信
    for i := 0; i < goroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            for j := 0; j < 100; j++ {
                ctx := context.Background()
                c := pb.NewGreeterClient(conn)

                _, err := c.SayHello(ctx, &pb.HelloRequest{Name: *cont})
                if err != nil {
                    log.Fatalf("could not greet: %v", err)
                }
            }
        }()
    }

    wg.Wait()
    log.Printf("ok")
}

アーキテクチャ


テストの準備

gRPC クライアントの整備

今回のテストではクライアント一台で十分なので、Amazon EC2 および Amazon Linux 2 を利用しています。

# install golang
$ sudo yum update -y
$ sudo yum install -y golang

# check golang version
$ go version
go version go1.18.6 linux/amd64

# download sample code from gRPC
$ git clone https://github.com/grpc/grpc-go.git
$ cd grpc-go; git checkout d5dee5fdbdeb52f6ea10b37b2cc7ce37814642d7

# 今回は gRPC 公式の hellworld ベースで作ります
$ cd examples/helloworld/greeter_client

# テストケース 1 のコードを貼り付けます
$ vim test-1.go
$ go build -o test-1 ./test-1.go

# テストケース 2 のコードを貼り付けます
$ vim test-2.go
$ go build -o test-2 ./test-2.go

gRPC サーバーのベース AMI の構築

さて、クライアントの設定が終わりました。サーバーの構築に移ります。サーバーサイトを作るのに必要な AMI は先ほど同じクライアントのインスタンスを利用すれば、すぐできて楽です。

$ pwd
/home/ec2-user/grpc-go/examples/helloworld/greeter_client

$ cd ../greeter_server/

# サーバーの方はそのまま利用できます
$ go build -o server ./main.go

# 軽く動作確認

## これまでは test-1、test-2、server この3つのファイルが生成
$ cd ../; ls -al greeter**
greeter_client:
total 25252
drwxrwxr-x 2 ec2-user ec2-user       83 Oct 25 06:57 .
drwxrwxr-x 5 ec2-user ec2-user       68 Oct 25 06:27 ..
-rw-rw-r-- 1 ec2-user ec2-user     1605 Oct 25 06:27 main.go
-rwxrwxr-x 1 ec2-user ec2-user 12920610 Oct 25 06:57 test-1
-rw-rw-r-- 1 ec2-user ec2-user     1168 Oct 25 06:57 test-1.go
-rwxrwxr-x 1 ec2-user ec2-user 12921204 Oct 25 06:57 test-2
-rw-rw-r-- 1 ec2-user ec2-user     1066 Oct 25 06:57 test-2.go

greeter_server:
total 11712
drwxrwxr-x 2 ec2-user ec2-user       35 Oct 25 06:42 .
drwxrwxr-x 5 ec2-user ec2-user       68 Oct 25 06:27 ..
-rw-rw-r-- 1 ec2-user ec2-user     1580 Oct 25 06:27 main.go
-rwxrwxr-x 1 ec2-user ec2-user 11988423 Oct 25 06:42 server

## 新しいターミナルで、サーバーを起動
$ ./greeter_server/server

## test-1, local にある server に接続してテスト
$ ./greeter_client/test-1 -tls=false
2022/10/25 07:24:30 ok

## test-2
$ ./greeter_client/test-2 -tls=false
2022/10/25 07:24:35 ok

Auto Scaling Group (ASG) & ALB の設定

これで、クライアントもサーバーも動作確認しました。サーバー側を AMI 化し、ASG を利用すれば、同じ gRPC サーバーを複数台作り出すことが簡単にできます。今回はこちらの手法で 3 台を作りました。

手順としましては、1) 起動テンプレートの作成、2) ターゲットグループの作成、 3) ASG の作成、4) ALB の作成 となります。では、それぞれのポイントをご紹介します。

(手順 1) 起動テンプレートの作成

  1. gRPC クライアントのインスタンスから生成された AMI を利用
  2. ネットワーク設定」の中で、「セキュリティグループ」で ALB からアクセスできるように、インバウンド TCP:50051 の通信を許可
  3. 高度な詳細」の中で、最後の「ユーザーデータ」に gRPC サーバー起動するコマンドを入れることで、インスタンスが起動するときに自動的に実行してくれます。この手法を使えば、golang のインストールだったり、必要なファイルを入れるなど高度なこともできます。以下のユーザーデータは今回使っていました。
#!/bin/bash

cd /home/ec2-user/grpc-go/examples/helloworld/greeter_server

./server 2> /tmp/grpc_server.log

echo all is done >> /tmp/grpc_server.log

(手順 2) ターゲットグループの作成

ALB から先ほどの ASG を利用するため、ターゲットグループの作成を行います。

  1. 基本的な設定
    • ターゲットタイプの選択 : インスタンス
    • プロトコル : HTTP
    • ポート : 50051
    • プロトコルバージョン : gRPC
  2. ヘルスチェック
    • ヘルスチェックプロトコル : HTTP
    • ヘルスチェックパス : /
    • ヘルスチェックの詳細設定 (インスタンスのサービスインを早めるために微調整)
      • 正常のしきい値 : 2
      • 間隔 : 6
      • ターゲットの登録 : 何も登録しなくて大丈夫です

(手順 3) ASG の作成

  1. 起動テンプレート : 手順 1 で生成されたものを利用
  2. ロードバランシング : 手順 2 で作成したターゲットグループ
  3. 希望する容量、最大容量 : 3

問題なく設定ができたら、こちらのように 3 台の gRPC サーバーが起動されます。

クリックすると拡大します

(手順 4) ALB の作成

  1. スキーム : 内部
  2. セキュリティグループ : gRPC クライアントから通信できるように TCP:50051 を開けます
  3. リスナーとルーティング
    • プロトコル : HTTPS
    • ポート : 50051
    • 転送先 : 手順 2 で作成したターゲットグループ
  4. セキュアリスナーの設定
    • デフォルトの SSL/TLS 証明書 : HTTPS ご利用するため、適切な証明書を設定しましょう

このように、ALB を設定し、しばらくなると 3 台の gRPC サーバーが healthy になりましたら、準備が完了です。

クリックすると拡大します

テストしてみましょう

# at gRPCクライアントサーバー
$ cd grpc-go/examples/helloworld/greeter_client

# test-1 を実行
$ ./test-1 -addr=ALB_FQDN:50051
2022/10/26 06:18:12 ok

# test-2 を実行
$ ./test-2 -addr=ALB_FQDN:50051
2022/10/26 07:04:18 ok

# test-1 及び test-2 の結果を集約するため、
# まず grpc サーバーの ip を取得
$ IPS=$(aws ec2 describe-instances \
    --filters "Name=instance-state-name,Values=running" \
    --query 'Reservations[].Instances[].[PrivateIpAddress, InstanceId, Tags[?Key==`Name`].Value|[0]]' \
    --output text | grep -w grpc-server | awk '{print $1}')
$ echo $IPS
10.0.12.83 10.0.11.76 10.0.10.209

# 3台の grpc サーバーからログをローカルに転送
$ for ip in $IPS; do
    echo -- $ip --
    scp $ip:/tmp/grpc_server.log /tmp/${ip}_grpc_sever.log;
  done

# test-1 の結果を集計
$ grep test-1 /tmp/*_grpc_sever.log | awk -F_ '{print $1}' | tr -d '/tmp/' | sort | uniq -c
   3330 10.0.10.209
   3336 10.0.11.76
   3334 10.0.12.83

# test-2 の結果を集計
$ grep test-2 /tmp/*_grpc_sever.log | awk -F_ '{print $1}' | tr -d '/tmp/' | sort | uniq -c
   3333 10.0.10.209
   3334 10.0.11.76
   3333 10.0.12.83

# ログの中身はこのようになっています
$ head -3 /tmp/*_grpc_sever.log
==> /tmp/10.0.10.209_grpc_sever.log <==
2022/10/26 05:09:21 server listening at [::]:50051
2022/10/26 06:15:42 Received: test-1
2022/10/26 06:15:42 Received: test-1

==> /tmp/10.0.11.76_grpc_sever.log <==
2022/10/26 05:09:14 server listening at [::]:50051
2022/10/26 06:15:41 Received: test-1
2022/10/26 06:15:42 Received: test-1

==> /tmp/10.0.12.83_grpc_sever.log <==
2022/10/26 05:09:13 server listening at [::]:50051
2022/10/26 06:15:42 Received: test-1
2022/10/26 06:15:42 Received: test-1

$ tail -n3 /tmp/*_grpc_sever.log
==> /tmp/10.0.10.209_grpc_sever.log <==
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2

==> /tmp/10.0.11.76_grpc_sever.log <==
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2

==> /tmp/10.0.12.83_grpc_sever.log <==
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2
2022/10/26 07:04:18 Received: test-2

結果

以上の結果を表にまとめてみました。ご覧の通り Channel ごと接続 (test-1)、Channel 共有の接続 (test-2) ともうまくバランシングできました !!

接続方法 Server 1 の受信回数 Server 2 の受信回数 Server 3 の受信回数
Channel ごと接続 3330 3336 3334
Channel 共有の接続 3333 3334 3333
Channel ごと接続
受信数の割合
33.30% 33.36% 33.34%
Channel 共有の接続
受信数の割合
33.33% 33.34% 33.33%

まとめ

今回は gRPC における二つの接続手法を使って実際 ALB を透過するときのバランシグ状況を確認できました。また、テストの中で使っていた ASG は非常に便利なので、さまざまなテストでもご利用いただけます。ぜひ触ってみてくださいー。


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます


筆者プロフィール

邵 正
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト

Linux カーネル、コンテナ、データベースにフォーカスしています。お客様と協力して様々なクラウドインフラストラクチャのチャレンジを一緒に解決することで、よりヘルシーな設計・運用ができるように、サポートしています。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する