0
0

ECS Service Connectの負荷分散をgrpc-goで確かめる

Last updated at Posted at 2024-04-12

Amazon ECS Service Connectを使用して、gRPCのリクエストが各ECSタスクに振り分けられるかを確かめる
通常のHTTP APIでも同じ挙動になる

使用する言語、ライブラリ

  • Go 1.22
  • google.golang.org/grpc v1.63.0

Service Connectなし

まずはService Connectを使用せず、gRPCのリクエストが1つのサーバー側ECSタスクにリクエストし続けることを確かめる

検証するaws構成
image.png
クライアント側はAWS Cloud Mapを使用してサーバー側ECSタスクを名前解決する

1. gRPCサーバーをECSサービスとして起動する

サーバーは自身のECSタスクのprivate IPアドレスを返す
特にclient side load balancingの設定等はしていない
ECSタスクは2つ起動する

Go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"

    "google.golang.org/grpc"

    pb "google.golang.org/grpc/examples/features/proto/echo"
)

type ecServer struct {
    pb.UnimplementedEchoServer
}

func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
    metadataURL := os.Getenv("ECS_CONTAINER_METADATA_URI_V4")
    if metadataURL == "" {
        log.Println("ECS_CONTAINER_METADATA_URI_V4 environment variable is not set")
        return &pb.EchoResponse{}, nil
    }

    resp, err := http.Get(metadataURL + "/task")
    if err != nil {
        log.Printf("failed to get metadata: %v\n", err)
        return &pb.EchoResponse{}, nil
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Printf("failed to read response body: %v\n", err)
        return &pb.EchoResponse{}, nil
    }

    var metadata struct {
        Containers []struct {
            Name     string `json:"name"`
            Networks []struct {
                IPv4Addresses []string `json:"IPv4Addresses"`
            } `json:"networks"`
        } `json:"containers"`
    }

    if err := json.Unmarshal(body, &metadata); err != nil {
        log.Printf("failed to parse JSON: %v\n", err)
        return &pb.EchoResponse{}, nil
    }

    myIP := ""
    for _, container := range metadata.Containers {
        if container.Name == "server" && len(container.Networks) > 0 && len(container.Networks[0].IPv4Addresses) > 0 {
            myIP = container.Networks[0].IPv4Addresses[0]
            break
        }
    }

    return &pb.EchoResponse{Message: fmt.Sprintf("%s (at %s)", req.Message, myIP)}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterEchoServer(s, &ecServer{})
    log.Printf("serving on :50051\n")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

起動した2つのECSタスクのprivate IPアドレスは以下になった

  • 10.0.1.174
  • 10.0.17.182

なおECSタスクのコンテナヘルスチェックは設定していない

2. gRPCクライアントをECSサービスとして起動する

クライアントはgRPCサーバーに一定間隔でリクエストを送り、レスポンスを標準出力する

Go
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    ecpb "google.golang.org/grpc/examples/features/proto/echo"
)

func main() {
    conn, err := grpc.NewClient(
        os.Getenv("GRPC_TARGET"),
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    hwc := ecpb.NewEchoClient(conn)
    for i := 0; i < 100; i++ {
        r, err := hwc.UnaryEcho(context.Background(), &ecpb.EchoRequest{Message: "hello"})
        if err != nil {
            log.Fatalf("could not invoke: %v", err)
        }
        fmt.Println(r.Message)
        time.Sleep(5 * time.Second)
    }
}

ECSタスク定義のリクエスト先環境変数 GRPC_TARGETにはCloud MapのDNSとサーバー側ポート(ここでは50051)を設定する
e.g.) server.grpc.local:50051

3. クライアント側ECSタスクのログを確認する

1つのサーバー側ECSタスクにリクエストし続けることが確認できる
image.png

Service Connectあり

次にService Connectを使用し、gRPCのリクエストが2つのサーバー側ECSタスクに振り分けられてリクエストされるかを確かめる

検証するaws構成
image.png
クライアント側はService Connect経由でサーバーにリクエストする

1. サーバー側ECSサービスにService Connectを設定しサービス更新する

  1. 以下設定する
    image.png
  2. サービス検出をオフにする

起動した2つのECSタスクのprivate IPアドレスは以下になった

  • 10.0.2.102
  • 10.0.26.24

2. クライアント側ECSタスク定義のリクエスト先を更新する

環境変数 GRPC_TARGETのDNSにService ConnectのDNS(ここではserver-50051-tcp.grpc.local)を設定し新しくリビジョン作成する

3. クライアント側ECSサービスにService Connectと設定しサービス更新する

またタスク定義の新しいリビジョンを設定する
image.png

4. クライアント側ECSタスクのログを確認する

2つのサーバー側ECSタスクに振り分けられてリクエストしていることが確認できる
image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0