Skip to content

Commit 2e862fe

Browse files
travisoneillJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
Add microservices demo for flexible (GoogleCloudPlatform#509)
1 parent 8f090d7 commit 2e862fe

File tree

11 files changed

+315
-0
lines changed

11 files changed

+315
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Python Google Cloud Microservices Example - API Gateway
2+
3+
This example demonstrates how to deploy multiple python services to [App Engine flexible environment](https://cloud.google.com/appengine/docs/flexible/)
4+
5+
## To Run Locally
6+
7+
1. You will need to install Python 3 on your local machine
8+
9+
2. Install virtualenv
10+
```Bash
11+
$ pip install virtualenv
12+
```
13+
14+
3. To setup the environment in each server's directory:
15+
```Bash
16+
$ virtualenv -p python3 env
17+
$ source env/bin/activate
18+
$ pip install -r requirements.txt
19+
$ deactivate
20+
```
21+
22+
4. To start server locally:
23+
```Bash
24+
$ python <filename>.py
25+
```
26+
27+
## To Deploy to App Engine
28+
29+
### YAML Files
30+
31+
Each directory contains an `app.yaml` file. These files all describe a
32+
separate App Engine service within the same project.
33+
34+
For the gateway:
35+
36+
[Gateway <default>](gateway/app.yaml)
37+
38+
This is the `default` service. There must be one (and not more). The deployed
39+
url will be `https://<your project id>.appspot.com`
40+
41+
For the static file server:
42+
43+
[Static File Server <static>](static/app.yaml)
44+
45+
Make sure the `entrypoint` line matches the filename of the server you want to deploy.
46+
47+
The deployed url will be `https://<service name>-dot-<your project id>.appspot.com`
48+
49+
### Deployment
50+
51+
To deploy a service cd into its directory and run:
52+
```Bash
53+
$ gcloud app deploy app.yaml
54+
```
55+
and enter `Y` when prompted. Or to skip the check add `-q`.
56+
57+
To deploy multiple services simultaneously just add the path to each `app.yaml`
58+
file as an argument to `gcloud app deploy `:
59+
```Bash
60+
$ gcloud app deploy gateway/app.yaml static/app.yaml
61+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import requests
17+
import services_config
18+
19+
app = services_config.make_app(__name__)
20+
21+
@app.route('/')
22+
def root():
23+
'''Gets index.html from the static file server'''
24+
res = requests.get(app.config['SERVICE_MAP']['static'])
25+
return res.content
26+
27+
@app.route('/hello/<service>')
28+
def say_hello(service):
29+
'''Recieves requests from buttons on the front end and resopnds
30+
or sends request to the static file server'''
31+
#if 'gateway' is specified return immediate
32+
if service == 'gateway':
33+
return 'Gateway says hello'
34+
#otherwise send request to service indicated by URL param
35+
responses = []
36+
url = app.config['SERVICE_MAP'][service]
37+
res = requests.get(url + '/hello')
38+
responses.append(res.content)
39+
return '\n'.encode().join(responses)
40+
41+
@app.route('/<path>')
42+
def static_file(path):
43+
'''Gets static files required by index.html to static file server'''
44+
url = app.config['SERVICE_MAP']['static']
45+
res = requests.get(url + '/' + path)
46+
return res.content, 200, {'Content-Type': res.headers['Content-Type']}
47+
48+
if __name__ == '__main__':
49+
port = os.environ.get('PORT') or 8000
50+
app.run(port=int(port))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
service: default
2+
runtime: python
3+
vm: true
4+
entrypoint: gunicorn -b :$PORT api_gateway:app
5+
6+
runtime_config:
7+
python_version: 3
8+
9+
manual_scaling:
10+
instances: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
click==6.6
2+
Flask==0.11.1
3+
gunicorn==19.6.0
4+
itsdangerous==0.24
5+
Jinja2==2.8
6+
MarkupSafe==0.23
7+
requests==2.11.1
8+
Werkzeug==0.11.11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
from flask import Flask
17+
18+
#to add services insert key value pair of the name of the service and
19+
#the port you want it to run on when running locally
20+
SERVICES = {
21+
'default': 8000,
22+
'static': 8001
23+
}
24+
25+
def make_app(name):
26+
app = Flask(name)
27+
environment = 'production' if os.environ.get(
28+
'GAE_INSTANCE', os.environ.get('GAE_MODULE_INSTANCE')
29+
) else 'development'
30+
app.config['SERVICE_MAP'] = map_services(environment)
31+
return app
32+
33+
def map_services(environment):
34+
'''Generates a map of services to correct urls for running locally
35+
or when deployed'''
36+
url_map = {}
37+
for service, local_port in SERVICES.items():
38+
if environment == 'production':
39+
url_map[service] = production_url(service)
40+
if environment == 'development':
41+
url_map[service] = local_url(local_port)
42+
return url_map
43+
44+
def production_url(service_name):
45+
'''Generates url for a service when deployed to App Engine'''
46+
project_id = os.environ.get('GAE_LONG_APP_ID')
47+
project_url = '{}.appspot.com'.format(project_id)
48+
if service_name == 'default':
49+
return 'https://{}'.format(project_url)
50+
else:
51+
return 'https://{}-dot-{}'.format(service_name, project_url)
52+
53+
def local_url(port):
54+
'''Generates url for a service when running locally'''
55+
return 'http://localhost:{}'.format(str(port))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
service: static
2+
runtime: python
3+
vm: true
4+
entrypoint: gunicorn -b :$PORT static_server:app
5+
6+
runtime_config:
7+
python_version: 3
8+
9+
manual_scaling:
10+
instances: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
click==6.6
2+
Flask==0.11.1
3+
gunicorn==19.6.0
4+
itsdangerous==0.24
5+
Jinja2==2.8
6+
MarkupSafe==0.23
7+
requests==2.11.1
8+
Werkzeug==0.11.11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!--
2+
Copyright 2016 Google Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
<!DOCTYPE html>
17+
<html>
18+
<head>
19+
<meta charset="utf-8">
20+
<link rel="stylesheet" type="text/css" href="style.css" />
21+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
22+
<script src="index.js"></script>
23+
<title>API Gateway on App Engine Flexible Environment</title>
24+
</head>
25+
<body>
26+
<h1>API GATEWAY DEMO</h1>
27+
<p>Say hi to:</p>
28+
<button class='request-button' id='gateway'>Gateway</button>
29+
<button class='request-button' id='static'>Static File Server</button>
30+
<ul class='responses'></ul>
31+
</body>
32+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2016 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
function handleResponse(resp){
16+
const li = document.createElement('li');
17+
li.innerHTML = resp;
18+
document.querySelector('.responses').appendChild(li)
19+
}
20+
21+
function handleClick(event){
22+
$.ajax({
23+
url: `hello/${event.target.id}`,
24+
type: `GET`,
25+
success(resp){
26+
handleResponse(resp);
27+
}
28+
});
29+
}
30+
31+
document.addEventListener('DOMContentLoaded', () => {
32+
const buttons = document.getElementsByTagName('button')
33+
for (var i = 0; i < buttons.length; i++) {
34+
buttons[i].addEventListener('click', handleClick);
35+
}
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
h1 {
2+
color: red;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
from flask import Flask
17+
18+
app = Flask(__name__)
19+
20+
@app.route('/hello')
21+
def say_hello():
22+
'''responds to request from frontend via gateway'''
23+
return 'Static File Server says hello!'
24+
25+
@app.route('/')
26+
def root():
27+
'''serves index.html'''
28+
return app.send_static_file('index.html')
29+
30+
@app.route('/<path:path>')
31+
def static_file(path):
32+
'''serves static files required by index.html'''
33+
mimetype = ''
34+
if path.split('.')[1] == 'css':
35+
mimetype = 'text/css'
36+
if path.split('.')[1] == 'js':
37+
mimetype = 'application/javascript'
38+
return app.send_static_file(path), 200, {'Content-Type': mimetype}
39+
40+
if __name__ == "__main__":
41+
port = os.environ.get('PORT') or 8001
42+
app.run(port=port)

0 commit comments

Comments
 (0)