gRPC

In this guide you are going to create an API Doc associated with a gRPC service and add it as a new version of an API Product in an Environment. You will then see how you can make requests to your service, leveraging Gloo Edge.

We will do the following:

  1. Create an API Doc using a gRPC service
  2. Add the API Doc to an API Product that’s included in your Environment and Portal
  3. Try out a gRPC endpoint using the routing set up by Gloo Edge
  4. Try out a gRPC endpoint from the Portal UI

Prerequisites

You will need these things in place to follow the guide:

This guide also assumes that CoreDNS is the default DNS server in your cluster.

Install API Backend

In this guide we’ll be using a gRPC service similar to the OpenAPI Example Pet Store as the backend for our API Product version.

To install the gRPC Pet Store to Kubernetes:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: petstore-grpc
  name: petstore-grpc
  namespace: default
spec:
  selector:
    matchLabels:
      app: petstore-grpc
  replicas: 1
  template:
    metadata:
      labels:
        app: petstore-grpc
    spec:
      containers:
      - image: quay.io/solo-io/petstore-grpc:0.0.2
        name: petstore-grpc
        ports:
        - containerPort: 8080
          name: grpc
        env:
        - name: SERVER_PORT
          value: "8080"

---

apiVersion: v1
kind: Service
metadata:
  name: petstore-grpc
  namespace: default
  labels:
    service: petstore-grpc
spec:
  selector:
    app: petstore-grpc
  ports:
  - name: grpc
    port: 8080
    protocol: TCP

EOF
kubectl -n default rollout status deployment petstore

If the installation completed successfully, you should see the output:

deployment "petstore" successfully rolled out

Create an API Document

The next step in our process is to create an API Document based on the gRPC service defined by the Pet Store application. An API Document includes only the spec for the service and does not include any details on where the service is published or how to route to it.

The Pet Store application implements a reflection endpoint. Let’s register this service with the Gloo Portal operator by creating an APIDoc:

cat <<EOF | kubectl apply -f -
apiVersion: devportal.solo.io/v1alpha1
kind: APIDoc
metadata:
  name: petstore-grpc-doc
  namespace: default
spec:
  ## specify the type of schema provided in this APIDoc.
  grpc:
    reflectionSource:
      connectionTimeout: 5s
      insecure: true
      serviceAddress: petstore-grpc.default:8080
      # we use a reflection server here to tell the Gloo Portal
      # to fetch the schema contents directly from the petstore service.

EOF

Once the API Doc has been created, we can verify that the Gloo Portal has processed it by checking its status:

kubectl get apidoc -n default petstore-grpc-doc -oyaml
apiVersion: devportal.solo.io/v1alpha1
kind: APIDoc
# ...truncated for brevity
spec:
  grpc:
    reflectionSource:
      connectionTimeout: 5s
      insecure: true
      serviceAddress: petstore-grpc.default:8080
status:
  grpc:
    methods:
    - rpcName: ServerReflectionInfo
      rpcType: BIDIRECTIONAL_STREAMING
      serviceName: grpc.reflection.v1alpha.ServerReflection
    - rpcName: ListPets
      rpcType: UNARY
      serviceName: test.solo.io.PetStore
    - rpcName: FindPetById
      rpcType: UNARY
      serviceName: test.solo.io.PetStore
    - rpcName: AddPet
      rpcType: UNARY
      serviceName: test.solo.io.PetStore
    - rpcName: DeletePet
      rpcType: UNARY
      serviceName: test.solo.io.PetStore
    - rpcName: WatchPets
      rpcType: SERVER_STREAMING
      serviceName: test.solo.io.PetStore
  observedGeneration: 1
  state: Succeeded

When we see the Doc in a Succeeded state, it means its methods can now be published as an API Product.

You can also reference your gRPC service using a dataSource that specifies a protoset file.

Protoset files with imports must explicitly include imports. For example, if using the `protoc --descriptor_set_out out.protoset in.proto` command, you'll need to include the `--include_imports` flag. Otherwise, imported schemas will not be found, and you'll see an error stating "Could not resolve reference."

You can also create an API Doc by using the Admin Dashboard.

Add our API Doc to an API Product

In the Getting Started guide, you created an API Product. Let’s add a version to that API Product including all methods from the new Pet Store API Doc we just created.

Apply the API Product to the cluster:

