Technical Blog of Francis Setash

Server 2016 - Docker Abstraction API with Flask

Posted on — Nov 13, 2016

So; lets talk about a docker host on Windows Server 2016, and building an abstraction API with Python/Flask. This will be a multi-part post as my time permits.

The basic scenario is that a team may have a need to share a docker server, with standardized docker images, but with needs not addressed directly by the docker daemon API.

First up; in a previous post I addressed setting up Windows Server 2016 to listen to docker requests externally.  (note: I know this is a post about introspection, but this will also cover you for allowing access on external interfaces)   !

So, first off, why? Many reasons at hand:

Additional factors:

Needless to say, your reasons may not mirror these. And that’s okay. This may be an entirely irrevelvant configuration in your world, but - for me - this is useful. Let’s look at a little chunk of a proof of concept having python/Flask calling the docker daemon API First up, define the baseline necessary json data structure for a container and some data for the docker host:

from flask import Flask
import requests
import json

server = "DOCKER_HOST_IP_ADDRESS"
port = "2375"

containerTemplate = {
    "Image": 'ipy/iis',
    "ExposedPorts":
        {"80/tcp": None},
    "HostConfig":
        {"PublishAllPorts": True},
    "Cmd": [],
    "Env": []
}

Pretty simple to start with. IF you need documentation on a docker container creation and the possible values in the API, here you go: API Docs Right now, we’re not defining much at all. Let’s make a few notes though:

Next up; I feel it worth mentioning that when in a docker commandline and you run a “docker run img/imagename” its actually calling a create action on the API, followed by a run action. This needs to be done distinctly when using the API. Alrighty, now let’s look at a few Flask routes that gets some stuff done.

@app.route('/create', methods=['POST'])
def create():
    containerRequest = requests.post(("http://{0}:{1}/containers/create".format(server, port)), json=containerTemplate)
    data = json.loads(containerRequest.text)
    return data['Id']

@app.route('/createForTeam/<team>', methods=['POST'])
def createForTeam(team):
    #containerTest["Cmd"].append("powershell C:/scripts/pullWebSite.ps1 -Team {0}".format(team))

    containerTest["Env"].append("mobTeam={0}".format(team))
    containerRequest = requests.post(("http://{0}:{1}/containers/create".format(server, port)), json=containerTest)
    data = json.loads(containerRequest.text)
    return data['Id']

@app.route('/run/<containerId>', methods=['POST'])
def run(containerId):
    runningContainer = requests.post("http://{0}:{1}/containers/{2}/start".format(server, port, containerId))
    return "Running"

@app.route('/ports/<containerId>')
def ports(containerId):
    info = requests.get("http://{0}:{1}/containers/{2}/json".format(server, port, containerId))
    containerInfo = json.loads(info.text)
    cnPort = containerInfo["NetworkSettings"]["Ports"]["80/tcp"][0]["HostPort"]
    return "{0}".format(cnPort)

And now; let’s look at how we’d call this from PowerShell.

$containerId = Invoke-RestMethod -method Post -uri http://FLASK_APP_IP:5000/create
$state = Invoke-RestMethod -method post -uri http://FLASK_APP_IP:5000/run/$($containerId)
$ports = Invoke-RestMethod -method get -Uri http://FLASK_APP_IP:5000/ports/$($containerId)

#or


$containerId = Invoke-RestMethod -method Post -uri http://FLASK_APP_IP:5000/createForTeam/teamNameVariable
$state = Invoke-RestMethod -method post -uri http://FLASK_APP_IP:5000/run/$($containerId)
$ports = Invoke-RestMethod -method get -Uri http://FLASK_APP_IP:5000/ports/$($containerId)

#Oh, and just a simple quick way to hit the webpage:

Start-Process "chrome.exe" "DOCKER_HOST_IP_ADDR:$($ports)"

The second call, will hit the createForTeam route, which will take a value and insert an environment variable in the newly created container (which again, we can have the baseline container perform unique actions based on this value on run, rather than build!) All said, this is just an opening into really giving yourself a simplified, but supercharged API for docker management. We’ll take this a step further in the next blog post.