The Saga continues
This is a continuation of my previous post which was an introduction to docker geared for developers. In the previous post, we got somewhat familiar with the docker ecosystem and built a very simple hello world application. In this part we are going to get dynamodb running locally, run a one off script to preload data, build a sample api service that reads from dynamo, and finally a sample website that reads from that api service. As with the first part, you can retrieve a copy of the completed code base off github or directly download the assets for part 2 here.
Running DynamoDB
Before we can really start building our API server, we need a place to store data. As we learned in the previous part, we can use docker-compose files to orchestrate the various services that make up our application. This could be a postgres or mysql instance, however in this case the application is going to leverage DynamoDB for storage. When developing the application it doesn’t make sense to create an actual dynamo table on AWS as you would incur some costs for something that is only going to be used for development. There are some caveats and limitations to this however which you can read about here. Since we would never want a local dynamodb container running in any environment other than development, we want to go ahead and edit docker-compose.override.yml with the following:
1 2 3 4 5 6 7 8 9 10 11 |
$ cat docker-compose.override.yml movie-api: build: ./movie-api ports: - "8080:5000" volumes: - ./movie-api:/code dynamodb: image: kcyeu/dynamodb-local ports: - 8000 |
Save that file, and run docker-compose up -d and you should see output that the dynamodb container is running alongside our movie-api instance
1 2 3 4 |
→ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3ce66d3f8e7e kcyeu/dynamodb-local "java -Djava.library." 2 minutes ago Up 2 minutes 0.0.0.0:32775->8000/tcp dockermoviedbdemo_dynamodb_1 f34099a7531d dockermoviedbdemo_movie-api "/bin/sh -c 'python a" 6 days ago Up 5 minutes 0.0.0.0:8080->5000/tcp, 0.0.0.0:32774->5000/tcp dockermoviedbdemo_movie-api_1 |
Now that DynamoDB is running we need to be able to link our API container to it so that they can communicate. Our Compose file needs one small change..
1 2 3 4 5 6 7 8 9 10 11 12 |
movie-api: build: ./movie-api links: - dynamodb ports: - "8080:5000" volumes: - ./movie-api:/code dynamodb: image: kcyeu/dynamodb-local ports: - 8000 |
Rerun docker-compose up -d which will recreate the movie-api-1 container.
Loading data into DynamoDB
Now that we have dynamodb running, we can show how to run a one off command to invoke a script that will seed data into dynamo. First, we can create the directory structure for storing our sample data and a place for our seed scripts. Within the movie-api directory create two new directors. One called scripts and the other data. We can use an amazon published sample data set for building this application. You can retrieve it here and extract this to the data directory.
The application directory structure should now look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
→ tree docker-movie-db-demo docker-movie-db-demo ├── README.md ├── docker-compose.override.yml ├── docker-compose.yml ├── movie-api │ ├── Dockerfile │ ├── app.py │ ├── data │ │ └── moviedata.json │ ├── demo │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ └── services │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ ├── api.py │ │ └── api.pyc │ ├── requirements.txt │ └── scripts └── movie-web 6 directories, 13 files |
Now within the scripts directory lets write a simple script for seeding this data to our local dynamodb instance. Within the movie-api/scripts directory create a file called seed_movie_data.py and open it for editing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
from __future__ import print_function # Python 2/3 compatibility import boto3 import json import decimal import sys, os from boto3.dynamodb.conditions import Key, Attr def main(): dynamodb_endpoint_url = os.environ.get('DYNAMODB_ENDPOINT_URL', None) if dynamodb_endpoint_url: dynamodb = boto3.resource('dynamodb', endpoint_url=dynamodb_endpoint_url) else: dynamodb = boto3.resource('dynamodb') table = os.environ.get('DYNAMODB_TABLE_NAME', 'Movies') if table in (t.name for t in dynamodb.tables.all()): table = dynamodb.Table(table) else: table = dynamodb.create_table( TableName='Movies', KeySchema=[ { 'AttributeName': 'year', 'KeyType': 'HASH' #Partition key }, { 'AttributeName': 'title', 'KeyType': 'RANGE' #Sort key } ], AttributeDefinitions=[ { 'AttributeName': 'year', 'AttributeType': 'N' }, { 'AttributeName': 'title', 'AttributeType': 'S' }, ], ProvisionedThroughput={ 'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10 } ) print("Table status:", table.table_status) with open(os.path.dirname(os.path.realpath(__file__)) + "/../data/moviedata.json") as json_file: movies = json.load(json_file, parse_float = decimal.Decimal) count = 0 for movie in movies: year = int(movie['year']) title = movie['title'] info = movie['info'] print("Adding movie:", year, title) table.put_item( Item={ 'year': year, 'title': title, 'info': info, } ) count += 1 print("Number of seeded items:", count) if __name__ == '__main__': main() |
This script is pretty straightforward, this script will create the table within dynamodb if it doesnt exist, and then seed it with a little over 4,000 movies from the json file in our data directory.
Before we can run our script, we need to set a few environment variables on our movie-api instance. Go ahead and open up docker-compose.override.yml and adjust it to reflect the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
movie-api: build: ./movie-api environment: AWS_ACCESS_KEY_ID: 'foo' AWS_SECRET_ACCESS_KEY: 'bar' AWS_DEFAULT_REGION: 'us-east-1' DYNAMODB_TABLE_NAME: 'Movies' DYNAMODB_ENDPOINT_URL: 'http://dynamodb:8000' links: - dynamodb ports: - "8080:5000" volumes: - ./movie-api:/code dynamodb: image: kcyeu/dynamodb-local ports: - 8000 |
The AWS credentials do not need to be anything real, in fact, leave them as above (foo, and bar). They just need to be set to prevent boto from barfing when connecting to the local dynamodb instance. In a production setting, we would leverage IAM roles on the instance to connect which is far more secure than setting credentials via an environment variable.
Once you have created the script, and also set the environment variables we can run the following to recreate the container with the new environment variables and then run the script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ docker-compose up -d dockermoviedbdemo_dynamodb_1 is up-to-date Recreating dockermoviedbdemo_movie-api_1 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 22a530df6eef dockermoviedbdemo_movie-api "/bin/sh -c 'python a" 6 minutes ago Up 6 minutes 0.0.0.0:8080->5000/tcp, 0.0.0.0:32778->5000/tcp dockermoviedbdemo_movie-api_1 3ce66d3f8e7e kcyeu/dynamodb-local "java -Djava.library." 3 days ago Up About an hour 0.0.0.0:32775->8000/tcp dockermoviedbdemo_dynamodb_1 $ docker exec -it 22a python scripts/seed_movie_data.py Table status: ACTIVE Adding movie: 2013 Rush Adding movie: 2013 Prisoners Adding movie: 2013 The Hunger Games: Catching Fire Adding movie: 2013 Thor: The Dark World Adding movie: 2013 This Is the End Adding movie: 2013 Insidious: Chapter 2 Adding movie: 2013 World War Z Adding movie: 2014 X-Men: Days of Future Past Adding movie: 2014 Transformers: Age of Extinction ... Adding movie: 2012 Renoir Adding movie: 2006 The Contract Adding movie: 2010 The Clinic Adding movie: 2004 Little Black Book Number of seeded items: 4609 |
As you can see, we ran the script that we created within the container. We do not need to have our python environment setup locally. This is great, because the containers environment is isolated from every other application we may be developing (and even other services within this application). This provides a high degree of certainty that if we deploy this image, that it will function as we expect. Furthermore, we also know that if another developer pulls this code down and runs the project it will work.
Building our API
Now that we have the data in our datastore we can build a simple lightweight API to expose this information to a client. To keep things simple, we are going to create a simple endpoint which will return all the movies that were released in a specified year. Lets go ahead and open up movie-api/demo/services/api.py in our IDE and add a bit of code to make this happen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import os import json import flask import boto3 import decimal from boto3.dynamodb.conditions import Key, Attr from flask import request app = flask.Flask(__name__) dynamodb_endpoint_url = os.environ.get('DYNAMODB_ENDPOINT_URL', None) if dynamodb_endpoint_url: DYNAMODB = boto3.resource('dynamodb', endpoint_url=dynamodb_endpoint_url) else: DYNAMODB = boto3.resource('dynamodb') table = os.environ.get('DYNAMODB_TABLE_NAME', 'Movies') TABLE=DYNAMODB.Table(table) @app.route('/') def index(): return 'Hello World! This is a test of the instance editing ability.' @app.route('/movies/by/year', methods=['POST']) def movies_by_year(): request_data = request.get_json() year = int(request_data['year']) response = TABLE.query( KeyConditionExpression=Key('year').eq(year) ) data = [] for i in response['Items']: # convert decimals from dynamo i['info']['rating'] = float(i['info']['rating']) i['info']['rank'] = int(i['info']['rank']) i['info']['running_time_secs'] = int(i['info']['running_time_secs']) i['year'] = int(i['year']) data.append(i) return json.dumps(data) |
Save this and then we can try querying our service with a simple curl command:
1 2 |
$ curl -X POST -H "Content-Type: application/json" -d '{"year":1960}' http://192.168.99.100:8080/movies/by/year [{"info": {"rating": 7.9, "genres": ["Crime", "Drama"], "plot": "A young car thief kills a policeman and tries to persuade a girl to hide in Italy with him.", "release_date": "1960-03-16T00:00:00Z", "rank": 3521, "running_time_secs": 5400, "directors": ["Jean-Luc Godard"], "actors": ["Jean-Paul Belmondo", "Jean Seberg", "Daniel Boulanger"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTI4MDUwMDEzNl5BMl5BanBnXkFtZTcwMjYxNzM1MQ@@._V1_SX400_.jpg"}, "title": "A bout de souffle", "year": 1960}, {"info": {"rating": 8.0, "genres": ["Comedy", "Drama"], "plot": "A series of stories following a week in the life of a philandering paparazzo journalist living in Rome.", "release_date": "1960-02-03T00:00:00Z", "rank": 3641, "running_time_secs": 10440, "directors": ["Federico Fellini"], "actors": ["Marcello Mastroianni", "Anita Ekberg", "Anouk Aimee"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTcwNzM0NTkxMV5BMl5BanBnXkFtZTcwOTExNDEyNQ@@._V1_SX400_.jpg"}, "title": "La dolce vita", "year": 1960}, {"info": {"rating": 6.5, "genres": ["Comedy", "Crime", "Music"], "plot": "Danny Ocean gathers a group of his World War II compatriots to pull off the ultimate Las Vegas heist. Together the eleven friends plan to rob five Las Vegas casinos in one night.", "release_date": "1960-08-10T00:00:00Z", "rank": 3686, "running_time_secs": 7620, "directors": ["Lewis Milestone"], "actors": ["Frank Sinatra", "Dean Martin", "Sammy Davis Jr."], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTIxNTEzNTY1N15BMl5BanBnXkFtZTcwMzg0NTkxMQ@@._V1_SX400_.jpg"}, "title": "Ocean's Eleven", "year": 1960}, {"info": {"rating": 7.7, "genres": ["Crime", "Thriller"], "plot": "Tom Ripley is a talented mimic, moocher, forger and all-around criminal improviser; but there's more to Tom Ripley than even he can guess.", "release_date": "1960-03-10T00:00:00Z", "rank": 3623, "running_time_secs": 7080, "directors": ["Rene Clement"], "actors": ["Alain Delon", "Maurice Ronet", "Marie Laforet"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTIwNjE5ODA0OV5BMl5BanBnXkFtZTcwNDk4NzAyMQ@@._V1_SX400_.jpg"}, "title": "Plein soleil", "year": 1960}, {"info": {"rating": 8.0, "genres": ["Action", "Adventure", "Biography", "Drama", "History", "Romance", "Thriller"], "plot": "The slave Spartacus leads a violent revolt against the decadent Roman Republic.", "release_date": "1960-10-06T00:00:00Z", "rank": 1311, "running_time_secs": 11820, "directors": ["Stanley Kubrick"], "actors": ["Kirk Douglas", "Laurence Olivier", "Jean Simmons"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTAzNDcwODQ1MjJeQTJeQWpwZ15BbWU4MDY3NTAzMTAx._V1_SX400_.jpg"}, "title": "Spartacus", "year": 1960}, {"info": {"rating": 8.4, "genres": ["Comedy", "Drama", "Romance"], "plot": "A man tries to rise in his company by letting its executives use his apartment for trysts, but complications and a romance of his own ensue.", "release_date": "1960-06-15T00:00:00Z", "rank": 2646, "running_time_secs": 7500, "directors": ["Billy Wilder"], "actors": ["Jack Lemmon", "Shirley MacLaine", "Fred MacMurray"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTM1OTc4MzgzNl5BMl5BanBnXkFtZTcwNTE2NjgyMw@@._V1_SX400_.jpg"}, "title": "The Apartment", "year": 1960}, {"info": {"rating": 7.8, "genres": ["Action", "Adventure", "Drama", "Thriller", "Western"], "plot": "An oppressed Mexican peasant village assembles seven gunfighters to help defend their homes.", "release_date": "1960-10-23T00:00:00Z", "rank": 2485, "running_time_secs": 7680, "directors": ["John Sturges"], "actors": ["Yul Brynner", "Steve McQueen", "Charles Bronson"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMzYyNzU0MTM1OF5BMl5BanBnXkFtZTcwMzE1ODE1NA@@._V1_SX400_.jpg"}, "title": "The Magnificent Seven", "year": 1960}, {"info": {"rating": 7.5, "genres": ["Adventure", "Fantasy", "Romance", "Sci-Fi", "Thriller"], "plot": "A Victorian Englishman travels to the far future and finds that humanity has divided into two hostile species.", "release_date": "1960-08-17T00:00:00Z", "rank": 1417, "running_time_secs": 6180, "directors": ["George Pal"], "actors": ["Rod Taylor", "Alan Young", "Yvette Mimieux"], "image_url": "http://ia.media-imdb.com/images/M/MV5BMTQ4MzMyNTc2MV5BMl5BanBnXkFtZTYwMjc0NDc5._V1_SX400_.jpg"}, "title": "The Time Machine", "year": 1960}] |
Building our Website
Now that we have an API that returns the data we need, we can create a simple web interface with a form that will present this data in a nice fashion. There are a few ways to make this happen, normally i’d recommend using something like angular however to further demonstrate container linking and such I will use a seperate flask app.
Within the movie-web directory we need to create our app skeleton. To simplify things, copy the Dockerfile, app.py and requirements.txt files from movie-api to movie-web. Besides the copying of those 3 files, go ahead and create the following directory structure and empty files so that the output of tree matches the below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
tree movie-web movie-web ├── Dockerfile ├── app.py ├── demo │ ├── __init__.py │ └── services │ ├──__init__.py │ ├── site.py │ └── templates │ ├── index.html | └── results.html └── requirements.txt 2 directories, 5 files |
In requirements.txt remove the boto reference and add in requests.
1 2 3 |
$ cat requirements.txt Flask==0.10.1 requests==2.9.1 |
Open up app.py and edit the third line to reflect the below
1 2 3 4 5 6 7 |
#!/usr/bin/env python import sys from demo.services.site import app if __name__ == '__main__': port = int(sys.argv[1]) app.run('0.0.0.0',debug=True,port=port) |
Go ahead and create demo/services/site.py and open it up within your IDE.
1 2 3 4 5 6 7 8 9 |
import os import json import flask app = flask.Flask(__name__) @app.route('/') def index(): return 'Hello World!' |
Now to get things running we just need to edit our docker-compose.yml and docker-compose.override.yml files.
docker-compose.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
movie-api: image: TBD restart: always stdin_open: true ports: - 5000 movie-web: image: TBD restart: always stdin_open: true links: - movie-api ports: - 5000 |
docker-compose.override.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
movie-api: build: ./movie-api environment: AWS_ACCESS_KEY_ID: 'foo' AWS_SECRET_ACCESS_KEY: 'bar' AWS_DEFAULT_REGION: 'us-east-1' DYNAMODB_TABLE_NAME: 'Movies' DYNAMODB_ENDPOINT_URL: 'http://dynamodb:8000' links: - dynamodb ports: - "8080:5000" volumes: - ./movie-api:/code movie-web: build: ./movie-web environment: MOVIE_API_URL: 'http://movie-api:5000' ports: - "8081:5000" volumes: - ./movie-web:/code dynamodb: image: kcyeu/dynamodb-local ports: - 8000 |
With those changes saved. We can run docker-compose up -d which should launch our container. We can verify connectivity with curl.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
→ docker-compose up -d dockermoviedbdemo_dynamodb_1 is up-to-date dockermoviedbdemo_movie-api_1 is up-to-date Building movie-web Step 1 : FROM python:2.7 ---> 10b7e9592569 Step 2 : ADD . /code ---> 3d56f4523b5f Removing intermediate container 43dfc12038cf Step 3 : WORKDIR /code ---> Running in 13eb0bdf12c9 ---> 31e595a240b4 Removing intermediate container 13eb0bdf12c9 Step 4 : RUN apt-get update ---> Running in 9526d5db48d4 Get:1 http://security.debian.org jessie/updates InRelease [63.1 kB] Ign http://httpredir.debian.org jessie InRelease Get:2 http://httpredir.debian.org jessie-updates InRelease [136 kB] Get:3 http://security.debian.org jessie/updates/main amd64 Packages [241 kB] Get:4 http://httpredir.debian.org jessie Release.gpg [2373 B] Get:5 http://httpredir.debian.org jessie Release [148 kB] Get:6 http://httpredir.debian.org jessie-updates/main amd64 Packages [3619 B] Get:7 http://httpredir.debian.org jessie/main amd64 Packages [9035 kB] Fetched 9630 kB in 11s (859 kB/s) Reading package lists... ---> 78beb0ab18c5 Removing intermediate container 9526d5db48d4 Step 5 : RUN pip install -r requirements.txt ---> Running in 208bc635ae1b Collecting Flask==0.10.1 (from -r requirements.txt (line 1)) Downloading Flask-0.10.1.tar.gz (544kB) Collecting Werkzeug>=0.7 (from Flask==0.10.1->-r requirements.txt (line 1)) Downloading Werkzeug-0.11.3-py2.py3-none-any.whl (305kB) Collecting Jinja2>=2.4 (from Flask==0.10.1->-r requirements.txt (line 1)) Downloading Jinja2-2.8-py2.py3-none-any.whl (263kB) Collecting itsdangerous>=0.21 (from Flask==0.10.1->-r requirements.txt (line 1)) Downloading itsdangerous-0.24.tar.gz (46kB) Collecting MarkupSafe (from Jinja2>=2.4->Flask==0.10.1->-r requirements.txt (line 1)) Downloading MarkupSafe-0.23.tar.gz Building wheels for collected packages: Flask, itsdangerous, MarkupSafe Running setup.py bdist_wheel for Flask Stored in directory: /root/.cache/pip/wheels/d2/db/61/cb9b80526b8f3ba89248ec0a29d6da1bb6013681c930fca987 Running setup.py bdist_wheel for itsdangerous Stored in directory: /root/.cache/pip/wheels/97/c0/b8/b37c320ff57e15f993ba0ac98013eee778920b4a7b3ebae3cf Running setup.py bdist_wheel for MarkupSafe Stored in directory: /root/.cache/pip/wheels/94/a7/79/f79a998b64c1281cb99fa9bbd33cfc9b8b5775f438218d17a7 Successfully built Flask itsdangerous MarkupSafe Installing collected packages: Werkzeug, MarkupSafe, Jinja2, itsdangerous, Flask Successfully installed Flask-0.10.1 Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.3 itsdangerous-0.24 ---> 69bad3a7a0a5 Removing intermediate container 208bc635ae1b Step 6 : CMD python app.py 5000 ---> Running in 3095d4dc7d76 ---> 4b1ac1fb8a06 Removing intermediate container 3095d4dc7d76 Successfully built 4b1ac1fb8a06 Creating dockermoviedbdemo_movie-web_1 # ckelly at Helios in ~/Code/personal/docker-movie-db-demo on git:master ✖︎ [18:04:18] → docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 386e52df4a56 dockermoviedbdemo_movie-web "/bin/sh -c 'python a" 6 seconds ago Up 5 seconds 0.0.0.0:8081->5000/tcp, 0.0.0.0:32780->5000/tcp dockermoviedbdemo_movie-web_1 bf24c23b68cd kcyeu/dynamodb-local "java -Djava.library." 40 minutes ago Up 40 minutes 0.0.0.0:32779->8000/tcp dockermoviedbdemo_dynamodb_1 22a530df6eef dockermoviedbdemo_movie-api "/bin/sh -c 'python a" About an hour ago Up 58 minutes 0.0.0.0:8080->5000/tcp, 0.0.0.0:32778->5000/tcp dockermoviedbdemo_movie-api_1 # ckelly at Helios in ~/Code/personal/docker-movie-db-demo on git:master ✖︎ [18:04:20] → curl http://192.168.99.100:8081 Hello World!% |
Now lets create a couple templates, and a build out the endpoints on our webapp so that we can perform a simple search.
Open up demo/services/templates/index.html:
1 2 3 4 5 6 7 8 9 10 11 |
<html> <head> <title>Movie Database</title> </head> <body> <h1>Search Movie Database</h1> <form action='search' method='get'> Enter 4 digit year: <input type='text' name='year'> <input type='submit' value='Search'> </form> </body> </html> |
Open up demo/services/templates/results.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<!DOCTYPE html> <html> <head> <title>Search Results</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Bootstrap --> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" media="screen"> </head> <body> <div class="container"> <table class="table table-striped"> <thead> <tr> <th>Movie Title</th> <th>Release Date</th> <th>Rating</th> <th>Rank</th> </tr> <thead> <tbody> {% for movie in movies %} {% if movie.info %} <tr> <td>{{movie.title}}</td> <td>{{movie.info.release_date}}</td> <td>{{movie.info.rating}}</td> <td>{{movie.info.rank}}</td> </tr> {% endif %} {% else %} <p>No movies found. Try a different year</p> {% endfor %} </tbody> </table> </div> <script src="http://code.jquery.com/jquery.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> </body> </html> |
And finally edit demo/services/site.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import os import json import flask import requests from flask import render_template, url_for, request app = flask.Flask(__name__) MOVIE_API_URL = os.environ.get('MOVIE_API_URL', None) @app.route('/') def index(): return render_template('index.html') @app.route('/search') def search(): year = request.args.get('year') request_data = {'year':year} response = requests.post(MOVIE_API_URL + '/movies/by/year', json = request_data) data = response.json() return render_template('results.html', movies=data) |
If you visit your web browser at http://192.168.99.100:8081/ which should show the following:
Enter in 1993 and you should see the following results:
At this point that completes our application and this tutorial on docker for developers. To Recap, we installed and setup the docker toolbox on your machine. We then demonstrated how to use docker-machine, docker, and docker-compose to build a sample application that uses dynamodb, an api service, and finally a web application to view a sample dataset. You should be familiar now with creating a Dockerfile to build an image, and using compose to orchestrate and run your application. You have defined environment variables, container links, ports, and even leveraged the ability to map a volume coupled with flasks ability to reload on file changes to rapidly speed up development.
One thing you may have noticed is that we spent most of the time dealing with application code, and not a whole lot of time spent working with docker itself. Thats kind of the point. One of the greatest strengths of docker is that it simply gets out of your way. It mays it incredibly easy to rapidly iterate and start working on your application code.
While this wraps up my 2 part series on docker for developers, ill be writing additional posts centered around docker for QA and Operations Engineers. This will focus on testing, CI/CD, production deployments, service discovery, and more.