gRPC

gRPC has become a popular, high-performance framework used by many applications. In this guide, we will show you how to expose a gRPC Upstream through a Gloo Edge Virtual Service and connect to it with a gRPC client. Once we have basic connectivity, we will add in TLS connectivity between the gRPC client and the Gloo Edge proxy (Envoy).

In this guide we are going to:

  1. Deploy a gRPC demo service
  2. Verify that the gRPC descriptors were indeed discovered
  3. Add a Virtual Service that maps to the gRPC API
  4. Verify that everything is working as expected
  5. Add TLS and a domain to the Virtual Service and verify again

Let’s get started!


Prerequisites

To follow along with this guide, you will need to have a Kubernetes cluster deployed with Gloo Edge installed. You will also need the tool grpcurl, aka curl for gRPC, to act as the gRPC client for testing communications. Finally, we will be using openssl to generate a self-signed certificate for TLS.


Deploy the demo gRPC store

We have a container image on Docker Hub which has a simple Store service for gRPC. We are going to deploy that image and expose it using port 80.

Create a deployment and a service:

kubectl create deployment grpcstore-demo --image=docker.io/soloio/grpcstore-demo
kubectl expose deployment grpcstore-demo --port 80 --target-port=8080

Verify that gRPC functions were discovered

After a few seconds Gloo Edge should have discovered the service:

kubectl get upstream -n gloo-system default-grpcstore-demo-80

We should also enable Gloo Edge FDS, if it is not already (whitelist mode by default), so the proto descriptor is found:

kubectl label upstream -n gloo-system default-grpcstore-demo-80 discovery.solo.io/function_discovery=enabled

FDS should update the discovered upstream:

kubectl get upstream -n gloo-system default-grpcstore-demo-80 -o yaml

You should see output similar to this:

apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  labels:
    app: grpcstore-demo
    discovered_by: kubernetesplugin
  name: default-grpcstore-demo-80
  namespace: gloo-system
spec:
  discoveryMetadata: {}
  kube:
    selector:
      app: grpcstore-demo
    serviceName: grpcstore-demo
    serviceNamespace: default
    servicePort: 80
    serviceSpec:
      grpc:
        descriptors: Q3F3RkNoVm5iMjluYkdVdllYQnBMMmgwZE … bTkwYnpNPQ== # snipped for brevity
        grpcServices:
        - functionNames:
          - CreateItem
          - ListItems
          - DeleteItem
          - GetItem
          packageName: solo.examples.v1
          serviceName: StoreService
status:
  reportedBy: gloo
  state: 1

The descriptors field above was truncated for brevity.

As you can see Gloo Edge’s function discovery detected the gRPC functions on that service.

Enable HTTP/2 for the service

To use use gRPC, you must configure the Envoy proxy to use HTTP/2 for its communications protocol in one of the following ways:

For example, to change the port information for the grpcstore-demo:

spec:
  clusterIP: 10.101.199.96
  ports:
  - name: grpc
    port: 80
    protocol: TCP
    targetPort: 8080

After you annotate or edit the ports for the gRPC service, the Upstream value useHttp2 is set to true.


Adding a Virtual Service

Now let’s add a Virtual Service to Gloo Edge that will map to the gRPC service listening on port 80. The following yaml describes the Virtual Service:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: grpc
  namespace: gloo-system
spec:
  virtualHost:
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: default-grpcstore-demo-80
              namespace: gloo-system

The Virtual Service assumes that you are using the namespace gloo-system for your Gloo Edge installation. In this initial configuration, we are matching the prefix / for all domains. Save the yaml as the file grpc-vs.yaml and run the following:

kubectl apply -f grpc-vs.yaml

Validate with gRPC client

The next step is to test connectivity to the service using the tool grpcurl. We are going to get the IP address Gloo Edge is using for a proxy, and then issue a request using grpcurl. Since we are not using TLS, we will have to use the flag -plaintext to allow for unencrypted communications. Later in this guide, we’ll show how to add TLS to the configuration.

grpcurl expects a port number as part of the request. The Store service has Server Reflection enabled, which means that we do not have to specify a proto source file for grpcurl to use with our request. We are going to use the list argument to enumerate the services available.

grpcurl -plaintext $(glooctl proxy address --port http) list
grpc.reflection.v1alpha.ServerReflection
solo.examples.v1.StoreService

Excellent! We were able to communicate with our server and get a list of services. Now let’s see what methods are available in solo.examples.v1.StoreService using the describe argument.

