Last active
April 9, 2018 04:31
-
-
Save ddragosd/608bf8d3d13e3f688874 to your computer and use it in GitHub Desktop.
API Gateway setup in Mesos and Marathon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# PREREQUISITES: | |
# Setup a Mesos cluster and install Marathon framework | |
MARATHON_HOST="http://<marathon_host>/marathon" | |
# this could be an internal ELB that the Gateway nodes can use to auto-discover services | |
INTERNAL_MARATHON_HOST="http://<internal_marathon_host>/marathon" | |
# a wildcard domain configured with *.api.anydomain | |
API_DOMAIN="api.<my-domain>" | |
# ------------------------- | |
# install the API Gateway | |
# ------------------------- | |
# NOTE: in your setup you might not have Marathon started with acceptedResourceRoles=slave_public | |
# in which case you can remove that line enforcing it. However, it is recommended to have it set up | |
curl -X POST -H "Content-Type:application/json" ${MARATHON_HOST}/v2/apps?force=true --data ' | |
{ | |
"id": "api-gateway", | |
"container": { | |
"type": "DOCKER", | |
"docker": { | |
"image": "adobeapiplatform/apigateway:latest", | |
"forcePullImage": true, | |
"network": "HOST" | |
} | |
}, | |
"cpus": 4, | |
"mem": 4096.0, | |
"env": { "MARATHON_HOST": "'${INTERNAL_MARATHON_HOST}'" }, | |
"constraints": [[ "hostname","UNIQUE" ]], | |
"acceptedResourceRoles": ["slave_public"], | |
"ports": [ 80 ], | |
"healthChecks": [ { | |
"protocol": "HTTP", | |
"portIndex": 0, | |
"path": "/health-check", | |
"gracePeriodSeconds": 3, | |
"intervalSeconds": 10, | |
"timeoutSeconds": 10 | |
} ], | |
"instances": 1 | |
}' | |
# verify API Gateway installation | |
# NOTE: ${API_DOMAIN} should be in the shape of *.api.example.com and it should resolve to api-gateway instances | |
curl "http://api-gateway.${API_DOMAIN}/health-check" | |
# ... should yield | |
# API-Platform is running! | |
# LuaJIT-LuaJIT 2.1.0-alpha | |
# ----------------------------------- | |
# install a hello-world microservice | |
# ----------------------------------- | |
curl -X POST -H "Content-Type:application/json" ${MARATHON_HOST}/v2/apps?force=true --data ' | |
{ | |
"id": "hello-world", | |
"container": { | |
"type": "DOCKER", | |
"docker": { | |
"image": "tutum/hello-world", | |
"forcePullImage": true, | |
"network": "BRIDGE", | |
"portMappings": [ | |
{ | |
"containerPort": 80, | |
"hostPort": 0, | |
"protocol": "tcp" | |
} | |
] | |
} | |
}, | |
"cpus": 0.5, | |
"mem": 512, | |
"instances": 1 | |
}' | |
# verify Routing | |
curl "http://hello-world.${API_DOMAIN}/hello" | |
# ----------------------------------- | |
# API KEY MANAGEMENT W/ Redis | |
# ----------------------------------- | |
curl -X POST -H "Content-Type:application/json" ${MARATHON_HOST}/v2/apps?force=true --data ' | |
{ | |
"id": "api-gateway-redis", | |
"container": { | |
"type": "DOCKER", | |
"docker": { | |
"image": "redis:latest", | |
"forcePullImage": true, | |
"network": "HOST" | |
} | |
}, | |
"cpus": 0.5, | |
"mem": 1024.0, | |
"constraints": [ [ "hostname", "UNIQUE" ] ], | |
"ports": [ 6379 ], | |
"instances": 1 | |
}' | |
# ADD an API-KEY for the HELLO-WORLD service | |
# NOTE: this API SHOULD not be exposed publicly | |
# It's requiredd that service_id matches maratahon application name. the other fields are optional but they matter for analytics. | |
curl -X POST "http://api-gateway.${API_DOMAIN}/cache/api_key?key=key-1&app_name=app-1&service_id=hello-world&service_name=hello-world&consumer_org_name=demo-consumer" | |
# update hello-world microservice to require an API-KEY | |
# you can use the file bellow to configure hello-world service: hello-world-microservice.conf | |
curl "http://hello-world.${API_DOMAIN}/hello" | |
# {"error_code":"403000","message":"Api Key is required"} | |
# make another call including the api-key | |
curl "http://hello-world.${API_DOMAIN}/hello" -H "X-Api-Key:key-1" | |
# ----------------------------------- | |
# CAPTURE USAGE DATA W/ Graphite | |
# ----------------------------------- | |
curl -X POST -H "Content-Type:application/json" ${MARATHON_HOST}/v2/apps?force=true --data ' | |
{ | |
"id": "api-gateway-graphite", | |
"container": { | |
"type": "DOCKER", | |
"docker": { | |
"image": "hopsoft/graphite-statsd:latest", | |
"forcePullImage": true, | |
"network": "BRIDGE", | |
"portMappings": [ | |
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }, | |
{ "containerPort": 8125, "hostPort": 8125, "protocol": "udp" } | |
] | |
} | |
}, | |
"cpus": 2, | |
"mem": 4096.0, | |
"instances": 1 | |
}' | |
# verify that the Graphite instance is up by accessing it through the API Gateway | |
curl "http://api-gateway-graphite.${API_DOMAIN}/render/?from=-5min&format=raw&target=carbon.aggregator.*.metricsReceived" | |
# to open Graphite in a browser | |
python -mwebbrowser "http://api-gateway-graphite.${API_DOMAIN}/" | |
# generate traffic for the hello-world service in order to capture metrics | |
docker run jordi/ab ab -k -n 10000 -c 500 "http://hello-world.${API_DOMAIN}/hello?api_key=key-1" | |
# then check the Graphite stats in the browser | |
python -mwebbrowser "http://api-gateway-graphite.${API_DOMAIN}/render/?from=-15min&format=png&target=stats_counts.publisher.*.consumer.demo-consumer.application.app-1.service.hello-world.sandbox.region.undefined.request.hello.GET.200.count" | |
# ----------------------------------- | |
# Visualize metrics with Grafana | |
# ----------------------------------- | |
curl -X POST -H "Content-Type:application/json" ${MARATHON_HOST}/v2/apps?force=true --data ' | |
{ | |
"id": "api-gateway-grafana", | |
"container": { | |
"type": "DOCKER", | |
"docker": { | |
"image": "grafana/grafana:latest", | |
"forcePullImage": true, | |
"network": "BRIDGE", | |
"portMappings": [ { "containerPort": 3000, "hostPort": 0, "protocol": "tcp" } ] | |
} | |
}, | |
"cpus": 1, | |
"mem": 2048.0, | |
"instances": 1 | |
}' | |
# configure Graphite as the data source | |
curl -X POST -H "Content-Type:application/json; charset=UTF-8" "http://admin:admin@api-gateway-grafana.${API_DOMAIN}/api/datasources" --data ' | |
{ | |
"name":"api-gateway-graphite", | |
"type":"graphite", | |
"url":"http://api-gateway-graphite.'${API_DOMAIN}'", | |
"access":"proxy", | |
"isDefault":true, | |
"basicAuth":false | |
} | |
' | |
# add a grafana dashboard | |
# an example is the script bellow 'optional-grafana-dashboard.sh' | |
# then open the cdashboard | |
python -mwebbrowser "http://admin:admin@api-gateway-grafana.${API_DOMAIN}/dashboard/db/simple-api-usage-dashboard?theme=light" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# example of hello-world service protected with API KEY | |
# based on the api-gateway config at : https://github.com/adobe-apiplatform/apigateway/tree/master/api-gateway-config/conf.d | |
# you can drop it in /etc/api-gateway/conf.d: | |
# curl -s "https://gist.githubusercontent.com/ddragosd/608bf8d3d13e3f688874/raw/d14d084c819aa595774e328c3f49cca2cff57cc2/hello-world-microservice.conf" > /etc/api-gateway/conf.d/hello-world-microservice.conf | |
server { | |
listen 80; | |
server_name ~hello-world.api.(?<domain>.+); | |
server_tokens off; | |
uninitialized_variable_warn off; | |
# block ips of embargoed countries | |
if ( $blacklist ) { | |
return 403; | |
} | |
include /etc/api-gateway/conf.d/commons/common-headers.conf; | |
include /etc/api-gateway/conf.d/includes/resolvers.conf; | |
include /etc/api-gateway/conf.d/includes/default_validators.conf; | |
# Log locations with service name | |
access_log /var/log/api-gateway/access.log platform; | |
error_log /var/log/api-gateway/marathon_error.log debug; | |
# include environment variables | |
include /etc/api-gateway/environment.conf.d/api-gateway-env-vars.server.conf; | |
error_page 500 501 502 503 504 /50x.html; | |
location /50x.html { | |
more_set_headers 'Content-Type: application/json'; | |
more_set_headers 'X-Request-Id: $requestId'; | |
return 500 '{"code":$status, "message":"Oops. Something went wrong. Check your URI and try again."}\n'; | |
} | |
set $marathon_app_name hello-world; | |
location ~.*\.(png|jpg|jpeg|js)$ { | |
proxy_pass http://$marathon_app_name$request_uri; | |
} | |
location / { | |
# ---------------------------------- | |
# add X-Request-Id header | |
# ---------------------------------- | |
set $requestId $http_x_request_id; | |
set_secure_random_alphanum $requestId_random 32; | |
set_if_empty $requestId $requestId_random; | |
# add_header X-Request-Id $requestId; | |
proxy_set_header X-Request-Id $requestId; | |
proxy_connect_timeout 10s; # timeout for establishing a connection with a proxied server | |
proxy_read_timeout 10s; # Defines a timeout for reading a response from the proxied server. | |
# The timeout is set only between two successive read operations, | |
# not for the transmission of the whole response. | |
proxy_send_timeout 10s; # Sets a timeout for transmitting a request to the proxied server. | |
# The timeout is set only between two successive write operations, | |
# not for the transmission of the whole request. | |
keepalive_timeout 10s; # timeout during which a keep-alive client connection will stay open on the server side | |
proxy_buffering off; # enables or disables buffering of responses from the proxied server. | |
proxy_http_version 1.1; # Version 1.1 is recommended for use with keepalive connections. | |
proxy_set_header Connection ""; | |
set $service_id $marathon_app_name; # identify the service when verifying the api-key | |
set $api_key $arg_api_key; # read the api-key from the query string first | |
set_if_empty $api_key $http_x_api_key; # if missing, read it from the header | |
set $validate_api_key on; # add the api-key validator | |
access_by_lua "ngx.apiGateway.validation.validateRequest()"; # validate request | |
proxy_pass http://$marathon_app_name$request_uri; | |
# capture usage data | |
log_by_lua ' | |
if ( ngx.apiGateway.metrics ~= nil ) then | |
ngx.apiGateway.metrics.captureUsageData() | |
end | |
'; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
curl -X POST -H "Content-Type:application/json; charset=UTF-8" "http://admin:admin@api-gateway-grafana.${API_DOMAIN}/api/dashboards/db" --data ' | |
{ | |
"overwrite": true, | |
"dashboard": { | |
"id": null, | |
"title": "Simple API Usage Dashboard", | |
"originalTitle": "Simple API Usage Dashboard", | |
"tags": [], | |
"style": "dark", | |
"timezone": "browser", | |
"editable": true, | |
"hideControls": false, | |
"sharedCrosshair": false, | |
"rows": [ | |
{ | |
"collapse": false, | |
"editable": true, | |
"height": "250px", | |
"panels": [ | |
{ | |
"aliasColors": {}, | |
"bars": false, | |
"datasource": null, | |
"editable": true, | |
"error": false, | |
"fill": 1, | |
"grid": { | |
"leftLogBase": 1, | |
"leftMax": null, | |
"leftMin": null, | |
"rightLogBase": 1, | |
"rightMax": null, | |
"rightMin": null, | |
"threshold1": null, | |
"threshold1Color": "rgba(216, 200, 27, 0.27)", | |
"threshold2": null, | |
"threshold2Color": "rgba(234, 112, 112, 0.22)" | |
}, | |
"id": 1, | |
"legend": { | |
"avg": false, | |
"current": false, | |
"max": false, | |
"min": false, | |
"show": true, | |
"total": false, | |
"values": false | |
}, | |
"lines": true, | |
"linewidth": 2, | |
"links": [], | |
"nullPointMode": "connected", | |
"percentage": false, | |
"pointradius": 5, | |
"points": false, | |
"renderer": "flot", | |
"seriesOverrides": [], | |
"span": 9, | |
"stack": false, | |
"steppedLine": false, | |
"targets": [ | |
{ | |
"target": "aliasByNode(scaleToSeconds(groupByNode(stats.publisher.*.consumer.*.application.*.service.*.*.region.*.request.*.*.*.count, 6, '\''sum'\''), 60), 0)", | |
"textEditor": false | |
} | |
], | |
"timeFrom": null, | |
"timeShift": null, | |
"title": "Top Apps", | |
"tooltip": { | |
"shared": true, | |
"value_type": "cumulative" | |
}, | |
"type": "graph", | |
"x-axis": true, | |
"y-axis": true, | |
"y_formats": [ | |
"short", | |
"short" | |
] | |
}, | |
{ | |
"cacheTimeout": null, | |
"colorBackground": false, | |
"colorValue": false, | |
"colors": [ | |
"rgba(245, 54, 54, 0.9)", | |
"rgba(237, 129, 40, 0.89)", | |
"rgba(50, 172, 45, 0.97)" | |
], | |
"editable": true, | |
"error": false, | |
"format": "none", | |
"id": 4, | |
"interval": null, | |
"links": [], | |
"maxDataPoints": 100, | |
"nullPointMode": "connected", | |
"nullText": null, | |
"postfix": "", | |
"postfixFontSize": "50%", | |
"prefix": "", | |
"prefixFontSize": "50%", | |
"span": 3, | |
"sparkline": { | |
"fillColor": "rgba(31, 118, 189, 0.18)", | |
"full": false, | |
"lineColor": "rgb(31, 120, 193)", | |
"show": false | |
}, | |
"targets": [ | |
{ | |
"target": "sumSeries(summarize(stats_counts.publisher.*.consumer.*.application.*.service.*.sandbox.region.*.request.*.*.*.count, '\''365d'\'', '\''sum'\''))", | |
"textEditor": false | |
} | |
], | |
"thresholds": "", | |
"title": "Total Requests", | |
"type": "singlestat", | |
"valueFontSize": "80%", | |
"valueMaps": [ | |
{ | |
"op": "=", | |
"text": "N/A", | |
"value": "null" | |
} | |
], | |
"valueName": "avg" | |
}, | |
{ | |
"aliasColors": {}, | |
"bars": false, | |
"datasource": null, | |
"editable": true, | |
"error": false, | |
"fill": 1, | |
"grid": { | |
"leftLogBase": 1, | |
"leftMax": null, | |
"leftMin": null, | |
"rightLogBase": 1, | |
"rightMax": null, | |
"rightMin": null, | |
"threshold1": null, | |
"threshold1Color": "rgba(216, 200, 27, 0.27)", | |
"threshold2": null, | |
"threshold2Color": "rgba(234, 112, 112, 0.22)" | |
}, | |
"id": 5, | |
"legend": { | |
"avg": false, | |
"current": false, | |
"max": false, | |
"min": false, | |
"show": true, | |
"total": false, | |
"values": false | |
}, | |
"lines": true, | |
"linewidth": 2, | |
"links": [], | |
"nullPointMode": "connected", | |
"percentage": false, | |
"pointradius": 5, | |
"points": false, | |
"renderer": "flot", | |
"seriesOverrides": [], | |
"span": 12, | |
"stack": false, | |
"steppedLine": false, | |
"targets": [ | |
{ | |
"target": "aliasByNode(scaleToSeconds(groupByNode(stats.publisher.*.consumer.*.application.*.service.*.*.region.*.request.*.*.*.count, 8, '\''sum'\''), 60), 0)", | |
"textEditor": false | |
} | |
], | |
"timeFrom": null, | |
"timeShift": null, | |
"title": "Top Services", | |
"tooltip": { | |
"shared": true, | |
"value_type": "cumulative" | |
}, | |
"type": "graph", | |
"x-axis": true, | |
"y-axis": true, | |
"y_formats": [ | |
"short", | |
"short" | |
] | |
} | |
], | |
"title": "Row" | |
}, | |
{ | |
"collapse": false, | |
"editable": true, | |
"height": "250px", | |
"panels": [ | |
{ | |
"aliasColors": {}, | |
"bars": true, | |
"datasource": null, | |
"editable": true, | |
"error": false, | |
"fill": 1, | |
"grid": { | |
"leftLogBase": 1, | |
"leftMax": null, | |
"leftMin": null, | |
"rightLogBase": 1, | |
"rightMax": null, | |
"rightMin": null, | |
"threshold1": null, | |
"threshold1Color": "rgba(216, 200, 27, 0.27)", | |
"threshold2": null, | |
"threshold2Color": "rgba(234, 112, 112, 0.22)" | |
}, | |
"id": 2, | |
"legend": { | |
"avg": false, | |
"current": false, | |
"max": false, | |
"min": false, | |
"show": true, | |
"total": false, | |
"values": false | |
}, | |
"lines": false, | |
"linewidth": 2, | |
"links": [], | |
"nullPointMode": "connected", | |
"percentage": false, | |
"pointradius": 5, | |
"points": false, | |
"renderer": "flot", | |
"seriesOverrides": [], | |
"span": 6, | |
"stack": false, | |
"steppedLine": false, | |
"targets": [ | |
{ | |
"target": "aliasByNode(scaleToSeconds(groupByNode(stats.publisher.*.consumer.*.application.*.service.*.*.region.*.request.*.*.{4*,5*}.count, 15, '\''sum'\''), 60), 0)", | |
"textEditor": false | |
} | |
], | |
"timeFrom": null, | |
"timeShift": null, | |
"title": "5xx and 4xx Codes by minute", | |
"tooltip": { | |
"shared": true, | |
"value_type": "cumulative" | |
}, | |
"type": "graph", | |
"x-axis": true, | |
"y-axis": true, | |
"y_formats": [ | |
"short", | |
"short" | |
] | |
}, | |
{ | |
"aliasColors": {}, | |
"bars": false, | |
"datasource": null, | |
"editable": true, | |
"error": false, | |
"fill": 1, | |
"grid": { | |
"leftLogBase": 1, | |
"leftMax": null, | |
"leftMin": null, | |
"rightLogBase": 1, | |
"rightMax": null, | |
"rightMin": null, | |
"threshold1": null, | |
"threshold1Color": "rgba(216, 200, 27, 0.27)", | |
"threshold2": null, | |
"threshold2Color": "rgba(234, 112, 112, 0.22)" | |
}, | |
"id": 3, | |
"legend": { | |
"avg": false, | |
"current": false, | |
"max": false, | |
"min": false, | |
"show": true, | |
"total": false, | |
"values": false | |
}, | |
"lines": true, | |
"linewidth": 2, | |
"links": [], | |
"nullPointMode": "connected", | |
"percentage": false, | |
"pointradius": 5, | |
"points": false, | |
"renderer": "flot", | |
"seriesOverrides": [], | |
"span": 6, | |
"stack": false, | |
"steppedLine": false, | |
"targets": [ | |
{ | |
"target": "aliasByNode(scaleToSeconds(groupByNode(stats.publisher.*.consumer.*.application.*.service.*.*.region.*.request.*.*.{2*,3*}.count, 15,'\''sum'\''), 60), 0)", | |
"textEditor": false | |
} | |
], | |
"timeFrom": null, | |
"timeShift": null, | |
"title": "2xx and 3xx Codes by minute", | |
"tooltip": { | |
"shared": true, | |
"value_type": "cumulative" | |
}, | |
"type": "graph", | |
"x-axis": true, | |
"y-axis": true, | |
"y_formats": [ | |
"short", | |
"short" | |
] | |
} | |
], | |
"title": "New row" | |
} | |
], | |
"nav": [ | |
{ | |
"collapse": false, | |
"enable": true, | |
"notice": false, | |
"now": true, | |
"refresh_intervals": [ | |
"5s", | |
"10s", | |
"30s", | |
"1m", | |
"5m", | |
"15m", | |
"30m", | |
"1h", | |
"2h", | |
"1d" | |
], | |
"status": "Stable", | |
"time_options": [ | |
"5m", | |
"15m", | |
"1h", | |
"6h", | |
"12h", | |
"24h", | |
"2d", | |
"7d", | |
"30d" | |
], | |
"type": "timepicker" | |
} | |
], | |
"time": { | |
"from": "now-5m", | |
"to": "now" | |
}, | |
"templating": { | |
"list": [] | |
}, | |
"annotations": { | |
"list": [] | |
}, | |
"schemaVersion": 6, | |
"version": 1, | |
"links": [] | |
}}' |
I have setup a dcos cluster on AWS and delpoyed hello-world app and api-gateway as per your script above ... however when i try to access the app through api i get errors.
I have defined API_DOMAIN=api.akhil.com and then run the curl script for api-gateway. Below is the error i get when i access hello-world app at http://hello-world.api.akhil.com/
Network Error (dns_unresolved_hostname)
Your requested host "hello-world.api.akhil.com" could not be resolved by DNS.
Do i need to do some configuration on public machine where my api-gateway is running so its able to resolve "hello-world.api.akhil.com"
Thanks,
Akhil Kanaskar.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@codyhazelwood http://events.linuxfoundation.org/sites/events/files/slides/Container-Con-2015-for-pdf-optimised_0.pdf