Emissary-ingress makes it easy to access your services from outside your application. This includes gRPC services, although a little bit of additional configuration is required: by default, Envoy connects to upstream services using HTTP/1.x and then upgrades to HTTP/2 whenever possible. However, gRPC is built on HTTP/2 and most gRPC servers do not speak HTTP/1.x at all. Emissary-ingress must tell its underlying Envoy that your gRPC service only wants to speak to that HTTP/2, using the
grpc attribute of a
There are many examples and walkthroughs on how to write gRPC applications so that is not what this article will aim to accomplish. If you do not yet have a service written you can find examples of gRPC services in all supported languages here: gRPC Quickstart
This document will use the gRPC python helloworld example to demonstrate how to configure a gRPC service with Emissary-ingress.
Follow the example up through Run a gRPC application to get started.
After building our gRPC application and testing it locally, we need to package it as a Docker container and deploy it to Kubernetes.
To run a gRPC application, we need to include the client/server and the protocol buffer definitions.
For gRPC with python, we need to install
grpcio and the common protos.
Create the container and test it:
<docker_reg> is your Docker user or registry.
Switch to another terminal and from the same directory, run the
greeter_client. The output should be the same as running it outside of the container.
Once you verify the container works, push it to your Docker registry:
Mappings are based on URL prefixes; for gRPC, the URL prefix is the full-service name, including the package path (
package.service). These are defined in the
.proto definition file. In the example proto definition file we see:
so the URL
helloworld.Greeter and the mapping would be:
grpc: true line - this is what tells Envoy to use HTTP/2 so the request can communicate with your backend service. Also note that you'll need
rewrite the same here, since the gRPC service needs the package and service to be in the request to do the right thing.
The Host is declared here because we are using gRPC without TLS. Since Emissary-ingress terminates TLS by default, in the Host we add a
requestPolicy which allows insecure connections. After adding the Emissary-ingress mapping to the service, the rest of the Kubernetes deployment YAML file is pretty straightforward. We need to identify the container image to use, expose the
containerPort to listen on the same port the Docker container is listening on, and map the service port (80) to the container port (50051).
For more information on insecure routing, please refer to the
Once you have the YAML file and the correct Docker registry, deploy it to your cluster with
Make sure to test your Kubernetes deployment before making more advanced changes (like adding TLS). To test any service with Emissary-ingress, we will need the hostname of the running Emissary-ingress service which you can get with:
Which should return something similar to:
EXTERNAL-IP is the
$AMBASSADORHOST and 80 is the
You will need to open the
greeter_client.py and change
After making that change, simply run the client again and you will see the gRPC service in your cluster respond:
There is some extra configuration required to connect to a gRPC service through Emissary-ingress over an encrypted channel. Currently, the gRPC call is being sent over cleartext to Emissary-ingress which proxies it to the gRPC application.
If you want to add TLS encryption to your gRPC calls, first you need to tell Emissary-ingress to add ALPN protocols which are required by HTTP/2 to do TLS.
Next, you need to change the client code slightly and tell it to open a secure RPC channel with Emissary-ingress.
grpc.ssl_channel_credentials(root_certificates=None, private_key=None, certificate_chain=None)returns the root certificate that will be used to validate the certificate and public key sent by Emissary-ingress. The default values of
None tells the gRPC runtime to grab the root certificate from the default location packaged with gRPC and ignore the private key and certificate chain fields. Generally, passing no arguments to the method that requests credentials gives the same behavior. Refer to the languages API Reference if this is not the case.
Emissary-ingress is now terminating TLS from the gRPC client and proxying the call to the application over cleartext.
If you want to configure authentication in another language, gRPC provides examples with proper syntax for other languages.
Some gRPC clients automatically include the port in the Host header. This is a problem when using TLS because the certificate will match
myurl.com but the Host header will be
myurl.com:443, resulting in the error
rpc error: code = Unimplemented desc =. If you run into this issue, there are two ways to solve it depending on your use case, both using the Module resource.
The first is to set the
strip_matching_host_port property to
true. However, this only works if the port in the header matches the port that Envoy listens on (8443 by default). In the default installation of Emissary-ingress, the public port is 443, which then maps internally to 8443, so this only works for custom installations where the public service port matches the port in the Listener resource.
The second solution is to use the following Lua script, which always strips the port:
Emissary-ingress can originate TLS with your gRPC service so the entire RPC channel is encrypted. To configure this, first get some TLS certificates and configure the server to open a secure channel with them. Using self-signed certs this can be done with OpenSSL and adding a couple of lines to the server code.
Rebuild your docker container making sure the certificates are included and follow the same steps of testing and deploying to Kubernetes. You will need to make a small change to the client code to test locally.
Once deployed we will need to tell Emissary-ingress to originate TLS to the application.
We need to tell Emissary-ingress to route to the
service: over https and have the service listen on
443. We also need to tell Emissary-ingress to use ALPN protocols when originating TLS with the application, the same way we did with TLS termination. This is done by setting
alpn_protocols: ["h2"] in a
TLSContext telling the service to use that tls-context in the mapping by setting
Refer to the TLS document for more information on TLS origination.
gRPC services use HTTP/2 headers. This means that some header-based routing rules will need to be rewritten to support HTTP/2 headers. For example,
host: subdomain.host.com needs to be rewritten using the
headers: attribute with the
Some Kubernetes ingress controllers do not support HTTP/2 fully. As a result, if you are running Emissary-ingress with an ingress controller in front, you may find that gRPC requests fail even with correct Emissary-ingress configuration.
A simple way around this is to use Emissary-ingress with a
LoadBalancer service, rather than an Ingress controller. You can also consider using Emissary-ingress as your Ingress Controller.
As with any
Mapping, your gRPC service's
Mapping may include a
Some gRPC client libraries produce requests where the
:authority header includes the port number. For example, a request to the above service might include
host: api.example.com:443 instead of just
host: api.example.com. To avoid having Emissary-ingress return a 404 (not found) response to these requests due to the mismatched host, you may want to set
strip_matching_host_port in the Ambassador module.
Alternately, you may find it cleaner to make sure your gRPC client does not include the port in the
host header. Here is an example using gRPC/Go.
Emissary-ingress also supports the gRPC-Web protocol for browser-based gRPC applications.