Docker is something really hot recently. It allows you to run your software with linux container easily. It’s actually kind of OS level isolation rather than hardware simulation nor kernel simulation. So you won’t have too much performance penalty but still have pretty nice virtual machine features. I really like the analog used by Docker community, shipping software should be easier and Docker serves as just like the standard container in shipping industry.
Building docker images is not hard, but …
Although docker provides an easy way to deliver and run your software in linux container, there is still no an obvious and easy way to build a docker image for big projects. For building a large and complex project docker image, you would probably need to
- Clone your private software repo in to build folder
- Ensure base images are built before your project image
- Generate some files dynamically, such as current git commit revision
- Generate templates
- Upload image with your credentials
With Dockerfile, you can only have static steps for building the image. Obviously, it was not designed for doing any of these listed above. And since docker uses a kind of layering file system, you probably don’t want to put your Github credentials into the container and pull the repo inside it, because it works pretty similar to git commits, once you commit, then it’s hard to remove it from the history. So you definitely want to do these things outside the container and then put them together.
My first solution - Crane
With these requirements in mind, I actually feel it’s pretty similar to the experience I had with Omnibus - a tool for packing your software into a standalone dep package. So I built a simple tool in Python for building docker images, named Crane. It allows you to define steps for building the image, it also provides template generating with jinja2.
The final solution - ansible
Crane was working fine, but I actually don’t like to make a new wheel and maintain it if there is already an obvious better solution. After I played ansible for a while, I realized it is actually a way better solution for building docker images. So, what is ansible you may ask, well, it’s yet another deployment tool, like Puppet, Chef or SaltStack.
Wait, what? Why you are using a deployment tool for building docker image? It may sound odd to you at very beginning. But ansible is not actually just yet another deployment tool. Its design is pretty different from its predecessors. It uses SSH for pushing commands to target machines, other tools are all pulling based. It also provides many modules for different operations, including creating instances in EC2 or other cloud computing providers. Most importantly, it is able to do orchestration easily.
Of course it meets requirements we mentioned before
- Clone git repo? Check.
- Build base image? Check.
- Generate dynamic file? Check.
- Generate templates? Check.
- Upload images? Check.
Moreover, with ansible, you can launch an EC2 instance, build the image inside it, and run a series of tests before you publish the image. Or you can simply build the image in your vagrant machine or in the local machine. It makes building software extremely flexible, since you can run the building process anywhere you want as long as they can be pushed as commands via SSH, you can also provision the whole building environment, or even drive a fleet in cloud for building, that’s pretty awesome huh, isn’t it?
Show me the code
Okay, enough of talking, let’s see the code. The tasks/main.yml
looks like this
- assert:
that:
- 'hellobaby_version != ""'
- name: install apt packages
apt: "name='' state=present"
with_items:
- git
- python-pip
- name: install docker-py
pip: name=docker-py version=0.3.1
- name: create build directory
file: >
dest=""
state=directory
- name: clone hellobaby git repo
git: >
repo=""
dest="/hellobaby"
version="v"
register: hellobaby_repo
- name: remove git deploy key
file: dest=/tmp/github_deploy_key state=absent
- name: archive repo
shell: >
cd "/" &&
git archive -o ../.tar HEAD
with_items:
- hellobaby
- name: generate templates
template: >
src=""
dest="/"
with_items:
- { src: "Dockerfile", dest: "Dockerfile" }
- { src: "runapp.sh", dest: "runapp.sh" }
- name: build image
docker_image: >
name=""
tag=""
path=""
state=build
- name: tag
command: >
docker tag -f
:
:
when: hellobaby_extra_tag != ''
and the playbook looks like this
---
- name: Build Hello baby app image
hosts: all
sudo: yes
vars_prompt:
- name: hellobaby_version
prompt: "hellobaby_version"
default: "1.0.0"
private: no
- name: hellobaby_iteration
prompt: "hellobaby_iteration"
default: 1
private: no
roles:
- Ansibles.apt
- hellobaby_image
So, to build with vagrant, you can run something like this
ansible-playbook \
-i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory \
-u vagrant --private-key=~/.vagrant.d/insecure_private_key \
playbooks/hellobaby.yml
You can find the complete example here - ansible-docker-demo.
A tool for deployment but also amazing for building software
Although ansible was not designed for building software, it doesn’t necessary mean you cannot do not it. And surprisingly, it does so well in building software. With its machine provisioning and orchestration capability, you can integrate building and deployment together easily. The building environment itself can also be provisioned before building the software. Cloud computing resource can also be leveraged. I feel there are actually lots more interesting things can be done with ansible. Looking forward to see how people use it not just for deployment but also for building software :P