Envoy had grpc-web support ealy on. It’s used in the official grpc-web
tutorial docs.
In the envoy
config
of this tutorial, both grpc & grpcWeb are exposed via a single listener
port.
This is nice model when you have a production deployment in which some clients
(mobile apps) want to speak to the GRPC protocol directly, but web apps want to
go down to http/1.1 to work with browsers’ xhr or fetch requests.
In production, we use istio as our gateway, but we have always struggled to utilise one single port to proxy both GRPC and GRPC-Web requests, even though envoy - the underlying proxy supports it very well. I suppose it’s partially due to the lack of documentation out there and the GRPC-Web community is still relatively small.
As we adopted Istio & GRPC-Web quite early on, we used what some other bloggers suggested: using a sidecar to convert HTTP1.1 request to GRPC and use a separate gateway dedicated to GRPC. The solution roughly looks like this:
For grpc
gateway:31400
-> app
For grpc-web
gateway:443
-> pod sidecar
-> app
This has a few issues:
- We’re adding an extra open port to outside world: more attacking surface
- The new grpc-only port doesn’t have TLS or needs a separate TLS config
- We added a sidecar pod for every grpc service that we want to expose as GRPC-Web: wasted resources when we don’t need service mesh
This is definitely not right when evnoy supports the use case so well, but why does it feels so difficult on Istio’s side?
I finally sat down today and look at this from scratch: We have an effective envoy config, so we just need to configure istio such that the gateway envoy of istio has the most important elements that supports both GRPC & GRPC-Web. Namely:
- on the listener config we need the filter envoy.filters.http.grpc_web
- on the clusters config we need the http2_protocol_options
And we definitely don’t need the sidecar for this: sidecar is for east-west traffic, not north-south traffic. So the filter needs to be on the gateway, not the sidecar.
Well, it turns out it’s still in the same
EnvoyFilter
documentation. We just need to 1) configure the filter on the gateway; 2) use
grpc-web
as the protocol in the service. (I believe grpc
works just as
well)
The solution Link to heading
gateway:443
-> app
for both grpc
& grpc-web
envoy filter
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: gateway-grpc-web-filter
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
# importantly we're patching to the GATEWAY envoy, not sidecar
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
# apply the patch before the cors filter, just like the one in
# grpc-web example
name: "envoy.filters.http.cors"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.grpc_web
virtual service
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: hello-grpc
namespace: default
spec:
hosts:
- "*"
gateways:
- httpbin-gateway
http:
- route:
- destination:
host: hello-grpc
port:
number: 12345
match:
- uri:
prefix: /main.HelloService/
And importantly the service needs to have grpc
in the service name or use
appProtocol
:
ref
apiVersion: v1
kind: Service
metadata:
name: hello-grpc
namespace: default
spec:
selector:
app: hello-grpc
type: LoadBalancer
ports:
- name: grpc # name has to start with grpc, alternatively use appProtocol below
port: 12345
# or use appProtocol for Kubernetes 1.18+
# appProtocol: grpc
Full example on github. There are some useful debug commands that helped.