Begin Vultr With Ansible

Posted on 2018-03-18

  Sysadmin   Ansible   Vultr

Ansible 2.5 is just around the corner, perfect timing to show some of the new features I was involved with and included in this release. This is the first part of a post series What’s new in Ansible 2.5.

What is Vultr?

Vultr is a Public Cloud Service (IaaS) with a manageable feature set and datacenters in around 15 locations around the globe including North America, Europe, Asia and Australia.

The features include a.o. classical virtual servers, bare metal servers, firewall management, block storage, free DNS service, custom ISOs and predefined apps. An easy to use user interface allows to configure all of these services manually but unsurprisingly Vutlr also provides an API for a programming interface.

Ansible Vultr Modules

The modules related to Vultr included in 2.5 (Ansible docs) do not yet cover all APIs but some of the most interesting ones:

These modules allow to deploy a server with your selected plan in a chosen region with an SSH key, configure the firewall group and rules and run start scripts on the server.


So let’s get started quickly by creating a new account (You support my works by using this affiliate link).

Configure API Authentication

The modules uses the API and have to authenticate. So the first thing is to allow the modules to access the account using the API. You find the API key in your Account settings in the UI.

There are several ways to pass the API key to the modules. We won’t cover every option for now, just the most common way: create a .vultr.ini in your home directory just like the following:

key = <your api key>

Run a test, get some infos about your account:

$ ansible -m vr_account_facts localhost

The output we will get should look something like:

localhost | SUCCESS => {
   "ansible_facts": {
       "vultr_account_facts": {
           "balance": -212.87,
           "last_payment_amount": -250.0,
           "last_payment_date": "2017-04-28 12:29:50",
           "pending_charges": 0.06
   "changed": false,
   "vultr_account_facts": {
       "balance": -212.87,
       "last_payment_amount": -250.0,
       "last_payment_date": "2017-04-28 12:29:50",
       "pending_charges": 0.06
   "vultr_api": {
       "api_account": "default",
       "api_endpoint": "",
       "api_retries": 5,
       "api_timeout": 60

But what just happened? We run the module vr_account_facts locally, the module itself connects to the API by HTTPs. There is one more cool thing, did you note we didn’t have to install any other dependencies other then Ansible? Pretty nice isn’t it?

Define an Inventory

When authentication was successful, we can go to the next step, deploying our first server.

But first things first, create a inventory which contains the server we want to deploy, we use a classical INI style ansible inventory named production

One server, the webserver is called web-01 and we put it in the group cloud, this is just enough for the moment.

mkdir hosts
echo "[cloud]\nweb-01" > ./hosts/production

When we check the inventory with the command introduced in 2.4, ansible-inventory and pass the right arguments

ansible-inventory --inventory hosts/production --list

We should see a nice json about view of our inventory:

   "_meta": {
       "hostvars": {
           "web-01": {}
   "all": {
       "children": [
   "cloud": {
       "hosts": [
   "ungrouped": {}

So far, so good.

Your first playbook

As you may know, a playbook is usually a YAML document containing one or more plays with tasks for a target host or host group. That is just what we need:

mkdir playbooks
touch playbooks/cloud.yml

In the first play, we are going to deploy our cloud server defined in our production inventory.

- hosts: cloud
  gather_facts: no
  - name: Ensure a cloud server exists
      module: vr_server
      name: "{{ inventory_hostname_short }}"
      os: "CentOS 7 x64"
      plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
      region: New Jersey

We run the module as local action, that is why we turned off facts gathering as it would fail in case the server does not yet exist.

The playbook is ready, let’s give it a shot in --check mode:

ansible-playbook playbooks/cloud.yml --inventory hosts/production --check --diff -v
No config file found; using defaults

PLAY [cloud] *******************************************************************

TASK [Ensure a cloud server exists] ********************************************
changed: [web-01 -> localhost] => {"changed": true, "vultr_api": {"api_account": "default", "api_endpoint": "", "api_retries": 5, "api_timeout": 60}, "vultr_server": {}}

PLAY RECAP *********************************************************************
web-01                     : ok=1    changed=1    unreachable=0    failed=0

Before we continue without --check mode, I must note, that Vultr is quite popular and especially for datacenters located in Europe, plans may run out. If this happens, the module will fail but will give a decent error message:

fatal: [web-01 -> localhost]: FAILED! => {"changed": true, "msg": "URL, method POST with data OSID=167&DCID=9&VPSPLANID=201&notify_activate=no&label=web-01. Returned 412, with body: HTTP Error 412: Request Failed Plan is not available in the selected datacenter.  This could mean you have chosen the wrong plan (for example, a storage plan in a location that does not offer them), or the location you have selected does not have any more capacity.", "vultr_api": {"api_account": "default", "api_endpoint": "", "api_retries": 5, "api_timeout": 60}, "vultr_server": {}}

Use a Vultr CLI e.g. to get a list of available plans per region (DCID):

vultr plans -r <DCID>

Okay, back to the playbook,

$ ansible-playbook playbooks/cloud.yml --inventory hosts/production --diff -v
No config file found; using defaults

PLAY [cloud] *******************************************************************

TASK [Ensure a cloud server exists] ********************************************
changed: [web-01 -> localhost] => {"changed": true, "vultr_api": {"api_account": "default", "api_endpoint": "", "api_retries": 5, "api_timeout": 60}, "vultr_server": {"allowed_bandwidth_gb": 2000, "auto_backup_enabled": false, "cost_per_month": 10.0, "current_bandwidth_gb": 0, "date_created": "2018-03-18 12:54:29", "default_password": "aV6=DY}U(Y)n_x3,", "disk": "Virtual 40 GB", "firewall_group": null, "id": "14294916", "internal_ip": "", "kvm_url": "", "name": "web-01", "os": "CentOS 7 x64", "pending_charges": 0.02, "plan": "2048 MB RAM,40 GB SSD,2.00 TB BW", "power_status": "running", "ram": "2048 MB", "region": "New Jersey", "server_state": "ok", "status": "active", "tag": "", "v4_gateway": "", "v4_main_ip": "", "v6_main_ip": "", "v6_network": "", "v6_network_size": "", "v6_networks": [], "vcpu_count": 1}}

PLAY RECAP *********************************************************************
web-01                     : ok=1    changed=1    unreachable=0    failed=0

That seems to have worked!

However you may feel like what a mess of output. This is because we also added -v to get a verbose output. You are right. I wanted to show, that the module does return a lot of data which can be used later when registered in a variable, e.g. the vultr_server.default_password to login by SSH or the vultr_server.v4_main_ip which can be used later for a DNS A-record.

Is this it? Well, we have a lot more things to cover but this is enough for today. Stay tuned for the next part of this series.