Envoy as an API Gateway

The story is about building a gRPC microservices with Bazel, deploying it into the k8s cluster, and enabling RESTful API with Envoy.

Intro

Developers use RESTful APIs a lot. They are well-defined and well-known. There are many ways to implement it on the backend, and backend developers surely know how to do it right, while most frontend developers I’ve worked with prefer REST to talk to the backend.

Time goes by, new technologies appear here and there, and people start using them. For instance, the reasons to use gRPC and Protobuf on the backend are performance, faster prototyping, and automation. Backend can benefit from using it, while for frontend, it’s not so evident. gRPC support on the frontend side is not satisfying, and not all developers want to use it.

And here we have a dilemma: how to use gRPC on the backend and REST on the frontend?

So, our options are:

  • Build and support both gPRC and REST servers on the backend.
  • Build gRPC server on backend and force frontend devs to use it. (It’s a hard way, don’t do that.)
  • Build gRPC server and generate REST API with grpc-gateway.
  • Build gRPC server and “turn on” REST API with Envoy proxy.

Wait a sec? What does it mean “to turn on REST API”? - you’ll ask. Well, Envoy can transcode incoming HTTP requests into gRPC ones and vice versa on the fly. For that, it needs just an appropriate configuration file that explains how this transcoding will happen.

If you want to jump into code right away, you can find a working example here.

What’s the plan?

We’re going to build this:

Plan

We have a Kubernetes cluster with two services inside:

  • service-one is a gRPC server written in Go. It exposes container port 5000, with mapping onto port 55000 on localhost.
  • Envoy proxy, obvious, right? It knows about service-one, based on configuration and exposes HTTP endpoints:
    • on port 8080 - service-one.
    • on port 8081 - Envoy admin panel.

Inside the cluster between Envoy and service-one, we have a gRPC connection only, while we can talk to Envoy from outside by HTTP and reach service-one. For debug purposes only, the gRPC server port is also mapped onto localhost, where we can request it with grpcurl. Faded blocks will come to play later and we’ll talk about them in Authentication and authorization part.

Outcome

After we all set up, we will be able to request service-one from outside Kubernetes cluster with HTTP and gRPC requests like this:

% grpcurl -plaintext -d '{"name": "Bazel"}' \
    127.0.0.1:55000 svc.ServiceOne.Hello
{
  "msg": "Hello, Bazel"
}

% curl -H "token: abc" http://localhost:8080/v1/hello?name=Bazel
{
 "msg": "Hello, Bazel"
}

As a result, we will produce the following artifacts:

  • Docker image with Go binary inside (service-one).
  • Docker image with Envoy proxy, configuration file, and proto descriptor inside.
  • Kubernetes objects: services and deployments.

Major components

In this post I’ll be using the following component/tools. For some of them like Envoy proxy and Bazel version are somewhat important, for others they are not.

ComponentVersion
Bazel5.4.1
Bazeliskany
Tilt0.33.1
Golang1.20.5
Envoy proxy1.26.2
Kubernetes (minikube)any
Carvel Ytt0.45.0
rules_ytt0.1.0

See you the next part.