From c0e2851e6878fe23cc92898e124f42c6ef4b2d5f Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Thu, 16 Nov 2017 13:18:16 -0700 Subject: [PATCH 1/2] Add working multi-container version Add new version of application that leverages multiple containers by default. Application works as expected, but documentation has not been updated. Signed-off-by: Scott Lowe --- Dockerfile | 2 +- app/main.py | 81 ++++++++++++++++++++++++++++++---------- app/templates/index.html | 39 +++++++++++-------- docker-compose.yml | 23 ++++++++++++ 4 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 94c06c6..672a54d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ LABEL maintainer="Scott S. Lowe " 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/* diff --git a/app/main.py b/app/main.py index e0087bd..5bb5145 100644 --- a/app/main.py +++ b/app/main.py @@ -5,6 +5,7 @@ import os import socket import datetime +import requests __author__ = 'slowe' @@ -13,37 +14,77 @@ host_name = socket.gethostname() ip_address = socket.gethostbyname(socket.gethostname()) -@app.route('/', 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('//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('//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'))) diff --git a/app/templates/index.html b/app/templates/index.html index 42c5309..4d644e0 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -7,28 +7,37 @@
-

Server-Side Information

+

Information for "Users" Web Service:

-

Container hostname: {{ host.hostname }}

- -

Container private IP address: {{ host.ip }}

+

Container hostname: {{ users['container_hostname'] }}

+

Container private IP address: {{ users['container_ip'] }}

+

Client IP address: {{ users['client'] }}

+

Proxy IP address: {{ users['proxy'] }}

+

Base URL of request: {{ users['baseurl'] }}

+

URL root of request: {{ users['urlroot'] }}

+

Time of request: {{ users['time'] }}


-

Client-Side Information

- -

Client IP address: {{ client }}

- -

Proxy IP address: {{ proxy }}

- -
+

Information for "Orders" Web Service:

-

Request Information

+

Container hostname: {{ orders['container_hostname'] }}

+

Container private IP address: {{ orders['container_ip'] }}

+

Client IP address: {{ orders['client'] }}

+

Proxy IP address: {{ orders['proxy'] }}

+

Base URL of request: {{ orders['baseurl'] }}

+

URL root of request: {{ orders['urlroot'] }}

+

Time of request: {{ orders['time'] }}

-

Base URL: {{ baseurl }}

+

Information for "Front-End" Web Service:

-

URL Root: {{ urlroot }}

+

Container hostname: {{ frontend['container_hostname'] }}

+

Container private IP address: {{ frontend['container_ip'] }}

+

Client IP address: {{ frontend['client'] }}

+

Proxy IP address: {{ frontend['proxy'] }}

+

Base URL of request: {{ frontend['baseurl'] }}

+

URL root of request: {{ frontend['urlroot'] }}

+

Time of request: {{ frontend['time'] }}

-

Request time is {{ time }}

diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b5694af --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "2" +services: + web-ui: + image: newver-14 + ports: + - "5000:5000" + environment: + - USER_API_HOST=192.168.99.100 + - USER_API_PORT=6000 + - ORDER_API_HOST=192.168.99.100 + - ORDER_API_PORT=7000 + user-api: + image: newver-14 + ports: + - "6000:6000" + environment: + - PORT=6000 + order-api: + image: newver-14 + ports: + - "7000:7000" + environment: + - PORT=7000 From 79efd8c69b4fae56b759cfe4e3a40386c04f3d36 Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Fri, 17 Nov 2017 21:17:20 -0700 Subject: [PATCH 2/2] Update documentation Update README.md and docker-compose.yml Signed-off-by: Scott Lowe --- README.md | 59 +++++++++++++++++++++++++++------------------- docker-compose.yml | 10 ++++---- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8dfe695..b62ffc0 100644 --- a/README.md +++ b/README.md @@ -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://:6000/users/json/ + http://: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= -e USER_API_PORT=6000 -e ORDER_API_HOST= -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=`. + +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://: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 diff --git a/docker-compose.yml b/docker-compose.yml index b5694af..8a8bd38 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,22 @@ version: "2" services: web-ui: - image: newver-14 + image: slowe/flask-web-svc:0.4 ports: - "5000:5000" environment: - - USER_API_HOST=192.168.99.100 + - USER_API_HOST=user-api - USER_API_PORT=6000 - - ORDER_API_HOST=192.168.99.100 + - ORDER_API_HOST=order-api - ORDER_API_PORT=7000 user-api: - image: newver-14 + image: slowe/flask-web-svc:0.4 ports: - "6000:6000" environment: - PORT=6000 order-api: - image: newver-14 + image: slowe/flask-web-svc:0.4 ports: - "7000:7000" environment: