search by tags

for the user

adventures into the land of the command line

flask docker vagrant mac inceptions

A writeup on the steps I followed to start up a three container flask app.

First, some vagrant preparation stuff, cos I docker doesn’t play well on mac.

$ vagrant up
$ vagrant plugin install vagrant-vbguest
$ vagrant ssh
$ sudo apt-get install -y virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11
$ sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual

The Vagrantfile I used looks like this.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|

  config.vm.provider :virtualbox do |provider|
    provider.check_guest_additions = false
    provider.functional_vboxsf = false
    provider.memory = 1024
    provider.cpus = 1
  end

  config.vm.define "pewpew" do |pewpew|
    pewpew.vm.box = "ubuntu/trusty64"
    pewpew.vm.box_check_update = false
    pewpew.vm.box_download_insecure = true
    pewpew.vm.network "private_network", ip: "192.168.50.14", netmask: "255.255.255.0"
    pewpew.vm.hostname = "pewpew.mydomain.com"
    pewpew.vm.network "forwarded_port", guest: 80, host: 8080, auto_correct: true# > /etc/fstab"
    pewpew.vm.provision :shell, inline: "echo vm.swappiness = 10 >> /etc/sysctl.conf && echo vm.vfs_cache_pressure = 50 >> /etc/sysctl.conf && sysctl -p"
  end

  config.ssh.username = "vagrant"
  config.ssh.pty = true

  config.vm.provision "shell" do |shell|
    shell.privileged = true
    shell.inline = "sudo sed -i '/tty/!s/mesg n/tty -s \\&\\& mesg n/' /root/.profile"
  end

end

My /etc/hosts file contains this line:

192.168.50.14   pewpew.mydomain.com

My working directory on my mac looks like this:

$ tree
.
├── Vagrantfile
├── app
│   ├── Dockerfile
│   ├── index.py
│   └── pewpew.wsgi
├── db
│   └── Dockerfile
└── rp
    ├── Dockerfile
    └── site.conf

This directory is mounted into the Vagrant vm at /srv.

Let’s go through each file:

--- /srv/app/Dockerfile ---

FROM python:2.7

RUN pip install --no-cache-dir Flask==0.10.1
RUN pip install --no-cache-dir gunicorn==19.3.0
RUN pip install --no-cache-dir eventlet==0.17.4
RUN pip install --no-cache-dir pymongo==3.4.0

COPY index.py /app/
COPY pewpew.wsgi /app/

EXPOSE 5000

WORKDIR /app

CMD ["gunicorn", "-k", "eventlet", "-b", "0.0.0.0:5000", "-w", "1", "index:app"]


--- /srv/app/index.py ---

import os
from flask import Flask
from pymongo import MongoClient

app = Flask(__name__)

db = "mongodb"
client = MongoClient(db, 27017)

@app.route("/")
def hello():
    try:
        server_info = client.server_info()
        db_names = client.database_names()
        client.close()
        return "Pew Pew!
%s
%s
" % (server_info, db_names)
    except:
        return "Pew Pew! DB failing...
"

if __name__ == '__main__':
    app.run()



--- /srv/app/pewpew.wsgi ---

import sys

PROJECT_DIR = '/app/'

sys.path.append(PROJECT_DIR)
from pewpew import app as application


--- /srv/db/Dockerfile ---

FROM mongo

EXPOSE 27017


--- /srv/rp/Dockerfile ---

FROM nginx

COPY site.conf /etc/nginx/conf.d/site.conf

EXPOSE 80 443


--- /srv/rp/Dockerfile ---

server {
    listen      80;
    server_name pewpew.mydomain.com;

    access_log  /var/log/nginx/nginx_access_myapp.log;
    error_log   /var/log/nginx/nginx_error_myapp.log;

    location / {
        proxy_pass http://flaskapp:5000/;
    }
}


Ok let’s start. ssh into the vagrant box and check the kernel. To use docker you need 3.10+ or sum chit…

$ vagrant ssh
$ uname -r
3.13.0-98-generic

Ok cool, install the docker daemon.

$ sudo curl -sSL https://get.docker.com/ | sh

Now let’s build these images from the three Dockerfiles we have.

$ sudo docker build -t reverseproxy /srv/rp/
$ sudo docker build -t flaskapp /srv/app/
$ sudo docker build -t mongodb /srv/db/

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
reverseproxy        latest              fa2ead9fdb67        11 minutes ago      107MB
flaskapp            latest              48ce64a24bea        About an hour ago   681MB
nginx               latest              b8efb18f159b        12 days ago         107MB
python              2.7                 fa8e55b2235d        13 days ago         673MB
mongo               latest              b39de1d79a53        13 days ago         359MB

Start the database container first.

$ docker run -d -e DB_PORT_27017_TCP_ADDR='0.0.0.0' -v /srv/db:/data -p 27017:27017 --name mongodb mongo

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                      NAMES
c610b1a11752        mongo               "docker-entrypoint..."   3 seconds ago       Up 1 second         0.0.0.0:27017->27017/tcp   mongodb

Then start the flask application container.

$ docker run -d -p 5000:5000 --name flaskapp --link mongodb:mongodb flaskapp

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                      NAMES
ebf6ba70b2f8        flaskapp            "gunicorn -k event..."   2 seconds ago       Up 1 second         0.0.0.0:5000->5000/tcp     flaskapp
c610b1a11752        mongo               "docker-entrypoint..."   24 seconds ago      Up 23 seconds       0.0.0.0:27017->27017/tcp   mongodb

Send a request to the app

$ curl http://127.0.0.1:5000
Pew Pew!
{u'storageEngines': [u'devnull', u'ephemeralForTest', u'mmapv1', u'wiredTiger'], u'maxBsonObjectSize': 16777216, u'ok': 1.0, u'bits': 64, u'modules': [], u'openssl': {u'compiled': u'OpenSSL 1.0.1t  3 May 2016', u'running': u'OpenSSL 1.0.1t  3 May 2016'}, u'javascriptEngine': u'mozjs', u'version': u'3.4.6', u'gitVersion': u'c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5', u'versionArray': [3, 4, 6, 0], u'debug': False, u'buildEnvironment': {u'cxxflags': u'-Woverloaded-virtual -Wno-maybe-uninitialized -std=c++11', u'cc': u'/opt/mongodbtoolchain/v2/bin/gcc: gcc (GCC) 5.4.0', u'linkflags': u'-pthread -Wl,-z,now -rdynamic -Wl,--fatal-warnings -fstack-protector-strong -fuse-ld=gold -Wl,--build-id -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro', u'distarch': u'x86_64', u'cxx': u'/opt/mongodbtoolchain/v2/bin/g++: g++ (GCC) 5.4.0', u'ccflags': u'-fno-omit-frame-pointer -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -fno-builtin-memcmp', u'target_arch': u'x86_64', u'distmod': u'debian81', u'target_os': u'linux'}, u'sysInfo': u'deprecated', u'allocator': u'tcmalloc'}
[u'admin', u'local']

Nice! Now let’s try with the nginx container.

$ docker run -d -p 80:80 --name reverseproxy --link flaskapp:flaskapp reverseproxy

$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                  PORTS                         NAMES
716f3c7c321c        reverseproxy        "nginx -g 'daemon ..."   1 second ago        Up Less than a second   0.0.0.0:80->80/tcp, 443/tcp   reverseproxy
ebf6ba70b2f8        flaskapp            "gunicorn -k event..."   19 seconds ago      Up 18 seconds           0.0.0.0:5000->5000/tcp        flaskapp
c610b1a11752        mongo               "docker-entrypoint..."   41 seconds ago      Up 39 seconds           0.0.0.0:27017->27017/tcp      mongodb

Send a request to the nginx vhost.

$ curl http://127.0.0.1/

Pew Pew!
{u'storageEngines': [u'devnull', u'ephemeralForTest', u'mmapv1', u'wiredTiger'], u'maxBsonObjectSize': 16777216, u'ok': 1.0, u'bits': 64, u'modules': [], u'openssl': {u'compiled': u'OpenSSL 1.0.1t  3 May 2016', u'running': u'OpenSSL 1.0.1t  3 May 2016'}, u'javascriptEngine': u'mozjs', u'version': u'3.4.6', u'gitVersion': u'c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5', u'versionArray': [3, 4, 6, 0], u'debug': False, u'buildEnvironment': {u'cxxflags': u'-Woverloaded-virtual -Wno-maybe-uninitialized -std=c++11', u'cc': u'/opt/mongodbtoolchain/v2/bin/gcc: gcc (GCC) 5.4.0', u'linkflags': u'-pthread -Wl,-z,now -rdynamic -Wl,--fatal-warnings -fstack-protector-strong -fuse-ld=gold -Wl,--build-id -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro', u'distarch': u'x86_64', u'cxx': u'/opt/mongodbtoolchain/v2/bin/g++: g++ (GCC) 5.4.0', u'ccflags': u'-fno-omit-frame-pointer -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -fno-builtin-memcmp', u'target_arch': u'x86_64', u'distmod': u'debian81', u'target_os': u'linux'}, u'sysInfo': u'deprecated', u'allocator': u'tcmalloc'}
[u'admin', u'local']

Awesome, we can also go to our http://pewpew.mydomain.com URL in a browser on our mac, as we have forwarded the port on our Vagrant box and added a local DNS entry in /etc/hosts remember?