GRPC on Workflow

7 Nov 2016

Ask and You Might Receive

GRPC

The question has come up several times on Deis' community channel:

How can I connect my two apps with GRPC in Workflow?

Code speaks louder than words, so my colleague Keerthan Mala took the time to create a pair of example GRPC applications for Deis Workflow. Here's where we'll end up:

$ curl http://orange-nailhead.192.168.99.100.nip.io/  # our local minikube test URL
Powered by GRPC!

Deploy the apps as described below, then browse the code for the client and server, and you'll have a clear understanding of how to get your own GRPC server and client up and running.

What Is GRPC?

GRPC is a way to create distributed applications and services. Define a service by specifying methods that can be called remotely with their parameters and return types. A server implements that interface and handles clients that call its methods. Clients get a "stub" which provides the same methods, and can call the methods directly to interact with the server as if it were a local object.

If you've worked with RPC-type systems before, this scheme will sound familiar. For those who know HTTP, a GRPC service is like a set of well-defined REST endpoints. You can even stick with the ever-popular JSON format over the wire, although this example uses protocol buffers.

The code here is written in Go, but first-class support for Python, Java, Node.js, and most popular languages is also available.

Connecting Apps in Workflow

You have a user registered with a Deis Workflow cluster, right? If not, well then get yourself some Kubernetes and follow the Quick Start guide to get Workflow up and running.

Workflow is an open source Heroku-alike environment that favors twelve-factor apps. Most apps don't just run out of the box: they need some configuration. Config is stored as environment variables, keeping it separate from the code, and making us upstanding twelve-factor developers.

In this case, "configuration" means that the client needs to know how to connect to its server. That's a job for an environment variable, as we'll see when we set up the client.

The GRPC Server

Use git to clone example-grpc-server locally. From that directory, use the deis CLI to create an app. The server doesn't need to be accessible outside of the Kubernetes cluster, so disable routing before pushing the code to Workflow to deploy:

$ deis create
Creating Application... done, created wilder-espresso
$ deis routing:disable  # only the GRPC client needs access
Disabling routing for wilder-espresso... done
$ git push deis master
...
Build complete.
Launching App...
Done, wilder-espresso:v2 deployed to Workflow

Remember the name of the application! In this case, Workflow titled it "wilder-espresso." We will give that name to the client so it knows how to find the server.

The GRPC Client

Change directory out of the server project, then use git to clone example-grpc-client. Create the app with the deis CLI as above, then set SERVER_NAME in its configuration before doing a git push:

$ deis create
Creating Application... done, created orange-nailhead
$ deis config:set SERVER_NAME=wilder-espresso  # the app name for the server
$ deis config:set POWERED_BY='GRPC!'           # optional, just for fun
Creating config... done
$ git push deis master
...
Build complete.
Launching App...
Done, orange-nailhead:v3 deployed to Workflow
$ curl http://orange-nailhead.192.168.99.100.nip.io/
Powered by GRPC!

Wait, What Just Happened?

Diving into the Go code shows that the client app is itself a traditional HTTP server which routes requests on the root URL to a poweredByHandler func:

// wrap the poweredByHandler with logging middleware
http.Handle("/", logRequestMiddleware(http.HandlerFunc(poweredByHandler)))

The poweredByHandler reads the configuration we set, then calls the server via GRPC to set up a protocol buffer connection to its PoweredBy function:

server := getenv("SERVER_NAME", "")
address := fmt.Sprintf("%s.%s:80", server, server)
conn, err := grpc.Dial(address, grpc.WithInsecure())
c := pb.NewPoweredByClient(conn)
resp, err := c.PoweredBy(context.Background(), &pb.Request{Name: powered})
// print the string to the ResponseWriter
fmt.Fprintf(w, resp.Message)

The client finishes up the request handling by simply printing the GRPC server's response--in this case, Powered by GRPC!.

Yes, I skipped over error handling and other necessary code here to show you the highlights, but don't be afraid to use the source. There are only about 100 lines of code between both the example client and server.

To prove that the magic of GRPC is really happening here, configure SERVER_NAME to something else, or deis scale web=0 -a wilder-espresso to terminate all the server pods. The client will return an error, but you'll get the satisfaction of being appropriately skeptical.

Drop That Mic

Finally, thanks to Keerthan Mala for actually writing all the code that I just talked about here. If you have any questions or suggestions for improvement, why not leave a GitHub issue at example-grpc-client or example-grpc-server? We love to hear your feedback!

Posted in Deis Workflow

triangle square circle

Did you enjoy this post?