経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

etcdのgRPCコードリーディング

Do'er Advent Calendar 2022の22日目です。

etcdとは「a strongly consistent, distributed key-value store that provides a reliable way to store data that needs to be accessed by a distributed system or cluster of machines」であり、KubernetesやRookで使われていて、Quickstartを参考にすれば手元で動かせます。

いくつかコミットをしていますが、etcdではgRPCが使われているのでいろいろと思い出す必要がありました。
2年前くらいにやったThe complete gRPC courseをもう一度やったらなんとなく読めるようになったのでメモ書きをします。



コードリーディングの対象は命名からして簡単そうなclient.gocheckVersionにする。
Statusの返り値であるresp.Versionを使って処理がされているので、これがどこから来た何なのかを知りたい。

etcd/client.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

func (c *Client) checkVersion() (err error) {
...
   resp, rerr := c.Status(ctx, e)
   ...
   vs := strings.Split(resp.Version, ".")
...

Statusの定義ジャンプをすると、maintenance.goに飛ぶ。
etcd/maintenance.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) {
...
   resp, err := remote.Status(ctx, &pb.StatusRequest{}, m.callOpts...)
...

さらにStatusの定義ジャンプをすると、proto定義から生成されるrpc.pb.goに飛ぶ。
etcd/rpc.pb.go at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

type MaintenanceClient interface {
	// Alarm activates, deactivates, and queries alarms regarding cluster health.
	Alarm(ctx context.Context, in *AlarmRequest, opts ...grpc.CallOption) (*AlarmResponse, error)
	// Status gets the status of the member.
	Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
...

rpc.protoを見ると、関連しているmessageserviceが定義されている。
StatusResponseには目的のversionがある。

etcd/rpc.proto at 16e1fff519eeff66e626dd15fef399ea2b10b9cc · etcd-io/etcd · GitHub

message StatusRequest {
  option (versionpb.etcd_version_msg) = "3.0";
}

message StatusResponse {
  option (versionpb.etcd_version_msg) = "3.0";

  ResponseHeader header = 1;
  // version is the cluster protocol version used by the responding member.
  string version = 2;
...
}

service Maintenance {
...
  // Status gets the status of the member.
  rpc Status(StatusRequest) returns (StatusResponse) {
      option (google.api.http) = {
        post: "/v3/maintenance/status"
        body: "*"
    };
  }

RESTっぽいパスが定義されているし、scripts/genproto.sh--grpc-gateway_outがあったのでgRPC-Gatewayを使っていそう。だが完全に忘れているのでエンドポイントの関数がどこにあるのかわからない。Statusでコード検索をかけるとserver/etcdserver/api/v3rpc/maintenance.goという、いかにもそれっぽいパスに関数が定義されていた。
定数のversion.Versionが返り値であるrespに含まれている。

func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	...
	resp := &pb.StatusResponse{
		Header:           hdr,
		Version:          version.Version,

...

という感じでなんとなく読めたのでおしまい。