API Gateways in Kubernetes

Experiments with Ambassador API Gateway

There are several ways to expose your Kubernetes service to the outside world. But, if you have worked with Kubernetes much you’ll have noticed that the LoadBalancer/IngressController pairing does not provide the flexibility many of us have come to appreciate in full-featured API gateways or Reverse Proxies. Nor does it always integrate well into a declarative infrastructure.

I have been experimenting recently with two native K8S API gateways: Ambassador and Gloo. There are others as well (Contour, NginxPro) but these two are open source and both are built around envoy sidecars which means that they fit into the paradigm I want my environments to use. Both are easy to use and configure and are reasonably well documented. Ambassador seems to play nicer with my Istio Service Mesh so I’ll start with that one.

Why use an API gateway? Often you will want to manage L7 traffic at the perimeter, e.g. before it enters your cluster. Another use case is to direct incoming requests to services in order to restrict or control access to the cluster. You can think of an API gateway as a way to manage North-South traffic. (A Service Mesh, using this metaphor, controls East-West traffic.

Ambassador is available at https://getambassador.io and as of this writing is at version 0.52. It integrates with your K8S services via annotations. I ran it on a GKE cluster and by following the online setup, I had a simple API gateway up and running in less than 5 minutes. You can even run it against a working system by setting the service to LoadBalancer which will create an LB separate from your ingress controller and available to test without breaking anything.

Ambassador lets you add configuration statements to your system via Annotations in the service declaration. Here is a simple example for an httpbin service mapping the incoming call on :80 to :8000.

  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v1
      kind:  Mapping
      name:  httpbin_mapping
      prefix: /
      service: httpbin:8080

You can also define two mappings in the same service. In this example, I want to mirror traffic to two different locations (in this case to test a new cluster in GKE) to make sure it works as expected and the same as the locally hosted cluster I needs only separate the declarations in the same way you would in any yaml file using —.

annotations:
  getambassador.io/config: |
    ---
    apiVersion: ambassador/v1
    kind: Mapping
    name: httpbin_mapping
    prefix: /headers
    service: httpbin:8000
    ---
    apiVersion: ambassador/v1
    kind:  Mapping
    name:  httpbin_shadow_mapping
    prefix: /headers
    service: k8s-remote-gke.com:80
    shadow: true

The above sends all incoming traffic to both httpbin locally and forwards a shadow copy of the traffic to some external site (k8s-remote-gke.com). It could as easily be another local service. The point is that traffic from the shadow service never responds. This allows an efficient forking test of your service.