Skip to content

James Carr - Rants and Musings of an Agile Developer
Syndicate content James Carr - Medium
Random observations in technology, parenthood and life - Medium
Updated: 8 hours 35 sec ago

Packer, Ansible and Docker Part 3: Multiple Roles

Mon, 01/02/2017 - 19:55

Previously we modified our setup to use a role from ansible galaxy to install and configure redis. One key thing lacking here is that one rarely needs to just use a role from ansible galaxy by itself so next up we’ll modify our playbook to define the server as a role that uses the redis role.

Creating Our Role

First up we’ll create a role directory containing our role and a few subdirectories we’ll use later.

mkdir -p roles/redis/{meta,tasks,defaults}

Next up, we’ll move the geerlingguy.redis role into a dependency for this new role in roles/meta/main.yml.

---
dependencies:
- role: geerlingguy.redis

We also change the playbook.yml to reference this new role and remove the Hello World task we added in part one.

---
- name: A demo to run ansible in a docker container
hosts: all
roles:
- redis

That’s all for now. We’ll run packer build template.json one more time before we move on to make sure everything is working fine. If it’s not, check out the current directory structure at https://github.com/jamescarr/pad-tutorial/tree/part-3-a.

Making It Dynamic

This has been nice and all but now we’ve reached a point where we need to create more server roles. We could just copy and paste the existing template.json and change a few bits but that will become a headache, won’t it? What we should do is use a variable in packer to dynamically change which role we use.

The good news is that we already have a packer variable named ansible_role that we can repurpose for this need. We previously set it to default but we’re going to change it to specify the role to run. With this in mind, we modify our playbook.yml to use the ansible_host variable as the role that is included.

---
- name: A demo to run ansible in a docker container
hosts: all
roles:
- "{{ ansible_host }}"

While we’re at it, let’s also change the docker image name and add a variable for the version number too. Add a version variable to the variables section of template.json and default it to latest.

"variables": {
"ansible_host": "",
"version": "latest",
"ansible_connection": "docker",
"ansible_roles_path": "galaxy"
},

We also update the post-processor section to use the ansible_host and version.

