The problem
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 digital_ocean.py
dynamic
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_items
or 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.
Slooooooow
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 async
+ 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
issues.
Naming
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 host_vars
.
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
group_vars
usage.
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 digital_ocean
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 hosts:
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.
Alternate aproach
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 ansible
--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.
Example
Assuming you have an inventory directory in inventories/devel/
,
containing a hosts
file, you can spin up your droplets like this:
When you’re finished with your infrastructure, call the same command with
the deleted
parameter:
That’s all.
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”
Script
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
DO_API_TOKEN
.