Running gRPC on Google Cloud Run

Bendik August Nesbø
Bendik August Nesbø | 28 February, 2020
Find me on:

Developed by Google, gRPC is a high performance, open-source universal RPC framework that uses HTTP/2 and protocol buffers. Google Cloud Run is a managed compute platform that enables you to run stateless containers on Google Cloud.

I’ll be writing the gRPC server in Go, but since the server will be served with Docker, you can write your server in (almost) whatever language you prefer.

 

Rationale

Why gRPC?

When consuming a REST API, what is usually one of the first things you do? For me, it’s Googling “JSON to <insert language for client>”, to be able to generate models for the API I’m consuming. With gRPC, there is no need to do that.

The server and client are both based on the same protocol buffer files, which act as a contract of what is the expected input and output. The API shares the protocol buffer files, allowing the clients to generate type-safe models for all of the requests and responses, and generate ready-to-go classes/methods for the known RPC’s. Just enter the URL and you’re good to go!

gRPC also serializes the data-structures in a backward-compatible way, which is faster than REST+JSON.

Why Google Cloud Run?

Google Cloud Run lets you focus on the application code, rather than the infrastructure. You let Google handle SSL certificate provisioning, domain management, load-balancing, traffic-splitting, auto-scaling, checking the health of your server and killing/restarting them. It allows for containerization, without having to provision a full GKE cluster. Cloud Run also allows you to scale to zero, removing the need to pay when your server is not in use. You pay only when your code is running, rounded to the nearest 100ms!

Up until September 25, 2019, it was not possible to run a gRPC server on Cloud Run, but it is now possible, with one caveat: it only accepts unary gRPC for inbound requests, not streaming gRPC.port

 

 

Creating a gRPC server

If you want to look at the final code, you can go to the accompanying Github repo.

If you want to follow along, install the prerequisites listed at the bottom.

Hello World

Let’s first start off by creating a simple Hello World app in Go:

Create a new directory, and add main.go:

 

We also want to use go modules, so let’s run go mod init example.com/echo, where example.com/echo is our package.

To run the app, run the command go run main.go.

Protocol Buffers

Now, let’s define our service. We do that by creating a echo_api.proto file, and define our RPC service:

 

This service defines a single RPC, Echo. It takes an EchoRequest from the client, and returns an EchoResponse.

 

To compile this language-agnostic .proto file into generated code for Go, create a folder called echo, and run protoc echo_api.proto --go_out=plugins=grpc:echo. This compiles the echo_api.proto to go-code, with the gRPC-plugin. The name after the colon specifies where the output-files should go.

 

A file called echo/echo_api.pb.go will be created, with all the required types and interfaces to start implementing our API.

Let’s create echo/echo.go to hold our API-implementation:

 

EchoAPIServer is the interface that was created for us by running protoc. All we do in the Echo function, is to return a response with the same message as the request.

 

Now we need to wire our main.go file to listen for requests and use our API. Let’s replace the whole main.go file:

 

This code assumes that there is an environment-variable called PORT, or it will use 8080 as default. It creates an instance of our API-implementation, an instance of a gRPC server, and connects those two together, before starting to accept incoming requests.

 

We can now run our server, the same way we ran our Hello World: go run main.go

To test that our server is running correctly, we can use e.g. gRPCurl to call our server:

grpcurl -d '{"message":"Test"}' -proto ./echo_api.proto -plaintext 127.0.0.1:8080 echo.EchoAPI/Echo

And the response is:

{"message": "Test"}

Yay, it works!


Some notes about the gRPCurl command: We have to specify -plaintext, as there is no SSL set up yet. We also have to specify the data using the -d flag, and input the path to our proto file, to let gRPCurl convert the data in JSON format to the binary-serialized data that the server is expecting.

Deploying to Cloud Run

Dockerizing the app

Now that we have a fully working local version of the app, let’s put it in a docker-container, and deploy it to Cloud Run.

 

First, we need a Dockerfile:

 

We use a two-stage build, where we use a prebuilt Go image, which has Go pre-installed, build the app, and then copy the app to an Alpine-image. This reduces the final image-size by not including all the build-dependencies. We also add ca-certificates, which is required if we want the app to communicate to the outside world.

 

To build the image, run

docker build -t gcr.io/<your-google-project>/echo .
, and to push it to GCR (Google Container Registry), run
docker push gcr.io/<your-google-project>/echo

 

 

Deploying the app to Cloud Run

This can be done with infrastructure as code (IaC, Terraform, Deployment Manager), Google SDK/CLI or by using the Cloud Console. I’m choosing to use the Cloud Console to provide a clear understanding for this article.

Navigate to Cloud Run and click Create Service. Select your container image, and which region you want the service to be running in. We are making this API a public API, so let’s tick “Allow unauthenticated invocations”. If you want, you can review the other revision-settings, like auto-scaling and resource allocation.

 

 

 

 

Once you click Create, Google will set everything up for you, and a URL will show at the top of the screen. For me, the URL was https://echo-service-knccyqzhbq-ew.a.run.app. Notice that GCP has automatically set up SSL provisioning and domain, but you can map your own domain if you want.

To call the deployed service, we need to modify the gRPCurl command a bit:

grpcurl -d '{"message":"Test"}' -proto ./echo_api.proto echo-service-knccyqzhbq-ew.a.run.app:443 echo.EchoAPI/Echo

 


The IP:port should be changed to URL:port, but without the https://-prefix, and the port has changed to 443 (the default SSL port). Since we are now using SSL, we also remove the -plaintext flag.

 

If you get the same response now, you have succeeded in deploying gRPC to Cloud Run!

 

A quick note about the port/SSL:
Cloud Run services gets an out‐of‐the‐box stable HTTPS endpoint, with TLS termination handled for you. This means that no code-changes related to certificates are required, and the server is exposed to the internet on port 443, even though the server internally is listening to another port.

 

Further tasks for the reader

Following are some suggestions for other tasks you can attempt on your own:

  • Set up automatic deployment by using Cloud Build.
  • Set up a custom domain.
  • Deploy a new version of the app with server-side streaming responses.
  • Split traffic between versions.
  • Use the proto-files to generate a client, instead of calling the server via gRPCurl.
  • Extend the server to accept configuration from other environment-variables, and set them via Cloud Run.
  • Set up monitoring via Cloud Endpoints.
  • Deploy the server using Infrastructure as Code (IaC), e.g. with Terraform.
  • Configure authentication via IAM.

Troubleshooting

You might need to enable Container Registry and Cloud Run APIs.

 

If you have problems with pushing the docker-image to GCR, make sure you have configured gcloud with docker: https://cloud.google.com/container-registry/docs/pushing-and-pulling

 

If your server is expecting client-side streaming or bi-directional streaming, then fully managed Cloud Run is not for you. Cloud Run only allows unary gRPC for inbound requests, but allows unary and streaming gRPC for outbound requests.

If you want to use gRPC with streaming inbound requests, then you need to host the app in some other way, like Cloud Run on GKE, pure GKE, or Compute Engine.

Prerequisites

 

Links:

Topics

googlecloud GCP cloud googlecloudplatform Google Cloud Platform development tutorial gRPC golang Docker