
Docker the Pythonic Way — Part 2
Orchestrating containers with Docker SDK for Python Solution
In a previous article I stated the problem requirements for orchestrating Docker containers with WordPress and MySQL containers.
I also supplied a small InSpec script that can be used to test the features (requirements), to give you a systems TDD (Test Driven Development) style.
Here’s the final solution, with a walk through on using the library and a the solution in full source code.
Previous Article
The Solution with Explanations
Basic Operations
The scope of the code will consist of essential four operations to create the volume resource, network resource, and then run MySQL container, and WordPress container:
import docker
d = docker.from_env()d.volumes.create(...)
d.networks.create(...)d.containers.run('mysql:5.7',...)
d.containers.run('wordpress:latest',...)
User Specified Host Port
We need to allow the user to specify a host port through the WORDPRESS_PORT
environment variable, and default the port to 8080
if it is not set. We can arrange this by doing the following:
import oswordpress_port = os.environ.get('WORDPRESS_PORT') or '8080'
Volume Resource
The volume resource is pretty straight forward:
mysql_volume = 'db_data'd.volumes.create(mysql_volume, driver='local')
Network Resource
For the network resource, we need to not create it if it already exists. With the Python docker
library, we can actually create multiple ones of the same name. That is a state we do not want.
network_name = 'wordpress_net'if not d.networks.list(network_name):
d.networks.create(network_name, driver='bridge')
MySQL Container
This is straight forward: we specify the image as the first parameter, set it run detached (or else the script blocks until the container is finished running), set the restart policy to always
, use our network we created earlier, and a container name of db
.
d.containers.run(
'mysql:5.7',
detach=True,
network=network_name,
restart_policy={'Name': 'always'},
volumes={
mysql_volume: {'bind': '/var/lib/mysql', 'mode': 'rw'}
},
environment=[
'MYSQL_ROOT_PASSWORD=wordpress',
'MYSQL_PASSWORD=wordpress',
'MYSQL_USER=wordpress',
'MYSQL_DATABASE=wordpress'
],
name='db')
The unique parameters for the MySQL container are to specify a list of volumes to mount and environment variables to pass to the container.
The environment variables we use will trigger the built-in automation the MySQL container will use to configure itself.
For the volumes, we are only mounting one volume. We specify the key to be equal to a volume we created earlier, which is db_data
, and then have the value as a dictionary that represent how we want to mount it.
WordPress Container
This is similar to the container we launched previously, except we have a different image, and container name of wordpress
. We pass in environment variables that will trigger built-in automation from the WordPress container.
d.containers.run(
'wordpress:latest',
detach=True,
network=network_name,
restart_policy={'Name': 'always'},
ports={'80/tcp': wordpress_port},
environment=[
'WORDPRESS_DB_HOST=db:3306',
'WORDPRESS_DB_PASSWORD=wordpress',
'MYSQL_USER=wordpress',
'MYSQL_DATABASE=wordpress'
],
name='wordpress')
For the port mapping, we pass in a dictionary that contains the container port, and host port, which is set to WORDPRESS_PORT
or a default of 8080
.
Check for Existing Containers
This script is functional, but will only work once. Should the containers be already running, or one of them has stopped, the script will fail.
Here’s one way we can navigate around this.
First we need to get a list of running WordPress or MySQL containers, by using a list comprehension:
containers = [
c for c in d.containers.list()
if c.name in ['db', 'wordpress']
]
This the d.containers.list() will give us only running containers, so we filter in only containers named db
or wordpress
. From this list, we can stop the running containers:
if containers:
for container in containers:
container.stop()
Next we need to get a list of all containers, including the stopped ones, and use a list comprehension to produce the list:
containers = [
c for c in d.containers.list(all=True)
if c.name in ['db', 'wordpress']
]
We apply the same filter to all of the containers, as because we stopped them before, it should be safe to now remove them:
if containers:
for container in containers:
container.remove()
The Complete Solution Source
Below is the final solution when all put together.
You can download this and run it:
python sdk_run.py
And if using an alternative port:
export WORDPRESS_PORT=8888
python sdk_run.py
Conclusion
This is pretty straightforward, no quirky gotchas or tricks get basic to advance features to work. I was quite surprised how easy this was to put together.
I originally anticipated it would take me a full day, but really only took a few hours to explore the API and come up with a solution without any issues.