grpcurl -plaintext $(glooctl proxy address --port http) describe solo.examples.v1.StoreService
solo.examples.v1.StoreService is a service:
service StoreService {
  rpc CreateItem ( .solo.examples.v1.CreateItemRequest ) returns ( .solo.examples.v1.CreateItemResponse );
  rpc DeleteItem ( .solo.examples.v1.DeleteItemRequest ) returns ( .solo.examples.v1.DeleteItemResponse );
  rpc GetItem ( .solo.examples.v1.GetItemRequest ) returns ( .solo.examples.v1.GetItemResponse );
  rpc ListItems ( .solo.examples.v1.ListItemsRequest ) returns ( .solo.examples.v1.ListItemsResponse );

You can continue to describe the individual methods and messages using the same syntax. Let’s try using the CreateItem method to add an item to the store.

grpcurl -plaintext -d '{"item":{"name":"item1"}}' $(glooctl proxy address --port http) solo.examples.v1.StoreService/CreateItem
{
  "item": {
    "name": "item1"
  }
}

We can retrieve all items by using the ListItems method:

grpcurl -plaintext $(glooctl proxy address --port http) solo.examples.v1.StoreService/ListItems
{
  "items": [
    {
      "name": "item1"
    }
  ]
}

Looks like things are working pretty well. Now let’s make them a bit more complicated.


Adding TLS and a specific domain

In this section we are going to add a specific domain to expose the service on, and enable encryption on the communication between the client and Envoy.

Using a specific domain

You may want to narrow the availability of your service to a specific domain. This is done by editing the Virtual Service and adding a domain entry to the virtualHost configuration. Run the following command:

kubectl edit vs grpc -n gloo-system

And update the yaml by adding the highlighted lines:

spec:
  virtualHost:
    domains:
    - store.example.com
    routes:
    - matchers:
      - prefix: /
      routeAction:
        single:
          upstream:
            name: default-grpcstore-demo-80
            namespace: gloo-system
If using a port number that isn’t 443, you will need to include the domain with port appended as well. For more, see https://github.com/solo-io/gloo/issues/3505

Now if we try to list the items again:

grpcurl -plaintext $(glooctl proxy address --port http) solo.examples.v1.StoreService/ListItems

We get an error:

Error invoking method "solo.examples.v1.StoreService/ListItems": failed to query for service descriptor "solo.examples.v1.StoreService": server does not support the reflection API

The error message is not strictly true, but it’s the best that Envoy can figure out. gRPC is using HTTP/2 and we did not specify a authority header, which is the equivalent of a HOST header in curl. Envoy instead used whatever value was in $IP as the HOST name. Let’s update our command to use the -authority flag.

grpcurl -plaintext -authority store.example.com $(glooctl proxy address --port http) solo.examples.v1.StoreService/ListItems

We once again get the expect response.

{
  "items": [
    {
      "name": "item1"
    }
  ]
}
In our example we are using a public IP address in the form X.X.X.X:80 and specifying the authority header. If we were using a domain in our request instead, e.g. store.example.com:80, we would still need to specify the authority header. Otherwise Envoy will interpret the domain name including the :80 on end as the HOST header. Since store.example.com:80 does not match store.example.com, you will receive an error. By specifying the authority header explicitly, you will avoid this issue. If you cannot specify the authority header, you can update the domain match on the Virtual Service to use store.example.com*, which will match anything that begins with that domain.

Adding TLS

Now that we have things associated with a specific domain, let’s add a certificate. In our example, we are going to create a self-signed certificate, but in a production scenario you should use a certificate from public or private CA.

First, let’s generate the certificate using openssl.

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
   -keyout tls.key -out tls.crt -subj "/CN=store.example.com"

Now we will create the Kubernetes secret to hold this cert:

kubectl create secret tls grpc-tls --key tls.key \
   --cert tls.crt --namespace gloo-system

Lastly, let’s configure the Virtual Service to use this cert via the Kubernetes secrets:

glooctl edit virtualservice --name grpc --namespace gloo-system \
   --ssl-secret-name grpc-tls --ssl-secret-namespace gloo-system

Now if we get the grpc Virtual Service, we should see the new SSL configuration:

glooctl get virtualservice grpc -o kube-yaml
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: grpc
  namespace: gloo-system
spec:
  sslConfig:
    secretRef:
      name: grpc-tls
      namespace: gloo-system
  virtualHost:
    domains:
    - store.example.com
    routes:
    - matchers:
      - prefix: /
      routeAction:
        single:
          upstream:
            name: default-grpcstore-demo-80
            namespace: gloo-system
status:
  reportedBy: gateway
  state: 1
  subresourceStatuses:
    '*v1.Proxy.gloo-system.gateway-proxy':
      reportedBy: gloo
      state: 1

We’ll need to update the grpcurl command to use the -insecure flag instead of the -plaintext flag. We also need to update the address to use port 443 instead of 80.

Alright, let’s try to connect to our service on port 443 (note the --port https flag) and invoke the ListItem method.

grpcurl -insecure -authority store.example.com $(glooctl proxy address --port https) solo.examples.v1.StoreService/ListItems
{
  "items": [
    {
      "name": "item1"
    }
  ]
}

Nice! If you happen to be using a certificate that has the correct domain listed and is trusted by the client, you can skip the -insecure flag.


Summary

In this guide we saw how to present a gRPC Upstream through Gloo Edge and connect to it using a gRPC client. We also saw how to add a domain filter and enable TLS. For more information on gRPC, check out the guide for presenting a gRPC service as a REST API through Gloo Edge. You can find out more about using TLS with Gloo Edge in the Network Encryption section of our guides.