Building docker images with ansible


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 dynamiclly, 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 defititely 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 begining. 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 marchines, 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 togehter easily. The building environment itself can also be provisioned before building the software. Cloud computing resource can also be liveraged. 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