LinkerD and the API Gateway Pattern

Posted by Riomhaire Research on Tuesday, February 7, 2017

In modern microservices architecture the [API Gateway Pattern] (http://microservices.io/patterns/apigateway.html API Pattern Definition) is a common one for producing a single unified API for use by user interfaces and 3rd party applications. What this short article is about is how to achieve this with the Linkerd application in a simple way for developers and small companies. The result should produce a unified API that allows services to be clustered as well as providing some level of resilience and scalability.

This article assumes you know what the [API Gateway Pattern] (http://microservices.io/patterns/apigateway.html API Pattern Definition) is, how it is used and that you have already installed the LinkerD application. We will only cover how to configure LinkerD using the file-access naming provider to access services rather than one of the other mechanisms such as namerd which are commonly used.

The Scenario

Say we have three services:

  1. Service-1 which we want to access via a URL in the form ‘host:port/api/service-1/…’
  2. Service-2 which we want to access via a URL in the form ‘host:port/api/service-2/…’
  3. Service-3 which we want to access via a URL in the form ‘host:port/api/service-3/…’

Each of these services may have more than one instance over which calls could be made. It is important to note that is is ‘best practice’ in Restful services to have stateless API Services - IE no sessions. The reason for this is simple, all calls can be distributed across all the available service instances based on whatever routing algorithm deemed suitable: round-robin, load, call-response etc without the need to copy across session information. This increases both performance and scalability, as well as simplifying things no end.

A UI application can access the service instances with the API URL patterns mentioned above.

The Services

We have three services which have multiple instances running:

 Service      |     Host         | Port
------------- | ---------------- | -----
 service-1    |   192.168.1.10   | 1000
              |   192.168.1.11   | 1001
              |   192.168.1.12   | 1002
 service-2    |   192.168.1.20   | 1001
              |   192.168.1.21   | 1002
 service-3    |   192.168.1.30   | 1000

We will assume that these services are implemented and up and running.

LinkerD

LinkerD is an interesting application designed for many scenario’s, but is aimed clearly at the micro-services ecosystem. What we found however was although LinkerD has a lot of documentation, there is little documentation which details common usage scenarios and how to set them up. This is why we are writing this article.

Lets say we have installed LinkerD in the directory, we then for the above scenario and services perform the following steps:

  1. Create a directory called ‘api’ in the root directory.
  2. In the ‘api’ directory create a text file called ‘service-1’
  3. In the ‘api’ directory create a text file called ‘service-2’
  4. In the ‘api’ directory create a text file called ‘service-3’
  5. In the ‘service-1’ file add on a single line ‘192.168.1.10 1000’
  6. In the ‘service-1’ file add on a single line ‘192.168.1.11 1001’
  7. In the ‘service-1’ file add on a single line ‘192.168.1.12 1002’
  8. In the ‘service-2’ file add on a single line ‘192.168.1.20 1001’
  9. In the ‘service-2’ file add on a single line ‘192.168.1.22 1002’
  10. In the ‘service-3’ file add on a single line ‘192.168.1.30 1000’

we then need to create the actual Linkerd configuration file. Create one - say ’example.yaml’ (although you can call it whatever you like) and in it add the following:

admin:
  port: 9990

namers:
- kind: io.l5d.fs
  prefix: /api
  rootDir: api

routers:
- protocol: http
  identifier:
    kind: io.l5d.methodAndHost
    httpUriInDst: true
  baseDtab: |
   /http/1.1/*/*/api/service-1 => /#/api/service-1;
   /http/1.1/*/*/api/service-2 => /#/api/service-2;
   /http/1.1/*/*/api/service-3 => /#/api/service-3;
  httpAccessLog: logs/access.log
  label: int
  dstPrefix: /http
  servers:
  - port: 20202
    ip: 0.0.0.0
  client:
    retries:
      budget:
        minRetriesPerSec: 100
        percentCanRetry: 1.0
        ttlSecs: 15
      backoff:
        kind: jittered
        minMs: 10
        maxMs: 10000

If say LinkerD is running on the machine with name ‘company.com’, then we have defined that the admin interface is running on host/port ‘http://company.com:9990’ and that the API Gateway will be running on ‘http://company.com:20202’ when the LinkerD application is started.

So a service or UI wanting to access ‘service-1’ then they would need to make a GET/POST etc call to ‘http://company.com:20202/api/service-1' which would end up making a call on ‘/api/service-1’ in one of the service instances running on the hosts ‘192.168.1.10’, ‘192.168.1.10’ or ‘192.168.1.12’. If one of those instances go down then it will be recognized by LinkerD and calls won’t be forwarded to that instance until it is available again.

Essentially thats it - a functional API Gateway.