You’ve been there too. Spinning up droplets on DigitalOcean with Ansible and using a dynamic inventory script is quite a pain.
Most approaches use the
digital_ocean ansible module in
playbooks to spin up droplets, along with the
inventory script, using this kind of workflow:
- define your droplets in a YAML file (eventually with size, region, etc…)
- create a playbook that will loop over droplet list (
with_itemsor equivalent) and spin up the droplet
- dynamically add started droplets to inventory
This approach has many drawbacks, and, to be honest, is not really usable.
First, it is damn slow. Droplet creation is serialized. Since
digital_ocean waits for the droplet to come up, and since DO itself
advertizes ‘Start your droplet in 55 seconds !’, you can do the math.
Starting a single droplet is quite long, so spinning up your multi-tier,
fault-tolerant, distributed architecture will take ages.
You probably can use
poll to spin up the droplets. I didn’t
try and don’t know where this would lead. But you’d still face the other
You droplets won’t have real names. They will be known by their IPs.
Sure, if you use the
name parameter during creation, you might be able
to use it, but at best, this will be a group name.
You could also use
add_host in your bootstrapping script, but this is
a run time hack, so forget about setting variables in
Since droplets are mostly nameless, grouping them is hard. Sure, you can
do it at run time with
add_host too, but you won’t leverage
Anyway, all those run-time naming hacks will force you to loop over all your droplets definitions, hit DO API to make sure they’re alive, then loop over API responses to add hosts and groups EVERY time you execute a playbook.
localhost is forced in
Spinning up instances on DO will require to run the
module as a
local_action or using
delegate_to: localhost. This means
that you are bound to declare localhost in your inventory. This is a
real pain, since it makes the
all group mostly unusable, unless you
change all your playbook hosts definitions from
hosts: all to
all:!localhost. Pretty bad for readability.
Let’s stop here, there are already enough reasons to find an alternate way. There are probably other cons, and certainly pros too for the dynamic approach, but I fell that this way of doing it is barely usable for serious, repeatable stuff.
In the end, we would like to work as we do with on-prem hardware: have a static inventory.
The idea is to create this static inventory first, and then use a bootstrapping script that will use this inventory as a contract to apply on DigitalOcean.
The script will list all hosts in your inventory (using
--list-hosts), and parallelize droplet creation on digital ocean.
When all droplets are created, it will create a complementary inventory file in your inventory directory containing hosts with their respective IPs.
At this point, you have a perfectly static inventory, and can run your ansible playbook normally, without hitting external APIs (serialized), without naming problems, … Things are just normal, fast and reliable, without edge cases introduced by dynamic inventories.
Using this approach on a 8 droplets setup, the time to set-up instances went from 9’33” down to 1’56”. And the time to destroy instances went from 0’55” down to 0’3” (see demo below). Of course, more droplets, more gain.
And these are just create/destroy gains. You also benefit from static inventory for all your lifecycle playbook runs, since you never hit DO API and don’t have to build inventory at run time, which is always slower despite the inventory cache.
Assuming you have an inventory directory in
hosts file, you can spin up your droplets like this:
When you’re finished with your infrastructure, call the same command with
The script has defaults regarding droplet size, region, image and ssh key. You can change the defaults in the script to something that suits you, and override these defaults per droplet in your inventory:
###Spinning up and down 8 droplets in 2’15”
You can grab the script in this gist.
UPDATE: if you run Ansible v2.0+, use this script
instead. It will
use the new digital Ocean API (v2.0 too). You just need to set