"post-processors": [
[
{
"type": "docker-tag",
"repository": "jamescarr/{{ user `ansible_host` }}",
"tag": "{{ user `version` }}"
}
]

Now we can run packer again but this time we specify the ansible_host. We’ll just build a docker image with the latest tag but if we wanted to override it we could specify the version too.

> $ packer build -var 'ansible_host=redis' template.json

Now we can create separate roles for any new docker image we want to build with this template and can include any cross cutting, “all images get this” tasks to the playbook.yml but it is good to keep them all encapsulated in a single role.

Add Another Role

With all this snazzy dynamic pieces in place, let’s add one more role to build a docker image for. For fun, we’ll select RabbitMQ. A quick google makes it appear that Mayeu.RabbitMQ is the role to use so let’s run with that and add it to our requirements.yml.

---
- src: geerlingguy.redis
version: 1.1.5
- src: https://github.com/jamescarr/ansible-playbook-rabbitmq/archive/e303777.tar.gz
name: Mayeu.RabbitMQ

In this case, I actually discovered a defect in the latest release of the role that I was able to easily fix so I instead reference the git commit hash of my fork. Thankfully anisble-galaxy is flexible like that.

Since we’ll probably find ourselves changing this often, let’s add a shell-local provisioner to packer under the provisioners section to run ansible-galaxy all the time as the first step.

{
"type": "shell-local",
"command": "ansible-galaxy install -p galaxy -r requirements.yml"
}

Next up we’ll create the role structure for our RabbitMQ role.

> $ mkdir -p roles/rabbitmq/{meta,tasks,defaults}

This time around we won’t just use the galaxy role, we’ll also configure it to our likings. For our needs we want to configure RabbitMQ with a host of plugins and define a default vhost with one user that can write to any exchange (but not configure or read from queues) and one user that is the administrator. With this in mind we add the following to roles/rabbitmq/meta/main.yml.

---
dependencies:
- role: Mayeu.RabbitMQ
rabbitmq_ssl: false
rabbitmq_plugins:
- rabbitmq_management
- rabbitmq_management_agent
- rabbitmq_shovel
- rabbitmq_federation
- rabbitmq_shovel_management
rabbitmq_vhost_definitions:
- name: "{{ main_vhost }}"
rabbitmq_users_definitions:
- vhost: "{{ main_vhost }}"
user: user1
password: password
configure_priv: "^$"
read_priv: "^$" # Disallow reading.
write_priv: ".*" # allow writing.
- vhost: "{{ main_vhost }}"
user: admin
password: password
force: no
tags:
- administrator

We also use the same vhost name multiple times so rather than duplicate it all over we define a variable for it in roles/rabbitmq/defaults/main.yml.

---
main_vhost: /faxanadu

With all of these bits in place, let’s run packer to build out our new rabbitmq docker image!

> $ packer build -var 'ansible_host=rabbitmq' template.json

When this is done, we should have a docker image that can run rabbitmq with our expected configuration details!

jamescarr@Jamess-MacBook-Pro-2 ~/Projects/docker-ansible                                                                                                                        [11:47:53]
> $ docker run -it -p 15672:15672 -p 5672:5672 jamescarr/rabbitmq:latest rabbitmq-server ⬡ 6.2.2 [±master ●]
RabbitMQ 3.6.6. Copyright (C) 2007-2016 Pivotal Software, Inc.
## ## Licensed under the MPL. See http://www.rabbitmq.com/
## ##
########## Logs: /var/log/rabbitmq/rabbit@730b08abceab.log
###### ## /var/log/rabbitmq/rabbit@730b08abceab-sasl.log
##########
Starting broker...
completed with 9 plugins.

Looks good! You can see the latest state of the project at https://github.com/jamescarr/pad-tutorial/tree/efc9e035eaa0a27ba2d6066ed04a37f5efa4bf42.

Next

Unfortunately when we try to login to the rabbitmq management console we’ll notice that none of our configured users work. That’s because they were created under the hood by rabbitmqctl and not persisted. In the next post we’ll modify our little build system we have going here to run a subset of tasks on container start!

Packer, Ansible and Docker Part 3: Multiple Roles was originally published in James Carr on Medium, where people are continuing the conversation by highlighting and responding to this story.

Categories: Blogs

Packer, Ansible and Docker Part 2: Using Ansible Galaxy

Sat, 12/31/2016 - 23:51

Previously we setup packer, docker and ansible to build a very simple docker image that simply placed a file under /root with some content, a very simple start. Today we’ll go further and explore using ansible roles and making some pieces a bit more dynamic.

A Real World Example

In this tutorial, we’ll build a docker image that has Redis installed. While we could go through the steps of adding an apt repository, installing and configuring Redis ourselves why not go strong and take advantage of the fact that someone else has already done this? Enter ansible-galaxy.

Using Ansible Galaxy

Simply put, ansible galaxy is pretty much like any package manager for your favorite language like pip for python or npm for node. You can install packages stand-alone by running something like ansible-galaxy install geerlinguy.redis or you can define a requirements file just like in python to specify multiple dependencies.

Finding the Right Role

If we do a search we’ll soon discover that there are 150 or so odd redis roles out there… which do we use? Usually we’ll want to temper our choice to the one with the most downloads. A very quick method I use is to just google ansible-galaxy <service> and run with the first result. Thankfully the search for ansible-galaxy redis returns geerlingguy.redis which is by a guy I know puts out some pretty high quality roles. But if I didn’t know that, the number of downloads are a good indicator!

Adding our Requirements File

Our requirements.yml file is off to a simple start.

---
- src: geerlingguy.redis
version: 1.1.5

While the ansible-local provisioner lets you define a galaxy_file and installs roles for you the ansible-remote provisioner (which we’re using) does not. So we’ll need to install the roles and specify the directory in they’ll be installed in. While roles is typically a good directory name I prefer to store my galaxy related roles in a separate directory and leave roles for my own roles (which we’ll cover in a later blog post). For today, let’s use galaxy for roles installed from ansible-galaxy. You can specify the target path with the -p option below.

$ ansible-galaxy install -p galaxy -r requirements.yml

This is also a good time to update our playbook to use the role.

---
- name: A demo to run ansible in a docker container
hosts: all
tasks:
- name: Add a file to root's home dir
copy:
dest: /root/foo
content: Hello World!
owner: root
roles:
- geerlingguy.redis

Finally, we need to tell ansible where our role directories are. We can do this by defining the ANSIBLE_ROLES_PATH environment variable. So we update our template.json to the below content with that added.

{
"variables": {
"ansible_host": "default",
"ansible_connection": "docker",
"ansible_roles_path": "galaxy"
},
"builders": [{
"type": "docker",
"image": "ubuntu:16.04",
"commit": "true",
"run_command": [ "-d", "-i", "-t", "--name", "{{user `ansible_host`}}", "{{.Image}}", "/bin/bash" ]
}],
"provisioners": [
{
"type": "shell",
"inline": [
"apt-get update",
"apt-get install python -yq"
]
},
{
"type": "ansible",
"ansible_env_vars": [
"ANSIBLE_ROLES_PATH={{user `ansible_roles_path` }}"
],
"user": "root",
"playbook_file": "./playbook.yml",
"extra_arguments": [
"--extra-vars",
"ansible_host={{user `ansible_host`}} ansible_connection={{user `ansible_connection`}}"
]
}
],
"post-processors": [
[
{
"type": "docker-tag",
"repository": "jamescarr/demo",
"tag": "0.1"
}
]
]
}

With all of these changes made, let’s run packer again.

$ packer build template.json

If all goes well, we’ll see the tasks to install redis get executed.

And if we run our container we can see redis is indeed installed!

$ docker run -it jamescarr/demo:0.1 bash

For reference, you can see the project in its entirety at https://github.com/jamescarr/pad-tutorial/tree/part-two.

This is pretty nifty stuff… no more Dockerfiles masquerading as glorified bash scripts. How about we take this further and use the same docker template to build out multiple different types of docker images?

Next Up

Tomorrow I’ll write up part three and we’ll explore making this template more dynamic and utilize our own roles to layout specific tasks.

Packer, Ansible and Docker Part 2: Using Ansible Galaxy was originally published in James Carr on Medium, where people are continuing the conversation by highlighting and responding to this story.

Categories: Blogs

Build Docker Images with Packer and Ansible

Fri, 12/30/2016 - 21:50

Recently someone asked me where a good place is to get started learning tools like docker, packer and ansible. I did some quick googling and didn’t find what I thought were really good, in-depth tutorials so I decided to write one myself!

Getting Started

This tutorial assumes you are working with OSX although you should be able to accomplish the same results on a linux workstation by seeking out the packages required for your platform.

To get started, let’s install the following tools. If you don’t already have homebrew installed I recommend installing it first.

  • Install packer. You can install it via brew install packer.
  • Install Docker.
  • Install ansible (brew install ansible).
Our First Packer Template

The goal of this tutorial is to get a packer template together that will build a docker image using ansible to provision it. With that in mind, we’re going start by first dipping our toes into packer and use the docker builder, the local shell provisioner and finally a docker post-processor to export docker image with a single file added to /root.

{
"builders": [{
"type": "docker",
"image": "ubuntu:16.04",
"commit": "true"
}],
"provisioners": [
{
"type": "shell",
"inline": ["echo 'hello!' > /root/foo"]
}
],
"post-processors": [
[
{
"type": "docker-tag",
"repository": "jamescarr/demo",
"tag": "0.1"
}
]
]
}

Save this as template.json and run packer build template.json. Once the build finishes you should be able to run the docker image and inspect it.

jamescarr@Jamess-MacBook-Pro-2 ~/Projects/docker-ansible                                                                                                [13:11:49]
> $ docker run -it jamescarr/demo:0.1 bash ⬡ 6.2.2
root@bec87474b4a7:/# ls /root
foo
root@bec87474b4a7:/# cat /root/foo
hello!
Adding Ansible

Next, let’s swap out our shell provisioner with ansible. There are two options you can go with, ansible-local or ansible-remote. The difference here is that ansible-local will run on the target (in this case, in a docker container) while ansible remote will run on your local machine against the target (typically over ssh). Basically your needs will determine which you want to use. If you want ansible on the target image (perhaps to render templates on container start) then ansible-local is the right path to go while ansible-remote is great if we want to use ansible to provision the image but want to leave ansible off of the resulting image.

This one will be a rather big change… we need to use docker as the ansible_connection as the default will try to connect over ssh and transfer files via scp… obviously this won’t work in docker unless our container runs an ssh server! So we need to instruct ansible to use the docker connection driver to connect. This also requires us to create a consistent hostname, so we define a container name to use when building. We also use the shell provisioner again to install python since ansible will require python on the target and the docker image doesn’t have it by default.

Here’s the updated template.json:

{
"variables": {
"ansible_host": "default",
"ansible_connection": "docker"
},
"builders": [
{
"type": "docker",
"image": "ubuntu:16.04",
"commit": "true",
"run_command": [
"-d",
"-i",
"-t",
"--name",
"{{user `ansible_host`}}",
"{{.Image}}",
"/bin/bash"
]
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"apt-get update",
"apt-get install python -yq"
]
},
{
"type": "ansible",
"user": "root",
"playbook_file": "./playbook.yml",
"extra_arguments": [
"--extra-vars",
"ansible_host={{user `ansible_host`}} ansible_connection={{user `ansible_connection`}}"
]
}
],
"post-processors": [
[
{
"type": "docker-tag",
"repository": "jamescarr/demo",
"tag": "0.1"
}
]
]
}

And the new playbook.yml file which has a single task that uses copy to generate a file similar to what we created previously with the shell provisioner.

---
- name: A demo to run ansible in a docker container
hosts: all
tasks:
- name: Add a file to root's home dir
copy:
dest: /root/foo
content: Hello World!
owner: root

Run packer build template.json and once it completes, let’s test it out.

jamescarr@Jamess-MacBook-Pro-2 ~/Projects/docker-ansible                                                                                                [13:47:00]
> $ docker run -it jamescarr/demo:0.1 bash ⬡ 6.2.2 [±master ●●]
root@2c46ddf2d657:/# cat /root/foo
Hello World!root@2c46ddf2d657:/# ^C
(failed reverse-i-search)`pa': ^C
root@2c46ddf2d657:/# exit
exit
Next Up

I hope this has been a good quick overview on getting started using packer, ansible and docker. This didn’t really produce much useful aside from introducing the pieces and providing a foundation to start with. Tomorrow’s post I’ll tackle some larger “real world” solutions and how to make this a bit more dynamic!

Build Docker Images with Packer and Ansible was originally published in James Carr on Medium, where people are continuing the conversation by highlighting and responding to this story.

Categories: Blogs