How-To: Invoke services using gRPC

Call between services using service invocation

This article describe how to use Dapr to connect services using gRPC.

By using Dapr’s gRPC proxying capability, you can use your existing proto-based gRPC services and have the traffic go through the Dapr sidecar. Doing so yields the following Dapr service invocation benefits to developers:

  1. Mutual authentication
  2. Tracing
  3. Metrics
  4. Access lists
  5. Network level resiliency
  6. API token based authentication

Dapr allows proxying all kinds of gRPC invocations, including unary and stream-based ones.

Step 1: Run a gRPC server

The following example is taken from the “hello world” grpc-go example. Although this example is in Go, the same concepts apply to all programming languages supported by gRPC.

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "net"
  6. "google.golang.org/grpc"
  7. pb "google.golang.org/grpc/examples/helloworld/helloworld"
  8. )
  9. const (
  10. port = ":50051"
  11. )
  12. // server is used to implement helloworld.GreeterServer.
  13. type server struct {
  14. pb.UnimplementedGreeterServer
  15. }
  16. // SayHello implements helloworld.GreeterServer
  17. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  18. log.Printf("Received: %v", in.GetName())
  19. return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
  20. }
  21. func main() {
  22. lis, err := net.Listen("tcp", port)
  23. if err != nil {
  24. log.Fatalf("failed to listen: %v", err)
  25. }
  26. s := grpc.NewServer()
  27. pb.RegisterGreeterServer(s, &server{})
  28. log.Printf("server listening at %v", lis.Addr())
  29. if err := s.Serve(lis); err != nil {
  30. log.Fatalf("failed to serve: %v", err)
  31. }
  32. }

This Go app implements the Greeter proto service and exposes a SayHello method.

Run the gRPC server using the Dapr CLI

  1. dapr run --app-id server --app-port 50051 -- go run main.go

Using the Dapr CLI, we’re assigning a unique id to the app, server, using the --app-id flag.

Step 2: Invoke the service

The following example shows you how to discover the Greeter service using Dapr from a gRPC client. Notice that instead of invoking the target service directly at port 50051, the client is invoking its local Dapr sidecar over port 50007 which then provides all the capabilities of service invocation including service discovery, tracing, mTLS and retries.

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "google.golang.org/grpc"
  7. pb "google.golang.org/grpc/examples/helloworld/helloworld"
  8. "google.golang.org/grpc/metadata"
  9. )
  10. const (
  11. address = "localhost:50007"
  12. )
  13. func main() {
  14. // Set up a connection to the server.
  15. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
  16. if err != nil {
  17. log.Fatalf("did not connect: %v", err)
  18. }
  19. defer conn.Close()
  20. c := pb.NewGreeterClient(conn)
  21. ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  22. defer cancel()
  23. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
  24. r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Darth Tyrannus"})
  25. if err != nil {
  26. log.Fatalf("could not greet: %v", err)
  27. }
  28. log.Printf("Greeting: %s", r.GetMessage())
  29. }

The following line tells Dapr to discover and invoke an app named server:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")

All languages supported by gRPC allow for adding metadata. Here are a few examples:

  1. Metadata headers = new Metadata();
  2. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
  3. GreeterService.ServiceBlockingStub stub = GreeterService.newBlockingStub(channel);
  4. stub = MetadataUtils.attachHeaders(stub, header);
  5. stub.SayHello(new HelloRequest() { Name = "Darth Malak" });
  1. var metadata = new Metadata
  2. {
  3. { "dapr-app-id", "server" }
  4. };
  5. var call = client.SayHello(new HelloRequest { Name = "Darth Nihilus" }, metadata);
  1. metadata = (('dapr-app-id', 'server'),)
  2. response = stub.SayHello(request={ name: 'Darth Revan' }, metadata=metadata)
  1. const metadata = new grpc.Metadata();
  2. metadata.add('dapr-app-id', 'server');
  3. client.sayHello({ name: "Darth Malgus" }, metadata)
  1. metadata = { 'dapr-app-id' : 'server' }
  2. response = service.sayHello({ 'name': 'Darth Bane' }, metadata)
  1. grpc::ClientContext context;
  2. context.AddMetadata("dapr-app-id", "server");

Run the client using the Dapr CLI

  1. dapr run --app-id client --dapr-grpc-port 50007 -- go run main.go

View telemetry

If you’re running Dapr locally with Zipkin installed, open the browser at http://localhost:9411 and view the traces between the client and server.

Deploying to Kubernetes

Set the following Dapr annotations on your deployment:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: grpc-app
  5. namespace: default
  6. labels:
  7. app: grpc-app
  8. spec:
  9. replicas: 1
  10. selector:
  11. matchLabels:
  12. app: grpc-app
  13. template:
  14. metadata:
  15. labels:
  16. app: grpc-app
  17. annotations:
  18. dapr.io/enabled: "true"
  19. dapr.io/app-id: "server"
  20. dapr.io/app-protocol: "grpc"
  21. dapr.io/app-port: "50051"
  22. ...

The dapr.io/app-protocol: "grpc" annotation tells Dapr to invoke the app using gRPC.

If your app uses a TLS connection, you can tell Dapr to invoke your app over TLS with the app-protocol: "grpcs" annotation (full list here). Note that Dapr does not validate TLS certificates presented by the app.

Namespaces

When running on namespace supported platforms, you include the namespace of the target app in the app ID: myApp.production

For example, invoking the gRPC server on a different namespace:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server.production")

See the Cross namespace API spec for more information on namespaces.

Step 3: View traces and logs

The example above showed you how to directly invoke a different service running locally or in Kubernetes. Dapr outputs metrics, tracing and logging information allowing you to visualize a call graph between services, log errors and optionally log the payload body.

For more information on tracing and logs see the observability article.

Proxying of streaming RPCs

When using Dapr to proxy streaming RPC calls using gRPC, you must set an additional metadata option dapr-stream with value true.

For example:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
  2. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-stream", "true")
  1. Metadata headers = new Metadata();
  2. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
  3. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-stream", "true");
  1. var metadata = new Metadata
  2. {
  3. { "dapr-app-id", "server" },
  4. { "dapr-stream", "true" }
  5. };
  1. metadata = (('dapr-app-id', 'server'), ('dapr-stream', 'true'),)
  1. const metadata = new grpc.Metadata();
  2. metadata.add('dapr-app-id', 'server');
  3. metadata.add('dapr-stream', 'true');
  1. metadata = { 'dapr-app-id' : 'server' }
  2. metadata = { 'dapr-stream' : 'true' }
  1. grpc::ClientContext context;
  2. context.AddMetadata("dapr-app-id", "server");
  3. context.AddMetadata("dapr-stream", "true");

Streaming gRPCs and Resiliency

When proxying streaming gRPCs, due to their long-lived nature, resiliency policies are applied on the “initial handshake” only. As a consequence:

  • If the stream is interrupted after the initial handshake, it will not be automatically re-established by Dapr. Your application will be notified that the stream has ended, and will need to recreate it.
  • Retry policies only impact the initial connection “handshake”. If your resiliency policy includes retries, Dapr will detect failures in establishing the initial connection to the target app and will retry until it succeeds (or until the number of retries defined in the policy is exhausted).
  • Likewise, timeouts defined in resiliency policies only apply to the initial “handshake”. After the connection has been established, timeouts do not impact the stream anymore.

Community call demo

Watch this video on how to use Dapr’s gRPC proxying capability:

Last modified June 19, 2023: Merge pull request #3565 from dapr/aacrawfi/skip-secrets-close (b1763bf)