とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

Go言語でgRPCを使ってHello Worldしてみよう

Go言語でgRPCをやることになったので、勉強を兼ねてHello Worldレベルの簡単なものを作ってみます。

gRPCとは

リモートプロシージャコール(RPC)用のフレームワークで、Googleによって次世代版として開発されたものです。 gRPCのgはGoogleのことを指していたという説もあるようですが、現在では何も意味していないそうです。

grpc.io

プロシージャコールとは

gRPCの前にRPCやプロシージャコール(PC)というものがありますが、まずはプロシージャコールについて説明します。 プロシージャコールと聞くと、何か難しいことをしているように聞こえますが、簡単に言ってしまうと以下のプログラムような関数を呼び出すことです。

package main

import "fmt"

func add(a int, b int) int {
    return a + b
}

func main() {
    result := add(1, 2)
    fmt.Println(result) // 3
}

これをリモートなサーバーにある関数を呼び出す仕組みのことをリモートプロシージャコール(RPC)と言いい、Googleによって開発されたものがgRPCです。

Hello World

今回はGo言語の開発環境が準備できていることを前提に話を進めます。執筆時点のGo言語のバージョンは1.22.4です。

プロジェクトの準備

お決まりのコマンドを実行してプロジェクトを作成します。

go mod init main

go.modファイルが作成されます。

.
└── go.mod

ツールのインストール

protoc

gRPCを使うためには、プロトコルバッファをコンパイルするためのツールが必要です。

インストールする方法はいくつかありますが、今回はHomebrewを使ってインストールします。

brew install protobuf

インストールが完了したら、以下のコマンドでバージョンを確認します。

protoc --version

protoc-gen-go

Go言語のプロトコルバッファをコンパイルするためのプラグインです。

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28

インストールが完了したら、以下のコマンドでバージョンを確認します。

protoc-gen-go --version

protoc-gen-go-grpc

Go言語のgRPCを使うためのプラグインです。

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

インストールが完了したら、以下のコマンドでバージョンを確認します。

protoc-gen-go-grpc --version

protoc-gen-goprotoc-gen-go-grpcのバージョンが表示されればインストール完了です。

最後に、protoc-gen-goprotoc-gen-go-grpcのパスを環境変数に追加します。

export PATH="$PATH:$(go env GOPATH)/bin"

これで必要なツールのインストールが完了しました。

プロトコルバッファの定義

gRPCを使うためには、プロトコルバッファを定義する必要があります。

protoディレクトリを作成し、hello.protoというファイルを作成します。

mkdir proto

ファイルを作成したら次の内容を書いて保存します。

syntax = "proto3";

package Hello;

option go_package = "./go_protocol_buffer";

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

service HelloService {
    rpc SayHello (HelloRequest) returns (HelloResponse);
}

ファイルツリーでみるとこんか感じです。

.
├── go.mod
└── proto
    └── hello.proto

プロトコルバッファのコンパイル

プロトコルバッファをコンパイルします。

protoc -I. --go_out=. --go-grpc_out=. proto/*.proto

コンパイルが成功すると、go_protocol_bufferディレクトリが作成されます。

.
├── go.mod
├── go_protocol_buffer
│   ├── hello.pb.go
│   └── hello_grpc.pb.go
└── proto
    └── hello.proto

ここまでで、gRPCの準備が完了しました。

サーバーの実装

コードを書く前に必要なパッケージをインストールします。

go get google.golang.org/grpc

パッケージのインストールができたらserver/main.goを作成します。

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "main/go_protocol_buffer"
)

type server struct {
    pb.UnimplementedHelloServiceServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloResponse{Message: "Hello " + in.GetName()}, nil
}

func main() {
    listener, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
        log.Fatalf("Error: %v", err)
    }

    grpcServer := grpc.NewServer()

    pb.RegisterHelloServiceServer(grpcServer, &server{})

    fmt.Println("Server is running on port: 8080")

    if err := grpcServer.Serve(listener); err != nil {
        log.Fatalf("Failed to server: %v", err)
    }
}

ポイントをかいつまんで解説します。

listener, err := net.Listen("tcp", "localhost:8080")

net.Listen関数を使って、サーバーを起動するためのリスナーを作成します。

grpcServer := grpc.NewServer()

grpc.NewServer関数を使って、gRPCサーバーを作成します。

pb.RegisterHelloServiceServer(grpcServer, &server{})

pb.RegisterHelloServiceServer関数を使って、gRPCサーバーにサービスを登録します。

if err := grpcServer.Serve(listener); err != nil {
    log.Fatalf("Failed to server: %v", err)
}

grpcServer.Serve関数を使って、サーバーを起動します。

サーバーを試しに起動してみます。

go run server/main.go

次のようなメッセージが表示されればサーバーが起動しています。

Server is running on port: 8000

次にクライアントを作成します。

クライアントの実装

client/main.goを作成します。

package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    pb "main/go_protocol_buffer"
)

func main() {
    conn, err := grpc.Dial("localhost:8000", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewHelloServiceClient(conn)

    response, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "World"})
    if err != nil {
        log.Fatalf("Failed to call SayHello: %v", err)
    }

    log.Printf("Response: %v", response.GetMessage())
}

ポイントをかいつまんで解説します。

conn, err := grpc.Dial("localhost:8000", grpc.WithInsecure())

grpc.Dial関数を使って、gRPCサーバーに接続します。

client := pb.NewHelloServiceClient(conn)

pb.NewHelloServiceClient関数を使って、gRPCクライアントを作成します。

response, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "World"})

client.SayHello関数を使って、gRPCサーバーのSayHelloメソッドを呼び出します。

クライアントを試しに起動してみます。

go run client/main.go

次のようなメッセージが表示されればクライアントが起動しています。

2024/06/18 16:23:09 Response: Hello World

これで、gRPCのHello Worldが完了しました。

おわりに

今回はGo言語でgRPCを使ってHello Worldをしてみました。 まだ使いこなせているかは微妙ですが、これからもっと知識を深めていきたいですね。