cat << EOF | kubectl apply -f -
apiVersion: devportal.solo.io/v1alpha1
kind: APIProduct
metadata:
  name: petstore-product
  namespace: default

spec:
  displayInfo: 
    description: Petstore Product
    title: Petstore Product
    
  # Specify one or more version objects that will each include a list
  # of APIs that compose the version and routing for the version  
  versions:
  - name: v1
    apis:
    - apiDoc:
        name: petstore-schema
        namespace: default 
    defaultRoute:
      inlineRoute:
        backends:
        - kube:
            name: petstore
            namespace: default
            port: 8080
    tags:
      stable: {}
  - name: v2
    apis:
    - apiDoc:
        name: petstore-grpc-doc
        namespace: default
  
    # Each imported method must have a 'route' associated with it.
    # Routes can be specified on each imported method, in the API Doc itself.
    # The Default Route provided here will be used for any methods which do not have a route defined.
    # A route must be provided for every method to enable routing for an API Product.  
    defaultRoute:
      inlineRoute:
        backends:
        - kube:
            name: petstore-grpc
            namespace: default
            port: 8080
    tags:
      stable: {}
EOF

We can verify that our product was accepted into the system by checking its status.state:

kubectl get apiproducts.devportal.solo.io -n default petstore-product -ojsonpath='{.status.state}'

The command should result in an output of Succeeded once the product has been updated.

The complete status of an API Product gives detailed information. You can view the complete status and confirm that the new version is present by running:

kubectl get apiproducts.devportal.solo.io -n default petstore-product -oyaml

Add our new API Product Version to an Environment

The final piece of the puzzle is to include our new API Product version in the Environment.

cat << EOF | kubectl apply -f -
apiVersion: devportal.solo.io/v1alpha1
kind: Environment
metadata:
  name: dev
  namespace: default
spec:
  domains:
  - api.example.com
  # If you are using Gloo Edge and the Gateway is listening on a port other than 80,
  # you need to include a domain in this format: <DOMAIN>:<PORT>.
  # In this example we expect the Gateway to listen on port 32000.
  - api.example.com:32000
  displayInfo:
    description: This environment is meant for developers to deploy and test their APIs.
    displayName: Development
  apiProducts:
  - name: petstore-product
    namespace: default
    plans:
    - authPolicy:
        apiKey: {}
      displayName: Basic
      name: basic
      rateLimit:
        requestsPerUnit: 5
        unit: MINUTE
    publishedVersions:
    - name: v1
    - name: v2
EOF

We can check on the status of our Environment by running the following:

kubectl get environment -n default dev -ojsonpath='{.status.state}'

The command should result in an output of Succeeded once the Environment has been updated.

We can now take a look at the Virtual Service which corresponds to our Environment:

kubectl get virtualservices.gateway.solo.io dev -n default -oyaml
apiVersion: gateway.solo.io/v1
kind: VirtualService
# ...truncated for brevity
spec:
  displayName: Development
  virtualHost:
    domains:
    - api.example.com
    - api.example.com:32000
    options:
      cors:
        allowCredentials: true
        allowHeaders:
        - api-key
        - Authorization
        allowOrigin:
        - http://petstore.example.com:32000
        - https://petstore.example.com:32000
        - http://petstore.example.com
        - https://petstore.example.com
    routes:
    - matchers:
      - exact: /api/pets
        methods:
        - GET
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-schema.default.findPets
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /api/pets
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-schema.default.addPet
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - methods:
        - DELETE
        - OPTIONS
        regex: ^/api/pets/[^/]+?$
      name: dev.default.petstore-product.default.petstore-schema.default.deletePet
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - methods:
        - GET
        - OPTIONS
        regex: ^/api/pets/[^/]+?$
      name: dev.default.petstore-product.default.petstore-schema.default.findPetById
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.grpc.reflection.v1alpha.ServerReflection.ServerReflectionInfo
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /test.solo.io.PetStore/AddPet
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.test.solo.io.PetStore.AddPet
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /test.solo.io.PetStore/DeletePet
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.test.solo.io.PetStore.DeletePet
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /test.solo.io.PetStore/FindPetById
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.test.solo.io.PetStore.FindPetById
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /test.solo.io.PetStore/ListPets
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.test.solo.io.PetStore.ListPets
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
    - matchers:
      - exact: /test.solo.io.PetStore/WatchPets
        methods:
        - POST
        - OPTIONS
      name: dev.default.petstore-product.default.petstore-grpc-doc.default.test.solo.io.PetStore.WatchPets
      options:
        extauth:
          configRef:
            name: default-petstore-product-dev
            namespace: default
        rateLimitConfigs:
          refs:
          - name: default-petstore-product-dev
            namespace: default
      routeAction:
        multi:
          destinations:
          - destination:
              upstream:
                name: petstore-grpc-8080-default
                namespace: gloo-system
            weight: 1
