This is the "latest" release of Envoy Gateway, which contains the most recent commits from the main branch.
This release might not be stable.
Please refer to the /docs documentation for the most current information.
gRPC-JSON Transcoding
5 minute read
This task shows how to use HTTPRouteFilter to transcode JSON/HTTP requests into gRPC calls, so REST clients can talk to a gRPC backend without a separate gateway service.
Envoy derives the mapping from the google.api.http options in your protobuf definitions, so the JSON paths a client
calls are the ones already declared in the .proto.
The filter is only supported at the rule level of an HTTPRoute. Referencing it from a GRPCRoute or from
a backendRef makes the route unresolvable: the incoming request has to be JSON/HTTP for there to be anything to
transcode, and a backendRef filter has no route table on which to enable the transcoder.
Prerequisites
Follow the steps below to install Envoy Gateway and the example manifest. Before proceeding, you should be able to query the example backend using HTTP.
Expand for instructions
Install the Gateway API CRDs and Envoy Gateway using Helm:
Gateway API CRD compatibilityThis command installs Gateway API CRDs. If your Kubernetes provider already manages compatible Gateway API CRDs for the cluster, use the provider-managed Gateway API CRD install steps instead.
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v0.0.0-latest -n envoy-gateway-system --create-namespaceInstall the GatewayClass, Gateway, HTTPRoute and example app:
kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/latest/quickstart.yaml -n defaultVerify Connectivity:
Get the External IP of the Gateway:
export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}')Curl the example app through Envoy proxy:
curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/getThe above command should succeed with status code 200.
Get the name of the Envoy service created the by the example Gateway:
export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}')Get the deployment of the Envoy service created the by the example Gateway:
export ENVOY_DEPLOYMENT=$(kubectl get deploy -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}')Port forward to the Envoy service:
kubectl -n envoy-gateway-system port-forward service/${ENVOY_SERVICE} 8888:80 &Curl the example app through Envoy proxy:
curl --verbose --header "Host: www.example.com" http://localhost:8888/getThe above command should succeed with status code 200.
Generate the proto descriptor
The transcoder needs a binary FileDescriptorSet describing your services. Generate it with --include_imports, which
bundles the files your protos import — without them Envoy cannot build a descriptor pool and rejects the configuration
with only Unable to build proto descriptor pool to go on.
protoc --include_imports --descriptor_set_out=proto-descriptor.pb path/to/your.proto
Store it in a ConfigMap in the same namespace as the HTTPRoute. --from-file puts the bytes in binaryData, which the
transcoder reads as-is:
kubectl create configmap greeter-proto-descriptor --from-file=proto-descriptor=proto-descriptor.pb
The key proto-descriptor is used when present; a binaryData entry is also found when it is the ConfigMap’s only one.
A data entry is accepted too, but it must be base64-encoded and must use the proto-descriptor key.
Reach the backend over HTTP/2
What leaves the transcoder is gRPC, which requires HTTP/2 upstream. On an HTTPRoute the upstream protocol defaults to
HTTP/1.1, so the backend has to declare HTTP/2 explicitly — either appProtocol: kubernetes.io/h2c on the Service port,
or appProtocols: [gateway.envoyproxy.io/h2c] on a Backend. Nothing in the route status reports the mismatch: the
route stays Accepted and every transcoded request fails at runtime.
apiVersion: v1
kind: Service
metadata:
name: grpc-service
spec:
selector:
app: grpc-service
ports:
- name: grpc
protocol: TCP
port: 9000
targetPort: 9000
appProtocol: kubernetes.io/h2c
Configuration
Create an HTTPRouteFilter that points at the ConfigMap, and reference it from the HTTPRoute rule that receives the
JSON traffic. matchIncomingRequestRoute: true keeps the request on the route that matched it, so one rule is enough:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: HTTPRouteFilter
metadata:
name: grpc-transcoder
spec:
grpcJSONTranscoder:
protoDescriptor:
valueRef:
group: ""
kind: ConfigMap
name: greeter-proto-descriptor
services:
- example.Greeter
matchIncomingRequestRoute: true
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grpc-route
spec:
parentRefs:
- name: eg
rules:
# The JSON path clients call, from the google.api.http option on SayHello.
- matches:
- path:
type: PathPrefix
value: /v1/hello
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: HTTPRouteFilter
name: grpc-transcoder
backendRefs:
- name: grpc-service
port: 9000
Then call the JSON path:
curl -H "Host: grpc.example.com" http://${GATEWAY_HOST}/v1/hello/world
When services is omitted, every service declared by the descriptor’s own proto files is transcoded, excluding services
that come from imported files. Naming them explicitly is worth doing when the descriptor carries more than you want to
expose.
Routing the rewritten path separately
matchIncomingRequestRoute defaults to false, which is Envoy’s own default. In that mode the transcoder rewrites
:path to the gRPC method (/example.Greeter/SayHello) and Envoy matches the routing table again, so a route for the
rewritten path must exist or the request gets a 404.
Leave it unset when you want the gRPC method to route on its own terms — a different backend, a different timeout, or policies attached to a GRPCRoute. Either a second HTTPRoute rule or a GRPCRoute for the service satisfies the re-match:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grpc-route
spec:
parentRefs:
- name: eg
rules:
- matches:
- path:
type: PathPrefix
value: /v1/hello
filters:
- type: ExtensionRef
extensionRef:
group: gateway.envoyproxy.io
kind: HTTPRouteFilter
name: grpc-transcoder
backendRefs:
- name: grpc-service
port: 9000
# Serves the rewritten path. A GRPCRoute for example.Greeter works here too.
- matches:
- path:
type: PathPrefix
value: /example.Greeter
backendRefs:
- name: grpc-service
port: 9000
Only the rule receiving the JSON request needs the filter — the response is still transcoded back to JSON even when the
re-match lands on a route with no transcoder config, because Envoy resolves filter enablement once, against the route
matched at decodeHeaders.
Diagnosing a bad descriptor
The descriptor is parsed and validated when the route is translated, not when Envoy loads the listener. A descriptor that
is malformed, missing its imports, or does not declare a service you named in services makes the rule return 500 and
records the reason on the HTTPRoute’s Accepted condition:
kubectl get httproute grpc-route -o yaml
Clean-Up
Follow the steps from the Quickstart to uninstall Envoy Gateway and the example manifest.
kubectl delete httproutefilter/grpc-transcoder
kubectl delete configmap/greeter-proto-descriptor
Next Steps
Check out the Developer Guide to get involved in the project.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.