Slide 1

Slide 1 text

Transparent gRPC Gateway in Go GopherCon Russia’21 Andrey Smirnov, Talos Systems

Slide 2

Slide 2 text

About Andrey Smirnov @smira github.com/smira Go developer since 2014 Working on Talos: revolutionary OS for clusters

Slide 3

Slide 3 text

Agenda Why gRPC? Why API gateway? Why Go? First iteration in Go, problems and solutions. Transparent proxying in Go using gRPC. Cutting and concatenating protobuf messages.

Slide 4

Slide 4 text

Why gRPC? API, but as easy as calling a function

Slide 5

Slide 5 text

API Gateway/Proxy gRPC gRPC backend gRPC backend API Gateway

Slide 6

Slide 6 text

Use Cases for gRPC API Gateway Migrating from monolith to smaller services (or vice versa) Migrating to new API version Common authentication or authorization layer Logging, traceability, … Non-trivial proxy logic: send one request to many backends, combine responses

Slide 7

Slide 7 text

gRPC API Gateway Implementation TCP loadbalancer HTTP reverse proxy (e.g. nginx) ... Implement our own in Go (!)

Slide 8

Slide 8 text

Ping-pong gRPC service message Ping { string value = 1; } message Pong { string value = 1; } service TestService { rpc PingPong(Ping) returns (Pong) {} }

Slide 9

Slide 9 text

Easy! (?) func (s *Proxy) connect() { s.conn = grpc.Dial(...) s.client = pb.NewTestServiceClient(conn) } func (s *Proxy) PingPong(ctx context.Context, ping *pb.Ping) (*pb.Pong, error) { return s.client.PingPong(ctx, ping) }

Slide 10

Slide 10 text

gRPC metadata gRPC metadata: headers, trailers Go gRPC Client Metadata: Metadata Go gRPC Server Metadata: Headers Trailers

Slide 11

Slide 11 text

Metadata handling md, _ := metadata.FromIncomingContext(ctx) outCtx := metadata.NewOutgoingContext(ctx, md) var header, trailer metadata.MD resp, err := s.client.Ping(outCtx, ping, grpc.Header(&header), grpc.Trailer(&trailer)) grpc.SendHeader(ctx, header) grpc.SetTrailer(ctx, trailer) return resp, err

Slide 12

Slide 12 text

Streaming Calls Unary calls Client streaming calls Server streaming calls Bi-directional streaming

Slide 13

Slide 13 text

Streaming Service message Counter { int32 counter = 1; } service TestService { rpc Counter(Empty) returns (stream Counter) {} }

Slide 14

Slide 14 text

Streaming Proxy (½) ctx, cancel := context.WithCancel(srv.Context()) defer cancel() cli, err := s.client.Counter(ctx, in) if err != nil { return err } ...

Slide 15

Slide 15 text

Streaming Proxy (½) for { msg, err := cli.Recv() switch { case err == io.EOF: return nil case err != nil: return err } err = srv.Send(msg) if err != nil { return err } }

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Ways out Code generation Libraries, common code, ...

Slide 18

Slide 18 text

Protobuf Update (v2) message Ping { string value = 1; int counter = 2; } message Pong { string value = 1; int counter = 2; } service TestService { rpc PingPong(Ping) returns (Pong) {} }

Slide 19

Slide 19 text

Version Mismatch gRPC backend API Gateway v1 v2 Ping value: “foo” counter: 42 Ping value: “foo” counter: 42 Pong value: “bar” counter: 24 Pong value: “bar” counter: 24

Slide 20

Slide 20 text

Solution grpc.CustomCodec(grpc.Codec) grpc.UnknownServiceHandler(grpc.StreamHandler) grpc.NewClientStream(context.Context, *StreamDesc, *ClientConn, method string)

Slide 21

Slide 21 text

