This article is an excerpt from my book Mastering Python Networking, Third Edition – the one-stop solution to using Python for network automation, programmability, and DevOps.
Containers have become very popular over the last few years. Containers offer more abstractions and virtualization beyond hypervisor-based virtual machines. An in-depth discussion of containers is beyond the scope of this article. For our readers, we will offer a simple example of how we can run our Flask app in a Docker container. Flask is a micro web framework written in Python classified as a microframework because it does not require particular tools or libraries.
In this article, we will build our example based on the free DigitalOcean Docker tutorial on building containers on Ubuntu 18.04 machines. If you are new to containers, I would highly recommend that you go through the tutorial and then return to this section afterwards.
Let’s make sure Docker is installed:
$ sudo docker --version Docker version 19.03.2, build 6a30dfc
We will make a directory named TestApp to house our code:
$ mkdir TestApp $ cd TestApp/
In the directory, we will make another directory called app and create the __init__.py file:
$ mkdir app $ touch app/__init__.py
For more information on user session management, logging in, logging out, and remembering user sessions, I would highly recommend using the Flask-Login extension.
Under the app directory is where we will contain the logic of our application. We’ll use a single-file app:
$ cat app/__init__.py from flask import Flask, url_for, jsonify, request from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///network.db' db = SQLAlchemy(app) @app.route('/') def home(): return "Hello Python Netowrking!" <skip> class Device(db.Model): __tablename__ = 'devices' id = db.Column(db.Integer, primary_key=True) hostname = db.Column(db.String(64), unique=True) loopback = db.Column(db.String(120), unique=True) mgmt_ip = db.Column(db.String(120), unique=True) role = db.Column(db.String(64)) vendor = db.Column(db.String(64)) os = db.Column(db.String(64)) <skip>
We can also copy the SQLite database file we created to this directory:
$ tree app/ app/ ├── __init__.py ├── network.db
We will place the requirements.txt file in the TestApp directory, as well as creating the main.py file as our entry point and an ini file for uwsgi:
$ cat main.py from app import app $ cat uwsgi.ini [uwsgi] module = main callable = app master = true
We will use a pre-made Docker image and create a Dockerfile that builds the Docker image:
$ cat Dockerfile FROM tiangolo/uwsgi-nginx-flask:python3.7-alpine3.7 RUN apk --update add bash vim RUN mkdir /TestApp ENV STATIC_URL /static ENV STATIC_PATH /TestApp/static COPY ./requirements.txt /TestApp/requirements.txt RUN pip install -r /TestApp/requirements.txt
Our start.sh shell script will build the image, run it as a daemon in the background, then forward port 8000 to the Docker container:
$ cat start.sh #!/bin/bash app="docker.test" docker build -t ${app} . docker run -d -p 8000:80 \ --name=${app} \ -v $PWD:/app ${app}
We can now use the start.sh script to build the image and launch our container:
$ sudo bash start.sh Sending build context to Docker daemon 49.15kB Step 1/7 : FROM tiangolo/uwsgi-nginx-flask:python3.7-alpine3.7 python3.7-alpine3.7: Pulling from tiangolo/uwsgi-nginx-flask 48ecbb6b270e: Pulling fs layer 692f29ee68fa: Pulling fs layer <skip>
Our Flask now runs in the container that can be viewed from our host machine port 8000:
$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ac5384e6b007 docker.test "/entrypoint.sh /sta…" 55 minutes ago Up 46 minutes 443/tcp, 0.0.0.0:8000->80/tcp docker.test
We can see the management host IP displayed in the address bar as follows:
Figure 1: Management host IP
We can see the Flask API endpoint as follows:
Figure 2: Flask API endpoint
Once we are done, we can use the following commands to stop and delete the container:
$ sudo docker stop <container id> $ sudo docker rm <containter id>
We can also delete the Docker image:
$ sudo docker images -a -q #find the image id $ sudo docker rmi <image id>
As we can see, running Flask in a container environment provides us with even more flexibility and the option to deploy our own API abstraction in production. Containers, of course, offer their own complexity and add more management tasks so we need to weigh up the benefits and overhead when it comes to our deployment methods.
Additional Resources:
Mastering Python Networking, Third Edition
Automation & Programmability Training