Configuring Tyk for Eureka Service Discovery with autoscaling

Hi,

I’m using Tyk as gateway for some microservices in AWS using Eureka as service discovery. One problem I have is that we use autoscaling where we scale the number of microservice instances in AWS from one to many, depending on load. When there are many instance, eureka will return a json with a list of instances, like this :

{
  "application": {
    "name": "ROUTE",
    "instance": [
      {
        "hostName": "ip-172-31-57-136",
        ...
      },
      ...
      {
        "hostName": "ip-172-31-13-37",
        ...
      }
    ],
    ...
  }
}

but if we only have one instance, eureka will return json like this, i.e. no list:

{
  "application": {
    "name": "ROUTE",
    "instance": {
      "hostName": "ip-172-31-57-136",
      ...
    },
    ...
  }
}

In the Tyk API, it doesn’t seem possible to set up something than handles both cases dynamically, since if it’s set up to receive a list, it will fail when it doesn’t receive one, and vice versa. Or is there something I’m missing?

If it’s not possible, are there any workarounds, e.g. with endpoint designer?

For reference, the service discovery configuration I’m using for multiple instances :

{
  ...
  "proxy": {
    ...
    "service_discovery": {
      "use_discovery_service": true,
      "query_endpoint": "http://X.X.X.X/eureka/",
      "use_nested_query": false,
      "parent_data_path": "application.instance",
      "data_path": "hostName",
      "port_data_path": "",
      "target_path": "",
      "use_target_list": true,
      "cache_timeout": 60,
      "endpoint_returns_list": false
    }
  },
  ...
}

versus the configuration I use for a single instance:

{
  ...
  "proxy": {
    ...
    "service_discovery": {
      "use_discovery_service": true,
      "query_endpoint": "http://X.X.X.X/eureka/",
      "use_nested_query": false,
      "parent_data_path": "",
      "data_path": "application.instance.hostName",
      "port_data_path": "",
      "target_path": "",
      "use_target_list": false,
      "cache_timeout": 60,
      "endpoint_returns_list": false
    }
  },
  ...
}

That is going to be very tricky. You could add a transform template to the endpoint that returns the Eureka data, then force the hostname object to be converted to a single-host list.

You might be able to do this with the golang template conversion, since your response is JSON you should be able to write some logic that transforms the response.

Ok, I managed to get it to work with a transform template like this:

{
  "application" : {
    "instance" : [
      {{ if (eq "map[string]interface {}" (printf "%T" .application.instance)) }}
        {{/* single instance */}}
        { "hostName" : "{{ .application.instance.hostName }}" }
      {{ else if (eq "[]interface {}" (printf "%T" .application.instance)) }}
        {{/* multiple instances */}}
        {{ range $index, $element := .application.instance }}
          {{ if $index }}
            , { "hostName" : "{{ $element.hostName }}" }
          {{ else }}
            { "hostName" : "{{ $element.hostName }}" }
          {{ end }}
        {{end}}
      {{ else }}
        {{/* no instances! */}}
      {{ end }}			
    ]
  }
}

In the case where there are no instances currently available (i.e. service discovery returns an empty list), is it somehow possible to make the API (that depends on the service discovery) return a 503 or something similar instead of failing outright?

1 Like

Wow, nice one :slight_smile:

There’s no provision for handling an empty list yet, but it’s a good idea so will go on the roadmap.