More Related Content
Similar to GKEとgRPCで実装する多言語対応・スケーラブルな内部API (20)
More from BrainPad Inc. (20)
GKEとgRPCで実装する多言語対応・スケーラブルな内部API
- 6. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
5
gRPCとは
• Googleによって開発され、2015年にオープンソース化されたRPCフレームワーク
• HTTP/2 による通信
• Protocol Buffer を IDL として採用
• クライアント・サーバともに Java, go, Python 等複数言語に対応
• 高速かつ高機能
• Cloud Native Computing Foundationの6番目のプロジェクト
• Google Ads API (beta) では JSON REST形式の他にgRPC形式をサポート
- 8. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
7
サービスやデータの定義
// サービスの定義
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// リクエスト・レスポンスの型の定義
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- 9. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
8
サービスやデータの定義
// サービスの定義
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// リクエスト・レスポンスの型の定義
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- 10. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
9
サービスやデータの定義
// サービスの定義
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// リクエスト・レスポンスの型の定義
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- 11. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
10
.protoファイルから各言語のソースコードを生成する
• protoc コマンドを利用
protoc --proto_path=src --java_out=build/gen src/greeter.proto
• Mavenを利用している場合は os72 / protoc-jar-maven-plugin なども利用可能
- 12. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
11
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
- 13. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
12
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
.protoで定義したServiceに対応した
クラスが生成される
- 14. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
13
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
対応するメソッドをオーバーライド
- 15. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
14
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
.protoで指定した引数は第一引数
戻り値はメソッドの戻り値ではなく第二引数
- 16. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
15
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
クライアントからのリクエストは
reqからアクセス可能
- 17. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
16
サーバサイドの実装
@GRpcService
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + req.getName())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} // ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
戻り値はStreamObserverのonNextへ
- 18. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
17
クライアントサイドの実装
private ManagedChannel channel = ManagedChannelBuilder
.forAddress(HOST, PORT)
.usePlaintext(true).build();
private GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
// ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
- 19. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
18
クライアントサイドの実装
private ManagedChannel channel = ManagedChannelBuilder
.forAddress(HOST, PORT)
.usePlaintext(true).build();
private GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
// ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
アクセス先や通信方法を表す
ManagedChannelオブジェクトを生成
- 20. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
19
クライアントサイドの実装
private ManagedChannel channel = ManagedChannelBuilder
.forAddress(HOST, PORT)
.usePlaintext(true).build();
private GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
// ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
サービスのスタブを作成
- 21. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
20
クライアントサイドの実装
private ManagedChannel channel = ManagedChannelBuilder
.forAddress(HOST, PORT)
.usePlaintext(true).build();
private GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
// ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
リクエストのオブジェクトを
構築
- 22. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
21
クライアントサイドの実装
private ManagedChannel channel = ManagedChannelBuilder
.forAddress(HOST, PORT)
.usePlaintext(true).build();
private GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
// ※ 参考
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
rpc呼び出し
- 24. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
23
GKEとは
• コンテナ化されたアプリケーションをデプロイするためのマネージド環境
• Kubernetesのクラスタを自動でセットアップしてくれる
• GCPとのネイティブな連携
• アクセス制御
• モニタリング・ロギング
• ネットワーク・ファイアウォール・ロードバランサ 等
- 26. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
25
GKEとは
Node Node Node
Pod
Container
Container
Cluster
Pod
Container
Container
Pod
Container
Container … Kubernetes上で実行する
Dockerコンテナ
- 27. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
26
GKEとは
Node Node Node
Pod
Container
Container
Cluster
Pod
Container
Container
Pod
Container
Pod … クラスタ上で動作するプロセスに対応
いくつかのコンテナをまとめられる
- 28. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
27
GKEとは
Node Node Node
Pod
Container
Container
Cluster
Pod
Container
Container
Pod
Container
Node … Podが動作する環境
VMだったり物理的なマシンだったり
- 31. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
30
ヘルスチェック
• Kubernetesでは2種類のヘルスチェック機能が存在
• Liveness チェック … プロセスが稼働しているか
• 失敗したらPodは再起動
• Readiness チェック … リクエストを受け入れる準備が整ったか
• 成功するまでリクエストが割り当てられない
• httpのリクエストかコマンド実行によるチェックしかサポートされていない
• → gRPCでのチェックはサポートされていない…
• → ヘルスチェック用のEndPointを用意→gRPCの動作チェック
• LogNet / grpc-spring-boot-starter を利用するとgRPCとHTTP
のサーバを簡単に立てられて便利
- 32. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
31
ヘルスチェック
@RestController
@RequestMapping("/health")
public class HealthCheck {
@RequestMapping(method = RequestMethod.GET)
public String get() {
// gRPCのサービス呼び出し
stub.healthCheck();
return "OK";
}
}
- 33. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
32
ヘルスチェック
@RestController
@RequestMapping("/health")
public class HealthCheck {
@RequestMapping(method = RequestMethod.GET)
public String get() {
// gRPCのサービス呼び出し
stub.healthCheck();
return "OK";
}
}
Readinessチェックと
Livenessチェックの内容は同じ
- 34. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
33
ヘルスチェック
@RestController
@RequestMapping("/health")
public class HealthCheck {
@RequestMapping(method = RequestMethod.GET)
public String get() {
// gRPCのサービス呼び出し
stub.healthCheck();
return "OK";
}
}
spring-boot-web-starter → 8080
grpc-spring-boot-starter → 6565
- 35. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
34
ヘルスチェック
@RestController
@RequestMapping("/health")
public class HealthCheck {
@RequestMapping(method = RequestMethod.GET)
public String get() {
// gRPCのサービス呼び出し
stub.healthCheck();
return "OK";
}
}
ここで(DBアクセス等を伴う)
gRPCサービスの呼び出し
- 36. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
35
ヘルスチェック
spec:
template:
spec:
containers:
- livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 200
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
- 37. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
36
ヘルスチェック
spec:
template:
spec:
containers:
- livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 200
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
Livenessチェックの設定
- 38. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
37
ヘルスチェック
spec:
template:
spec:
containers:
- livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 200
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
initialDelaySeconds … 200秒待ってから
最初のチェックを行う
periodSeconds … チェックの間隔
- 39. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
38
ヘルスチェック
spec:
template:
spec:
containers:
- livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 200
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
Readinessチェックの設定
- 41. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
40
エラーの受け渡し
• StreamObserver.onErrorを利用する
public void sayHello(HelloRequest request,
StreamObserver<HelloResponse> responseObserver) {
try {
// …
} catch (Exception e) {
responseObserver.onError(Status.INTERNAL
.withDescription(“an error occurred”)
.withCause(e) // causeはクライアントへは送信されない
.asException());
}
}
- 42. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
41
エラーの受け渡し
利用可能なステータス (io.grpc.Status)
OK
CANCELLED
UNKNOWN
INVALID_ARGUMENT
DEADLINE_EXCEEDED
NOT_FOUND
ALREADY_EXISTS
PERMISSION_DENIED
RESOURCE_EXHAUSTED
FAILED_PRECONDITION
ABORTED
OUT_OF_RANGE
UNIMPLEMENTED
INTERNAL
UNAVAILABLE
DATA_LOSS
UNAUTHENTICATED
- 43. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
42
エラーの受け渡し
* クライアント側ではRpcErrorの例外として受け取れる (Pythonの場合)
try:
stub.SayHello(request)
Except grpc.RpcError as e:
e.code() # => StatusCode.INTERNAL
e.details() # => “an error accurred”
- 46. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
45
シャットダウン
•方針
• gRPCのリクエストにタイムアウトを設定する(例: 60秒)
• ※ gRPCではクライアント側でタイムアウトを設定
• PodのterminationGracePeriodSecondsを長めに設定する
(例: 90秒)
• 各コンテナのpreStopでタイムアウトまで待つ
- 47. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
46
シャットダウン
• gRPCのリクエストにタイムアウトを設定する
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = stub.withDeadlineAfter(60L, TimeUnit.SECONDS)
.sayHello(request);
logger.info("Greeting: " + response.getMessage());
}
- 48. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
47
シャットダウン
• PodのterminationGracePeriodSecondsを長めに設定する(例: 90秒)
• 各コンテナのpreStopでタイムアウトまで待つ
spec:
template:
spec:
terminationGracePeriodSeconds: 90
containers:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 60"]
- 51. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
50
Java <-> protobuf 変換
• 既存のシステムにgRPCの機能を追加したかった
• 既存クラス
• → .proto定義
• → javaコード生成
• → 既存クラスと.protoから生成されたクラス間のコンバート
• ⇒ 既存クラスから.protoファイルとコンバータを自動生成する処理を自前で実装した
• BAData / protobuf-converter などもある
既存クラス .proto
生成された
クラス
- 52. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
51
Java <-> protobuf 変換
• Protocol Bufferには継承を表現するような機能はない
• “Don't go looking for facilities similar to class inheritance, though – protocol
buffers don't do that.” – Protocol Buffers / Basics: Java
- 53. Analytics Innovation Company
©BrainPad Inc.
Strictly Confidential
52
Java <-> protobuf 変換
nullの扱いに注意
• Protocol Bufferのmessage型はuser.hasBirthDay()のようにhas{フィールド名}で値を持
つか知ることができる
• → false なら null
• 文字列や数値などのプリミティブな型はhas{フィールド名}が利用できない
• → wrappers.proto
• Enumもhas{フィールド名}が利用できない
• → .protoファイルを生成する際にnullを表す要素も生成する
// wrappers.proto
message StringValue {
// The string value.
string value = 1;
}
message Sample {
string s1 = 1;
StringValue s2 = 2;
}