Skip to content

Commit

Permalink
Merge pull request #1 from lowescott/newver
Browse files Browse the repository at this point in the history
Update to multi-container architecture
  • Loading branch information
scottslowe authored Nov 18, 2017
2 parents 92acb2e + 79efd8c commit 768557b
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LABEL maintainer="Scott S. Lowe <[email protected]>"
ARG GIT_COMMIT=unspecified
LABEL git_commit=$GIT_COMMIT

RUN pip install flask && \
RUN pip install flask requests && \
apk add --no-cache curl && \
rm -rf /var/cache/apk/*

Expand Down
59 changes: 35 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,57 @@
# A Simple Flask Web Service

A simple but flexible Flask web service used to demonstrate microservices architectures.
A simple but flexible Flask web service used to demonstrate microservices architectures. It is designed to operate across three different containers:

* A "front-end" container that provides an HTML response based on information gathered from two "back-end" JSON-based web services
* The "users" API, which is a JSON-based web service queried by the front-end container(s)
* The "orders" API, which is a JSON-based web service queried by the front-end container(s)

## Usage

This web service does nothing other than respond, either in HTML or JSON, with information about the web request, the system making the request, and the system handling the request.
This web service is a single application designed to run in three separate containers.

### Using Docker Compose

To run the web service, simply execute the Python script in the `app` directory, like this:
The easiest way to spin up the application is to use Docker Compose. A `docker-compose.yml` file is provided in the repository.

```
python app/main.py
docker-compose up -d
```

Or, as a Docker container:
### Running the Containers Manually

```
docker run -d -p 5000:5000 slowe/flask-web-svc:latest
```
Running the containers manually is a bit more difficult, but certainly possible.

Specify the `PORT` environment variable to have the web service listen on a port _other_ than 5000, like this:
1. First, launch the "users" API container:

```
PORT=7000 python app/main.py
```
docker run -d -e PORT=6000 -p 6000:6000 slowe/flask-web-svc:latest

Or, when using a Docker container:
Make note of the IP address where the container is running, as you'll need it later.

```
docker run -d -e PORT=7000 -p 7000:7000 slowe/flask-web-svc:latest
```
2. Next, launch the "orders" API container:

Once the application is running, make web requests to it (using `curl` or a web browser). _Any_ top-level URL is supported, and will all return the same information. Assuming you have the application listening on port 5000 on IP address 192.168.99.100, these requests would all return a valid HTML response:
docker run -d -e PORT=7000 -p 7000:7000 slowe/flask-web-svc:latest

```
http://192.168.99.100:5000/auth
http://192.168.99.100:5000/api
http://192.168.99.100:5000/test
```
As with the previous step, make note of the IP address where this container is scheduled.

3. Before proceeding, ensure that the back-end containers are working properly. Use `curl` or a web browser to access the URL for the containers:

http://<IP address from step 1>:6000/users/json/
http://<IP address from step 2>:7000/users/json/

You should get back a JSON-formatted response that contains information about the request. If this doesn't work, resolve the issue before continuing.

4. Finally, launch the front-end container:

docker run -d -e USER_API_HOST=<IP address from step 1> -e USER_API_PORT=6000 -e ORDER_API_HOST=<IP address from step 2> -e ORDER_API_PORT=7000 -p 5000:5000 slowe/flask-web-svc:latest

If you want the front-end container to listen on a port _other_ than 5000, change the `-p` parameter and add an additional environment parameter in the form `-e PORT=<desired port>`.

The application is now running and ready to use. You can use `curl` or a web browser to access the application:

The web service will **not** respond to multi-level paths, like `/auth/user` or `/api/object`. Only a single-level path is currently supported.
http://<IP address from step 4>:5000/

Appending `/json` to the URL will cause the web service to respond in JSON instead of HTML.
This will provide an HTML-formatted response that contains information about the request and the requests/responses from the back-end JSON-based web services. You can use this information to see how various container orchestration systems and other technologies affect the communications between containers.

## License

Expand Down
81 changes: 61 additions & 20 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import socket
import datetime
import requests

__author__ = 'slowe'

Expand All @@ -13,37 +14,77 @@
host_name = socket.gethostname()
ip_address = socket.gethostbyname(socket.gethostname())

@app.route('/<svc_url>', methods=['GET'])
def show_info_html(svc_url):
# Define method to fetch information from remote web service
def get_svc_info(host,port,service):
url = "http://" + host + ":" + str(port) + "/" + service + "/json/"
payload = ""
headers = {
'content-type': "application/json",
'accept': "application/json",
'cache-control': "no-cache",
}

# Make the request, then parse/process the response
raw_response = requests.request("GET", url, data = payload,
headers = headers)
json_response = json.loads(raw_response.text)

return json_response

# Define back-end route for web services
@app.route('/<svc_url>/json/', methods=['GET'])
def show_info_json(svc_url):
# Gather information about the request and return as JSON
if request.headers.getlist('X-Forwarded-For'):
proxy_addr = request.remote_addr
client_addr = request.headers.getlist('X-Forwarded-For')[0]
else:
proxy_addr = 'No proxy (direct)'
client_addr = request.remote_addr
host = {'hostname': host_name, 'ip': ip_address}
time = datetime.datetime.now().strftime("%Y-%b-%d %H:%M:%S")
client = client_addr
proxy = proxy_addr
baseurl = request.base_url
urlroot = request.url_root
return render_template('index.html', title = 'Home', host = host, client = client, proxy = proxy, baseurl = baseurl, urlroot = urlroot, time = time)

@app.route('/<svc_url>/json', methods=['GET'])
def show_info_json(svc_url):
return jsonify(
container_hostname = host_name,
container_ip = ip_address,
time = datetime.datetime.now().strftime("%Y-%b-%d %H:%M:%S"),
proxy = proxy_addr,
client = client_addr,
baseurl = request.base_url,
urlroot = request.url_root)

# Define front-end route that pulls from back-end web services
@app.route('/', methods=['GET'])
def show_info_html():
# Get information from "users" web service
user_ws_response = get_svc_info(
host = str(os.getenv('USER_API_HOST', 'localhost')),
port = str(os.getenv('USER_API_PORT', '5000')),
service = 'users')

# Get information from "orders" web service
order_ws_response = get_svc_info(
host = str(os.getenv('ORDER_API_HOST', 'localhost')),
port = str(os.getenv('ORDER_API_PORT', '5000')),
service = 'orders')

# Gather information for front-end service
if request.headers.getlist('X-Forwarded-For'):
proxy_addr = request.remote_addr
client_addr = request.headers.getlist('X-Forwarded-For')[0]
else:
proxy_addr = 'No proxy (direct)'
client_addr = request.remote_addr
return jsonify(container_hostname = host_name,
container_ip = ip_address,
time = datetime.datetime.now().strftime("%Y-%b-%d %H:%M:%S"),
proxy = proxy_addr,
client = client_addr,
baseurl = request.base_url,
urlroot = request.url_root)
fe_ws_response = { 'container_hostname': host_name,
'container_ip': ip_address,
'time': datetime.datetime.now().strftime("%Y-%b-%d %H:%M:%S"),
'proxy': proxy_addr,
'client': client_addr,
'baseurl': request.base_url,
'urlroot': request.url_root }

# Render and return template
return render_template('index.html', title = 'Home',
users = user_ws_response, orders = order_ws_response,
frontend = fe_ws_response)

if __name__ == "__main__":
app.run(host='0.0.0.0', debug=False, threaded=True, port=int(os.getenv('PORT', '5000')))
app.run(host = '0.0.0.0', debug = False, threaded = True,
port=int(os.getenv('PORT', '5000')))
39 changes: 24 additions & 15 deletions app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,37 @@

<hr/>

<h2>Server-Side Information</h2>
<h2>Information for "Users" Web Service:</h2>

<p>Container hostname: {{ host.hostname }}</p>

<p>Container private IP address: {{ host.ip }}</p>
<p>Container hostname: {{ users['container_hostname'] }}</p>
<p>Container private IP address: {{ users['container_ip'] }}</p>
<p>Client IP address: {{ users['client'] }}</p>
<p>Proxy IP address: {{ users['proxy'] }}</p>
<p>Base URL of request: {{ users['baseurl'] }}</p>
<p>URL root of request: {{ users['urlroot'] }}</p>
<p>Time of request: {{ users['time'] }}</p>

<hr/>

<h2>Client-Side Information</h2>

<p>Client IP address: {{ client }}</p>

<p>Proxy IP address: {{ proxy }}</p>

<hr/>
<h2>Information for "Orders" Web Service:</h2>

<h2>Request Information</h2>
<p>Container hostname: {{ orders['container_hostname'] }}</p>
<p>Container private IP address: {{ orders['container_ip'] }}</p>
<p>Client IP address: {{ orders['client'] }}</p>
<p>Proxy IP address: {{ orders['proxy'] }}</p>
<p>Base URL of request: {{ orders['baseurl'] }}</p>
<p>URL root of request: {{ orders['urlroot'] }}</p>
<p>Time of request: {{ orders['time'] }}</p>

<p>Base URL: {{ baseurl }}</p>
<h2>Information for "Front-End" Web Service:</h2>

<p>URL Root: {{ urlroot }}</p>
<p>Container hostname: {{ frontend['container_hostname'] }}</p>
<p>Container private IP address: {{ frontend['container_ip'] }}</p>
<p>Client IP address: {{ frontend['client'] }}</p>
<p>Proxy IP address: {{ frontend['proxy'] }}</p>
<p>Base URL of request: {{ frontend['baseurl'] }}</p>
<p>URL root of request: {{ frontend['urlroot'] }}</p>
<p>Time of request: {{ frontend['time'] }}</p>

<p><small><em>Request time is {{ time }}</em></small></p>
</body>
</html>
23 changes: 23 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: "2"
services:
web-ui:
image: slowe/flask-web-svc:0.4
ports:
- "5000:5000"
environment:
- USER_API_HOST=user-api
- USER_API_PORT=6000
- ORDER_API_HOST=order-api
- ORDER_API_PORT=7000
user-api:
image: slowe/flask-web-svc:0.4
ports:
- "6000:6000"
environment:
- PORT=6000
order-api:
image: slowe/flask-web-svc:0.4
ports:
- "7000:7000"
environment:
- PORT=7000

0 comments on commit 768557b

Please sign in to comment.