Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2e862fe

Browse filesBrowse files
travisoneillJon Wayne Parrott
authored andcommitted
Add microservices demo for flexible (GoogleCloudPlatform#509)
1 parent 8f090d7 commit 2e862fe
Copy full SHA for 2e862fe

File tree

Expand file treeCollapse file tree

11 files changed

+315
-0
lines changed
Filter options
Expand file treeCollapse file tree

11 files changed

+315
-0
lines changed
+61Lines changed: 61 additions & 0 deletions
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+
```
+50Lines changed: 50 additions & 0 deletions
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))
+10Lines changed: 10 additions & 0 deletions
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
+8Lines changed: 8 additions & 0 deletions
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
+55Lines changed: 55 additions & 0 deletions
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))
+10Lines changed: 10 additions & 0 deletions
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
+8Lines changed: 8 additions & 0 deletions
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
+32Lines changed: 32 additions & 0 deletions
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>
+36Lines changed: 36 additions & 0 deletions
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+
});
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
h1 {
2+
color: red;
3+
}
+42Lines changed: 42 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.