status:
  reportedBy: gateway
  state: 1
  subresourceStatuses:
    '*v1.Proxy.gloo-system.gateway-proxy':
      reportedBy: gloo
      state: 1

In addition to the routes it already included for the OpenAPI operations in v1 of our API Product, it specifies a route corresponding to each of the gRPC methods from v2 of our API Product.

Test our API using gRPCurl

Now that we have added v2 of our API Product to an Environment, we should be able to make client requests to the gRPC service.

Let’s get the address of the Gateway, if you didn’t already set these in the Getting Started guide. Choose the option corresponding to your Ingress Service Type:

export INGRESS_HOST=$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export INGRESS_PORT=$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.spec.ports[?(@.name=="http")].port}')
export INGRESS_HOST=$(kubectl get po -l gloo=gateway-proxy -n gloo-system -o jsonpath='{.items[0].status.hostIP}')
export INGRESS_PORT=$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}')
export INGRESS_HOST=127.0.0.1
export INGRESS_PORT=$(kubectl -n gloo-system get service gateway-proxy -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}')

With the Ingress address, we can now try to call one of our published operations. You’ll need to use the API Key you generated for your user:

grpcurl -plaintext -H "api-key: {your-api-key}" -authority api.example.com ${INGRESS_HOST}:${INGRESS_PORT} test.solo.io.PetStore/ListPets

We should see the output:

{
  "pets": [
    {
      "id": "1",
      "name": "Dog",
      "tags": [
        "puppy"
      ]
    },
    {
      "id": "2",
      "name": "Cat"
    }
  ]
}

Great! We’ve just seen how the Gloo Portal can publish a gRPC API on Gloo Edge without you needing to directly configure those resources. Let’s now see how to expose our APIs to developers using the Portal resource.

Testing the gRPC service from the Portal

Let’s take a look at the Portal you created in the Getting Started guide.

kubectl get portal -n default petstore-portal -oyaml
apiVersion: devportal.solo.io/v1alpha1
kind: Portal
# ...
status:
  observedGeneration: 1
  publishedEnvironments:
  - apiProducts:
    - name: petstore-product
      namespace: default
    name: dev
    namespace: default
  state: Succeeded

Since it already contains the entire API Product with our gRPC service, it’s already configured to display the gRPC methods in the UI.

Let’s take a look at our APIs in the Portal UI in the browser.

open $(echo http://petstore.example.com:${INGRESS_PORT}/apis/)

Click on the gRPC version of your API to view the methods.

It should look something like this: Portal UI

“Try it out” won’t yet work for this guide because Kubernetes does not know how to resolve the api.example.com host name.

To get it working, we’ll modify the default DNS setup by editing the CoreDNS ConfigMap such that requests sent to api.example.com will be rerouted to our gateway:

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        rewrite name exact api.example.com gateway-proxy.gloo-system.svc.cluster.local
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
EOF

Note that you may have to wait about 30 seconds for the DNS setup to refresh, due to caching.

While we’re waiting, since we have an API Key, we are first going to click on Authorize to add that key to the test commands, as we did in the Users and Groups guide.

In the Available Authorizations pop-up, enter the API Key you generated for your user and click Authorize.

Petstore Auth Page

Now we are ready to test the API commands with an authorized key. Expand the Unary /test.solo.io.PetStore/ListPets method and click on the Try it out button in the top right corner and then on Execute below.

You should see something like this: Portal UI

At the moment, we only support the "Try it out" feature for Unary methods.

Congratulations! Your gRPC service is ready to use with Gloo Portal!

Next steps

Please see the guides section for guides on using more advanced features of the Gloo Portal.

Questions

For any questions using the Gloo Portal, please visit the Solo.io slack channel at https://slack.solo.io.

If you’d like to report an issue or bug, please see the Gloo Portal Issues Repository on GitHub.