Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ddragosd
Last active April 9, 2018 04:31
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ddragosd/608bf8d3d13e3f688874 to your computer and use it in GitHub Desktop.
Save ddragosd/608bf8d3d13e3f688874 to your computer and use it in GitHub Desktop.
API Gateway setup in Mesos and Marathon
# 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"
# 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
';
}
}
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": []
}}'
@codyhazelwood
Copy link

@ddragosd where can we find the link for the slides from this webinar? Thanks.

@kanaskaa
Copy link

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