Building containers and composing them together can often be a messy process, especially when using Ansible. You need a Dockerfile with basic bash’y commands, an ansible role to deploy them, and some sort of tool like Kubernetes or docker-compose that don’t all exactly communicate very well.
Enter ansible-container: a new tool by the awesome folks at ansible to more tightly integrate your roles with docker to create flattened images, and cleanly test and deploy your entire stack easily.
NOTE: this project is relatively immature (ok REALLY immature), but given it’s potential to simplify lives and code; it deserves a good amount of attention in its beta form.
Goal
In this article, we’re going to demonstrate how to set up a quick apache / mysql site in static, single layer containers. Drupal makes this nice and easy for demonstration purposes, so we’ll spin up a working example.
In the process we attempt to explain the workings of the ansible-container tool and how it will make your ansible ecosystem more integrated.
How ansible-container works
Ansible container works by spinning up an ‘orchestration’ container that acts as a ansible-playbook runner connecting to other containers as if they were named inventory groups.
Conceptually, it looks something like this:
What makes ansible-container nice is that you can use all your existing ansible roles to provision your instances. All that needs to be done is set up ansible-container config to know how to apply them.
Getting started
First you’ll need some tools to get things running properly; I recommend running this on a ubuntu xenial VM.
If you want an actual working example, use the repo here.
Ensure you use the most recent version of docker
Xenial:
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo deb https://apt.dockerproject.org/repo ubuntu-xenial main | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
Follow instructions here for systems other than Xenial.
Install system pre-req’s
sudo apt-get install docker-engine python python-pip libffi-dev libssl-dev python-dev git -y
Setup virtualenv (optional)
sudo pip install virtualenv
virtualenv venv
source venv/bin/activate
Install ansible-container
git clone https://github.com/ansible/ansible-container.git ansible-container
cd ansible-container
# build and install the package on your local machine
python setup.py install
Setup docker to allow access via port rather than socket
Ansible-container works by having an orchestration guest container that talks to the host docker engine. As a result, you need to modify the init script for docker-engine to bind to a port accessible to your containers. Edit /etc/systemd/system/multi-user.target.wants/docker.service
file:
cat - <<EOF | sudo tee /etc/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/docker daemon -H 0.0.0.0:2375
EOF
Then restart the engine:
sudo systemctl daemon-reload && sudo service docker restart
You’ll also want to set an environment variable so your client docker calls know how to talk to the docker engine:
export DOCKER_HOST="tcp://<host IP address>:2375"
NOTE: This leaves your docker instance open to anyone on your network. There are two ways to lock this down: secure your docker engine via the steps here, or isolate it to a specific local ip address that only your container and host have access to.
Init new ansible-container project
This can be run in an existing project, but to keep things simple, lets start with the basic setup.
Start by running the init command in a new directory to get a skeleton layout:
mkdir my-ac-project && cd my-ac-project
ansible-container init
This creates a directory structure that looks like so:
my-ac-project
|- ansible
|- container.yml # docker-compose compatible definition of containers
|- main.yml # ansible-playbook that gets run in orchestration container to provision containers
|- requirements.txt # any additional python dependencies needed in orchestration container
Modify your docker-compose / ansible-container file
Start with the template files given to you by the ansible-container init
command, you should modify the container.yml
and main.yml
files to fit your needs.
To accomplish the goals in this article, we want to set up our files to look something like this:
# in ansible/container.yml
version: "1" # docker-compose file compatibility version
services:
apache: # how our apache container should be configured
image: php:5.6-apache # staring image you want to apply ansible commands to
command: apache2-foreground # container commands are NOT run on build, and
# if we want them run on run time, they need to be here.
links: # make sure we connect to the mysql container
- mysql
ports:
- "80:80"
environment:
HTTPD_PREFIX: /usr/local/apache2
PATH: ${PATH}:/usr/local/apache2/bin
mysql: # make sure we create a mysql container too
image: mysql:5.6
command: docker-entrypoint.sh mysqld
ports:
- "3306"
environment: # some startup variables on run time
MYSQL_ROOT_PASSWORD: root_pass
MYSQL_DATABASE: drupal
MYSQL_USER: drupal
MYSQL_PASSWORD: drupal_password
# in ansible/main.yml
# Install some basic tools on all of our containers so they'll work as
# ansible targets
- hosts: all
gather_facts: false
tasks:
- raw: which python || apt-get update
- raw: which python || apt-get install -y python python-apt
# These are the tasks that get run on our apache hosts at build time
# To simplify this for your setup, you can use all the techniques you use
# already, such as including roles, other playbooks, etc.
- hosts: apache
vars:
web_root: /var/www/html
drupal_path: "{{ web_root }}/drupal-8.1.3"
tasks:
- name: install needed packages for drupal
apt:
name: "{{ item }}"
state: latest
with_items:
- php5
- git
- libapache2-mod-php5
- php5-mysql
- php5-gd
- name: create directory for webroot
file:
state: directory
dest: "{{ web_root }}"
- name: get drupal package
unarchive:
src: https://ftp.drupal.org/files/projects/drupal-8.1.3.tar.gz
copy: no
dest: "{{ web_root }}"
owner: www-data
group: www-data
- name: copy in default settings file
shell: "cp {{ drupal_path }}/sites/default/default.settings.php {{ drupal_path }}/sites/default/settings.php"
- name: set up settings file with dynamic docker host arg
lineinfile:
state: present
dest: "{{ drupal_path }}/sites/default/settings.php"
owner: www-data
insertafter: "^ \\$databases = array();"
line: >
$databases['default']['default'] = array (
'database' => 'drupal',
'username' => 'drupal',
'password' => 'drupal_password',
'host' => getenv('MYSQL_PORT_3306_TCP_ADDR'),
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
'collation' => 'utf8mb4_general_ci',
);
## You'll still need to run the install.php script in drupal to set up the database,
## but that can be easily automated away too
Build images using build subcommand
Now that your docker composition is set up the way you want, you can start by building the flattened images that will consist of your service:
ansible-container [--debug] build
NOTE: During the build process, your containers don’t run with the command in your container.yml file, but rather a special command meant to keep the instance running while ansible does it’s work. This means that if theres anything that you need to be running in order to set up the containers, start it in your ansible playbook.
That “build” command is something like:
command: sh -c "while true; do sleep 1; done"
Run the newly built images, and test
Bring up the new containers and make sure everything works as intended.
ansible-container run
If everything works as intended, you can ship to whatever repository you want using the ‘shipit’ command:
ansible-container shipit
Troubleshooting
Can’t find build container
If during the build process, you encounter an error like this:
NameError: No image with the name ansible-container-builder
E: GPG error: http://httpredir.debian.org stable InRelease: Clearsigned file isn't valid, got 'NODATA' (does the network require authentication?)
The trick here is to ensure that your networking is working properly: not able to reading the clearsign file indicates something is breaking the ability to pull down apt packages.
Timeout with long running ansible commands
Certain long running processes in ansible will cause your build command to timeout, which can be resolved by setting this environment variable:
export COMPOSE_HTTP_TIMEOUT=50000
Timeout on “flattening image”
This occurs because your system doesn’t have enough allocatable memory based on the size of your images. Try allocating more memory to the system or shrinking the size of your finalized images.