本文へ移動

gRPCの名前解決と検出

go-grpc:etcdをバックエンドとしてgRPCエンドポイントを解決する

etcdは、etcdからエンドポイントを取得してgRPCサービスを検出する代替の名前システムを、gRPCリゾルバーで提供します。その仕組みは、サービス名を接頭辞に持つキーの更新の監視に基づいています。

この機能は、grpc-goでまだ実験的なgoogle.golang.org/grpc/resolver パッケージに依存しているため、実験的機能です。

go-grpcでetcdの検出機能を使う

etcdクライアントは、etcdをバックエンドとしてgRPCエンドポイントを解決するgRPCリゾルバーを提供します。リゾルバーはetcdクライアントで初期化します。

import (
	clientv3 "go.etcd.io/etcd/client/v3"
	etcdnaming "go.etcd.io/etcd/client/v3/naming/resolver"

	"google.golang.org/grpc"
)

...

cli, err := clientv3.NewFromURL("http://localhost:2379")
if err != nil {
    // ...
}
r, err := etcdnaming.NewBuilder(cli)
if err != nil {
    // ...
}
conn, gerr := grpc.NewClient("my-service", grpc.WithResolvers(r), ...)

サービスエンドポイントの管理

etcdリゾルバーは、解決対象に"/“を付けた接頭辞(例:“foo/bar/my-service/")の下にあるすべてのキーのうち、 JSONでエンコードされた値(従来はgo-grpcのnaming.Update)を持つものを、サービスエンドポイントの候補として扱います。 新しいキーを作成するとエンドポイントがサービスに追加され、キーを削除するとサービスから削除されます。

エンドポイントの追加

etcdctlで新しいエンドポイントをサービスに追加できます。

ETCDCTL_API=3 etcdctl put foo/bar/my-service/1.2.3.4 '{"Addr":"1.2.3.4"}'

etcdクライアントのendpoints.Managerメソッドでも、Addrに対応するキーを使って新しいエンドポイントを登録できます。


em := endpoints.NewManager(client, "foo/bar/my-service")
err := em.AddEndpoint(context.TODO(),"foo/bar/my-service/e1", endpoints.Endpoint{Addr:"1.2.3.4"})

複数のエンドポイントを持つサービスへの接続時にラウンドロビン負荷分散を有効にするには、grpc内蔵の ラウンドロビンロードバランサーを使用するように接続を設定します。


conn, gerr := grpc.NewClient("etcd:///foo", grpc.WithResolvers(etcdResolver),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`))

エンドポイントの削除

etcdctlでホストをサービスから削除できます。

ETCDCTL_API=3 etcdctl del foo/bar/my-service/1.2.3.4

etcdクライアントのendpoints.Managerメソッドでも、エンドポイントを削除できます。

em := endpoints.NewManager(client, "foo/bar/my-service")
err := em.DeleteEndpoint(context.TODO(), "foo/bar/my-service/e1")

リース付きのエンドポイント登録

リース付きでエンドポイントを登録すると、ホストがキープアライブのハートビートを維持できなくなった場合(マシンの故障など)に、そのホストがサービスから確実に削除されます。

lease=`ETCDCTL_API=3 etcdctl lease grant 5 | cut -f2 -d' '`
ETCDCTL_API=3 etcdctl put --lease=$lease my-service/1.2.3.4 '{"Addr":"1.2.3.4"}'
ETCDCTL_API=3 etcdctl lease keep-alive $lease

Goでは、次のように記述します。

em := endpoints.NewManager(client, "foo/bar/my-service")
err := em.AddEndpoint(context.TODO(), "foo/bar/my-service/e1", endpoints.Endpoint{Addr:"1.2.3.4"})

エンドポイントのアトミックな更新

1つのトランザクションで複数のエンドポイントを変更するには、endpoints.Managerを直接使用できます。

em := endpoints.NewManager(c, "foo")

err := em.Update(context.TODO(), []*endpoints.UpdateWithOpts{
    endpoints.NewDeleteUpdateOpts("foo/bar/my-service/e1", endpoints.Endpoint{Addr: "1.2.3.4"}),
	endpoints.NewAddUpdateOpts("foo/bar/my-service/e1", endpoints.Endpoint{Addr: "1.2.3.14"})})