Microservices architecture using Traefik as reverse proxy and Ambassador as gateway

Dooley
5 min readDec 27, 2019

--

A cloud native application tutorial using traefik as reverse-proxy and Ambassador as api gateway with Docker and Docker-Compose.

Photo The Gaspé Peninsula by Dooley

Microservices architecture is now one of the pattern commonly used to build an ambitious application. Microservices architecture is an architectural principle where the entire application is divided and designed as loosely-coupled, independent services modeled around a business domain.

Microservices, as an architectural approach, brings some benefits compared to a monolith architecture. But, microservices design comes also with a lot of challenges once is implemented.
Two of the seven principles followed by Microservices architectures are among others:
- Hide implementation details: meaning the technical details are encapsulated within the service boundary.
- Highly decentralized: services are developed to shared nothing between them. The goal is to decrease coupling as much as possible even if this implies code duplication.

In this tutorial, we will implement the reverse proxy and gateway to respect those two principles.
In the example, we are going to use Traefik as a reverse-proxy and Ambassador as an api gateway.

The high level architecture of our solution looks like below.
We will use ambassador to receive requests coming from public clients through traefik reverse proxy. The api gateway will be hidden as a private service where traefik will be a public front service.
All services: API Gateway, Service A, Service B, Service C,… will be private.

High level architecture

Considerations

The ssl certificate for HTTPS can be applied at the reverse proxy level. And Traefik has a native support of Let’s Encrypt is a free open Certificate Authority.
The authentication or authorization can also be applied at the gateway level if needed.

Prerequisites

Docker and Docker-Compose installed, admin permission to edit host file can be necessary. We will use for testing the hostname “localhost.com”. Our working dire is “tuto”.

Traefik setup with docker-compose

Inside the working directory we can create a docker compose file and configure traefik as reverse proxy. We are using, in this tutorial, traefik version 2.0.2. Traefik will be exposed on ports 80 and the traefik api dashboard on 8081.

$mkdir tuto && cd tuto
$touch docker-compose.yml

Here the content of compose file docker-compose.yml.

version: '3.4'

services
:
traefik:
image: traefik:v2.0.2
ports:
- "80:80"
- "443:443"
- "8081:8080"
labels
:
- "traefik.http.routers.traefik.rule=Host(`localhost.com`)"
container_name
: traefik
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik/traefik.toml:/etc/traefik/traefik.toml
- ./traefik/log:/var/log
networks:
- external-proxy
- internal-proxy
networks:
external-proxy:
external: true
internal-proxy:
external: false

We can add the traefik configuration file as requested in volume section line 2.

$mkdir traefik && cd traefik && touch traefik.toml

Here is the configuration file of traefik exposing an entry point on the port 80.

#traefik/traefik.toml[providers.docker]
endpoint = "unix:///var/run/docker.sock"

[api]
insecure = true
dashboard = true
debug = true

[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web-secure]
address = ":443"

[accessLog]
filePath = "/var/log/traefik/access.log"
[accessLog.filters]
statusCodes = "404"

We can run docker:

$cd .. && docker-compose up --build

We can check the configuration by going to http://localhost:8081 or http://localhost.com/8081 if you can add localhost.com in your host file. We should see something like this:

Ambassador api gateway configuration

Ambassador is an open source, Kubernetes-native microservices API gateway built on the Envoy Proxy.

Traefik will receive requests prefixed by api like in the examples below. And will strip the “api” prefix before sending request to the gateway.
- to call the ip service: http://localhost.com/api/ip
- to call the weather service: http://localhost.com/api/weather

We can update the docker-compose file like:

#docker-compose.yml...

gateway:
image: quay.io/datawire/ambassador:0.86.1
command: --port 8080
volumes:
- ./gateway-config:/ambassador/ambassador-config
environment:
- AMBASSADOR_NO_KUBEWATCH=no_kubewatch
labels:
- "traefik.http.routers.gateway.rule=Host(`localhost.com`) && PathPrefix(`/api`)"
- "traefik.http.middlewares.gateway-stripprefix.stripprefix.prefixes=/api"
- "traefik.http.routers.gateway.middlewares=gateway-stripprefix@docker"
- "traefik.http.routers.gateway.service=gateway"
- "traefik.http.services.gateway.loadbalancer.server.port=8080"
- "traefik.docker.network=external-proxy"
container_name
: gateway
networks:
- internal-proxy

We can add the ambassador configuration file as requested in the config file volumes section.

$mkdir gateway-config && cd gateway-config 
$touch mapping-api.yaml

Let add the content below to the file. For simplicity, we are using the same endpoint “https://httpbin.org” with two distinct paths: “public-ip” and “public-address”.


#gateway-config/mapping-api.yaml
---
apiVersion: ambassador/v1
kind: Mapping
name: public-ip
prefix: /public-ip/
rewrite: /
service: https://httpbin.org
---
apiVersion: ambassador/v1
kind: Mapping
name: public-address
prefix: /public-address/
rewrite: /
service: https://httpbin.org

Now let test this end to end configuration. We have to make a request from a public client with the prefix “/api/”.

$cd .. && docker-compose up
$curl -v http://localhost.com/api/public-ip/ip

The result should be like this:

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost.com (127.0.0.1) port 80 (#0)
> GET /api/public-ip/ip HTTP/1.1
> Host: localhost.com
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Date: Fri, 27 Dec 2019 01:03:29 GMT
< Referrer-Policy: no-referrer-when-downgrade
< Server: envoy
< X-Content-Type-Options: nosniff
< X-Envoy-Upstream-Service-Time: 125
< X-Frame-Options: DENY
< X-Xss-Protection: 1; mode=block
< Content-Length: 68
<
{
"origin": "172.27.0.1,172.28.0.3, 192.222.238.37, 172.27.0.1"
}
* Connection #0 to host localhost.com left intact

And for the other service endpoint:

$curl -v http://localhost.com/api/public-address/ip

The response should be like this:

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost.com (127.0.0.1) port 80 (#0)
> GET /api/public-address/ip HTTP/1.1
> Host: localhost.com
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Date: Fri, 27 Dec 2019 01:03:38 GMT
< Referrer-Policy: no-referrer-when-downgrade
< Server: envoy
< X-Content-Type-Options: nosniff
< X-Envoy-Upstream-Service-Time: 123
< X-Frame-Options: DENY
< X-Xss-Protection: 1; mode=block
< Content-Length: 68
<
{
"origin": "172.27.0.1,172.28.0.3, 192.222.238.37, 172.27.0.1"
}
* Connection #0 to host localhost.com left intact

Et voila…

If you have any trouble with getting it working do not hesitate to ask for help.
If you like it, please -> 👏

References:

https://microservices.io/patterns/microservices.html

https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04

https://www2.hazelcast.com/microservices-springboot?gclid=Cj0KCQiA0ZHwBRCRARIsAK0Tr-pR4N4tisq8OPO9vtBCDJQ9zqz4vKa2J61iaJ5VRv3A13yTpyiwVOMaAh9SEALw_wcB

--

--

Dooley

Software lover, clean architeture/software supporter