grpc.Codec type Codec interface { // Marshal returns the wire format of v. Marshal(v interface{}) ([]byte, error) // Unmarshal parses the wire format into v. Unmarshal(data []byte, v interface{}) error }

Slide 22

Slide 22 text

Raw Codec type frame struct { payload []byte } func (c *rawCodec) Marshal(v interface{}) ([]byte, error) { out, ok := v.(*frame) if !ok { return fmt.Errorf("expected frame") } return out.payload, nil } func (c *rawCodec) Unmarshal(data []byte, v interface{}) error { dst, ok := v.(*frame) if !ok { return fmt.Errorf("expected frame")} dst.payload = data return nil }

Slide 23

Slide 23 text

grpc.UnknownServiceHandler grpc.NewServer( grpc.CustomCodec(proxy.Codec()), grpc.UnknownServiceHandler(handler)) func handler(srv interface{}, serverStream grpc.ServerStream) error { fullMethodName, ok := grpc.MethodFromServerStream(serverStream) ... }

Slide 24

Slide 24 text

grpc.NewClientStream func handler(srv interface{}, serverStream grpc.ServerStream) error { conn, err = grpc.Dial(addr, grpc.WithCodec(proxy.Codec())) clientStream, err = grpc.NewClientStream( ctx, &grpc.StreamDesc{ ServerStreams: true, ClientStreams: true, }, conn, fullMethodName) // copy clientStream <> serverStream }

Slide 25

Slide 25 text

Transparent gRPC Proxy Flow API Gateway grpcServerStream grpcClientStream Recv() Recv() Send() Send()

Slide 26

Slide 26 text

Proxying one → many Aggregating responses Encoding errors Attributing result to a backend

Slide 27

Slide 27 text

Response Metadata message ResponseMetadata { string upstream_node = 1; string upstream_error = 2; } message Pong { ResponseMetadata metadata = 99; string value = 1; }

Slide 28

Slide 28 text

Enriching Response gRPC backend node-1 API Gateway Pong value: “bar” Pong value: “bar” metadata: upstream_node: node_1

Slide 29

Slide 29 text

Protobuf Glue Pong value: “bar” (1) Pong value: “bar” (1) metadata: (99) upstream_node: node_1 (1) Pong metadata: (99) upstream_node: node_1 (1) (type: bytes, field: 1, length: 3): “bar” (type: bytes, field: 99, length: N): [(type: bytes, field: 1, length: 6): “node_1”] (type: bytes, field: 1, length: 3): “bar” (type: bytes, field: 99, length: N): [(type: bytes, field: 1, length: 6): “node_1”] protobuf serialization: messages:

Slide 30

Slide 30 text

Embedding Errors gRPC backend API Gateway Ping value: “foo” Pong metadata: upstream_node: node_1 upstream_error: ECONNREFUSED connection refused

Slide 31

Slide 31 text

Server Streaming API Gateway Ping value: “foo” Pong metadata: upstream_node: node_1 upstream_error: ECONNREFUSED Pong value: “bar” metadata: upstream_node: node_2

Slide 32

Slide 32 text

Unary Calls API Gateway Ping value: “foo” Pong metadata: upstream_node: node_1 upstream_error: ECONNREFUSED Pong value: “bar” metadata: upstream_node: node_2

Slide 33

Slide 33 text

Unary Protobuf Definition message ResponseMetadata { string upstream_node = 99; string upstream_error = 100; } message Pong { ResponseMetadata metadata = 99; string value = 1; } message PongResponse { repeated Pong messages = 1; }

Slide 34

Slide 34 text

Protobuf Scissors PongResponse messages: (1) - Pong: value: “bar” (1) (type: bytes, field: 1, length: N): [(type: bytes, field: 1, length: 3): bar] PongResponse messages: (1) - Pong: value: “bar” (1) metadata: (99) u_node: node_1 (1) (type: bytes, field: 1, length: N’): [(type: bytes, field: 1, length: 3): bar (type: bytes: field: 99, length: K): [(type: bytes: field: 1, length: 6): node_1 ]

Slide 35

Slide 35 text

grpc-proxy library https://github.com/talos-systems/grpc-proxy https://pkg.go.dev/github.com/talos-systems/grpc-proxy Thank you! @smira (https://github.com/smira) Talos Systems

Slide 36

Slide 36 text

Q&A