Fixes #12, #14: added the capability to sync the `api-gateway-config` folder from the cloud ( S3 for now )
4 files changed
tree: 4acaab0337d6e34106067275a673a1f0f8148fbb
  1. api-gateway-config/
  2. .gitignore
  3. Dockerfile
  4. init.sh
  5. LICENSE
  6. Makefile
  7. README.md
README.md

apigateway

A performant API Gateway based on Openresty and NGINX.

Quick start

docker run --name="apigateway" \
            -p 80:80 \
            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
            -e "LOG_LEVEL=info" \
            adobeapiplatform/apigateway:latest

This command starts an API Gateway that automatically discovers the services running in Marathon. The discovered services are exposed on individual VHosts as you can see in the config file.

Accessing a Marathon app locally

For example, if you have an application named hello-world you can access it on its VHost in 2 ways:

  1. Edit /etc/hosts and add <docker_host_ip> hello-world.api.localhost then browse to http://hello-world.api.localhost
  2. Sending the Host header in a curl command: curl -H "Host:hello-world.api.localhost" http://<docker_host_ip>

The discovery script is provided as an example for a quick-start and it can be replaced with your favourite discovery mechanism. The script updates a configuration file containing all the NGINX upstreams that are used in the config file.

Accessing a Marathon app remotely

All you need to do is to create a DNS entry like *.api.example.com or *.gw.example.com and have it resolve to the nodes running the Gateway.

Assuming there is an application deployed in Marathon called hello-world it can be accessed at the URL: http://hello-world.api.example.com The Gateway is automatically proxying the request to the hello-world application in Marathon.

If you call http://my-custom-app.api.example.com the Gateway will proxy to a Marathon app named my-custom-app. If the application is not deployed in Marathon a 5xx error is returned back to the client. This behaviour is ofcourse configurable.

Running with a different configuration folder

One way to simplify the configuration of the Gateway is to copy the api-gateway-config folder in S3, having all nodes syncing from that location. For the moment only AWS S3 is supported but the plan is to add support for other clouds too.

In order to work with S3 the Gateway needs to know the bucket. The command is similar to the one above with an extra environment variable named REMOTE_CONFIG

docker run --name="apigateway" \
            -p 80:80 \
            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
            -e "REMOTE_CONFIG=s3://api-gateway-config-bucket" \
            -e "LOG_LEVEL=info" \
            adobeapiplatform/apigateway:latest

s3://api-gateway-config-bucket should contain something similar to what‘s in the api-gateway-config folder. For any customizations it would be good to start from there. There are only 2 files that won’t be synchronised:

  • /etc/api-gateway/conf.d/includes/resolvers.conf . Read bellow for more info
  • https://github.com/adobe-apiplatform/apigateway/blob/master/api-gateway-config/environment.conf.d/api-gateway-upstreams.http.conf which is automatically generated by the Marathon Discovery script.

By default the access to the S3 bucket is done using IAM roles, but AWS Credentials can also be specified through environment variables:

docker run --name="apigateway" \
            -p 80:80 \
            -e "MARATHON_HOST=http://<marathon_host>:<port>" \
            -e "REMOTE_CONFIG=s3://api-gateway-config-bucket" \
            -e "AWS_ACCESS_KEY_ID=--change-me--" \
            -e "AWS_SECRET_ACCESS_KEY=--change-me--" \
            -e "LOG_LEVEL=info" \
            adobeapiplatform/apigateway:latest

By default the remote config is checked for changes every 10s and it's configurable through the REMOTE_CONFIG_SYNC_INTERVAL env variable. There are a few things to take into consideration when changing this value:

  • How often the configs really change
  • Cost. Some tools may make 1 API request per file to compare it. I.e. in S3 72 config files checked every 10s costs $7.46 but when checked every 30s it's only $2.4, times number of GW nodes.
  • Average time for an API Request. When reloading the GW the existing NGINX processes handling active connections are kept in the background until the request completes. So reloading the Gateway too fast may have the side effect of keeping too many processes running at the same time. This may, or may not be a problem but it's good to be aware of it.

Resolvers

While starting up this container automatically creates the /etc/api-gateway/conf.d/includes/resolvers.conf config file using /etc/resolv.conf as the source. To learn more about the resolver directive in NGINX see the docs.

Running the API Gateway outside of Marathon and Mesos

Besides the discovery part which is dependent on Marathon at the moment, the API Gateway can run on its own as well. The Marathon service discovery is activated with the -e "MARATHON_HOST=http://<marathon_host>:<port>/".

Developer guide

To build the docker image locally use:

 make docker

To SSH into the newly built image use ( note that this is not the running image):

 make docker-ssh

Running and Stopping the Docker image

 make docker-run

The main API Gateway process is exposed to port 80. To test that the Gateway works see its health-check:

 $ curl http://<docker_host_ip>/health-check
   API-Platform is running!

If you're up for a quick performance test, you can play with Apache Benchmark via Docker:

 docker run jordi/ab ab -k -n 200000 -c 500 http://<docker_host_ip>/health-check

To run docker mounting the local api-gateway-config directory into /etc/api-gateway/ issue:

$ make docker-debug

In debug mode the docker container starts a special api-gateway compiled --with-debug providing very detailed debugging information. When started with -e "LOG_LEVEL=info" the output is quite verbose. To learn more about this option visit NGINX docs.

When done stop the image:

make docker-stop

SSH into the running image

make docker-attach

Running the container in Mesos with Marathon

Make an HTTP POST on http://<marathon-host>/v2/apps with the following payload. For optimal performance leave the network on HOST mode. To learn more about the network modes visit the Docker documentation.

{
  "id": "api-gateway",
  "container": {
    "type": "DOCKER",
    "docker": {
      "image": "adobeapiplatform/apigateway:latest",
      "forcePullImage": true,
      "network": "HOST"
    }
  },
  "cpus": 4,
  "mem": 4096.0,
  "env": {
    "MARATHON_HOST": "http://<marathon_host>:<marathon_port>"
  },
  "constraints": [  [ "hostname","UNIQUE" ]  ],
  "ports": [ 80 ],
  "healthChecks": [
    {
      "protocol": "HTTP",
      "portIndex": 0,
      "path": "/health-check",
      "gracePeriodSeconds": 3,
      "intervalSeconds": 10,
      "timeoutSeconds": 10
    }
  ],
  "instances": 1
}

To run the Gateway only on specific nodes marked with slave_public you can add the property bellow to the main JSON object:

"acceptedResourceRoles": [ "slave_public" ]
Auto-discover and register Marathon tasks in the Gateway

To enable auto-discovery in a Mesos with Marathon framework define the following Environment Variables:

MARATHON_URL=http://<marathon-url-1>
MARATHON_TASKS=ws-.* ( NOT USED NOW. TBD IF THERE'S A NEED TO FILTER OUT SOME TASKS )

So the Docker command is now :

docker run --name="apigateway" \
            -p 8080:80 \
            -e "MARATHON_HOST=http://<marathon_host>:<port>/" \
            -e "LOG_LEVEL=info" \
            adobeapiplatform/apigateway:latest